Reputation: 68847
tl;dr: Revert my entire local repository to a specific point in time. I want all branches to be reverted to that point. I want all commits that came after that point in time to be completely gone. I guess a good way of depicting it would be to cut off all the tips of all the branches (as you would see in SourceTree) that are newer than a specific time.
Let's say I have 3 branches that each implement "a way of doing it". In fact I want to keep these three branches completely separate. However, when I find a bug, I want to fix it in all three branches, for example. That's where I made a mistake, and I'm trying to fix it in a git-way. I merged the wrong branch into the all of these three branches. (The the way git merges branches is slightly different than I expected.) In fact, I'd love to revert the entire git repository to the the last point in real-life time where it was ok.
I've been trying for hours now to do this. I tried using git reset --hard <commit>
, but that doesn't revert the repository itself. Only the working directory, it seems.
Upvotes: 1
Views: 917
Reputation: 487993
It sounds like you want three separate git reset --hard
s.
Let me draw a graph that I think represents what you had before (and want to get back to), and what you had after:
--- x <-- badbranch
/
... - o - o - o <-- branch1
\
\-- o - o - o <-- branch2
\
- o - o <-- branch3
This is "before you did three merges": branch1
had at least 3 unique commits, branch2
had at least 3 unique commits, and branch3
had at least 2 (these are the o
nodes).
Meanwhile, a fourth branch (badbranch
above) had a "bad" commit x
.
You then did:
$ git checkout branch1; git merge badbranch
$ git checkout branch2; git merge badbranch
$ git checkout branch3; git merge badbranch
(plus possible resolvings, but I'll assume the merges all merged easily and created merge commits, below).
Just as a reminder, what merge
does is get two diffs, one for the current branch tip (let's say branch1
) vs the merge-base (somewhere back in the ...
series of commits), and one for the to-be-merged tip (x
) vs that same merge-base. It then applies the diff from base-to-x
to the tip of the current branch after screening away all the changes that are already in the base-to-tip diff; and if all goes well, it makes a new commit from the result:
--- x <-- badbranch
/ `---------\
... - o - o - o - M <-- branch1
We don't need to look at branch2
and branch3
yet, but if we want to, it helps to temporarily ignore branch1
. Let's draw in just branch3
this time, including its merge (which we'll call M3
to distinguish it from M
):
--- x <-- badbranch
/ \
... \
\ \
\ \
\ \
- o - o - M3 <-- branch3
Now let's look at what git reset
does, graph-wise. (You already know that with --hard
it resets your work-tree.)
Let's go back to just considering branch1
:
$ git checkout branch1
which has the bad merge M
on it. I'll expand the drawing to make it a bit taller for reasons that will become obvious, and then I'll move M
up likewise:
- x <-- badbranch
/ \
/ ------ M <-- branch1
/ /
... - o - o - o
(With any luck it's still obvious that despite now looking like a cartoon dog's head, it's the same graph as before).
When you run git reset
with a commit-ID—or with a name that specifies a commit-ID—it looks at the current branch (now branch1
) and moves that label to the target commit. In this case, let's say you use HEAD^
or the commit-ID of the tip-most o
; either one gives the same result, of moving branch1
back down to point to the o
. Now the graph is:
- x <-- badbranch
/ \
/ ------ M
/ /
... - o - o - o <-- branch1
The "bad" merge commit M
is no longer directly visible, because the view you get (with git log
or graphical viewers) starts from the branch tips, which no longer display the merge commit M
. So what you see is this:
- x <-- badbranch
/
/
/
... - o - o - o <-- branch1
which is what you had before you did the merge.
Of course, branches branch2
and branch3
still have their "bad" merges, but you can fix that easily by checking out those branches and doing git reset --hard HEAD^
while on each of those branches.
Each reset backs up the current branch one commit, to the place it was before you did the "bad" merge. You must repeat for each commit since each branch was and still is separate, since each merge was a real merge, not a "fast forward".
If some branch(es) did get a "fast forward", you must be a little bit more careful: HEAD^
may no longer suffice for each reset. But in general, resetting each branch to the desired commit-ID, which you can find in the reflogs for those branches, will do the trick.
Upvotes: 2
Reputation: 311496
Actually, git reset --hard <commit>
reverts both the information in the repository regarding your current branch and your working directory (without --hard
it only reverts the information in the repository and leaves your working directory untouched).
If you only have three branches, the easiest thing to do is just git checkout
each branch and then git reset --hard <commit>
to some commit meeting your requirements.
In place of <commit>
, you can also use a time specification, like HEAD@{yesterday}
, which might simplify things a bit. See the SPECIFYING REVISIONS
section of the git-rev-parse man page for details on specifying dates (and the myriad other ways in which you can refer to commit objects).
If you had more than a few branches you could probably automate things a bit, possibly starting with git for-reach-ref refs/heads/
for a list of branches, but I haven't worked through that particular solution yet.
Upvotes: 0