GitButler ⧓

GuidesCLI Tutorial

Rubbing

Learn how to use the rub command to apply changes to branches.

As we saw in the Branching and Committing section, the but rub command can be used to assign changes to branch lanes.

However, it can be used to do so much more. Rubbing is essentially combining two things. Since there are lots of things in the tool, combining them together can do lots of different operations. Most of them should be fairly intuitive once you understand the concept.

Let’s take a look at what is possible with this very straightforward command.

Unassigning Changes

We already showed how you can use rub to assign a file change or set of changes to a branch for later committing (rubbing a file and a branch), but what if you want to undo that? Move assignments to a different lane or revert them to being unassigned for later?

As you may have noticed in the but status output, there is a special identifier 00 which is always the “unassigned” ID. If you rub anything to 00 then it will move it to unassigned.

So given this status:

$ but status
╭┄00 [Unassigned Changes] 
┊   te A README.new.mdrt A app/views/bookmarks/index.html.erb 
┊
┊╭┄t5 [gemfile-fixes]  
┊│   q2 M README-es.md 🔒 da42d06
┊●   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

We can re-unassign the README.new.md file with but rub np 00. Or, we can re-assign that file to the sc-branch-26 parallel branch with but rub np q6.

Amending Commits

However, branch assignment is not all we can do with rubbing. We can also use it to move things to and from commits. A common example would be to amend a commit with new work.

Let’s say that we sent commits out for review and got feedback and instead of creating new commits to address the review, we wanted to actually fix up our commits to be better. This is somewhat complicated to do in Git (something something fixup commit, autosquash, etc).

However, with rub it’s incredibly simple. Just rub the new changes into the target commit rather than a branch.

Let’s say that we have a branch with some commits in it, we’ve made changes to two files and want to amend two different commits with the new changes.

$ but status
╭┄00 [Unassigned Changes] 
┊   te A README.new.mdrt A app/views/bookmarks/index.html.erb 
┊
┊╭┄t5 [gemfile-fixes]  
┊│   q2 M README-es.md 🔒 da42d06
┊●   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

If we want to update the first commit (da42d06) with the README-es.md changes and the last commit (fdbd753) with the app/views/bookmarks/index.html.erb changes, we can run the following two rub commands:

$ but rub q2 da
Amended README-es.md42423c4
$ but rub rt fd
Amended app/views/bookmarks/index.html.erbd077078
$ but status --files
╭┄00 [Unassigned Changes] 
┊   te A README.new.md 
┊
┊╭┄t5 [gemfile-fixes]  
┊●   b0f62d6 Add Spanish README and bookmarks feature    
┊│     pc M Gemfile
┊│     yq A README-es.md
┊│     gy A app/controllers/bookmarks_controller.rb
┊│     iy M app/views/dashboard/index.html.erb
┊●   d077078 just the one    
┊│     od M app/models/user.rb
┊│     v8 A app/views/bookmarks/index.html.erb
┊│
┊├┄qz [feature-bookmarks]  
┊   223fdd6 feat: Add bookmarks table to store user-tweet rela    
┊│     wj A app/models/bookmark.rb
┊│     i0 M config/routes.rb
┊│     hq A spacer.txt
┊│     ww A testing.md
├╯
┊
┊╭┄q6 [sc-branch-26]  
┊●   f55a30e add bookmark model and associations    
┊│     ls M app/models/tweet.rb
┊│     p0 A db/migrate/20250925000001_create_bookmarks.rb
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Notice that the SHAs have changed for those commits. It has rewritten the commits to have the same messages but incorporated the changes you rubbed into those patches.

If you wanted to rub all the unassigned changes into a specific commit, you could also do that by rubbing the unassigned section to a commit, for example but rub 00 1l which would take all unassigned changes and amend commit 1l with them.

Squashing Commits

File changes are not the only thing that you can rub. You can also rub commits into things. To squash two commits together, you simply rub them together. Let’s take the last status output and squash the two commits in gemfile-fixes together:

$ but rub 21 f6
Squashed 212f9faf6532f9

Now we can see that we only have one commit in our branch:

$ but status
╭┄00 [Unassigned Changes] 
┊   te A README.new.md 
┊
┊╭┄t5 [gemfile-fixes]  
┊●   5e982e0 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

You probably want to edit the commit message after this too, since it will simply combine the two commit messages.

Uncommitting

Let’s say that we want to just undo a commit - that is, pretend that we had not made that commit and instead put the changes back to unassigned status. In this case we would use the special 00 ID that we talked about earlier, just like unassigning changes, we can unassign commits.

So, if we’re back to this status:

$ 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

And we want to un-commit the first commit (fdbd753) as though we had never made it, you can rub to 00:

$ but rub fd 00
Uncommitted fdbd753

Now if we look at our status again, we will see that commit removed and those files back in the unassigned status:

$ but status
╭┄00 [Unassigned Changes] 
┊   wu M README-es.md 🔒 eeaf0efte A README.new.mdku M app/models/user.rbrt A app/views/bookmarks/index.html.erb 
┊
┊╭┄t5 [gemfile-fixes]  
┊●   eeaf0ef Add Spanish README and bookmarks feature    
┊│
┊├┄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

Moving Commits

We can also use rubbing to move a commit from one branch to another branch if we have multiple active branches and committed to the wrong one, or otherwise decide that we want to split up independent work.

