Use git rebase interactively

When you need to do any of the following, use an “interactive rebase” to achieve it.

  • Reorder commits
  • Remove/delete a commit or commits from your history
  • Reword a commit that is not the top commit in your branch
  • Mash (aka “squash” or “fixup”) 2 or more commits into one
  • Any combination of the above
A tip about interactive rebasing
Avoid regular rebasing (adding some other branch’s commits into the history of your branch) and interactive rebasing (changing commit order, commit messages, squashing, etc.) in the same step. When you’re trying to get commits from another branch into your branch, just rebase without any adjustment/interaction. Only rebase interactively against a commit that’s already in your branch’s history (i.e. a fast-forward commit).

How to do it

Pick a commit that’s farther back in history than the one you want to modify, and pass it to “git rebase.”

git rebase -i <some-older-commit>

Your editor will pop open with a list of commits between your current commit and the one you passed to git rebase. You’ll notice each commit is preceded by the word “pick,” and there is a list of “commands” in a comment at the bottom (not shown here).

pick c81bf8b7 Fix wrong harbulary battery insertion instructions
pick 97b462c8 Fix typo in harbulary battery insertion instructions
pick 6ce6071c Add battery diagram in chassis design
pick f2c8d028 WIP boost search results for context fields
pick 7ce398ba Fix another typo in harbulary battery insertion instructions
pick 08bf54c9 Improve search results boost for context fields
pick 0940ee30 Break breadcrumb nav between items on mobile
pick 28fe8891 TEMP debugging/hacking some difficult problem
pick fcbbb31f Fix case sensitivity in search results highlighting
pick 7884a09a Fix error when no avlabiable LEDs are found
pick 87c7fede Add "boost" button to the search chassis

This is where you tell git what you want to do with your commits. Note that these commits are oldest at the top, which is different from what you may be accustomed to when using git log or git show-branch. Each line is in the format:

<command> <commit-id> <commit-message-title>

Make edits to the list of commits, and change the “pick” command to whatever you’d like to modify your commits. You can use the long or short form of the command you want. At this point, you can also reorder these lines, and the commits will be reordered in the rebased branch. You can also delete lines, and those commits will be excluded from the rebased branch. Of the available commands, you will at first find the following most useful:

command short form what it does
pick p use commit
reword r use commit, but edit the commit message
squash s use commit, but meld into previous commit (and let you edit the new commit’s message). All the changes from both commits will end up in a single commit.
fixup f like “squash”, but discard this commit’s log message (just use the previous commit’s log message)

In our case, we could modify the lines to look like this:

pick c81bf8b7 Fix wrong harbulary battery insertion instructions
f 97b462c8 Fix typo in harbulary battery insertion instructions
f 7ce398ba Fix another typo in harbulary battery insertion instructions
pick 6ce6071c Add battery diagram in chassis design
pick f2c8d028 WIP boost search results for context fields
s 08bf54c9 Improve search results boost for context fields
pick 0940ee30 Break breadcrumb nav between items on mobile
pick fcbbb31f Fix case sensitivity in search results highlighting
r 7884a09a Fix error when no avlabiable LEDs are found
pick 87c7fede Add "boost" button to the search chassis

As you can see

  • I’ve reordered some commits to make them easier to squash and fixup.
  • I’ve removed commit 28fe8891 “TEMP debugging/hacking some difficult problem”
  • I want to “fixup” commits 2 and 3 into the first commit (they will all form one commit and use the commit message from the first commit).
  • I want to “squash” the sixth commit into the 5th commit (they will form one commit and I’ll edit the commit message).
  • I want to “reword,” or change the commit message (I had a typo) of the second-to-last commit.

Now save and close the editor. At this point, for any commands for which you must edit the commit message (e.g. “squash” and “reword”), your editor will pop open in sequence asking you to edit those messages. Edit, save and close, and the next will pop open until all your changes are done. Now my commits will look something like this

c81bf8b7 Fix wrong harbulary battery insertion instructions
6ce6071c Add battery diagram in chassis design
f2c8d028 Boost search results for context fields
0940ee30 Break breadcrumb nav between items on mobile
fcbbb31f Fix case sensitivity in search results highlighting
7884a09a Fix error when no available LEDs are found
87c7fede Add "boost" button to the search chassis

Much cleaner looking! This is organized and there’s nothing in here I don’t want in here.

Rebase conflicts

It’s of course possible to encounter conflicts during rebasing. You should take steps to minimize them as much as possible, and then, when they occur, just resolve them.

Minimizing rebase conflicts

To minimize rebase conflicts, have a look at the following.

  • Try to commit atomically as best you can
  • When reordering commits, avoid placing dependent commits before their dependencies. For example, if one commit adds a new feature, and another commit modifies that feature, make sure the “modifies the feature” commit comes after the “adds the feature” commit.

Resolving rebase conflicts

Sometimes conflicts are unavoidable, of course. And when you encounter rebase conflicts, the resolution process can be a bit confusing. When rebasing, git “rewinds” the commits to the first commit in the rebase, then applies each of your commits (taking into account the commands if it’s an interactive rebase) in sequence to that. So the incoming commit is the commit from your current branch, as it’s getting added in sequence.

For example, in the following merge conflict situation, the “older” commit is HEAD, and the commit from your branch that’s currently getting applied is:

e0a8f28f Harbulate the crontab slap-bracelet

This can be confusing because normally we associate HEAD with the context we’re currently working in. It still does represent that here, but we’re in the middle of applying commits sequentially, so in this case HEAD merely represents the previously applied commit.

      port: 666
      log_level: WARNING
      path: <%= Rails.application.secrets.harbulator_path %>
++<<<<<<< HEAD
 +    beef_timeout: 2
++=======
+     timeout: 60
++>>>>>>> e0a8f28f (Harbulate the crontab slap-bracelet)
      lettuce_timeout: 0.5
      species: <%= ActiveRecord::Type::Boolean.new.cast(Rails.application.secrets.harbulator_species).present? %>