Question

History of my commits looks like:

A - B - C - D - E
     \     /
      X - Y

Branch ABCDE is master, branch XY is some other branch (say test), and D is a merge commit. HEAD now is at E. I need to return my working directory to the state of A on the assumption of not using git reset --hard <SHA1(A)>, only git revert. As far as I know, in such a case this can be done by two ways:

First way:

git revert --no-commit <SHA1(E)>
git revert -m 1 --no-commit <SHA1(D)>
git revert --no-commit <SHA1(C)>
git revert --no-commit <SHA1(B)>
git commit -m "Reverted to state A"

Second way:

git revert --no-commit <SHA1(E)>
git revert -m 2 --no-commit <SHA1(D)>
git revert --no-commit <SHA1(Y)>
git revert --no-commit <SHA1(X)>
git revert --no-commit <SHA1(B)>
git commit -m "Reverted to state A"

A stumbling block is git revert -m. In this example I assume that 1 allows to continue the chain of reverts along branch master, and 2 — along branch test.

The question is: how do I actually know which number means which branch?

Was it helpful?

Solution 3

You can inspect a merge commit to see who it's parents are:

git show <SHA1(D)>

The parents will be listed in the order to which you can refer to them. This information is also printed in the output of git log. Use that to deduce which commits are meant when you do git revert -m.

Edit: it seems that you're working with a public repo and looking for a way to change the a published branch, which is quite different than changing the state of a working copy. Git has a way to revert without having to figure out each parent of merges along the way. Just do it all at once:

git revert <SHA1(B)>..

Update: I was perusing some old stuff and realized this answer I gave is completely incorrect. If you do the above, you will not get state A, but you will instead get a Frankenstein you don't want:

A - C' - D' - E'
 \     /
  X - Y

That ^ is the equivalent of what you'd get, which is not at all what you want. There is really only one way to do this and preserve your history entirely:

git revert HEAD              # reverts E, labeled as E' below
git revert -m 1 <SHA1(D)>    # reverts D, labeled as D' below
git revert HEAD              # reverts C, labeled as C' below
git revert HEAD              # reverts B, labeled as B' below

This will leave you history looking like this:

A - B - C - D - E - E' - D'{C} - C' - B'
     \     /
      X - Y

This method will get you what you want and preserve every bit of history. @Kaz and @torek's methods are more efficient and less verbose and if you don't care to ever reintroduce X and Y then I recommend one of those solutions; however, they make it potentially harder to reintroduce X and Y into your main branch, if that's something you care about. Reverting each piece makes it possible to reintroduce X and Y without rewriting/C&P the code again, which is an attractive option ifX and Y were large changes or if you are obsessive about keeping the most accurate history possible (I tend to be that way).

To reintroduce X and Y, you should do this:

git revert D'

This will likely create merge conflicts, especially if you don't also revert B' and C', but that might be better than re-doing X and Y by hand. Linux Torvalds wrote a very thorough explanation of this scenario that may be of some help to you.

OTHER TIPS

Another method is to simply empty out the work tree and then insert the work-tree-as-it-was-at-commit-A:

# assumes you're in the top level
$ git rm -rf .
$ git checkout sha-or-other-specifier-for-A -- .
$ git commit -m 'revert to state A'

The idea here is that git rm -rf completely empties the work tree and index/staging-area, and then git checkout <rev> -- . completely re-populates the work tree and index/staging, but gets the trees and files from the specified revision, rather than from the latest version.

Once those two are done, a new commit writes the same trees-and-files as in commit A.

I would not rack my brain over git too much in this case and just do:

git diff <sha(A)> HEAD | git apply -R
git commit -a -m "undoing everything since A"

Of course, review everything before the commit. Also, after the commit, verify: what are the differences between A and HEAD now? Ideally, nothing:

git diff <sha(A)> HEAD
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top