GitButler ⧓

GuidesCLI Tutorial

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:

  1. 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
  2. A list of the active branches that you have and
    1. All assigned file changes in each branch
    2. 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:

$ but status
╭┄00 [Unassigned Changes] 
┊   wu M README-es.md 🔒 da42d06te A README.new.mdrt A app/views/bookmarks/index.html.erb 
┊
┊╭┄t5 [gemfile-fixes]  
┊●   da42d06 Add Spanish README and bookmarks feature    
┊●   fdbd753 just the one    
┊│
┊├┄qz [feature-bookmarks]  
┊   223fdd6 feat: Add bookmarks table to store user-tweet rela    
├╯
┊
┊╭┄q6 [sc-branch-26]  
┊●   f55a30e add bookmark model and associations    
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Here we can see three applied branches: gemfile-fixes stacked on feature-bookmarks and independent sc-branch-26. There are also three unassigned files.

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:

$ but status
╭┄00 [Unassigned Changes] 
┊   nx M Gemfileie A app/controllers/bookmarks_controller.rbxw A app/models/bookmark.rbku M app/models/user.rbrt A app/views/bookmarks/index.html.erbrv M config/routes.rb 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

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.

$ but branch new user-bookmarks
Created branch user-bookmarks

Now if you run but status you can see your new empty branch:

$ but status
╭┄00 [Unassigned Changes] 
┊   nx M Gemfileie A app/controllers/bookmarks_controller.rbxw A app/models/bookmark.rbku M app/models/user.rbrt A app/views/bookmarks/index.html.erbrv M config/routes.rb 
┊
┊╭┄nd [user-bookmarks] (no commits) 
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

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.

$ but commit -m 'all the user bookmarks'
Created commit 3a1658b 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:

$ but status
╭┄00 [Unassigned Changes] 
┊
┊╭┄nd [user-bookmarks]  
┊●   3a1658b all the user bookmarks    
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

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, and managing stacked branches. That is, both multiple independent and dependent active branches.

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:

$ but branch new liked-tweets
Created branch liked-tweets

Now if we run but status we can see our previous branch and our new empty branch.

$ but status
╭┄00 [Unassigned Changes] 
┊   t3 M app/controllers/likes_controller.rbou M app/models/like.rb 
┊
┊╭┄nd [user-bookmarks]  
┊●   3a1658b all the user bookmarks    
├╯
┊
┊╭┄u1 [liked-tweets] (no commits) 
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

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

$ but commit -m "liked tweets changes" liked-tweets
Created commit 87b1f79 on branch liked-tweets

And now we have one commit in each lane.

$ but status
╭┄00 [Unassigned Changes] 
┊
┊╭┄nd [user-bookmarks]  
┊●   3a1658b all the user bookmarks    
├╯
┊
┊╭┄u1 [liked-tweets]  
┊●   87b1f79 liked tweets changes    
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

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:

$ but status -f
╭┄00 [Unassigned Changes] 
┊
┊╭┄nd [user-bookmarks]  
┊●   3a1658b all the user bookmarks    
┊│     rn M Gemfile
┊│     ui A app/controllers/bookmarks_controller.rb
┊│     p7 A app/models/bookmark.rb
┊│     oq M app/models/user.rb
┊│     nr A app/views/bookmarks/index.html.erb
┊│     jq M config/routes.rb
├╯
┊
┊╭┄u1 [liked-tweets]  
┊●   87b1f79 liked tweets changes    
┊│     xn M app/controllers/likes_controller.rb
┊│     o1 M app/models/like.rb
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

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:

$ but branch new -a user-bookmarks liked-tweets-stacked
Created branch liked-tweets-stacked
$ but status
╭┄00 [Unassigned Changes] 
┊   nx M Gemfileku M app/models/user.rbrv M config/routes.rb 
┊
┊╭┄kq [liked-tweets-stacked] (no commits) 
┊│
┊├┄nd [user-bookmarks]  
┊●   e677a2e user bookmarks feature    
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Now we can commit to our stacked branch.

$ but commit -m "liked tweets changes" liked-tweets-stacked
Created commit 71f4fe2 on branch liked-tweets-stacked
$ but status
╭┄00 [Unassigned Changes] 
┊
┊╭┄kq [liked-tweets-stacked]  
┊●   71f4fe2 liked tweets changes    
┊│
┊├┄nd [user-bookmarks]  
┊●   e677a2e user bookmarks feature    
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

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.

