364

I have run into a bit of a problem here: I had a problem-specific branch 28s in Git, that I merged in the general develop branch. Turns out I had done it too fast, so I used git-revert to undo the merge. Now, however, the time has come to merge 28s into develop, but git-merge command sees the original merge, and happily announces that all is well and branches have been already merged. What do I do now? Create a 'Revert "Revert "28s -> develop"" ' commit? Doesn't seem to be a good way to do it, but I can't imagine any other at the moment.

What the tree structure looks like:

Git log output

1
  • 4
    It's GitX (gitx.frim.nl). Commented Jul 6, 2009 at 6:56

10 Answers 10

242

You have to "revert the revert". Depending on you how did the original revert, it may not be as easy as it sounds. Look at the official document on this topic.

---o---o---o---M---x---x---W---x---Y / ---A---B-------------------C---D 

to allow:

---o---o---o---M---x---x-------x-------* / / ---A---B-------------------C---D 

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.

Sign up to request clarification or add additional context in comments.

10 Comments

Good link (+1). I took the liberty to copy part of the document in your answer in order to allow readers to see immediately the relevant options in this case. If you disagree, feel free to revert.
We just ran into a case where we needed to do this and found that the fun doesn't quite stop here. It was a long running branch that was merged in, so we needed to continue to update it. My approach here: tech.patientslikeme.com/2010/09/29/…
@jdwyah that seems to be a broken link, but it sounds like an interesting read. Here is a archive.org mirror but it is missing the images: web.archive.org/web/20111229193713/http://…
Current link to @jdwyah's post blog.jdwyah.com/2015/07/07/…
|
87

Let's assume you have such history

---o---o---o---M---W---x-------x-------* / ---A---B 

Where A, B failed commits and W - is revert of M

So before I start fixing found problems I do cherry-pick of W commit to my branch

git cherry-pick -x W 

Then I revert W commit on my branch

git revert W 

After I can continue fixing.

The final history could look like:

---o---o---o---M---W---x-------x-------* / / ---A---B---W---W`----------C---D 

When I send a PR it will clearly shows that PR is undo revert and adds some new commits.

6 Comments

This looks like it could be helpful, but is so sparse on the details (what are C, D in the last diagram) that it's more frustrating than useful
@Isochronous C and D seem to be commits that fix the problems introduced by A and B.
@Thomas exactly
It's good that you have highlighted using a PR in this instance as it can provide a final 'sanity check' before merging back to master.
use why do you restart your branch from the original W? I.e., let your topic continue from W, with W` followed by C and D? That removes some duplication
|
60

To revert the revert without screwing up your workflow too much:

  • Create a local trash copy of develop
  • Revert the revert commit on the local copy of develop
  • Merge that copy into your feature branch, and push your feature branch to your git server.

Your feature branch should now be able to be merged as normal when you're ready for it. The only downside here is that you'll a have a few extra merge/revert commits in your history.

3 Comments

Just to prevent any further mix ups I also created a 'trash' copy of my feature branch and merged the reverted develop into it.
Thank you! This is the only answer that actually explains how to do it instead of saying you shouldn't do this. Really helpfull.
thank you, my case was pretty simple so this totally got the job done for me. The other answers are awesome too if you are in a more complex situation.
29

To revert a revert in GIT:

git revert <commit-hash-of-previous-revert> 

2 Comments

Using this in my work branch to revert the revert, then new PR to develop. Now git sees all changes that were in the previous PR that was reverted. Thanks.
Lifesaving revert method! Now to revert the revert of the original revert.
5

Instead of using git-revert you could have used this command in the devel branch to throw away (undo) the wrong merge commit (instead of just reverting it).

git checkout devel git reset --hard COMMIT_BEFORE_WRONG_MERGE 

This will also adjust the contents of the working directory accordingly. Be careful:

  • Save your changes in the develop branch (since the wrong merge) because they too will be erased by the git-reset. All commits after the one you specify as the git reset argument will be gone!
  • Also, don't do this if your changes were already pulled from other repositories because the reset will rewrite history.

I recommend to study the git-reset man-page carefully before trying this.

Now, after the reset you can re-apply your changes in devel and then do

git checkout devel git merge 28s 

