gitgit-detached-head

Git- Resolve Detached HEAD on server


I am fairly new to Git- having only been using it since I started working for my current employer.

I recently pushed some changes to the server, having merged a bug fix implemented by a colleague on my local development machine. I did this by pulling their branch on which they had implemented the bug fix from the server, and merging it with my local master branch, which was up-to-date with the live version of the code. I tested this on my local development machine, and it all seemed to be working correctly, so I pushed it to the server.

However the changes I made appear to have introduced another bug into the live version (the bug it introduced was not apparent when testing the fix locally, and only presented itself once the code had gone live).

This bug that I introduced actually caused a heavily used part of the internal company website to be inaccessible, so I immediately checked out the last working commit & restarted the server, so that the site was then accessible again.

I am now working on resolving the issues that the fix has caused on my local development machine, before pushing the fix up to the server again, but having checked out an old commit on the server has left the live version of the project in a detached HEAD state. The detached HEAD state that it is in is currently mostly functional (i.e. it is working the same as it was before my colleague started working on this bug fix- so the bug is still present).

I now want to resolve the detached HEAD on the server, so that I can pull the working code from the server again, to start afresh from this point, but I'm not sure how I 'match' the rest of the code with the detached HEAD that I am currently working from.

If I run git branch on the server, the output shows that I only have the master branch (which is broken- as it has the bug that I introduced when pushing the original bug fix), and (detached from 0e57d3d), which is the HEAD that is currently being pointed to.

Other posts I've looked at seem to indicate that I should checkout master in order to resolve the detached HEAD, but I can't do this, as I know that master is currently broken.

So how can I 'attach' the HEAD again, so that the code works as it did in the state it was in during the commit that the HEAD is pointing to? Would I just run a git branch from there, then checkout that branch, and make it the master? Or is there some other way to do this?


Solution

  • At first blush, this looks like a duplicate of Fix a Git detached head? It is in some ways, and not in others. Also, as Thorbjørn Ravn Andersen said in various comments, you probably want to coordinate with other colleagues as well. However, let's make a few notes that aren't in (and don't really go with) that other question.

    A detached HEAD doesn't change anything—not yet, anyway

    First, a "detached HEAD" simply means that you have checked out one specific commit, often by its hash ID. There is only one functional difference between this and git checkout branch-name, as that also checks out one specific commit, and this sounds like a circular definition, because it is: the difference is that when you do that, you are on a branch, and when you do this, you are not on a branch, i.e., you have a detached HEAD.

    Hence, all "detached HEAD" means is "not on a branch". That is, if you check out one specific commit and get on a branch, you are "on a branch", as git status will say; but if you check out the same specific commit and don't get on a branch, you have a "detached HEAD", as git status will say.

    git checkout is how you get on, or off, a branch

    There's only one user-oriented Git command to get onto any particular branch, or to detach your HEAD to get off a branch, and that's git checkout. If you git checkout a branch name, it checks out that commit and puts you on the branch. If you git checkout anything else—including a tag, or a remote-tracking branch, or a raw hash ID—it gives you a detached HEAD. Hence, as in that other question's accepted answer, if you just want to get back on branch master, you just git checkout master.

    You ran git checkout 1234567 or similar, to check out an older commit, and now have a "detached HEAD".

    But git checkout also updates your index and/or work-tree

    (Very short reminder here: the work-tree is where Git writes, and reads back, copies of what you ultimately save forever as commits in the repository. It's in the form that the rest of the computer's systems understand, instead of a Git-specific form. The index is where you and Git build the next commit you will make; it's in a very Git-specific form, and has some extra goop to coordinate between Git-specific internal form, and "normal computer use" form. The commits are, in effect, saved indexes, minus the extra goop.)

    Your server is, apparently, running off the work-tree—and the reason you ran git checkout 1234567 in the first place was because the commit that's the tip of branch master, which is some commit other than 1234567, doesn't work. If you git checkout master you'll re-break the server, by restoring the tip commit.

    This is where you need to coordinate with others, because to re-attach your HEAD, you must do at least one of two things now:

    To create a new branch, use git checkout -b newbranch

    If you want to, and are allowed to, have the server be "on a branch" without changing commits, just create a new branch whose tip is the current commit. To do so, use git checkout -b:

    git checkout -b mostly-working
    

    Now your HEAD is attached as you are on branch mostly-working, which you just created. Nothing happens to the index and work-tree because the new branch name mostly-working names commit 1234567 (or whatever one it was you checked out earlier).

    This commit is probably somewhere behind master. That is, if we were to draw part of the commit graph, it would look like this, before we do this git checkout -b thing:

    ...--o--o--*--o--o   <-- master
               ^
         commit 1234567
              HEAD
    

    All that git checkout -b does is add a new name pointing to this same commit. To draw it in plain text, we need to shove a few commits up or down (I'll go with up):

                 o--o   <-- master
                /
    ...--o--o--*   <-- mostly-working (HEAD)
               ^
      still commit 1234567
    

    Since we haven't moved commits, the index and work-tree remain unchanged.

    If you make new commits now, they advance the branch

    Just to complete the above, let's look at what happens if you modify something in the work-tree, git add, and git commit. Git will make a new commit on the new branch mostly-working:

                 o--o   <-- master
                /
    ...--o--o--o--*   <-- mostly-working (HEAD)
    

    (incidentally, I'm using * here to mark the current commit, a la git branch marking the current branch).

    The same thing happens with a detached HEAD

    Let's say that you didn't do the git checkout -b, but did modify a file and commit. This would make a new commit as usual—but instead of being on a branch, the new commit would be the new detached HEAD. That is, we would draw the new commit like this:

                 o--o   <-- master
                /
    ...--o--o--o--*   <-- HEAD
    

    This, then, is really the difference between "on a branch" and "detached HEAD". There's nothing special about being "on a branch" except that new commits, when made, advance that branch. The branch name points to the tip of the branch. When you git checkout the branch, you check out the tip-most commit of that branch. When you make new commits, you add them to the branch, by making the name point to the new commit. (The new commit, itself, points back to the previous HEAD commit.) Since HEAD just says which branch is current, updating the branch name to point to the new commit also updates HEAD to point to the new commit.

    When you have a detached HEAD, the process is exactly the same: the new commit points back to the previous commit, and the new commit becomes the new HEAD commit. There's just no name for this commit (well, except HEAD of course, but HEAD changes when you git checkout!).