Assigning Changes

So, how do we assign 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.

$ but status
╭┄00 [Unassigned Changes] 
┊   nx M Gemfileie A app/controllers/bookmarks_controller.rbxw A app/models/bookmark.rbku M app/models/user.rbrt A app/views/bookmarks/index.html.erbrv M config/routes.rb 
┊
┊╭┄nd [user-bookmarks] (no commits) 
├╯
┊
┊╭┄h4 [user-changes] (no commits) 
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

We will assign each file to a different branch and then see the result. We assign file changes to branches using the but rub command, which combines things. We'll go more into all that rubbing can do later.

You can either rub 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 any of:

  • xw
  • app/models/
  • bookmark
  • app/models/bookmark.rb

In order to differentiate a shortcode from a path, a shortcode is exactly 2 characters and a path needs to be at least 3. This is the same pattern matching used for the branch ID too.

So lets rub the bookmark changes into the bookmarks branch:

$ but rub xw,ie,rt user-bookmarks
Assigned app/models/bookmark.rb[user-bookmarks].
Assigned app/controllers/bookmarks_controller.rb[user-bookmarks].
Assigned app/views/bookmarks/index.html.erb[user-bookmarks].

Now let's rub the user changes into the user-changes branch:

$ but rub ku user-changes
Assigned app/models/user.rb[user-changes].

Now we have some file changes assigned to each branch and still some unassigned changes:

$ but status
╭┄00 [Unassigned Changes] 
┊   nx M Gemfilerv M config/routes.rb 
┊
┊╭┄nd [user-bookmarks] (no commits) 
┊│   md A app/controllers/bookmarks_controller.rb 
┊│   pe A app/models/bookmark.rb 
┊│   v0 A app/views/bookmarks/index.html.erb 
├╯
┊
┊╭┄h4 [user-changes] (no commits) 
┊│   kx M app/models/user.rb 
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Now, if we want to create a commit in the user-bookmarks branch, we can either run but commit nd 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.

$ but commit -o -m "liked tweets view" h4
Created commit d9d0b94 on branch user-changes

Now if we look at our status we can see a commit on our branch instead of the assigned changes:

$ but status
╭┄00 [Unassigned Changes] 
┊   nx M Gemfilerv M config/routes.rb 
┊
┊╭┄nd [user-bookmarks] (no commits) 
┊│   md A app/controllers/bookmarks_controller.rb 
┊│   pe A app/models/bookmark.rb 
┊│   v0 A app/views/bookmarks/index.html.erb 
├╯
┊
┊╭┄h4 [user-changes]  
┊●   d9d0b94 liked tweets view    
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Now let's commit all the rest of the changes (assigned and unassigned) to our other branch:

$ but commit -m 'bookmarks stuff' nd
Created commit e36adcf on branch user-bookmarks
$ but status
╭┄00 [Unassigned Changes] 
┊
┊╭┄nd [user-bookmarks]  
┊●   e36adcf bookmarks stuff    
├╯
┊
┊╭┄h4 [user-changes]  
┊●   d9d0b94 liked tweets view    
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

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:

$ but status
╭┄00 [Unassigned Changes] 
┊   nx M Gemfileie A app/controllers/bookmarks_controller.rbxw A app/models/bookmark.rbku M app/models/user.rbrt A app/views/bookmarks/index.html.erbrv M config/routes.rb 
┊
┊╭┄nd [user-bookmarks] (no commits) 
├╯
┊
┊╭┄h4 [user-changes] (no commits) 
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Then you can assign the first, third, and fifth file to a branch with:

$ but rub nx,xw,rt user-bookmarks
Assigned Gemfile[user-bookmarks].
Assigned app/models/bookmark.rb[user-bookmarks].
Assigned app/views/bookmarks/index.html.erb[user-bookmarks].
$ but status
╭┄00 [Unassigned Changes] 
┊   ie A app/controllers/bookmarks_controller.rbku M app/models/user.rbrv M config/routes.rb 
┊
┊╭┄nd [user-bookmarks] (no commits) 
┊│   ro M Gemfile 
┊│   pe A app/models/bookmark.rb 
┊│   v0 A app/views/bookmarks/index.html.erb 
├╯
┊
┊╭┄h4 [user-changes] (no commits) 
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Last updated on

On this page

Edit on GitHubGive us feedback