Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remerge after reverting failed merge

I have two branches in repository: feature and master. I have merged master into feature and pushed result to remote feature branch:

git merge master

because it contains necessary changes of external interfaces. Than I have discovered that merge conflicts were resolved wrong and I have reverted this merge with:

git revert -n -m 1 78e7ebfa8237

So I'm back on feature not merged. But according to history merge already happened and feature branch already contains necessary changes. Can I try merge branches again (inspecting conflicts more carefully and not commiting before checking build)?

like image 982
Odysseus Avatar asked Jan 28 '15 10:01

Odysseus


2 Answers

If you have not published the bad merge and its reversion, you can remove them and publish a correct merge instead.

If you have published (pushed or otherwise given out) the bad merge, your best bet is probably to work out a correct merge by creating a new branch starting from just before the bad merge. For instance, suppose the commit graph fragment looks like this:

...--i--j--m--w   <-- feature
          /
...---k--l        <-- master

where merge commit m is the one that went wrong, and w (m upside down1) is the reversion of m. (Note: if you have a more complex history, you probably should use a different strategy; see the link in the footnote.)

Here, the idea would be to check out commit j directly:

git checkout <sha1-of-j>

You are now in "detached HEAD" mode. At this point you can run a new git merge:

git merge master

This will (based on your mention of merge conflicts) stop with a merge conflict, since it's repeating the step that got you bad-merge-m. (If it won't stop on its own, add --no-commit to the merge command.)

Now resolve the conflicts correctly this time :-) and add and commit as needed. This makes a new merge which I'll call M, and I will draw the new graph like this:

...--i--j------m--w   <-- feature
         \    /
          M  /        <-- HEAD
          | /
         / /
         |/
...---k--l            <-- master

This new commit M is not (yet) on any branch, and in fact, you don't really need it to be on any branch: what you want is the tree you obtained at this point.

Now we'll make this a new (but temporary) branch to remember the SHA-1 of commit M:

git checkout -b temp

(we could have done this earlier; you can do it at the "check out commit j" step if you like; but I have some other, un-tested, methods in mind that I'll outline below). Now let's get back on feature and make a new commit that uses M's tree, rather than that of m or w. There are several ways to do this, but I will illustrate this one since it's pretty simple:

git checkout feature
git rm -r .  # assumes you're in the top level of the work dir
git checkout temp -- .

The first of these, checkout feature, simply gets us back on branch feature. The second empties out the index (the "next commit")—this step is only necessary if M is missing some file(s) that are in m and w—and then the third extracts the entire tree from commit M into the index and work-tree.

Now we're ready to commit the result:

git commit -m "replace everything with corrected merge"

The graph now looks like this:

...--i--j------m--w--n   <-- HEAD=feature
         \    /
          M  /           <-- temp
          | /
         / /
         |/
...---k--l               <-- master

The files under commit n are the same as those under commit M. We no longer need commit M and branch temp at all, so we can simply delete them (git branch -D temp), giving:

...--i--j--m--w--n   <-- HEAD=feature
          /
...---k--l           <-- master

If you're comfortable with using lower level git commands, there's a simpler (?) way to copy the tree from M to a new commit we'll put on feature. In particular we just need to make a new commit whose parent is w and whose tree is that of M. We can do that in one step while still on M and the anonymous HEAD, with git commit-tree:

id=$(git commit-tree -p feature -m "message" $(git rev-parse HEAD^{tree}))

Assuming this works (I haven't tested this particular form and you might have to use git rev-parse to convert the name feature to a raw SHA-1), we can then use git update-ref to make refs/heads/feature contain id $id:

git update-ref -m "add corrected merge" refs/heads/feature $id

after which it's safe to simply git checkout feature to get back on the (updated) branch.

This being git, there are more ways to do it, e.g., when on the anonymous branch, you could do this:

git symbolic-ref HEAD refs/heads/feature
git commit -m "replace everything with corrected merge"

which is probably simpler than the git commit-tree method (the commit-tree method is just what I thought of first, due to having recently written a complicated shell script that used commit-tree for a fancy repo shadowing thing). The way this works is that the symbolic-ref puts you back on branch feature but does not touch the index (nor work-tree) at all, so it/they still match the tree for commit M. Then we make a new commit in the ordinary way, using the current index; and since nothing remains to point to commit M, the garbage-collector will eventually delete that commit (but not the tree itself, which is now safely saved on branch feature).


1The m and w thing is stolen directly from Linus Torvalds and Junio Hamano.

like image 101
torek Avatar answered Oct 05 '22 12:10

torek


If you execute the git revert, it will undo the merge, so I do not think the feature contains necessary changes. I will goes to the state before merge only with some additional git log. So you can freely merge it. But, if you do not want the additional git log, you can use git reset.

  1. Check which is your wanted previous state:git log

  2. git reset {commit-id-founded}

  3. resolve your conflicts

  4. merge again

like image 23
vmcloud Avatar answered Oct 05 '22 12:10

vmcloud