Frage

I ran a git rebase on a branch and fixed all the conflicts, then realised for this particular branch I need to run git merge instead, to keep the history. Is there a way to "undo" the rebase but automatically reapply my fixes when running the git merge?

War es hilfreich?

Lösung

As Amber said in a comment, if you turned on rerere, git will have "re"corded your previous "re"solution (two of the three re's). Then if you reset things to pre-rebase state and "git merge" it should "re"use them (the last "re").

But if not, it won't.

What to do then? Assuming you're quite confident of your resolution :-) you could try this.

The original chain of commits (pre-rebase) is likely still pointed-to by ORIG_HEAD. If not, find the old branch-tip in the reflogs. Give it a label (branch or tag name), while also keeping a label on the post-rebase chain. That is, we want to resurrect the old branch tip, and also keep the new branch tip. (Exactly why should be obvious once you understand the rest of this.)

I'll assume that in rebasing you modified commits C and F and dropped D and E entirely as no-longer needed. Not that this is really matters. F' is the only crucial commit. The point is that commit F' captures the "final, all-resolved" version of the work-tree:

        C - D - E - F   <-- feature_orig
      /
A - B - G - H - I - J   <-- mainline
                      \
                        C' - F'  <-- feature

The above is what you can see after you do git branch feature_orig ORIG_HEAD (or recover the original tip from reflogs and use that to create feature_orig).

What you want now (per the original question) is a merge: either "merge feature into mainline" or "merge mainline into feature". Except, you want it to happen on/with feature_orig, rather than the rebased feature.

One other question though: do you want to keep the feature branch around? (If you're merging mainline into feature here, you probably do. If merging feature into mainline, maybe not. But at most, I suspect you will want to keep feature_orig, not the rebased line.) Let's move one more branch name around now, to—I hope—make the picture clearer:

$ git branch -m feature feature_rebased

Now we have this:

        C - D - E - F   <-- feature_orig
      /
A - B - G - H - I - J   <-- mainline
                      \
                        C' - F'  <-- feature_rebased

Now for the merge. Get onto the branch you want the merge commit to show up in:

$ git checkout ...

(I don't know which branch this is, feature_orig or mainline!)

Then do a merge, ignoring the result and not committing:

$ git merge --no-commit ...

(whichever branch you're merging-in).

Now just replace every single file with the version taken from commit F' (note, I'm assuming you're in your top level work directory here):

$ git rm -rf .; git checkout feature_rebased -- .

The rm step completely empties out the index, including all conflicts, and also the work tree. Then the checkout step completely repopulates the index and work-tree using whatever is in commit F' (as obtained through the branch name feature_rebased).

You may now commit the merge, on whichever branch you're on. For concreteness let's say you're on feature_orig and asked git to merge mainline. After you git commit you should have this as your commit graph:

        C - D - E - F - M  <-- feature_orig
      /               /
A - B - G - H - I - J   <-- mainline
                      \
                        C' - F'  <-- feature_rebased

I think it's worth mentioning here that this is just the commit graph. The contents of the tree—the files you get in your work-dir when you git checkout any of these commits—is determined by the contents of the index/staging-area at the time the commit was created.

That's why we can git rm everything and then git checkout id -- . to set up the contents of the merge commit. All we do when we commit the merge is create a commit M whose parents are F and J, and whose contents are "whatever is now in the index".

(The order of the parents F and J, and to which branch-name the new commit ID is written, is determined by which branch we are on when we start and finish the git merge process. If we are "on branch feature_orig then F is the first parent and M goes "on branch feature_orig". If we are "on branch mainline, then J is the first parent and M goes "on branch mainline". Also, of course, the prepared commit text, that you can edit before the commit happens, is a bit different. But you can edit that to read whatever you like.)


At this point (or maybe even earlier) you can now rename feature_orig to feature, if you want to keep the name. You can also delete branch feature_rebased as there's no need to save commits C' and F' any more. In fact, we only really wanted to hang on to F' so that we could git checkout its tree—we could have deleted the branch as soon as we finished that checkout.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top