gitgit-checkoutgit-detached-head

What is difference between git checkout remotes/origin/branch and git checkout branch?


When I execute

git checkout remotes/origin/test_branch

my HEAD goes in a detached state. Below is output:

C:\..\git_test>git checkout remotes/origin/test_branch
Note: checking out 'remotes/origin/test_branch'.

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 4590fa2 Test branch commit
M       src/test/resources/**

C:\..\git_test>git branch -a
* (HEAD detached at origin/test_branch)
  test_branch
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/test_branch
  remotes/origin/master

And if I execute

git checkout test_branch

on the same branch then I am no more in a detached state.

C:\..\git_test>git checkout test_branch
Switched to branch 'test_branch'
M       src/test/resources/**
Your branch is up to date with 'origin/test_branch'.

C:\..\git_test>git branch -a
* test_branch
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/test_branch
  remotes/origin/master

Please can anyone explain what is the difference and why it is going to the detached state while the branch is same?


Solution

  • Unlike traditional version control systems, Git does not habitually talk to a central server. You have a complete copy of the repository and all its history. All commands are happening locally except a handful like git pull, git push, and git fetch.

    Instead, Git keeps track of the state of remote repositories with "remote tracking branches". These are just like normal branches, except they mark the last place it saw a branch on a remote server, and you can't commit to them. These are updated when you do git fetch which happens as part of a git pull.

    (One of the important things to know is that a Git "branch" is nothing more than a label pointing at a commit.)

    test_branch is a local branch in your repository. remotes/origin/test_branch, usually referred to as origin/test_branch, is the location of a branch called test_branch on the remote called origin the last time you did a git fetch. If they're related, test_branch will have origin/test_branch as its upstream.

    When you git checkout test_branch git checks out the commit test_branch is pointing at, moves HEAD to that commit (HEAD tracks the currently checked out commit), and marks you as being on the test_branch.

    If you git commit you advance HEAD and test_branch.

    git checkout master
    
                  [HEAD]
    A - B - C - D [master]
             \
              E - F [test_branch]
    
    git checkout test_branch
    
    A - B - C - D [master]
             \
              E - F [test_branch]
                    [HEAD]
    
    git commit -a
    
    A - B - C - D [master]
             \
              E - F - G [test_branch]
                        [HEAD]
    

    When you git checkout origin/test_branch git checks out the commit origin/test_branch is pointing at, moves HEAD to that commit (HEAD tracks the currently checked out commit)... and because its a remote tracking branch that's it. You are no longer on a branch, you are in a "detached HEAD" state.

    If you git commit you advance HEAD, but origin/test_branch remains where it was.

    git checkout master
    
                  [HEAD]
    A - B - C - D [master]
             \
              E - F [origin/test_branch]
    
    git checkout origin/test_branch
    
    A - B - C - D [master]
             \
              E - F [origin/test_branch]
                    [HEAD]
    
    git commit -a
    
    A - B - C - D [master]
             \
              E - F [origin/test_branch]
                   \
                    G [HEAD]
    

    With no associated branch there is nothing referring to this new commit. If you were to git checkout master then G would be dangling, effectively lost, though it can be found using git reflog.

    git checkout master
    
                  [HEAD]
    A - B - C - D [master]
             \
              E - F [origin/test_branch]
                   \
                    G
    

    See Working with Remotes for more.