Flipping fixups downwards with git commits

If you do a lot of heavy lifting in git, sometimes when doing interactive rebases you might find yourself something like this:

pick 9cc0da4925b0 (fixup): Some fix for 272
pick fe9f818cd53e (fixup): Another fix for 272
pick 23da7ec20187 treewide: Linux 4.14.272
pick d751d28e01a2 (fixup): Fix yet again for 272
pick be4acf7ee8dd treewide: Linux 4.14.273

Moving around downstream

The above recently happened to me when trying to upgrade my phone's 4.14 kernel to its latest kernel SUBLEVEL. As Qualcomm uses its own fork of the kernel (usually called CAF or vendor kernels), merging mainline linux-stable branches becomes fairly painful due to downstream commits, which are either from CAF itself or from the ACK (Android's Common Kernel) that end up generating merge conflicts.

Once you fix a merge conflict, you commit the fix locally and continue with your merging until you reach the end of it; you then combine the fixed commits into the commits they are supposed to be in. This could lead to you slipping into the situation here described, where you have a commit or more that must be merged into a specific commit.

Turning against the flow

As simple as a fixup sounds, if you read the comments towards the end of your editor, you will find:

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

Which means we are trying to do it the opposite way here: we want our commands to be executed from bottom to top, so fixups can be issued towards the next commit, not the previous one.

In the best case scenario, we can move the two commits we want to be merged (9cc0da4925b0 and fe9f818cd53e) below the commit you want them merged into (23da7ec20187), but because we are dealing with a massive codebase here, this very likely will result into more painful merge conflicts because some code might either be missing or be already present. At worst, you can fixup everything above d751d28e01a2 and pick 9cc0da4925b0, manually copy the commit message from 23da7ec20187 and paste it at the end of the magic mixing.

There is this other command that you might have (like me) ignored until now: squash, which unlike fixup, it merges the specified commits into one but keeps the commit messages of them all. This seems to be especially useful in case you want to reword your final commit on the fly. In our case, instead of following the worst case scenario above, we can just delete the irrelevant commit messages and keep the one we want.

f is for Fixdown

There is, though, a trick to do the above more easily: fixup, yet again, but with a catch.

If you carefully read through git's rebase-todo file in the editor, in the commands section you should see:

f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor

That flag is just what we needed! Instead of throwing away the fixed-up commit's message, by using -C, we can use that specific message when the melding is complete. To solve our commits issue at the beginning, simulating a bottom to top execution order, we have now obtained this:

pick 9cc0da4925b0 (fixup): Some fix for 272
f fe9f818cd53e (fixup): Another fix for 272
f -C 23da7ec20187 treewide: Linux 4.14.272
f d751d28e01a2 (fixup): Fix yet again for 272
pick be4acf7ee8dd treewide: Linux 4.14.273

What happens here is interesting, but also simple to understand:

  1. Merge fe9f818cd53e into 9cc0da4925b0 and throw away the commit message of the first.
  2. Merge 23da7ec20187 into 9cc0da4925b0 but keep the commit message of the first, overriding 9cc0da4925b0's message.
  3. Finally, merge d751d28e01a2 into 9cc0da4925b0 and discard its commit message.

Ahead of the curve

The final result is a clean commit with the message treewide: Linux 4.14.272 that contains the 3 commits melded into one, with very few to no merge conflicts happening during the operation. This is especially relevant as the possibility of merge conflicts increases dramatically with larger codebases, due to the number of small commits creeping up to ease code reviews.

If you want to be extra sure of what you are doing during the fixups, you can also use -c to open up the text editor to view what is going to happen with the commits.