What happened
You mean amend, not append, right? I'm going to pretend this was on a branch called master, for convenience. This is what your repository looks like now:
A---B' (master)
\
\-B---C---D
Git commits explicitly depend on their parents - the same patch on top of a different parent is a different commit.
How to recover
You can recover the previous position of a few ways. There's a nice shorthand for previous positions, which you can use to directly check it out or create a branch:
git checkout master@{1}
git branch oldmaster master@{1}
This is assuming it's the first previous position. It might be the second (master@{2}
)... or if you know when it was, you can use master@{7:35}
or master@{23.hours.ago}
. For a summary of these forms, see the "specifying revisions" section of man git-rev-parse
(online here).
If you're not sure exactly how to get to it, try
git reflog show master
This will give you a list of previous positions of master
, and you should be able to tell from the descriptions which one you want (or maybe try a few). You can simply copy hashes from the list, and use git checkout
or git branch
as above.
What you should have done
Warning: editing history is a bad idea if it's been published already - in that case, you should simply commit the fix. Yes, it's kind of ugly having it split into two commits in the repository, but other users have to be able to trust what they've seen in the public repo not to change!
That said, to do this particular kind of history editing, you want interactive rebase:
git rebase -i master~4 master
master~4
represents the commit four commits before the tip of master. You can use any form you want here - maybe it's another branch, maybe a commit hash - whatever works.
This will open up in an editor a list of the commits you're playing with:
pick <hash-A> <message-A>
pick <hash-B> <message-B>
pick <hash-C> <message-C>
pick <hash-D> <message-D>
# Rebase <hash-A^>..<hash-D> onto <hash-A^>
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
The commented-out help text there is pretty self-explanatory. In this case, you want to change 'pick' to 'edit' on commit B's line, save, and quit. The rebase will start, and it'll pause after applying B
to let you make changes. You'll do what you need to, add, use git commit --amend
, and then git rebase --continue
. It'll apply C
and D
, and you'll be done. If anything goes wrong in the middle, use git rebase --abort
to get back to where you started.
Rebasing can be kind of scary - for example, don't accidentally remove lines in that list! If you're not comfortable with it yet, it's a good idea to make heavy use of gitk
and backup branch names.