Kyle
Kyle

Reputation: 4366

Merging after Git revert

I have two branches (let's call them A and B).

A is my regular development branch and B is a long-running feature branch.
A is routinely merged into B in order to keep it up-to-date and one day B will be merged into A, but we are not at that point yet.

A week or so ago a developer accidentally merged B into A and then immediately un-did the merge by running git revert.

Since that time, A has had numerous changes include having other feature branches merged into it.

Today, I attempted to do a merge of A into B as I do on a somewhat regular basis, but now I am having problems.

The merge is removing a number of files from B that were added in B (presumably because they were removed from A when git revert was run) and adding a number of files that were removed from B (presumably because they were added by the git revert).

It's also un-doing a lot of changes that should be in B.

My question is is there any way to cleanly fix this?
I'm open to history re-write as we have a small team and we could coordinate this.

I tried resetting A back to the point before the bad merge was done and cherry-picking the changes we've made to it since then, but since there have been a number of feature branch merges it's been difficult (the feature branches contain the bad merge commits and revert).

Upvotes: 3

Views: 238

Answers (3)

Daphoa
Daphoa

Reputation: 191

So the problem is that even though you reverted the merge commit, Git doesn't track this in a meaningful way (for the purpose of future merges). So basically now Git thinks that A has most of B in it and can't do meaningful merges.

There are a couple ways to fix this issue. While there might be some clever ones that don't require rewriting history, the two I can think of do require rewriting history. Obviously, all the caveats with rewriting history apply here.

Method 1: Remove the merge on A

The first method involves removing the merge from the history of A. While this solution produces a cleaner looking history, it probably has a bigger impact that the first one, especially if you have branched off A after the merge.

(Taken from https://stackoverflow.com/questions/1338728/delete-commits-from-a-branch-in-git)

The easiest way to do this is an interactive rebase. Running git rebase -i <sha> (where <sha> is the SHA of the problematic merge conflict) will allow you to selectively remove the problematic commit. As long as you remove the revert commit too while you are at it, and this should be relatively painless.

Method 2: Replace B with a new branch

The other option is to create a new version of B so that git tracks them separately. Let's assume your tree starts like this:

E -- F -- G -- H (branch A)
  \
   \
     I -- J -- K (branch B)

I believe the follow line will work to resolve it:

git rebase -f <sha of E> B

This should create a copy of B which hasn't ever been merged into A.

Note: For either of the above method, there will be some weirdness if you branched off the changed branch (A or B depending on method 1 or 2) at any point after the bad merge. Specifically, the branch will be created off of the old version of the branch, and not the updated one.

To fix this, for each branch in this state you (or another developer) will need to rebase onto the new version of the initial commit. I can be more descriptive here if this is a concern.

Upvotes: 1

Joseph K. Strauss
Joseph K. Strauss

Reputation: 4903

The simplest solution, assuming you want to tolerate the ugly history id to revert the revert.

git revert SHA1-of-revert

Subsequent merges should work just fine. Should you want to clean it up at a later point that is relatively easy, assuming you have already merged in all feature branches based off the bad reverts (including future ones).

git rebase --interactive SHA1-of-revert~ 

Then remove the lines of the reverts. This will give you a completely liner history. If you want to preserve the merges of the feature branches add the --preserve-merges option.

git rebase --interactive --preserve-merges SHA1-of-revert~

Upvotes: 2

Jonathan.Brink
Jonathan.Brink

Reputation: 25373

I would try to rebase branch B onto branch A.

Prior to this step, it may be a good idea to save your place in case you later on realize a mistake was made. So, first create a temporary branch:

git branch savePoint

Before you do the rebase, I would strongly recommend reading a tutorial or two on rebasing such as this one.

Now, run this command:

git rebase A

This will re-play each commit on B (starting with the oldest that is not on A) on top of branch A.

If a code-conflict is detected follow the instructions that Git provides, it will be similar to resolving a merge conflict.

When the rebase is complete and you have tested and verified things are working you will need to force-push your changes like this, so use the -f option in your push command.

The final result will be as if someone wrote all the code on B immediately after forking A. Use a tool like gitk to visualize the tree to make sure you understand this.

If you feel the rebase went wrong somehow, simply reset back to your save point:

git reset --hard savePoint

A very important note: This rebase will clean up your history, but everyone else who is pulling from branch B will now be "out-of-sync". This means the tip of their B branch will not be reachable by the new remote tip of B.

They can get "in sync" with the newly rewritten B branch with:

git checkout B
git fetch
git rebase origin/B

Upvotes: 1

Related Questions