Having a hard time understanding git-fetch

First, there's no such concept of local tracking branches, only remote tracking branches. So origin/master is a remote tracking branch for master in the origin repo.

Typically you do git fetch $remote which updates all your remote tracking branches, and creates new ones if needed.

However, you can also specify a refspec, but that will not touch your remote tracking branches, instead, it will fetch the branch you specified and save it on FETCH_HEAD, unless you specify a destination. In general you don't want to mess with this.

Finally,

fetch = +refs/heads/*:refs/remotes/origin/*

That means if you do

git fetch origin

It will actually do:

git fetch origin +refs/heads/*:refs/remotes/origin/*

Which means a remote heads/foobar will be local remotes/origin/foobar, and the plus sign means they'll be updated even if they are not fast-forward.

Perhaps what you think as a tracking branch is something related to git pull and the merge config.


felipec have answered most of issues in question in his answer.

A few remaining (most taken from git fetch manpage; which is a bit dated in some places, unfortunately):

  • If remote-tracking branch (branch which tracks some branch in some remote repository) does not exists, it would be created.

  • The branch you fetch into (the <dst> in [+]<src>:<dst>) doesn't need to reside in remotes/<remote>/ namespace. For example for mirroring repositories (git clone --mirror) refspec is 1 to 1. In old days before separate remotes layout (before remotes/<remote>/ namespace for remote-tracking refs) master branch was fetched into branch called origin. Even currently tags are fetched directly into tags/ namespace in mirroring fashion.

  • If branch you are fetching into (the right hand side of refspec <src>:<dst> does exist, Git would check if download would result in fast-forward, i.e. if current state in <dst> is ancestor of state in <src> in given remote repository. If it isn't, and you don't use -f/--force option to git-fetch, or prefix refspec with '+' (use +<src>:<dst> refspec) fetch would refuse to update that branch.

  • git fetch origin master is equivalent to git fetch origin master:, not to git fetch origin master:master; it stores fetched value of master branch (of remote origin) in FETCH_HEAD, and not in master branch or remote-tracking remotes/origin/master branch. It can be followed by git merge FETCH_HEAD. Usually not used directly, but as part of one-time pull without setting remote-tracking branch: git pull <URL> <branch>.

  • +refs/heads/*:refs/remotes/origin/* as value for remote.origin.fetch configuration variable means that each branch (ref in refs/heads/ namespace) in remote origin is fetched into respectively named remote-tracking branch in refs/remotes/origin/ namespace, e.g. master branch in origin (i.e. refs/heads/master ref) would be fetched into origin/master remote-tracking branch (i.e. refs/remotes/origin/master ref). The '+' prefix means that fetch would succeed even in non fast-forward case, which means when branch on remote is rebased, or rewound (reset to some state in past) or otherwise amended.

Sidenote: You would probably want to use higher level git remote command to manage remote repositories and get updates.


Note that the main maintainer for Git has now (Git 2.1, August 2014) added this explanation for git fetch:
(See commit fcb14b0 by Junio C Hamano (gitster):

CONFIGURED REMOTE-TRACKING BRANCHES

You often interact with the same remote repository by regularly and repeatedly fetching from it. In order to keep track of the progress of such a remote repository, git fetch allows you to configure remote.<repository>.fetch configuration variables.

Typically such a variable may look like this:

[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*

This configuration is used in two ways:

  • When git fetch is run without specifying what branches and/or tags to fetch on the command line, e.g. git fetch origin or git fetch, remote.<repository>.fetch values are used as the refspecs---they specify which refs to fetch and which local refs to update.
    The example above will fetch all branches that exist in the origin (i.e. any ref that matches the left-hand side of the value, refs/heads/*) and update the corresponding remote-tracking branches in the refs/remotes/origin/* hierarchy.

  • When git fetch is run with explicit branches and/or tags to fetch on the command line, e.g. git fetch origin master, the <refspec>s given on the command line determine what are to be fetched (e.g. master in the example, which is a short-hand for master:, which in turn means "fetch the 'master' branch but I do not explicitly say what remote-tracking branch to update with it from the command line"), and the example command will fetch only the 'master' branch.
    The remote.<repository>.fetch values determine which remote-tracking branch, if any, is updated.
    When used in this way, the remote.<repository>.fetch values do not have any effect in deciding what gets fetched (i.e. the values are not used as refspecs when the command-line lists refspecs); they are only used to decide where the refs that are fetched are stored by acting as a mapping.


Note also that, with Git 2.5+ (Q2 2015), git merge FETCH_HEAD can merge multiple git fetch's.

See commit d45366e by Junio C Hamano (gitster), 26 Mar 2015.
(Merged by Junio C Hamano -- gitster -- in commit bcd1ecd, 19 May 2015)

"git merge FETCH_HEAD" learned that the previous "git fetch" could be to create an Octopus merge, i.e. recording multiple branches that are not marked as "not-for-merge";
this allows us to lose an old style invocation "git merge <msg> HEAD $commits..." in the implementation of "git pull" script; the old style syntax can now be deprecated.

The git merge doc now mention:

When FETCH_HEAD (and no other commit) is specified, the branches recorded in the .git/FETCH_HEAD file by the previous invocation of git fetch for merging are merged to the current branch.


Git 2.13 (Q2 2017) officially retires the old syntax for git merge.
See commit b439165 (26 Mar 2015) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit 1fdbfc4, 30 Mar 2017)

merge: drop 'git merge <message> HEAD <commit>' syntax

Stop supporting "git merge <message> HEAD <commit>" syntax that has been deprecated since October 2007, and issues a deprecation warning message since v2.5.0.

That means the warning message old-style "'git merge <msg> HEAD <commit>' is deprecated." is no more.