I'm working in a branch and needed to undo a couple of commits. So I did
$> git reset --hard bd53134
This had the desired effect, except that I have now a detached head :(
$> git status
HEAD detached at bd53134
How can I fix this ?
It looks like you may still be poking around with this, so here's a bit of background. You mentioned in a comment that you started a git rebase and were stuck with git rebase --continue always complaining. (This really should have been part of the original question.)
First a note: HEAD can be normal ("not detached"?) or "detached". A "normal" HEAD consists of nothing but a branch name. If you're "on branch master", HEAD just says "on branch master". A "detached HEAD", which sounds like something from the French Revolution, consists instead of a raw SHA-1 commit-ID. I like to write attached HEADs, in the drawings below, as HEAD=branch, and then show where the branch points, using an arrow to point to one particular commit.
Before you start a rebase, you have a commit graph that might look something like this:
...- A - B - E - F <-- master, origin/master
\
C - D <-- HEAD=branch
In this case, I've assumed that you did a git checkout -b branch from master, and made a few commits on branch (creating C and D), and then maybe did a git checkout master and git pull which brought over commits E and F from "remote origin". Then you went back to branch with git checkout branch (hence HEAD=branch).
You then decide that you should re-base both C and D on top of F, giving the more linear commit sequence:
...- A - B - E - F <-- master, origin/master
\
C' - D' <-- HEAD=branch
(I'll show in a moment why those are C' and D' instead of C and D.) So you run git rebase master to "move" commits C and D onto the tip of master.
Rebase does not actually move commits. What it does is copy some existing commits, making new ones that "do the same thing" and are "just as good" (we hope!). So, rebase keeps C and D around. It uses a special label, ORIG_HEAD, to keep track of commit D (and a bunch of additional .git/rebase-apply/ files to track all progress throughout the rebase operation—these files are in fact how git knows that the rebase is "in progress").
Rebase starts the process by adding this ORIG_HEAD and "detaching HEAD". It sets the "detached HEAD" to point directly to the target of the rebase (commit F, in this case). (By poking around, it seems the documentation lies slightly about it resetting the branch, too. But this may differ in older versions of git; I think the docs were accurate at one point.) Thus:
...- A - B - E - F <-- master, origin/master
\ \
\ \...... HEAD [detached]
\
C - D <-- ORIG_HEAD, branch
Then, for each commit (C and D here), it gets the changes made in the commit, and tries to apply those same changes to the HEAD commit. If all goes well—it usually does—it makes a new commit with the same message as the old commit. That's commit C':
...- A - B - E - F <-- master, origin/master
\ \
\ C'..... HEAD [detached]
\
C - D <-- ORIG_HEAD, branch
After successfully applying C to F to make C' coming off F, the rebase command goes on to attempt to apply D to the new (but still detached) HEAD, C'. However, this time something goes wrong: the patch does not apply:
CONFLICT ...
Failed to merge in the changes.
Patch failed at ...
The copy of the patch that failed is found in:
...
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
At this point, your commit graph looks like the (somewhat messy) graph I drew above.
As rebase prints, you can:
git rebase --continue, orD, in this case) with git rebase --skip, orgit rebase --abort.Choosing the last option tells rebase to stop the rebase attempt, put HEAD back the way it was, and delete the "rebase in progress" status/tracking files. This also drops the entire chain of new (but not yet labeled with a branch) commits. (Technically, they are still in there, in the reflog. You just won't see them, normally.)
Doing the first ("resolve manually"), or choosing the middle ("skip") option, lets rebase continue onward. Let's say you resolve the problem and git rebase --continue. Once rebase runs out of commits—and D is the last one—it moves the branch name to the final commit, and sets HEAD to the branch name again. So in this case, you get this:
...- A - B - E - F <-- master, origin/master
\ \
\ C' - D' <-- HEAD=branch
\
C - D <-- ORIG_HEAD
Since commits pointed-to by ORIG_HEAD are not normally shown, it looks like C and D are gone, and the copies (C' and D') are the only commits left. (As always though, C and D are actually still in there, and will stick around for a while, until the reflog entries expire.)
With all that in mind, once you're in "detached HEAD" state, git reset --hard has no branch name to affect. It will move HEAD, and change the working directory, but HEAD will still point directly to a commit. So any git resets you do in the middle of a rebase are, well, weird at best. (They'll fiddle with what happens with the rebase if you continue it, as continuing a rebase just keeps adding on to wherever HEAD points at the time.)
In the more normal case (when you're "on a branch"), HEAD has the name of the branch. Then (and only then), git reset can and does change the commit the branch-name points-to.
How do you tell which actions git reset will take, and what's going on? The answer is to use git status. If you're not sure what's going on, git status is meant to help. It's gotten much better in the last few years at actually helping, too! :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With