Detached What? Dude, Where’s My Commit?
Checking out a commit’s hash or object name enters what the git documentation refers to as detached HEAD state.
It is sometimes useful to be able to checkout a commit that is not at the tip of any named branch, or even to create a new commit that is not referenced by a named branch. Let’s look at what happens when we checkout commit b (here we show [three] ways this may be done):
$ git checkout v2.0 # or
$ git checkout master^^ # or
$ git checkout b
HEAD (refers to commit 'b')
|
v
a---b---c---d branch 'master' (refers to commit 'd')
^
|
tag 'v2.0' (refers to commit 'b')
Notice that regardless of which checkout command we use, HEAD
now refers directly to commit b. This is known as being in detached HEAD state. It means simply that HEAD
refers to a specific commit, as opposed to referring to a named branch.
As you observed, git will happily create new commits, rebase, merge, and so on with a detached HEAD, but because no tag or branch refers to the resulting unnamed branch, losing it will be easy. You were able to find your original commit with git log
. When you have done more drastic surgery, the output of git reflog
is another place to look.
The Fix
Fixing your branch as described in your question will involve the following sequence.
- Reattach
HEAD
to your branch (referred to below as topic/my-branch
)
- Reset
topic/my-branch
to where it was before
- Fix
origin/topic/my-branch
from the earlier push.
- Either do it all at once with a force push, or
- Delete the old remote branch and push your fixed history
Reattach HEAD
Rather than by its SHA-1 hash, check out your branch by name
git checkout topic/my-branch
Fix your local branch
Next, put it back where it was with git reset --hard
. You will use the same command, but the context is different: HEAD
after git checkout
points to topic/my-branch
rather than to commitId directly.
git reset --hard commitId
Fix the remote branch
You said you pushed your rebased branch, so update the remote repository to reflect your changes. The way to do it all in one command is
git push --force origin topic/my-branch
The administrator of the remote repository may have taken the highly reasonable step of denying force pushes (for why, see below). If so, try a sequence of
- Delete your branch on the remote side, and then
- Push your corrected local branch
In the bad old days, we had to delete remote branches with the non-obvious
git push origin :topic/my-branch
but nowadays, it’s spelled
git push --delete origin topic/my-branch
Having cleared the way, now push your branch to put things back to where they were before.
git push origin topic/my-branch
If both force pushes and deletes are disabled on your remote, ask for help from someone with administrative access to that repository.
Words of Caution
Most of the time, you have to work very hard to convince git to destroy work. However, git reset --hard
, deleting remote branches, and git push --force
are all sharp tools — useful when you need them but dangerous when used carelessly. As with rm -rf
, pause to consider whether you really mean the command you’re about to run.