This will be a real merge from 28s into devel like the initial one (which is now erased from git's history).

3 Comments

For anyone who is not super familiar with git and might want to follow these instructions: careful with combining reset --hard and push origin. Also be aware a force push to origin may really muck up open PRs on GitHub.
Very helpful for fixing some merge issues on a private git server. Thanks!
+1 for this technique. Potentially destructive, but can save you a lot of headache (and a mangled history) when applied judiciously.
3

I would suggest you to follow below steps to revert a revert, say SHA1.

git checkout develop #go to develop branch git pull #get the latest from remote/develop branch git branch users/yourname/revertOfSHA1 #having HEAD referring to develop git checkout users/yourname/revertOfSHA1 #checkout the newly created branch git log --oneline --graph --decorate #find the SHA of the revert in the history, say SHA1 git revert SHA1 git push --set-upstream origin users/yourname/revertOfSHA1 #push the changes to remote 

Now create PR for the branch users/yourname/revertOfSHA1

Comments

2

I just found this post when facing the same problem. I find above wayyy to scary to do reset hards etc. I'll end up deleting something I don't want to, and won't be able to get it back.

Instead I checked out the commit I wanted the branch to go back to e.g. git checkout 123466t7632723. Then converted to a branch git checkout my-new-branch. I then deleted the branch I didn't want any more. Of course this will only work if you are able to throw away the branch you messed up.

1 Comment

The git reflog will protect you on a hard reset for a couple of months in case you later discover that you need the lost commits. The reflog is limited to your local repo.
2
  1. create new branch at commit prior to the original merge - call it it 'develop-base'
  2. perform interactive rebase of 'develop' on top of 'develop-base' (even though it's already on top). During interactive rebase, you'll have the opportunity to remove both the merge commit, and the commit that reversed the merge, i.e. remove both events from git history

At this point you'll have a clean 'develop' branch to which you can merge your feature brach as you regularly do.

2 Comments

This approach will create problem if the develop branch is shared with others.
This can likely be improved by flipping it around and forcing the rebase in the opposite direction. Take the changes from the branch that was reverted and replay them on top of the revert to produce a new branch that can be merged downstream of the merge and you'll get a slightly cleaner looking history vs the rather hard to follow revert revert.
2

If needed you can create a new branch and cherry-pick the commits (separate the commit SH with space so it will be one command) from the reverted pull request, that way you'll be able to merge again with out losing files/changes.

Comments

2

Solution for never risking losing anything

TL;DR

What commands to run (assuming you are creating a PR):

# Starting from main branch git checkout -b new-fix-branch git revert <commit-hash-of-revert> git reset --soft <commit-hash-of-commit-one-before-the-revert-of-revert> # Gain peace of mind as you check everything is there. git add -A git commit -m "fix: brought back old changes" # Congrats, syncing your branch will no longer remove the reverted changes. 

Explanation

  • Revert the revert. This creates a new commit which gives you back all your reverted changes.
  • Reset --soft to before reverting the revert. This puts all the changes you've gotten back into your working tree locally as unstaged files.
  • Stage and commit changes again. As there is no revert associated with your changes anymore thanks to the reset, git thinks these changes are new and you can sync to your hearts content.

Pros

  • Every step can be a PR if you do not have permissions to do stuff on main/develop/prod etc.
  • No scary risks of losing code
  • Any amount of commits can happen between any step
  • Can see all your changes before final step for peace of mind

Cons

  • At least 8 commands total (checkout -b, revert, reset --soft, add, commit, push, merge, push).

Long Visual Explanation:

Check-out to main-branch after a revert:

>_ git checkout main-branch main-branch ---o---o---(merge)---(revert)---o---(cool)* / feature-branch ---o---o o = miscellaneous commits (merge) = the commit where you merged in some new changes (revert) = the commit where you reverted the (merge) commit (cool) = the latest commit on main-branch * = head 

Create new branch new-fix-branch:

>_ git checkout -b new-fix-branch main-branch ---o---o---(merge)---(revert)---o---(cool) / | feature-branch ---o---o | new-fix-branch * 

Revert the revert, giving us the reverted changes back

>_ git revert <hash-of-(revert)> main-branch ---o---o---(merge)---(revert)---o---(cool) / \ feature-branch ---o---o \ new-fix-branch (X-revert)* (X-revert) = changes from original feature-branch, now on your new branch 

Soft reset your revert of the revert , putting the changes into your working tree

>_ git reset --soft <(cool)-commit-hash> main-branch ---o---o---(merge)---(revert)---o---(cool) / | feature-branch ---o---o | new-fix-branch X* X = changes from the reverted (merge) commit now in your working tree 

Stage & commit entire working tree, git now thinks changes are new

>_ git add -A >_ git commit -m "bringing back old changes" main-branch ---o---o---(merge)---(revert)---o---(cool) / \ feature-branch ---o---o \ new-fix-branch   (X)* (X) = changes from the reverted (merge) commit are now back, committed and with no revert associated with them 

Sync your branch with main-branch - notice reverted changes DO NOT disappear and will never disappear if you merge back in

>_ git merge origin/main-branch main-branch ---o---(merge)---(revert)---o---(cool)---o---o---o / \ \ feature-branch ---o \ \ new-fix-branch (X)---(get-latest)* (get-latest) = changes from the `main-branch` put into your branch 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.