Nik Trenchi
Published on

#TIL: Using git rebase to combine commits

I normally use git rebase with --fixup commits, and haven't had a need [until now] to take a bunch of commits made at different times and combine them.

Where possible, I prefer commits on the main branch to represent some "unit" of work1, to prevent a bunch of commits where you might be going back and forth on something from cluttering up the history2.

The PRs3 I submit day-to-day are normally isolated features that make this easy to accomplish. I use rebase a lot within a branch for myself before submitting a PR, but the extent is normally:

$ git add -p                           # add the specific thing I want to revise
$ git commit --fixup <hash>            # mark this as a fixup
$ git rebase -i --autosquash <hash>~1  # go to 1 commit _before_ the commit to be fixed

However, in building this blog there's been a few times where I added bits of things at different times, 20+ commits apart, and (for whatever reason4) didn't realize I wanted them all combined at the time of committing.

Specifically, I wanted to combine some commits to know "here's everything I did to get <specific feature> working".

Fortunately, git rebase makes this easy and includes helpful instructions in the rebase message:

These lines can be re-ordered; they are executed from top to bottom.

It's clearly laid out if you follow along on the git docs and is as simple as re-ordering the lines so that the commits to be combined are together, and marking them as squash.

For example, given a repo where these are all the commits:

$ git log --oneline
60d10b7 (HEAD -> example) finally figured out A
e68f105 did something for C
f8b495c hack related to A
2ce4970 did something for B
240ffc7 did something for A
ff37364 initial

Using git rebase -i 240ffc7~1 will show you:

pick 240ffc7 did something for A
pick 2ce4970 did something for B
pick f8b495c hack related to A
pick e68f105 did something for C
pick 60d10b7 finally figured out A

# Rebase ff37364..60d10b7 onto ff37364 (5 commands)

If you want to combine everything related to A (the 3 lines above), you can just reorder them like below and mark the two commits to be merged as squash:

pick 240ffc7 did something for A
squash f8b495c hack related to A
squash 60d10b7 finally figured out A
pick 2ce4970 did something for B
pick e68f105 did something for C

# Rebase ff37364..60d10b7 onto ff37364 (5 commands)

Then just save and close, and when you're done 5 it will look like:

$ git log --oneline
179c64f (HEAD -> example) did something for C
092aa5f did something for B
79f744b did something for A
ff37364 initial

Footnotes

  1. "Unit" as in, if you're submitting a PR for a new feature, that PR would get "squashed" into 1 commit.

    The PR itself should still have multiple commits to make it easy to follow along and review.

    It's a personal preference and not everyone likes this.

  2. You can always dig back into the PR to see the back and forth if you really wanted.

  3. PR means "Pull Request"

  4. Probably because lately it's been after 3am when I'm working on this 😅

  5. assuming you have no merge conflicts 🏆