I amended instead of resolving conflict in interactive rebase. Can I undo?

1.7k views Asked by At

I was doing a big interactive rebase where I was editing something like 8 commits. This is done with git commit --amend. My issue is that at one point I got a merge conflict which I did not notice, so I kept on naively amending and git rebase --continue-ing. Apparently I now have amended multiple commits into one, and about 4 of my commits are gone after finishing the rebase. The changes has been amended into other commits.

Is there any way for me to undo these amends, or maybe reapply the commits that are gone?

Of course; one solution is to go back and do the rebase again, but I did quite a lot of edits in the rebase, so I would love to not have to re-do all my work. How I got into this predicament is another story.

Git tree example:

Before rebase: A-B-C-D-E-F

After rebase: A-B-C-F (C now has changes from D and E amended)

3

There are 3 answers

0
AnoE On BEST ANSWER

Yes, you can.

Normally, I would go right back and redo everything, but I can understand the problem - it sounds like you have invested a lot of time into the rebase already, so it does not seem inviting to do all of it again.

I assume that the contents of F in your example are exactly what you want your end result to be, you are just missing the D and E commits.

What you can do is this:

git checkout C             # "git status" should be empty right now
git checkout F .
git reset .
git add only-changes-from-D ; git commit -m D
git add only-changes-from-E ; git commit -m E
git add -A ; git commit -m F

The git checkout F . ; git reset . makes it so that your working directory contains F, but your HEAD is still pointed at C, and nothing is in the index. git status will now show you the complete diff between C and F.

Now you take your favourite add/commit tool (I like git gui) and pick out every change that belonged to D originally, and commmit. Repeat for E. After that, only the changes from F are there; you can add and commit them in one go. Of course, if there are more between E and F you will have to rinse and repeat.

This has the benefit that you do not actually need to change anything. The end result will be exactly F, but you will get a few intermediate commits as needed.

0
Melou On

After rebasing/amending a commit, the original commit is still here. Though if it is not reachable through a branch, it doesn't appear in a git log.

I think with the help of git reflog you can manage to do what you want.

git reflog //identify the hash of the commits you want, look for your commit --amend that you want to undo git checkout B git cherry-pick hash-of-commit-C-as-resolved-during-rebase-but-before-amending git cherry-pick hash-of-commit-C-after-amending-with-D git cherry-pick hash-of-commit-C-after-amending-with-E git cherry-pick F

0
torek On

Every commit that you made, amended or not, is still "in there", and they're all find-able through the reflogs. This is because git commit --amend does not actually change anything, it just makes a new commit that "shoves the old one aside", as it were.

Commits that you did not make (i.e., if there was a conflicted state in the index so that git commit refused to make a commit) were, of course, not saved.

Use git reflog (or git reflog HEAD) to find each commit made. You may want to save their raw hash IDs in a file for cut-and-paste, since the easy ways to name them, HEAD@{3} and so on, are all relative and will keep shifting. (I.e., every time you adjust HEAD, what was HEAD@{3} is now HEAD@{4}, HEAD@{5}, soon HEAD@{7}, and eventually HEAD@{198}, augh! :-) )

You can also get your original chain back right away (if you have not run any other commands) using the name ORIG_HEAD. That is, what happens when you run git rebase -i and then do all your editing, amend-commits, and so on, and finally finish, is that git rebase saves the original branch-tip under the name ORIG_HEAD. You can make a new branch or tag name point to that original commit, and then use name, name~1, name~2, and so on to refer back to the original (pre-rebase) commits:

git tag oops ORIG_HEAD

Now oops is commit F, oops~1 is E, and so on.

You can git show any commit, git checkout <commit-id> -- <path> to get a file out of it, git cherry-pick <commit-id> to apply that commit as a delta to your current position, and so on.