Reputation: 1915
How can I reset an entire repo, i.e. all branches go back to their last state before a given commit?
tl;dr
Suppose I have a repo with four branches: master, one, two and three:
git init
echo a > a.txt && git add . && git commit -m "added a"
git checkout -b one
echo b > b.txt && git add . && git commit -m "added b"
git checkout master
git checkout -b two
echo c > c.txt && git add . && git commit -m "added c"
git checkout -b three
echo d > d.txt && git add . && git commit -m "added d"
Here's my tree (due to simplicity, three is just a fast-forward of two):
git checkout master
git --no-pager log --graph --oneline --decorate --all
* a7457d6 (one) added b
| * a7e62c6 (three) added d
| * c84c43b (two) added c
|/
* 6ec61a1 (HEAD -> master) added a
Then I make some error which I later want to erase, e.g. miss-merging a branch.
git merge --strategy=ours one -m "accidentally lose ability to merge one"
Unwittingly, I propagate it to some of those branches (two and three):
git checkout two
git merge master -m "propagating problem to two"
git checkout three
git merge master -m "propagating problem to three"
The tree now looks like:
git checkout master
git --no-pager log --graph --oneline --decorate --all
* 83448b1 (three) propagating problem to three
|\
* | a7e62c6 added d
| | * f33efb4 (two) propagating problem to two
| | |\
| |/ /
|/| /
| |/
| * 69bee42 (HEAD -> master) accidentally lose ability to merge one
| |\
| | * a7457d6 (one) added b
| |/
* | c84c43b added c
|/
* 6ec61a1 added a
Examining the reflog, we can see where I went wrong and what's affected:
git --no-pager reflog --oneline --all
69bee42 HEAD@{6}: checkout: moving from three to master
83448b1 refs/heads/three@{1}: merge master: Merge made by the 'recursive' strategy.
83448b1 HEAD@{7}: merge master: Merge made by the 'recursive' strategy.
a7e62c6 (three) HEAD@{8}: checkout: moving from master to three
69bee42 HEAD@{9}: checkout: moving from two to master
f33efb4 refs/heads/two@{1}: merge master: Merge made by the 'recursive' strategy.
f33efb4 HEAD@{10}: merge master: Merge made by the 'recursive' strategy.
c84c43b (two) HEAD@{11}: checkout: moving from master to two
69bee42 refs/heads/master@{1}: merge one: Merge made by the 'ours' strategy.
69bee42 HEAD@{12}: merge one: Merge made by the 'ours' strategy.
6ec61a1 (HEAD -> master) HEAD@{13}: checkout: moving from three to master
6ec61a1 (HEAD -> master) refs/heads/master@{2}: commit (initial): added a
a7457d6 (one) refs/heads/one@{0}: commit: added b
6ec61a1 (HEAD -> master) refs/heads/one@{1}: branch: Created from HEAD
a7e62c6 (three) refs/heads/three@{2}: commit: added d
c84c43b (two) refs/heads/three@{3}: branch: Created from HEAD
c84c43b (two) refs/heads/two@{2}: commit: added c
6ec61a1 (HEAD -> master) refs/heads/two@{3}: branch: Created from HEAD
a7e62c6 (three) HEAD@{14}: commit: added d
c84c43b (two) HEAD@{15}: checkout: moving from two to three
c84c43b (two) HEAD@{16}: commit: added c
6ec61a1 (HEAD -> master) HEAD@{17}: checkout: moving from master to two
6ec61a1 (HEAD -> master) HEAD@{18}: checkout: moving from one to master
a7457d6 (one) HEAD@{19}: commit: added b
6ec61a1 (HEAD -> master) HEAD@{20}: checkout: moving from master to one
6ec61a1 (HEAD -> master) HEAD@{21}: commit (initial): added a
So I look back and see my error was at (the first) 69bee42. Now I want to roll the whole repo back to that time.
The only way I could see to do it was to eyeball the reflog to see what branches I'd contaminated and individually roll them back to a state before they were broken. Because this example is artificially simple, those states are all ~1:
git checkout master
git reset --hard master~1
git checkout two
git reset --hard two~1
git checkout three
git reset --hard three~1
[[ Edit: it is a more elegant to branch -f
from a branch I don't need update:
git checkout one
git branch -f master master~1
git branch -f two two~1
git branch -f three{,~1} # get cute with bash {} expansion
]]
In real life, this was extremely error-prone:
Git: Roll back to much earlier version implies the above branch-by-branch solution.
Upvotes: 0
Views: 217
Reputation: 487725
You are in fact doing it the right way: you must use git reset
or git branch -f
to force each branch name to point to the desired commit, and the place to find the commit is in the appropriate branch's reflog. (I recommend using git branch -f
as much as possible. The only branch you cannot fix with git branch -f
is the current branch, whatever that is; there, you must use git reset
instead, probably with --hard
—with all that git reset --hard
implies.)
There is a bit of a shortcut you can use, which is that the reflog syntax allows name@{when}
, e.g., master@{1.hour.ago}
or three@{yesterday}
. The @{relative time offset}
mode translates into whatever hash ID that name would have produced had you run it that long ago. You can also use an absolute time. (But you noted in your answer that the time window here was not very large and hence this was not all that helpful.)
To view existing reflogs with dates, use git reflog --date=local
or git reflog --date=relative
(there are more options; see the documentation.
Upvotes: 1