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?

SMBiggs
  • 11,034
  • 6
  • 68
  • 83
  • 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 Feb 06 '19 at 23:18
  • Do `git for-each-ref '**/foo'` and include the results? – jthill Feb 06 '19 at 23:19
  • Was there anything unusual happening when you ran `git pull` after `git checkout feature1`? – mkrieger1 Feb 06 '19 at 23:24
  • @axiac `git fsck` revealed a number of dangling blobs, commits and trees. Too many to even display. – SMBiggs Feb 06 '19 at 23:24
  • Not a concrete answer, but using `gitk --all` can help you figuring out the state of your repository. – mkrieger1 Feb 06 '19 at 23:25
  • @jthill git for-each-ref... results in: `f5a29b108321a2446adb1ff0d33f4c7689c53de7 commit refs/heads/foo f5a29b108321a2446adb1ff0d33f4c7689c53de7 commit refs/remotes/origin/foo` – SMBiggs Feb 06 '19 at 23:27
  • Note that `foo` and `feature1` aren't the real names--I'm changing them to protect the innocent (actually, my company :) ). – SMBiggs Feb 06 '19 at 23:28
  • @mkrieger1 I don't have gitk, sigh (on a macintosh). – SMBiggs Feb 06 '19 at 23:30
  • 1
    There should be something equivalent. See https://stackoverflow.com/questions/17582685/install-gitk-on-mac or https://git-scm.com/download/gui/mac. – mkrieger1 Feb 06 '19 at 23:32
  • Hey guys! I missed something while typing (and consequently while posting this question). I've corrected my question, which should now be easy. Who wants some easy points? – SMBiggs Feb 06 '19 at 23:47
  • Sometimes the head can detach if there is a poor internet connection I.e. If you are trying to push commits while connected to a corporate vpn. You may have to somehow acquire a better connection, maybe change your dns settings to a server that is less populated. – Zach Pedigo Feb 07 '19 at 02:56

2 Answers2

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.

torek
  • 448,244
  • 59
  • 642
  • 775
  • 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 Feb 06 '19 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 Feb 06 '19 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 Feb 06 '19 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 Feb 07 '19 at 00:10
  • I particularly liked your explanation about the 2 "heads" in Git. This double-meaning has often confused me. Thanks! – SMBiggs Feb 26 '19 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.

vfalcao
  • 332
  • 1
  • 3
  • 12