问题
We had two branches that existed simultaneously, like so:
A--B---C--------D--H---> (Branch A)
\ /
\---E--F--G-/--> (Branch B)
The issue is that we decided that we didn't want to merge Branch B into Branch A just yet, this was a mistake.
So, we (on Branch A) reverted the merge commit:
git checkout branch-a
git revert H
Resulting in:
A--B---C--------D--H--I--> (Branch A)
\ /
\---E--F--G-/--> (Branch B)
Which restored Branch A to its proper state. (Via the revert commit I
) We then did a bit more work on both branches, and now want to merge in again:
A--B---C--------D--H--I--J--K------?> (Branch A)
\ / /
\---E--F--G-/---L---M----N--/ (Branch B)
The problem is that according to Git, when we do the merge, the revert commit I
is newer than the commits E
, F
, and G
, and also newer than the merge commit H
so it's not adding the new files (and changes) from Branch B.
We can do some quick reverting of the reverting (yikes), but we'd like to prevent this from happening in the future. Is there any proper solution for undoing a merge (to a public repository), while still maintaining the ability to merge that branch back in in the future?
Edit: This Git blog post: http://git-scm.com/blog/2010/03/02/undoing-merges.html suggests that we should just 'revert the revert', as we're doing. But we'd like to find a better way, if possible, to undo a merge while maintaining the ability to merge the branch back in in the future - without having to remember that we reverted a similar merge sometime in the past.
回答1:
The straightforward way to undo a merge
A better way to undo a merge is to use git reset
. If you don't care to save any of the results of the merge, and assuming a clean working copy, you can just do the following with branch A checked out to your working copy.
git reset --hard D
If the workspace is not clean, or you want to save the results of the merge for some reason, you can do this:
git reset --mixed D # actually, you don't have to specify 'mixed', as this is the default
Both commands will leave your graph like this:
A--B---C--------D---> (Branch A)
\
\---E--F--G--> (Branch B)
However, the second command will leave changes in your working copy. See the git help reset
manpage for more info. Here are the key parts:
git reset [<mode>] [<commit>]
This form resets the current branch head to <commit> and possibly updates the index (resetting it to the tree of <commit>) and
the working tree depending on <mode>. If <mode> is omitted, defaults to "--mixed". The <mode> must be one of the following:
--mixed
Resets the index but not the working tree (i.e., the changed files are preserved but not marked for commit) and reports what
has not been updated. This is the default action.
--hard
Resets the index and working tree. Any changes to tracked files in the working tree since <commit> are discarded.
Doing it this way makes future merges perfectly normal because there is no history of reversion. As far as git is concerned, the next time you do a merge will be the first time and your life will carry on.
As a side note, the original merge commit, H, will still actually remain in your repository - however: if you have gc.auto
set, then some commands will cause a gc
, which prunes unreachable commits from your repository, which would include H. If you for some reason want to hang on to the merge commit, you need to create another branch to hold a reference to it or turn off gc.auto
and write down the first 7-8 characters of its commit id so you can find it again.
Oh crap, I did this on a public branch
Official guidance would have you avoid rewriting history on a published branch at all costs. I think the right way to think about it is to evaluate your costs. How many downstream clones do you have? Do any of them actually contribute work, or are they just mirrors? Is it really going to bother you that much to possibly have duplicate commits in your log history (only one time, hopefully)?
The nut of the question:
Is there any proper solution for undoing a merge (to a public repository), while still maintaining the ability to merge that branch back in in the future?
doesn't have a nice and tidy answer. One way is to revert the merge as suggested, leaving you with commit I:
A--B---C--------D--H--I--> (Branch A)
\ /
\---E--F--G-/--> (Branch B)
To make this more obvious, let's relabel the revert as H'. When you're ready to merge again, first enable rerere (rerere.enabled
) and then revert the revert:
A--B---C--------D--H--H'--J--K--H''--O-?> (Branch A)
\ / /
\---E--F--G-/---L---M----N----/ (Branch B)
If there are conflicts, then work through them the best you can and let rerere record the results (see the man page for details and how to do it right).
Junio Hamano and Linus Torvalds wrote a thorough explanation of this "revert-the-revert" scenario, and since writing this answer, I think "revert-the-revert" is the right answer for your scenario.
来源:https://stackoverflow.com/questions/21143660/git-revert-on-published-commits-while-maintaining-ability-to-merge-in-future