0

Let's say I merge a feature branch feature/123 into master, then a problem occurs and we have to remove it. So far I know about two ways to do this:

  1. git revert --mainline 1 <commitid> && git push
    (where <commitid> is the merge commit)

  2. git checkout master && git reset --hard <commitid> && git push --force
    (where <commitid> is from before the merge commit)

#1 has an issue where if feature/123 is fixed and needs to be re-merged into master, only a portion of the work will be merged in. (Specifically, the work that was done since it was merged into master the first time.)

#2 has that whole "repository surgery" issue where if anyone else pulled master in the intervening time, have fun.

Are there other solutions to un-doing a merge that have neither of the above issues? #1 seems to be the least bad because developers can recreate their branches and replay their work into them but I don't want that either.

Darren Embry
  • 155
  • 1
  • 2
  • Go ahead with `git revert`. Then when you want to merge the branch again, use `git replace --graft` like I explain in [this answer](https://stackoverflow.com/questions/57904970/reverting-a-git-merge-while-allowing-for-the-same-merge-later/57905240#57905240). – j6t Nov 20 '20 at 08:18
  • Hi Darren! If any of the answers below answered your question, please go ahead and mark it as answered. Here's more on asking and answering questions: https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – Ali Dowair Nov 30 '20 at 17:30

3 Answers3

1

Option 1 is definitely the more hygienic option, and your concern is a very valid and well documented one =D. Here's an idea from this article from datree.io:

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.

From the same article, here is a previous thread from Git's official docs answering the same problem.

Ali Dowair
  • 168
  • 9
1

The answer by Ali Dowair links to the right discussions. But to best understand how to fix it I'd like to explain what is going wrong here first:

After your second merge your history looks something like this:

*   master
|\
| * (F2) fixing commit on feature branch
* |
* | (R) revert of merge commit (M)
* |
* | (M) merge of feature into master
|\|
| * (F) faulty commit on feature branch
|/
*   (B) common base commit
|

So, when you do a git merge feature on the master branch git will look for the common base commit in those two branches and merge all changes from feature committed since that base commit.

By the second time you git merged this base commit was F with the faulty changes, i.e. F was already included in master and feature and thus git determined only F2 needed to be merged.

As the linked discussions in the answer by Ali Dowair say

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.

So since F is already included in master - as far as git is able to detect this - it won't be merged again leaving you with two options (ignoring rewriting/force-pushing the history here):

  1. Revert the revert: git revert R right before you git merge feature again. This will re-apply the previously merged and then undone changes - i.e. F in this example - and then apply/merge the new changes from the feature branch:
*   master
|\
| * (F2) fixing commit on feature branch
* | (R') revert of revert commit (R)
* |
* | (R) revert of merge commit (M)
* |
* | (M) merge of feature into master
|\|
| * (F) faulty commit on feature branch
|/
*   (B) common base commit
|
  1. Rebase feature onto the new master after the revert:
git checkout feature
git rebase B --onto master
# rebases all commits done since (B) onto the new master

git checkout master
git merge feature

Resulting in:

*   master
|\
| * (F2') fixing commit on feature branch
* |
| * (F') faulty commit on feature branch
|/
*
|
*   (R) revert of merge commit (M)
|
| * (F2) fixing commit on feature branch
| |
* | (M) merge of feature into master
|\|
| * (F) faulty commit on feature branch
|/
*   (B) common base commit
|

Here git merge will pull in the changes from F' and F2' which are the rebased versions of F and F2. Using --onto here is necessary to stick to the original base commit B here because just git rebase master would omit F for the same reasons git merge would do.

acran
  • 7,070
  • 1
  • 18
  • 35
0

I would do the following:

Step 1: if it has already been merged into master, then I would create the branch from the merged commit point:

git checkout master   // checkout the master branch
git log               // to find the commit point where feature/123 was merged in
git checkout <commit-hash>   // checkout the commit point 
                             // (you will get a warning about a detached head)
git checkout -b feature123 // create a new branch from this commit (you'll be remerging this later)
git push origin feature123 // push this branch to the server

Step 2: surgically remove the commit point from master history by doing an interactive rebase:

git checkout master // checkout the master branch
git pull origin master  // get latest of master
git log   // to find a commit point that is past the commit point from. i.e. <any-commit-point>
git rebase -i <any-commit-point> // start an interactive rebase

(opens up editor)

pick dbf4522c
drop 035d52e0  // drop the target commit from Step 1
pick 02441dee

(complete the rebase)

git push origin master --force // force push to server (be careful!)

You may have to resolve merge conflicts as part of this rebase.

Step 3: Merge feature123 branch back into master

git checkout feature123
git merge master
git add *
git commit
git push origin master

Finally, since you've re-written history on the master branch, make sure to let everyone know to re-clone the repository.

Michael Kang
  • 52,003
  • 16
  • 103
  • 135