Reputation: 11688
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?
Upvotes: 0
Views: 7002
Reputation: 331
git checkout origin/foo
this conflicts with the local foo
branch.
You can just git checkout foo; git pull
or you can delete your current foo branch, and check it out again;
You are not going to lose work.
Upvotes: 0
Reputation: 487745
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.
Upvotes: 1