Reputation: 15
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?
Upvotes: 0
Views: 84
Reputation: 20686
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.
Upvotes: 0
Reputation: 488003
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
.
Upvotes: 2
Reputation: 58558
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
Upvotes: 1