AdityaGovardhan
AdityaGovardhan

Reputation: 103

Why does git fetch origin master:master fast forward the local branch?

I have been playing around with git and stuck with concepts of git fetch.

I have tried and observed following scenarios:

1) $ git fetch

All the remote branches are fetched and all the corresponding remote-tracking branches are updated. I understand that it looks at .git/config file and uses default remote repository location and default fetch refspec which is generally "refs/heads/:refs/remotes/origin/". It does not fast forward any local branches.

2) $ git fetch origin

Observed same as 1)

3) $ git fetch origin master

Only master branch is fetched and remote-tracking branch origin/master is updated. Does not fast forward the local 'master' branch.

4) $ git fetch origin master:master

Only master branch is fetched and remote-tracking branch origin/master is updated. Fast forwards the local 'master' branch.

Understanding 1) and 2) was straightforward. However 3) and 4) confuses me.

What does a fetch command had to do anything with local branches? Why does it fast forward the local branch? (Proof that it affects the local branch is when I try git fetch origin master:master, while the master branch is checked out, it throws the following error: fatal: Refusing to fetch into current branch refs/heads/master of non-bare repository)

According to my intuition, on any type of fetch command, updating the remote-tracking branches is default behavior. However when git fetch origin master is executed, it has an additional behavior of updating FETCH_HEAD. When git fetch origin master:master is executed, it has an additional behavior of updating local branch 'master'. Is my intuition right?

Does it interpret git fetch origin master:master as git fetch origin refs/heads/master:refs/heads/master? (Maybe redundant to my previous question)

Does it interpret git fetch origin master as git fetch origin refs/heads/master:?

Upvotes: 5

Views: 2054

Answers (2)

torek
torek

Reputation: 489848

RomainValeri's answer is mostly right but misses a few items.

First, we have to make note of a special case about Git versions that predate 1.8.4. The 1.8.4 release notes mention that

  • "git fetch origin master" unlike "git fetch origin" or "git fetch" did not update "refs/remotes/origin/master"; this was an early design decision to keep the update of remote tracking branches predictable, but in practice it turns out that people find it more convenient to opportunistically update them whenever we have a chance, and we have been updating them when we run "git push" which already breaks the original "predictability" anyway.

Hence:

git fetch origin master

behaves differently in Git 1.8.3 and earlier. In 1.8.4 and later, this updates refs/remotes/origin/master (by default—but it depends on your default fetch refspecs).

The description of a refspec is correct but missing one item: the syntax in general is:

  • an optional leading plus sign + indicating --force (for this particular refspec only);
  • a source ref;
  • a separator :; and
  • a destination ref.

Both source and destination can be fully qualified, e.g., refs/heads/master, or not, e.g., master. Git will figure out, if it can, the fully qualified spelling if you use an unqualified variant.

In general, either (or sometimes even both) of the source and destination can be omitted. The meaning of such a refspec is different for git fetch and git push. Omitting the source requires including a colon, while omitting the destination allows you to omit the colon too. That is:

  • master:master, or branch1:branch2, or tag:tag, etc., all provide both source and destination.
  • master is a source without a destination.
  • master: is also a source without a destination.
  • :master is a destination without a source. This is valid only for git push, where it means ask the other Git to delete their reference.
  • : indicates that both source and destination are omitted. This is valid only for git push, where it means find matching branch names and push those.

Ignoring git push, we then allow only source-and-destination, as in master:origin/master or master:master, or source-without-destination, as in master or master:. So there are only two cases:

  1. You provided a source but no destination. git fetch will not update any names, except that Git since 1.8.4 will opportunistically update any remote-tracking names as specified in the default fetch refspec. So if your Git is at least 1.8.4, git fetch origin master updates your origin/master, which is your Git's remote-tracking name for origin's Git's master.

  2. You provided both a source and a destination. git fetch will attempt to update the destination name. If you did not set a force flag—either the global one or the leading +—this update is required to be a fast-forward. If you did not specify --update-head-ok, the destination must not be the current branch either.1 In any case, if your Git is 1.8.4 or newer, your Git will opportunistically update the remote-tracking name for the source name.

Note that:

git fetch origin

(which specifies no refspecs on the command line) will use the default refspecs from your configuration. Assuming this is origin, see git config --get-all remote.origin.fetch to see those default refspecs.


1In what I think amounts to a bug, the test here, for branch is checked-out, tests only the main work-tree's HEAD. If the branch is currently checked out in an added work-tree, the added work-tree's index and work-tree are no longer in sync with its branch. See also Why does Git allow pushing to a checked-out branch in an added worktree? How shall I recover?—the bug is symmetric across fetch and push.

Upvotes: 5

Romain Valeri
Romain Valeri

Reputation: 22067

Updating the matching local ref is the expected behaviour when you explictly mention the destination in the <refspec>.

With the format :

git fetch [options] <remote> <refspec>

where refspec takes the form

<src>:<dst>

(obviously, for "source" and "destination")

it's said in the manual that

The remote ref that matches <src> is fetched, and if <dst> is not an empty string, an attempt is made to update the local ref that matches it.

You can also take a look at this paragraph above this specific syntax.


And to answer the side questions :

However when git fetch origin master is executed, it has an additional behavior of updating FETCH_HEAD. When git fetch origin master:master is executed, it has an additional behavior of updating local branch 'master'. Is my intuition right?

Not exactly. If the <refspec> is given not as <src>:<dst>, the given name is taken as the <src>, so when you did git fetch origin master it's the same as git fetch origin master:master. FETCH_HEAD is used for internals, namely those of git pull, and is available for scripting, but you don't need to bother in everyday git use.

"Does it interpret git fetch origin master:master as git fetch origin refs/heads/master:refs/heads/master? (Maybe redundant to my previous question)"

Yes.

Does it interpret git fetch origin master as git fetch origin refs/heads/master:?

No, if the <dst> side of the <refspec> is empty, nothing is updated at all since you're pointing to nothing. (not to be confused with the case where you omit the : itself, mentioned above. Anecdotically, the reverse :my-branch is deleting my-branch since it's updated with a void ref)

Upvotes: 3

Related Questions