When I rebase my local feature branch onto the develop branch, there were some conflicts. So I need to resolve them, but this resolving is actually modify my commit. So the question is what if the resolving is wrong (then, I've lost my correct code)? How do I restore to the previous state except back up that branch in advance?
2 Answers
As mention in "Undoing a git rebase", you always can restore the branch as it was before the rebase with git reflog
, or, if you were just done with the rebase, ORIG_HEAD
(also HEAD@{1})
Even if you don't want to undo your rebase, but simply refer to the code as it was before the rebase, you can make a tmp
branch where it was before said rebase:
git branch tmp @{1}
That allows you to compare a file between your current branch and tmp
:
git diff tmp feature_branch -- myfile.cs
-
Your suggestion is great – Benson Aug 03 '14 at 06:16
You're in luck, because git (mostly1) only adds new commits every time you do anything with commits. The old ones are still in there, they just become harder to find.
Specifically, you had your local feature branch—let's call it feat
—based on the remote development branch, which we can call origin/devel
. This means you had some set of commits
"branching off" this origin/devel
. Let's draw that:
A - B - C <-- feat
/
...- o - o - * <-- origin/devel
Here A
through C
are three commits you made, while the existing ones are mostly just labeled o
here, except for the *
that marks where the tip of origin/devel
was when you started.
At some point, you did a git fetch
(or git pull --rebase
, which does a git fetch
) and your git contacted whatever git you originally cloned from, to pick up new commits they made. Let's say they made two new ones, and draw them in:
A - B - C <-- feat
/
...- o - o - * - o - x
The last thing git fetch
usually2 does before it's done (and git pull --rebase
resumes, if that's how you did your git fetch
) is to update the origin/
branch-labels, so origin/devel
now points to the new branch-tip:
A - B - C <-- feat
/
...- o - o - * - o - x <-- origin/devel
What git rebase
does, including when invoked by git pull --rebase
, is simple to describe: it copies your original commits,3 then moves your label. The originals, before the copies are made, are still in there:
A - B - C [see below for naming]
/
...- o - o - * - o - x <-- origin/devel
\
A' - B' - C' <-- feat
The copies have the same changes with respect to their parent nodes that the originals had to their own parents. That is, whatever you did between *
and A
, git tries to make the same changes to x
to come up with A'
. Then, whatever you did between A
and B
, git tries to do the same thing to A'
to get B'
, and so on.
When git can't quite do these automatically, you get those merge conflicts. (Even if git thinks it can do them automatically, you should check the results somehow, because git can get this wrong.)
You can look at your original branch—your original A
through C
commits, specifically—by using a name that refers to commit C
. The easiest one is ORIG_HEAD
, which git rebase
sets to point to C
just before it moves your feat
label to point to C'
. However, there is also the "reflog", which keeps track of which commit feat
used to name. You can view the feat
-branch reflog with git reflog show feat
. You can also use git log -g
(alternative spelling: git log --walk-reflogs
). The reflog generally lasts longer: 30 to 90 days, by default. The ORIG_HEAD
name is replaced by various other commands (git merge
and git cherry-pick
in addition to git rebase
) so it's just meant for immediate corrections.
If you know in advance that you want to save your original branch, you can make a new name (a branch or tag name) to point to the tip of the branch as it stands now (in this case, to commit C
), before running git rebase
:
git branch save-feat feat
Or, as I sometimes do:
git branch -m feat feat-1
git checkout feat-1
git checkout -b feat-2
and only then:
git rebase ...
so that I now have two versions of my feat
ure branch.
1Git will delete commits (and other unused repository objects), but only after they're truly un-referenced. In particular, the reflog holds on to old commits for a month or three, unless you do something to delete the reflog (including deleting the branch).
2In newer git (post 1.8.5 or so), git fetch
always updates; in older versions, git pull
invokes it in a way that inhibits updating the origin/branch
labels.
3Specifically, git rebase
uses a series of git cherry-pick
operations to copy each commit. (Note that it also tries to skip commits that are no longer necessary, except when invoked interactively. For instance, if your A
-to-B
change is exactly the same as something added to origin/devel
, git rebase
can simply skip over B
entirely while copying, giving you just A'
and C'
.)

- 448,244
- 59
- 642
- 775
-
-
Great answer! Should `git branch save-feat feat` have been `git branch feat save-feat`? – holdenweb Aug 16 '18 at 22:34
-
1@holdenweb: no, the syntax is `git branch
[ – torek Aug 16 '18 at 22:46]` to create the new branch ` `, and we want to create the `save-feat` name at this particular time. The optional ` ` part is the commit hash ID to stuff into the new name; the default is whatever hash ID `HEAD` resolves to. Writing `feat` here resolves the existing name `feat` to the correct hash ID, then creates the new name. -
Thanks - I now see the relevant command reference is `git branch [--track | --no-track] [-l] [-f]
[ – holdenweb Aug 16 '18 at 22:50]`. I had been confused by seeing `git branch (-m | -M) [ ] ` and assumed more than I should have. `