Branching and Commiting
Branch and commit and whatnot
Now that your project is setup and GitButler is installed and configured, you can start branching and committing.
The Simple Flow
Let’s begin with a simple workflow, one that should be familiar to Git users. We will:
- Do some work
- Create a new branch
- Commit to that branch
Status
Let’s begin by seeing what the status of the working directory is by running but status. This will tell you a little more than git status, it will list:
- All files in your working directory that differ from your base branch (
origin/main) the last time you updated it that aren’t assigned to a branch - A list of the active branches that you have and
- All assigned file changes in each branch
- All commits in each branch
So it's sort of like a combination of git status and a shortlog of what is on your branches that is not on origin/master.
It looks something like this:
╭┄zz [unstaged changes] ┊ g0 M README-es.md 🔒 da42d06 ┊ h0 A README.new.md ┊ i0 A app/views/bookmarks/index.html.erb ┊ ┊╭┄ge [gemfile-fixes] ┊● da42d06 Add Spanish README and bookmarks feature ┊● fdbd753 just the one ┊│ ┊├┄at [feature-bookmarks] ┊● 223fdd6 feat: Add bookmarks table to store user-tweet rela ├╯ ┊ ┊╭┄sc [sc-branch-26] ┊● f55a30e add bookmark model and associations ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
Here we can see three applied branches: gemfile-fixes stacked on feature-bookmarks and independent sc-branch-26. There are also three unassigned files.
You can also simply run but to get the status. Check out but alias to set the default of but to something else.
Create a Branch
Let’s look at a very simple case first. Let’s say we’ve just modified some files and don’t have a branch yet. Our status might look like this:
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 A app/controllers/bookmarks_controller.rb ┊ i0 A app/models/bookmark.rb ┊ j0 M app/models/user.rb ┊ k0 A app/views/bookmarks/index.html.erb ┊ l0 M config/routes.rb ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but branch new` to create a new branch to work on
Now let’s say that we want to put those unassigned file changes into a commit on a new branch called user-bookmarks.
To do this, you can use the but branch new <branch-name> command.
✓ Created branch user-bookmarks
Now if you run but status you can see your new empty branch:
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 A app/controllers/bookmarks_controller.rb ┊ i0 A app/models/bookmark.rb ┊ j0 M app/models/user.rb ┊ k0 A app/views/bookmarks/index.html.erb ┊ l0 M config/routes.rb ┊ ┊╭┄us [user-bookmarks] (no commits) ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
Commit to a Branch
Now we can commit our unassigned changes to that branch. You can simply assign your changes to the branch first to commit later (we'll cover that later in Rubbing), but for now let's keep it simple and just commit them directly using the but commit command.
✓ Created commit d4147cc on branch user-bookmarks
If you don’t specify the -m commit message, GitButler will try to open an editor with a tempfile where you can write a longer commit message. It will use the $EDITOR environment variable if it’s set, or the core.editor Git or GitButler config setting, or it will prompt you for a command to run if you’re in an interactive terminal.
Now our status looks like this, with all unassigned files in a new commit on our new branch:
╭┄zz [unstaged changes] ┊ no changes ┊ ┊╭┄us [user-bookmarks] ┊● d4147cc all the user bookmarks ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but help` for all commands
You also don't need to create a branch to commit if you have none currently applied. If you run but commit and there are no active branches, GitButler will simply create a temporarily named one that you can later rename if you want.
Stacked and Parallel Branches
Ok, that’s the simple case, pretty straightforward. However, GitButler can also do some pretty cool things that Git either cannot do or struggles with, namely:
- Having multiple active branches that you can work on in parallel.
- Managing stacked branches.
That is, both multiple independent and dependent active branches. Even at the same time if you want.
Parallel Branches
Parallel branches is very simple, you can create multiple simultaneously active branches that you can assign and commit changes to in your workspace.
To create a parallel branch, you simply create a new branch the same way we did before. Let’s say that we want to create a liked-tweets branch alongside our existing user-bookmarks. We simply run the same but branch new command again:
✓ Created branch liked-tweets
Now if we run but status we can see our previous branch and our new empty branch.
╭┄zz [unstaged changes] ┊ g0 M app/controllers/likes_controller.rb ┊ h0 M app/models/like.rb ┊ ┊╭┄us [user-bookmarks] ┊● d4147cc all the user bookmarks ├╯ ┊ ┊╭┄li [liked-tweets] (no commits) ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
We can see our previous branch and the commit we made, our new empty branch and a couple of modified files. Now we can commit the unassigned changes to that branch with but commit -m "liked tweets changes" liked-tweets
✓ Created commit 1f3e69e on branch liked-tweets
And now we have one commit in each lane.
╭┄zz [unstaged changes] ┊ no changes ┊ ┊╭┄us [user-bookmarks] ┊● d4147cc all the user bookmarks ├╯ ┊ ┊╭┄li [liked-tweets] ┊● 1f3e69e liked tweets changes ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but help` for all commands
Here we specified the entire branch name as the commit target (as there is more than one), but you can also use the two character short code that is next to each one.
If you don’t specify a branch identifier and you have more than one active branch, then GitButler will prompt you for which branch you wish to commit the unassigned changes to.
We can also see which files were modified in each commit with the --files or -f option to but status:
╭┄zz [unstaged changes] ┊ no changes ┊ ┊╭┄us [user-bookmarks] ┊● d4147cc all the user bookmarks ┊│ d4:0 M Gemfile ┊│ d4:1 A app/controllers/bookmarks_controller.rb ┊│ d4:2 A app/models/bookmark.rb ┊│ d4:3 M app/models/user.rb ┊│ d4:4 A app/views/bookmarks/index.html.erb ┊│ d4:5 M config/routes.rb ├╯ ┊ ┊╭┄li [liked-tweets] ┊● 1f3e69e liked tweets changes ┊│ 1f:0 M app/controllers/likes_controller.rb ┊│ 1f:1 M app/models/like.rb ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but help` for all commands
Stacked Branches
The other way you can create new branches is to make them stacked, that is, one depends on another one and has to be merged in that order.
To create a new stacked branch in GitButler, you can run but branch new with a target branch ID. If we go back in time and instead stack our liked-tweets branch, we can make it dependent on the user-bookmarks branch by providing it as a stacking "anchor" with -a option:
✓ Created branch liked-tweets-stacked stacked on user-bookmarks
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 M app/models/user.rb ┊ i0 M config/routes.rb ┊ ┊╭┄li [liked-tweets-stacked] (no commits) ┊│ ┊├┄us [user-bookmarks] ┊● e677a2e user bookmarks feature ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
Now we can commit to our stacked branch.
✓ Created commit df499a6 on branch liked-tweets-stacked
╭┄zz [unstaged changes] ┊ no changes ┊ ┊╭┄li [liked-tweets-stacked] ┊● df499a6 liked tweets changes ┊│ ┊├┄us [user-bookmarks] ┊● e677a2e user bookmarks feature ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but help` for all commands
Now if you push to a forge, GitButler will set up the reviews (Pull Request or Merge Request) as a stacked request, where user-bookmarks has to be merged either before or with liked-tweets but they can be reviewed independently.
Assigning and Committing Changes
The other way to commit to a branch is to explicitly assign changes to it. This is somewhat like running git add in Git, where you’re staging some changes for a future commit. However, unlike Git where you have to do this or override it with -a or something, the default in GitButler is to commit all changes by default and only leave out unassigned changes with the flag -o or --only.
Staging Changes
So, how do we stage changes to a specific branch and then only commit those changes?
Let’s look at an example but status with six modified files and two empty, parallel branches and assign and commit one file to each branch as a separate commit.
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 A app/controllers/bookmarks_controller.rb ┊ i0 A app/models/bookmark.rb ┊ j0 M app/models/user.rb ┊ k0 A app/views/bookmarks/index.html.erb ┊ l0 M config/routes.rb ┊ ┊╭┄bo [user-bookmarks] (no commits) ├╯ ┊ ┊╭┄ch [user-changes] (no commits) ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
We will assign each file to a different branch and then see the result. We assign file changes to branches using the but stage command, which assigns changes to branches, much like git add, but you can do this for multiple branches.
You can either stage the file identifier that you see next to each file, or all or part of the file path. For example, in this case to identify the app/models/bookmark.rb file, you can do either:
g0app/models/bookmark.rb
So lets stage the bookmark changes to the bookmarks branch:
Staged the only hunk in app/controllers/bookmarks_controller.rb in the unassigned area → [user-bookmarks]. Staged the only hunk in app/models/bookmark.rb in the unassigned area → [user-bookmarks]. Staged the only hunk in app/views/bookmarks/index.html.erb in the unassigned area → [user-bookmarks].
Now we can run status and see that these are staged.
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 M app/models/user.rb ┊ i0 M config/routes.rb ┊ ┊ ╭┄u0 [staged to user-bookmarks] ┊ │ j0 A app/controllers/bookmarks_controller.rb ┊ │ k0 A app/models/bookmark.rb ┊ │ l0 A app/views/bookmarks/index.html.erb ┊ │ ┊╭┄bo [user-bookmarks] (no commits) ├╯ ┊ ┊╭┄ch [user-changes] (no commits) ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
Now let's rub the user changes into the user-changes branch:
Staged all hunks in app/models/user.rb in the unassigned area → [user-changes].
Now we have some file changes assigned to each branch and still some unassigned changes:
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 M config/routes.rb ┊ ┊ ╭┄u0 [staged to user-bookmarks] ┊ │ j0 A app/controllers/bookmarks_controller.rb ┊ │ k0 A app/models/bookmark.rb ┊ │ l0 A app/views/bookmarks/index.html.erb ┊ │ ┊╭┄bo [user-bookmarks] (no commits) ├╯ ┊ ┊ ╭┄v0 [staged to user-changes] ┊ │ i0 M app/models/user.rb ┊ │ ┊╭┄ch [user-changes] (no commits) ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
Now, if we want to create a commit in the user-bookmarks branch, we can either run but commit bo which will create a commit with the files assigned as well as both files that are unassigned, but not the file assigned to the user-changes lane.
Or, we can make a commit with only the assigned files in user-bookmarks by using the -o option to but commit.
✓ Created commit 23b044d on branch user-bookmarks
Now if we look at our status we can see a commit on our branch instead of the assigned changes:
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 M config/routes.rb ┊ ┊╭┄bo [user-bookmarks] ┊● 23b044d liked tweets view ├╯ ┊ ┊ ╭┄p0 [staged to user-changes] ┊ │ i0 M app/models/user.rb ┊ │ ┊╭┄ch [user-changes] (no commits) ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
Now let's commit all the rest of the changes (assigned and unassigned) to our other branch:
✓ Created commit 2ac26db on branch user-changes
╭┄zz [unstaged changes] ┊ no changes ┊ ┊╭┄bo [user-bookmarks] ┊● 23b044d liked tweets view ├╯ ┊ ┊╭┄ch [user-changes] ┊● 2ac26db bookmarks stuff ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but help` for all commands
Committing Specific Files or Hunks
Instead of staging files first and then committing with -o, you can also directly specify which files or hunks to include in a commit using the -p or --changes option. This lets you commit only specific changes without having to assign them to a branch first.
For example, if you have multiple unassigned files and only want to commit some of them:
You can specify files in several ways:
- By CLI ID: Use the short identifier shown in
but status(e.g.,h0,i0) - Space-separated:
--changes h0 i0 k0 - Comma-separated:
-p h0,i0,k0 - By path:
-p app/models/bookmark.rb
This also works with hunk IDs. When a file has multiple hunks (shown in but status -f or but diff), you can commit individual hunks rather than the entire file. This is useful when you have changes in the same file that belong to different logical commits.
If you don't specify -p, all uncommitted changes (or changes staged to the target branch) are committed. Use -p when you need fine-grained control over what goes into a commit.
Assigning Ranges
If you happen to have a large number of changes, you can also use ranges or lists for rubbing assignment. So for example, if we go back to this status:
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 A app/controllers/bookmarks_controller.rb ┊ i0 A app/models/bookmark.rb ┊ j0 A app/views/bookmarks/index.html.erb ┊ k0 M config/routes.rb ┊ ┊╭┄bo [user-bookmarks] (no commits) ├╯ ┊ ┊ ╭┄v0 [staged to user-changes] ┊ │ l0 M app/models/user.rb ┊ │ ┊╭┄ch [user-changes] (no commits) ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
Then you can assign the everything in app/ to a branch with:
Staged the only hunk in app/controllers/bookmarks_controller.rb in the unassigned area → [user-bookmarks]. Staged the only hunk in app/models/bookmark.rb in the unassigned area → [user-bookmarks]. Staged the only hunk in app/views/bookmarks/index.html.erb in the unassigned area → [user-bookmarks].
╭┄zz [unstaged changes] ┊ g0 M Gemfile ┊ h0 M config/routes.rb ┊ ┊ ╭┄u0 [staged to user-bookmarks] ┊ │ j0 A app/controllers/bookmarks_controller.rb ┊ │ k0 A app/models/bookmark.rb ┊ │ l0 A app/views/bookmarks/index.html.erb ┊ │ ┊╭┄bo [user-bookmarks] (no commits) ├╯ ┊ ┊ ╭┄v0 [staged to user-changes] ┊ │ i0 M app/models/user.rb ┊ │ ┊╭┄ch [user-changes] (no commits) ├╯ ┊ ┴ 204e309 (common base) [origin/main] 2025-07-06 Merge pull request #10 from schacon/sc-description Hint: run `but diff` to see uncommitted changes and `but stage <file>` to stage them to a branch
Last updated on