0

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?

3
  • if you check out an explicit commit you are no longer following a branch. You will need to fix master. Commented Jan 25, 2017 at 16:47
  • The problem I'm having with that, is that master is working on my local machine, just doesn't seem to be on the server... I'm not sure why this is, and can't afford to allow the site to be down long enough to investigate it on the live server, so I want to 'restore' the live version to the state it was in when the commit that the detached HEAD is currently pointing to was the live version, so that I can pull that from the server, and reimplement my changes from there... Commented Jan 25, 2017 at 16:50
  • Sounds like a good opportunity to bring in a senior developer colleague. Being able to see what is going on while working is important. Commented Jan 25, 2017 at 17:10

1 Answer 1

1

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:

  • create a new branch, or
  • re-break the server, at least temporarily.

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!).

6
  • Thanks for the comprehensive answer. So just to make sure I understand correctly: when I run git branch on the server, it displays the output: " * (detached from 0e57d3d) (this is the currently checked out commit), master ": i.e. I have two branches - the detached HEAD that I'm currently on (which works- except the original bug that my colleague tried to fix), and 'master', which is now broken.If I run "git branch resolveHead" from the current detached HEAD that is checked out, this will create a new branch called 'resolveHead'- and the Head and 'body' will be checked out to this branch? Commented Jan 26, 2017 at 10:10
  • So since I would then no longer be in a 'detached HEAD' state, I could then delete the master branch, and run git branch master from there, to create a new 'master' branch, and check that out? Commented Jan 26, 2017 at 10:11
  • Not quite: git branch <newbranch> will create a new branch pointing to the same commit as HEAD but will not check it out, so you will still have a detached HEAD. (This is a key Git thing: different branch names and/or HEAD may all point to a single commit. Being "on a branch" means that HEAD contains the branch name instead of the commit ID. The branch name itself always contains a commit ID, so when HEAD has a branch name, Git uses the name to find the commit.) You must create the name, so that there is one, and then also check it out. You can do this ... [continued]
    – torek
    Commented Jan 26, 2017 at 10:47
  • with two commands: git branch resolveHead followed by git checkout resolveHead, or with just one command: git checkout -b resolveHead. Note, though, that if you delete master and re-create it pointing to an earlier commit, you have "rewritten history" by removing some commit(s) from master. Git itself doesn't care but other people can easily accidentally re-introduce the removed commits. It's generally wiser to only add new history for exported branches, unless you have pre-arranged with all users for them to be aware of these rewrites.
    – torek
    Commented Jan 26, 2017 at 10:48
  • What this usually means is that to back out a bad commit (or several-in-a-row) you should use git revert to add a new commit that "undoes" the bad one(s). Note that git revert is not "revert to" (i.e., doesn't take the commit state to go to) but rather means "undo some previous commit by reverse-applying it as a patch". (Again, though, this is all just common practice: different groups can make up their own rules.)
    – torek
    Commented Jan 26, 2017 at 10:52

Not the answer you're looking for? Browse other questions tagged or ask your own question.