First: be careful with the word pull when talking about Git, because git pull
is a Git command that means: run git fetch
, then if that succeeds, run a second Git command, usually git merge
. To "pull" a branch therefore means to run git fetch
and then run git merge
with a commit based on the result. Well, that is, unless you've configured or instructed git pull
to use a different second command, in which case, it means something different—but it still starts with git fetch
.
(In scripts, you should often avoid git pull
, because the way it behaves depends on the user's configuration. You have to decide whether you want to change what your script does based on the user's configuration.)
All that said, what git checkout
does is ... a little bit complicated. While it sometimes does do a simple kind of pattern matching, it does not invoke git fetch
. That means you shouldn't think of it as pulling anything.
To simplify things, let's talk only about the command git checkout X
, for some X
. (There are about four or five other kinds of git checkout
, depending on how you count.) For this one particular kind of git checkout
, Git goes through a four-step process to figure out what you want, and then do that:
Is X
an existing (local) branch name? That is, if you ran git branch
, would it list X
as a branch? If so, git checkout X
means to check out that (existing) branch. Afterwards, git status
will say on branch X
.
If X
is not an existing branch name, can X
be resolved to a Git commit hash ID? To answer this question for yourself, you can run git rev-parse X^{commit}
.1 If this works, git checkout
will check out that commit as a detached HEAD. Afterwards, git status
will say HEAD detached at X
, or in very old version of Git, HEAD detached at hash
for some hash.
The checkout itself will begin by printing, e.g.:
$ git checkout v2.18.1
Checking out files: 100% (1517/1517), done.
Note: checking out 'v2.18.1'.
You are in 'detached HEAD' state. You can look around, ...
(though some of this changes depending on your internationalization locale setting, e.g., the messages might be in French).
The example above could be what you're getting; it's what I got when using git checkout v2.18.1
to name a tag in the Git repository for Git.
If both 1 and 2 fail, git checkout
goes on to try what Git calls DWIM mode. It now lists all the remote-tracking names, which you can see for yourself using git branch -r
:
$ git branch -r
origin/HEAD -> origin/master
origin/maint
origin/master
origin/next
origin/pu
origin/todo
This is also a case you might be getting. Note that each of these names has a pattern: each one starts with origin/
, which is the name of the remote in this particular Git repository for Git:
$ git remote
origin
This particular repository has only one remote. You can have more than one remote, using git remote add
to add more. In that case, you'll often have multiple different remote-tracking names. For instance, if I were to add remotes named alice
and bob
, I might end up with remote-tracking names alice/master
, alice/dev
, bob/master
, and bob/feature-1234
(in addition to my usual origin/*
ones).
For case 3, Git will take the name you gave it and look through all your remote-tracking names. For each one, Git will take off the remote's name and the slash—for instance, origin/master
becomes just master
, alice/master
becomes just master
, and bob/master
becomes just master
. The same goes for alice/dev
and bob/feature-1234
. If exactly one of these stripped-down names match the argument you used, Git selects this "DWIM mode" checkout option.
Note that if two or more names match—e.g., if I have both an origin/dev
and an alice/dev
and I type git checkout dev
(and we're in case 3 in the first place)—then "DWIM mode" fails and we move on to case 4. I'll describe "DWIM mode" the rest of the way in a moment; for now, let's move on to case 4.
Having failed at all three of the earlier cases, git checkout
now tries to use X
as a path name. If that works, it does the equivalent of git checkout -- X
. This extracts the copy of file X
from the index and uses that to overwrite the copy of X
in the work-tree.
If all four cases fail, git checkout
itself fails. Note that if cases 1, 2, or 3 succeed at resolving the name X
, but the rest of the operation fails for some reason, git checkout
does not try any of the later cases. The four steps above are done only to figure out what X
should mean in order to decide whether to do a commit checkout (cases 1 and 2), a name-creating checkout (case 3), or a file checkout (case 4).
Branch-creating checkouts, including DWIM mode
The git checkout
command has the ability to create a branch. The most basic way to create a branch in Git is to use git branch
. Here, you pick some existing commit—often the tip commit of some existing branch—and tell git branch
to make a new name that identifies the same commit as the existing name:
git branch new-branch a123456
(where a123456
is the commit hash you want), or:
git branch new-branch master
(where you let the name master
resolve to the commit hash, then create the new name new-branch
, pointing to that same commit). If the name master
currently resolves to the hash ID a123456
, these two commands accomplish the exact same thing, which we can draw as:
...--o--o--o <-- master, new-branch
Now all the commits that end at a123456
are on both branches.
Having made a new branch, you can then git checkout
that branch:
git checkout new-branch
That puts you on new-branch
while checking out commit a123456
(again, assuming master
meant a123456
—use git rev-parse master
to see the actual hash ID).
To make this all a bit easier, git checkout
offers you the ability to:
- create a branch name, and then
- switch to it (and its commit)
as a single command. Typically, to do this, you do the same thing you do with git branch
: you pick out a commit, maybe using an existing branch name to do that, then you say: Make a new name, make it identify that commit, and check it out. So you could write:
git checkout -b new-branch a123456
to do the same thing we did with git branch
followed by git checkout
. That's not a huge savings, but it's convenient. It's especially convenient when you want to create the new branch from the same commit as whatever branch you're on right now, because then you can just run:
git checkout -b new-branch
and the new branch's commit is the same as the current commit.
But there's more to set for a branch. In particular, any branch can have one upstream. The upstream of a branch helps you out a bit (or helps Git help you out a bit) when you run git status
, git push
, and various other Git commands. See this answer for more—but in short, you might want to run:
git branch --set-upstream-to=origin/dev dev
after creating your own dev
from the commit identified by the name origin/dev
. If you did this the hard way, it would be:
git branch dev origin/dev
git branch --set-upstream-to=origin/dev dev
git checkout dev
or:
git branch dev origin/dev
git checkout dev
git branch --set-upstream-to=origin/dev
(you get to leave out one copy of the name dev
here as git branch
assumes you mean to operate on the current branch, which you set with the git checkout
command).
If you use git checkout dev
when you don't have a dev
, and invoke git checkout
's DWIM mode, Git assumes that you meant to do all three of these operations. So a simple:
git checkout dev
does all three, and that's a pretty significant savings: you don't have to type origin/dev
at all, because Git figured that out on its own, and you only have to type dev
once.
1There a few exceptions for particularly complicated strings. For instance, if we set X to the literal stringHEAD:/fix
and try to use git rev-parse X:/fix^{commit}
, we'll get the wrong answer. To get the right answer in all cases requires two steps:
hash=$(git rev-parse $X) || ... handle error; do not proceed ...
hash=$(git rev-parse $hash^{commit}) || ... handle error ...
If these two steps both succeed, $hash
contains the hash ID of the commit that git checkout
would check out.