|  | Date: Fri, 19 Dec 2008 00:45:19 -0800 | 
|  | From: Linus Torvalds <torvalds@linux-foundation.org>, Junio C Hamano <gitster@pobox.com> | 
|  | Subject: Re: Odd merge behaviour involving reverts | 
|  | Abstract: Sometimes a branch that was already merged to the mainline | 
|  | is later found to be faulty. Linus and Junio give guidance on | 
|  | recovering from such a premature merge and continuing development | 
|  | after the offending branch is fixed. | 
|  | Message-ID: <7vocz8a6zk.fsf@gitster.siamese.dyndns.org> | 
|  | References: <alpine.LFD.2.00.0812181949450.14014@localhost.localdomain> | 
|  | Content-type: text/asciidoc | 
|  |  | 
|  | How to revert a faulty merge | 
|  | ============================ | 
|  |  | 
|  | Alan <alan@clueserver.org> said: | 
|  |  | 
|  | I have a master branch. We have a branch off of that that some | 
|  | developers are doing work on. They claim it is ready. We merge it | 
|  | into the master branch. It breaks something so we revert the merge. | 
|  | They make changes to the code. they get it to a point where they say | 
|  | it is ok and we merge again. | 
|  |  | 
|  | When examined, we find that code changes made before the revert are | 
|  | not in the master branch, but code changes after are in the master | 
|  | branch. | 
|  |  | 
|  | and asked for help recovering from this situation. | 
|  |  | 
|  | The history immediately after the "revert of the merge" would look like | 
|  | this: | 
|  |  | 
|  | ---o---o---o---M---x---x---W | 
|  | / | 
|  | ---A---B | 
|  |  | 
|  | where A and B are on the side development that was not so good, M is the | 
|  | merge that brings these premature changes into the mainline, x are changes | 
|  | unrelated to what the side branch did and already made on the mainline, | 
|  | and W is the "revert of the merge M" (doesn't W look M upside down?). | 
|  | IOW, "diff W^..W" is similar to "diff -R M^..M". | 
|  |  | 
|  | Such a "revert" of a merge can be made with: | 
|  |  | 
|  | $ git revert -m 1 M | 
|  |  | 
|  | After the developers of the side branch fix their mistakes, the history | 
|  | may look like this: | 
|  |  | 
|  | ---o---o---o---M---x---x---W---x | 
|  | / | 
|  | ---A---B-------------------C---D | 
|  |  | 
|  | where C and D are to fix what was broken in A and B, and you may already | 
|  | have some other changes on the mainline after W. | 
|  |  | 
|  | If you merge the updated side branch (with D at its tip), none of the | 
|  | changes made in A nor B will be in the result, because they were reverted | 
|  | by W. That is what Alan saw. | 
|  |  | 
|  | Linus explains the situation: | 
|  |  | 
|  | Reverting a regular commit just effectively undoes what that commit | 
|  | did, and is fairly straightforward. But reverting a merge commit also | 
|  | undoes the _data_ that the commit changed, but it does absolutely | 
|  | nothing to the effects on _history_ that the merge had. | 
|  |  | 
|  | So the merge will still exist, and it will still be seen as joining | 
|  | the two branches together, and future merges will see that merge as | 
|  | the last shared state - and the revert that reverted the merge brought | 
|  | in will not affect that at all. | 
|  |  | 
|  | So a "revert" undoes the data changes, but it's very much _not_ an | 
|  | "undo" in the sense that it doesn't undo the effects of a commit on | 
|  | the repository history. | 
|  |  | 
|  | So if you think of "revert" as "undo", then you're going to always | 
|  | miss this part of reverts. Yes, it undoes the data, but no, it doesn't | 
|  | undo history. | 
|  |  | 
|  | In such a situation, you would want to first revert the previous revert, | 
|  | which would make the history look like this: | 
|  |  | 
|  | ---o---o---o---M---x---x---W---x---Y | 
|  | / | 
|  | ---A---B-------------------C---D | 
|  |  | 
|  | where Y is the revert of W. Such a "revert of the revert" can be done | 
|  | with: | 
|  |  | 
|  | $ git revert W | 
|  |  | 
|  | This history would (ignoring possible conflicts between what W and W..Y | 
|  | changed) be equivalent to not having W nor Y at all in the history: | 
|  |  | 
|  | ---o---o---o---M---x---x-------x---- | 
|  | / | 
|  | ---A---B-------------------C---D | 
|  |  | 
|  | and merging the side branch again will not have conflict arising from an | 
|  | earlier revert and revert of the revert. | 
|  |  | 
|  | ---o---o---o---M---x---x-------x-------* | 
|  | / / | 
|  | ---A---B-------------------C---D | 
|  |  | 
|  | Of course the changes made in C and D still can conflict with what was | 
|  | done by any of the x, but that is just a normal merge conflict. | 
|  |  | 
|  | On the other hand, if the developers of the side branch discarded their | 
|  | faulty A and B, and redone the changes on top of the updated mainline | 
|  | after the revert, the history would have looked like this: | 
|  |  | 
|  | ---o---o---o---M---x---x---W---x---x | 
|  | / \ | 
|  | ---A---B A'--B'--C' | 
|  |  | 
|  | If you reverted the revert in such a case as in the previous example: | 
|  |  | 
|  | ---o---o---o---M---x---x---W---x---x---Y---* | 
|  | / \ / | 
|  | ---A---B A'--B'--C' | 
|  |  | 
|  | where Y is the revert of W, A' and B' are rerolled A and B, and there may | 
|  | also be a further fix-up C' on the side branch. "diff Y^..Y" is similar | 
|  | to "diff -R W^..W" (which in turn means it is similar to "diff M^..M"), | 
|  | and "diff A'^..C'" by definition would be similar but different from that, | 
|  | because it is a rerolled series of the earlier change. There will be a | 
|  | lot of overlapping changes that result in conflicts. So do not do "revert | 
|  | of revert" blindly without thinking.. | 
|  |  | 
|  | ---o---o---o---M---x---x---W---x---x | 
|  | / \ | 
|  | ---A---B A'--B'--C' | 
|  |  | 
|  | In the history with rebased side branch, W (and M) are behind the merge | 
|  | base of the updated branch and the tip of the mainline, and they should | 
|  | merge without the past faulty merge and its revert getting in the way. | 
|  |  | 
|  | To recap, these are two very different scenarios, and they want two very | 
|  | different resolution strategies: | 
|  |  | 
|  | - If the faulty side branch was fixed by adding corrections on top, then | 
|  | doing a revert of the previous revert would be the right thing to do. | 
|  |  | 
|  | - If the faulty side branch whose effects were discarded by an earlier | 
|  | revert of a merge was rebuilt from scratch (i.e. rebasing and fixing, | 
|  | as you seem to have interpreted), then re-merging the result without | 
|  | doing anything else fancy would be the right thing to do. | 
|  | (See the ADDENDUM below for how to rebuild a branch from scratch | 
|  | without changing its original branching-off point.) | 
|  |  | 
|  | However, there are things to keep in mind when reverting a merge (and | 
|  | reverting such a revert). | 
|  |  | 
|  | For example, think about what reverting a merge (and then reverting the | 
|  | revert) does to bisectability. Ignore the fact that the revert of a revert | 
|  | is undoing it - just think of it as a "single commit that does a lot". | 
|  | Because that is what it does. | 
|  |  | 
|  | When you have a problem you are chasing down, and you hit a "revert this | 
|  | merge", what you're hitting is essentially a single commit that contains | 
|  | all the changes (but obviously in reverse) of all the commits that got | 
|  | merged. So it's debugging hell, because now you don't have lots of small | 
|  | changes that you can try to pinpoint which _part_ of it changes. | 
|  |  | 
|  | But does it all work? Sure it does. You can revert a merge, and from a | 
|  | purely technical angle, git did it very naturally and had no real | 
|  | troubles. It just considered it a change from "state before merge" to | 
|  | "state after merge", and that was it. Nothing complicated, nothing odd, | 
|  | nothing really dangerous. Git will do it without even thinking about it. | 
|  |  | 
|  | So from a technical angle, there's nothing wrong with reverting a merge, | 
|  | but from a workflow angle it's something that you generally should try to | 
|  | avoid. | 
|  |  | 
|  | If at all possible, for example, if you find a problem that got merged | 
|  | into the main tree, rather than revert the merge, try _really_ hard to | 
|  | bisect the problem down into the branch you merged, and just fix it, or | 
|  | try to revert the individual commit that caused it. | 
|  |  | 
|  | Yes, it's more complex, and no, it's not always going to work (sometimes | 
|  | the answer is: "oops, I really shouldn't have merged it, because it wasn't | 
|  | ready yet, and I really need to undo _all_ of the merge"). So then you | 
|  | really should revert the merge, but when you want to re-do the merge, you | 
|  | now need to do it by reverting the revert. | 
|  |  | 
|  | ADDENDUM | 
|  |  | 
|  | Sometimes you have to rewrite one of a topic branch's commits *and* you can't | 
|  | change the topic's branching-off point. Consider the following situation: | 
|  |  | 
|  | P---o---o---M---x---x---W---x | 
|  | \ / | 
|  | A---B---C | 
|  |  | 
|  | where commit W reverted commit M because it turned out that commit B was wrong | 
|  | and needs to be rewritten, but you need the rewritten topic to still branch | 
|  | from commit P (perhaps P is a branching-off point for yet another branch, and | 
|  | you want be able to merge the topic into both branches). | 
|  |  | 
|  | The natural thing to do in this case is to checkout the A-B-C branch and use | 
|  | "rebase -i P" to change commit B. However this does not rewrite commit A, | 
|  | because "rebase -i" by default fast-forwards over any initial commits selected | 
|  | with the "pick" command. So you end up with this: | 
|  |  | 
|  | P---o---o---M---x---x---W---x | 
|  | \ / | 
|  | A---B---C <-- old branch | 
|  | \ | 
|  | B'---C' <-- naively rewritten branch | 
|  |  | 
|  | To merge A-B'-C' into the mainline branch you would still have to first revert | 
|  | commit W in order to pick up the changes in A, but then it's likely that the | 
|  | changes in B' will conflict with the original B changes re-introduced by the | 
|  | reversion of W. | 
|  |  | 
|  | However, you can avoid these problems if you recreate the entire branch, | 
|  | including commit A: | 
|  |  | 
|  | A'---B'---C' <-- completely rewritten branch | 
|  | / | 
|  | P---o---o---M---x---x---W---x | 
|  | \ / | 
|  | A---B---C | 
|  |  | 
|  | You can merge A'-B'-C' into the mainline branch without worrying about first | 
|  | reverting W. Mainline's history would look like this: | 
|  |  | 
|  | A'---B'---C'------------------ | 
|  | / \ | 
|  | P---o---o---M---x---x---W---x---M2 | 
|  | \ / | 
|  | A---B---C | 
|  |  | 
|  | But if you don't actually need to change commit A, then you need some way to | 
|  | recreate it as a new commit with the same changes in it. The rebase command's | 
|  | --no-ff option provides a way to do this: | 
|  |  | 
|  | $ git rebase [-i] --no-ff P | 
|  |  | 
|  | The --no-ff option creates a new branch A'-B'-C' with all-new commits (all the | 
|  | SHA IDs will be different) even if in the interactive case you only actually | 
|  | modify commit B. You can then merge this new branch directly into the mainline | 
|  | branch and be sure you'll get all of the branch's changes. | 
|  |  | 
|  | You can also use --no-ff in cases where you just add extra commits to the topic | 
|  | to fix it up. Let's revisit the situation discussed at the start of this howto: | 
|  |  | 
|  | P---o---o---M---x---x---W---x | 
|  | \ / | 
|  | A---B---C----------------D---E <-- fixed-up topic branch | 
|  |  | 
|  | At this point, you can use --no-ff to recreate the topic branch: | 
|  |  | 
|  | $ git checkout E | 
|  | $ git rebase --no-ff P | 
|  |  | 
|  | yielding | 
|  |  | 
|  | A'---B'---C'------------D'---E' <-- recreated topic branch | 
|  | / | 
|  | P---o---o---M---x---x---W---x | 
|  | \ / | 
|  | A---B---C----------------D---E | 
|  |  | 
|  | You can merge the recreated branch into the mainline without reverting commit W, | 
|  | and mainline's history will look like this: | 
|  |  | 
|  | A'---B'---C'------------D'---E' | 
|  | / \ | 
|  | P---o---o---M---x---x---W---x---M2 | 
|  | \ / | 
|  | A---B---C |