How can I split up a Git commit buried in history?
There is a guide to splitting commits in the rebase manpage. The quick summary is:
Perform an interactive rebase including the target commit (e.g.
git rebase -i <commit-to-split>^ branch
) and mark it to be edited.When the rebase reaches that commit, use
git reset HEAD^
to reset to before the commit, but keep your work tree intact.Incrementally add changes and commit them, making as many commits as desired.
add -p
can be useful to add only some of the changes in a given file. Usecommit -c ORIG_HEAD
if you want to re-use the original commit message for a certain commit.If you want to test what you're committing (good idea!) use
git stash
to hide away the part you haven't committed (orstash --keep-index
before you even commit it), test, thengit stash pop
to return the rest to the work tree. Keep making commits until you get all modifications committed, i.e. have a clean work tree.Run
git rebase --continue
to proceed applying the commits after the now-split commit.
Here's how to do it with Magit.
Say commit ed417ae is the one you want to change; it contains two unrelated changes and is buried under one or more commits. Hit ll
to show the log, and navigate to ed417ae:
Then hit r
to open the rebase popup
and m
to modify the commit at point.
Notice how the @
there is now on the commit you want to split – that means HEAD is now at that commit:
We want to move HEAD to the parent, so navigate to the parent (47e18b3) and hit x
(magit-reset-quickly
, bound to o
if you're using evil-magit
) and enter to say "yes I meant commit at point". Your log should now look like:
Now, hit q
to go to the regular Magit status, then use the regular unstage u
command to unstage what doesn't go in the first commit, commit c
the rest as usual, then s
tage and c
ommit what goes in the second commit, and when done: hit r
to open the rebase popup
and another r
to continue, and you're done! ll
now shows:
To split a commit <commit>
and add the new commit before this one, and save the author date of <commit>
, — the steps are following:
Edit the commit before
<commit>
git rebase -i <commit>^^
NB: perhaps it will be also needed to edit
<commit>
as well.Cherry pick
<commit>
into the indexgit cherry-pick -n <commit>
Interactively reset unneeded changes from the index and reset the working tree
git reset -p && git checkout-index -f -a
As alternative, just stash unneeded changes interactively:
git stash push -p -m "tmp other changes"
Make other changes (if any) and create the new commit
git commit -m "upd something" .
Optionally, repeat the items 2-4 to add more intermediate commits.
Continue rebasing
git rebase --continue