gitgit-rebasegit-pushgit-checkoutgit-detached-head

After git reset --hard, why didn’t my topic branch change?


I accidentally rebased my branch with the DEV branch and then pushed it to a remote repository. With the rebase, I selected the current changes and hence my local changes got overwritten.

I lost my earlier commit in the rebase but found it by running git log. Then I ran git checkout commitId and even git reset --hard commitId. However, in both cases my code base still shows the latest rebased code on my branch.

How do I restore my branch back to its previous state?


Solution

  • 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.

    1. Reattach HEAD to your branch (referred to below as topic/my-branch)
    2. Reset topic/my-branch to where it was before
    3. 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

    1. Delete your branch on the remote side, and then
    2. 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.