Painless commit splitting in Git
It just occurred to me that since Git stores full tree states, the easiest way to split a commit would be to make a copy of the final tree repeatedly, and in each copy cancel away some changes relative to the previous amended copy of the tree. It turns out you can actually do the procedural steps fairly mechanically.
For the sake of this example, I want to split out the combined changes to the files foo
, bar
, baz
and quux
over four commits. The parts you have to fill in are bolded; the rest is mechanical.
Make sure you are at the root level directory of your repository’s working copy.
-
Set a bookmark on the commit you want to split so you can refer to it easily:
git branch splitme 007dead
-
Now, interatively rebase the current commit series, starting at the parent of the commit you want to split:
git rebase -i splitme^
When your editor comes up with the rebase todo list, mark the
007dead
commit for editing. -
Once you have been dropped back into the shell, undo all changes except the ones you want in the first of the split commits, then amend the commit:
git reset HEAD^ quux baz bar git commit -m 'change foo' --amend
Here, you undid the changes to
quux
,baz
andbar
; the amended commit therefore contains only the changes tofoo
, which the new commit message reflects. -
Now comes the point where you create extra commits. Each follows the same steps:
git checkout splitme . git reset HEAD^ quux baz git commit -m 'change bar'
git checkout splitme . git reset HEAD^ quux git commit -m 'change baz'
git checkout splitme . git commit -m 'change quux'
This step is where the magic happens: you repeatedly check out a copy of the final state of the tree into the index and working copy (using
checkout
with a path, which is.
i.e. the project root directory) and commit it after undoing progressively fewer changes (usingreset
). In the last step no changes are undone, you just need it to write a new commit message.The secret sauce is that
splitme
always refers to the final state of the tree andHEAD^
always refers to a state missing some of the changes. -
Finally, continue the rebase. Once it’s finished, you can drop the bookmark.
git rebase --continue git branch -D splitme
That’s it.