Let’s say that we have two commits on one branch and one commit on a second parallel branch and want to move one:

$ but status
╭┄00 [Unassigned Changes] 
┊   te A README.new.md 
┊
┊╭┄t5 [gemfile-fixes]  
┊●   212f9fa Add Spanish README and bookmarks feature    
┊●   f6532f9 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

We can move the “Add Spanish README and bookmarks feature” commit to the sc-branch-26 branch with but rub:

$ but rub 21 q6
Moved 212f9fa[sc-branch-26]

Now we can see that the commit has been moved to the top of the sc-branch-26 branch:

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

Notice that the only SHA that changed was the one that moved, since nothing else needed to be rebased. Rubbing a commit to another branch always adds it to the top of that branch.

As you might imagine, you can also simultaneously move and squash by rubbing a commit in one branch on a commit in another branch too.

Moving Files between Commits

You can also move specific file changes from one commit to another.

To do that, you need identifiers for the files and hunks in an existing commit, which you can get via a but status -f, or but status --files that tells status to also list commit file IDs.

$ but status -f
╭┄00 [Unassigned Changes] 
┊   te A README.new.md 
┊
┊╭┄t5 [gemfile-fixes]  
┊●   f6532f9 just the one    
┊│     p5 M app/models/user.rb
┊│     oq A app/views/bookmarks/index.html.erb
┊│
┊├┄qz [feature-bookmarks]  
┊   223fdd6 feat: Add bookmarks table to store user-tweet rela    
┊│     wj A app/models/bookmark.rb
┊│     i0 M config/routes.rb
┊│     hq A spacer.txt
┊│     ww A testing.md
├╯
┊
┊╭┄q6 [sc-branch-26]  
┊●   221a4a4 Add Spanish README and bookmarks feature    
┊│     wk M Gemfile
┊│     j8 A README-es.md
┊│     vo A app/controllers/bookmarks_controller.rb
┊│     j8 M app/views/dashboard/index.html.erb
┊●   f55a30e add bookmark model and associations    
┊│     ls M app/models/tweet.rb
┊│     p0 A db/migrate/20250925000001_create_bookmarks.rb
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

So now we can move the changes from one commit to another by rubbing pretty easily. Let’s take the app/controllers/bookmarks_controller.rb change and move it down to the "add bookmark model and associations" commit:

$ but rub vo f5
Moved files between commits!

Now the change is in the previous commit:

$ but status -f
╭┄00 [Unassigned Changes] 
┊   te A README.new.md 
┊
┊╭┄t5 [gemfile-fixes]  
┊●   f6532f9 just the one    
┊│     p5 M app/models/user.rb
┊│     oq A app/views/bookmarks/index.html.erb
┊│
┊├┄qz [feature-bookmarks]  
┊   223fdd6 feat: Add bookmarks table to store user-tweet rela    
┊│     wj A app/models/bookmark.rb
┊│     i0 M config/routes.rb
┊│     hq A spacer.txt
┊│     ww A testing.md
├╯
┊
┊╭┄q6 [sc-branch-26]  
┊●   474f1fb Add Spanish README and bookmarks feature    
┊│     yz M Gemfile
┊│     tm A README-es.md
┊│     tq M app/views/dashboard/index.html.erb
┊●   48bb228 add bookmark model and associations    
┊│     ud A app/controllers/bookmarks_controller.rb
┊│     sg M app/models/tweet.rb
┊│     sx A db/migrate/20250925000001_create_bookmarks.rb
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Splitting Commits

Ok, so now we can be pretty specifc about moving changes around to all these different states. The last thing we’ll cover here is splitting commits, which requires a new command that creates a new empty commit called but new.

By default, but new will create a new empty commit at the top of the most recently created branch, however you can specify a different branch, or even a specific place within a branch with existing commits.

Let’s say that we’re back to this state:

$ 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

Now we want to split the "add bookmark model and associations" into two separate commits. The way we do this is to insert a blank commit in between 06 and f5 and then rub changes into it (then probably edit the commit message).

We can insert a blank commit by running but new f5 which inserts a blank commit under the specified commit.

$ but new f5
Created blank commit before commit f55a30e

Now we have a blank commit:

$ but status -f
╭┄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    
┊│     r7 M Gemfile
┊│     wz A README-es.md
┊│     y5 A app/controllers/bookmarks_controller.rb
┊│     k6 M app/views/dashboard/index.html.erb
┊●   fdbd753 just the one    
┊│     v0 M app/models/user.rb
┊│
┊├┄qz [feature-bookmarks]  
┊   223fdd6 feat: Add bookmarks table to store user-tweet rela    
┊│     wj A app/models/bookmark.rb
┊│     i0 M config/routes.rb
┊│     hq A spacer.txt
┊│     ww A testing.md
├╯
┊
┊╭┄q6 [sc-branch-26]  
┊●   54e8cde (no commit message) (no changes)   
┊●   f55a30e add bookmark model and associations    
┊│     ls M app/models/tweet.rb
┊│     p0 A db/migrate/20250925000001_create_bookmarks.rb
├╯
┊
 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description

Now we can use the previous method of moving file changes from other commits into it, then edit the commit message with but describe 54 (for more on the describe command, see Editing Commits, coming up next).

Last updated on

On this page

Edit on GitHubGive us feedback