0

I'm working on a local branch foo that has a rich and useful history. I make a bunch of changes and commit them. A git status says:

On branch foo Your branch is ahead of 'origin/foo' by 1 commit. (use "git push" to publish your local commits)

So I go ahead and type git push.

Which seems work just fine. A quick git status reveals:

On branch foo Your branch is up to date with 'origin/foo'. nothing to commit, working tree clean

I switch to my local main main branch (called feature1), git checkout feature1. No problem. I then git pull to add all my co-workers' changes.

And now I want to switch back to foo to merge the feature1 changes I just pulled into foo.

EDIT (I missed this both when typing AND when posting! It's the key!!!)

git checkout origin\foo

And lo-and-behold! I get this message, which I've never seen:

Note: checking out 'origin/foo'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at f5a29b1083 cosmetic changes for code review

Now the result of git status is

EDIT (correction):

HEAD detached at origin/foo
nothing to commit, working tree clean

So my questions are multiple:

1) What did I do wrong? This is something that I've been doing over and over for a long time, yet nothing like this has happened before.

2) How can I ensure that this doesn't happen again?

3) how to I fix this without losing my work and (more importantly) without polluting everyone's history when I merge foo into feature1 in the future?

11
  • The message "checking out 'origin/foo'" is displayed on git checkout foo if a local branch foo does not exist but there is one (and only one) remote tracking branch foo (origin/foo in this case). But it should also create the local branch foo in this case. You can end up in a "detached HEAD" state if you checkout anything but a branch (a commit hash, a tag, a relative reference like HEAD~1 etc). However, I cannot tell why your local branch suddenly disappeared. It is possible that your repo became corrupt somehow. Run git fsck to find out.
    – axiac
    Commented Feb 6, 2019 at 23:18
  • Do git for-each-ref '**/foo' and include the results?
    – jthill
    Commented Feb 6, 2019 at 23:19
  • Was there anything unusual happening when you ran git pull after git checkout feature1?
    – mkrieger1
    Commented Feb 6, 2019 at 23:24
  • @axiac git fsck revealed a number of dangling blobs, commits and trees. Too many to even display.
    – SMBiggs
    Commented Feb 6, 2019 at 23:24
  • 1
    There should be something equivalent. See stackoverflow.com/questions/17582685/install-gitk-on-mac or git-scm.com/download/gui/mac.
    – mkrieger1
    Commented Feb 6, 2019 at 23:32

2 Answers 2

1

Per edit: Ah, you essentially ran git checkout origin/foo. (The backslash spelling is Windows-specific, but the forward-slash variant of this works everywhere.)

The git checkout command first tries whatever name you give it as a branch name, i.e., as refs/heads/whatever. If that works—if it's a valid branch name—Git checks out the tip commit of that branch and attaches HEAD to that branch, so that you are "on branch whatever" as git status will put it.

But if a branch whose full name is refs/heads/whatever does not exist, Git eventually tries resolving the name according to the six-step process outlined in the gitrevisions documentation. This process eventually finds refs/remotes/origin/foo in your case. That's not a branch name, but is a valid commit hash, so Git checks out that particular commit as a "detached HEAD". Instead of storing a branch name in HEAD, Git stores the raw hash ID of the commit.

Ultimately, all of this relies on the dual nature of HEAD in Git: it's both the current branch and the current commit. To achieve this, Git normally writes a branch name into HEAD and uses the branch name itself to record the commit hash. That's the "attached HEAD" case. To support moving off a branch, Git is willing to write a raw commit hash ID into HEAD.

You can ask Git: what branch does HEAD name? using git symbolic-ref HEAD. This gets the name out of HEAD without getting the commit ID. If you are in detached-HEAD mode, it gives you an error.

Or, you can ask Git: what commit hash ID does HEAD name? using git rev-parse HEAD. This gets the commit hash ID from HEAD, using the branch name if you are in attached-HEAD mode, or the raw hash ID if you are in detached-HEAD mode. Either way it works. (It fails in the rare but not impossible case in which HEAD contains a branch name, but the branch does not exist. That case is normal in a new, completely-empty repository, and is available via git checkout --orphan at any other time.)

(Note: there's an intermediate step where git checkout tries creating a branch name. This is sometimes called the "DWIM option", or "Do What I Mean". It works by looking through all your remote-tracking names to see if there's exactly one that matches the name you gave, except for the origin/ in it.)


For this to have happened, there must be a file in your .git directory named foo. This file must either contain the text:

ref: refs/remotes/origin/foo

or be a symbolic link to refs/remotes/origin/foo. To see which, try:

ls -l .git/foo

and:

cat .git/foo

Moreover, there must not be a branch named foo as Git will prefer the branch to the file/symlink:

$ git checkout diff-merge-base
Switched to branch 'diff-merge-base'
$ ln -s refs/remotes/origin/master .git/master
$ git checkout master
warning: refname 'master' is ambiguous.
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

But:

$ rm .git/master
$ echo 'ref: refs/remotes/origin/master' > .git/foo
$ git checkout foo
Note: checking out 'foo'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
... [snip]
$ git status
HEAD detached at origin/master
nothing to commit, working tree clean

(Removing .git/foo and using git checkout master works and puts things right.)

How all this happened inside your .git directory is a mystery.

5
  • Thank you for your quick response! But I made a mistake when pasting a little. Please take another look at my question and see the edit (I should have pasted 'foo' when I pasted 'story/RUN-16657_C_log_after_build'--I was trying to simplify the actual branch name).
    – SMBiggs
    Commented Feb 6, 2019 at 23:36
  • The meat of the answer remains: there must be a .git/foo that is a regular file or symlink that points Git to refs/remotes/origin/....
    – torek
    Commented Feb 6, 2019 at 23:45
  • And I discovered another mistake in posting. Look at the EDIT with the comment: "It's the key!". I think you'll identify my error immediately.
    – SMBiggs
    Commented Feb 6, 2019 at 23:45
  • Yep, added a front part to the answer, moving the remaining part (doesn't apply, as this was not the actual problem) to a second section.
    – torek
    Commented Feb 7, 2019 at 0:10
  • I particularly liked your explanation about the 2 "heads" in Git. This double-meaning has often confused me. Thanks!
    – SMBiggs
    Commented Feb 26, 2019 at 16:27
0
  1. When you issued
    git checkout origin/foo

this conflicts with the local foo branch.

  1. You can just git checkout foo; git pull or you can delete your current foo branch, and check it out again;

  2. You are not going to lose work.

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