18

Here's the contents of the remote and branch sections of my .git/config file.

[remote "origin"]  
    url = https://EvanAad@bitbucket.org/EvanAad/bitbucketstationlocations.git  
    fetch = +refs/heads/*:refs/remotes/origin/*  
[branch "master"]  
    remote = origin  
    merge = refs/heads/master

What is the meaning and purpose of the contents of these sections, in particular the fetch and merge subsections? How is this information used by Git to guide its operation?

torek
  • 448,244
  • 59
  • 642
  • 775
Evan Aad
  • 5,699
  • 6
  • 25
  • 36
  • 4
    You can read all this in the documentation... https://git-scm.com/docs/git-config – René Höhle Apr 10 '17 at 08:13
  • 1
    @Stony: I don't understand the documentation. I was hoping for a clearer explanation. – Evan Aad Apr 10 '17 at 08:21
  • @Rene: Seems to me that the docs on git-config splains all the ways you can use git-config to list and manipulate the git config file - but nothing about what the various contents cause in reference to git behavior. – qxotk Dec 24 '20 at 02:56

2 Answers2

27

TL;DR summary

Overall, we're interested in two or three things:

  1. If you run git fetch with no additional arguments, what happens?
  2. If you run git merge or git rebase with no additional arguments, what happens?
  3. If you run git pull with no additional arguments, what happens?

The short answer to question #1 is: Git picks one of your remotes to fetch from, and then fetches from that remote. The remote Git picks is from the remote = name setting under the [branch] section. If there isn't any such setting, Git uses origin.

The answer to question #2 is: Git picks some name to use, as if you had run git merge name or git rebase name. The name is based on the merge = ref setting under the [branch] section—but the way this works is somewhat obscure: if this same section says, for instance, remote = origin and branch = refs/heads/master, the name Git picks to use is not master but rather origin/master. If it says remote = origin and branch = develop, the name Git picks is origin/develop, and so on.

(While this looks very straightforward, the actual mapping inside Git is rather tricky: if the section says remote = . and branch = master, the name is master, not ./master, for instance. If you set up unusual fetch refspecs, even more weirdness can happen. This answer does not cover this last case at all.)

The answer to question #3 is in some ways the easiest: git pull simply runs git fetch first, then—provided that succeeds—one of the other two commands, git merge or git rebase, so you really only have to look at questions 1 and 2.

Long

The merge entry under each branch section is, I think, the least obvious. The Git documentation keeps it a bit obscure. Let's cover the others first.

Settings under a [remote "..."] section

There are many possible settings. In general, you do not have to set any of them with git config directly—almost all of them have wrapper commands to set them in a more "user friendly" manner. That includes both of the settings you see here. It's rare to want to change these, either.

The remote section for each named remote, such as origin, lists the URL for git fetch (and optionally a separate push URL for git push, and other remote.* configuration items as described in the git config documentation). It also has one or more fetch lines which supply the default refspec arguments for git fetch from that remote.

That is, if you run:

git fetch origin

Git will look up remote.origin.url to see where to connect, then connect there, then retrieve references based on all the remote.origin.fetch entries. The default that you see here:

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

tells Git to copy all branches1 from the remote, renaming them to an origin/-prefixed remote-tracking branch2 in your own repository, so:

git fetch origin

basically fetches everything. (The leading + says that Git should do this regardless of whether the remote-tracking branch update is a fast-forward operation. That is, it's like using --force, but without having to specify --force.)

On the other hand, if you run:

git fetch origin a:b c:d

Git will completely ignore all the fetch = lines, retrieving only references a and c from the remote, writing them to references b and d in your repository. (And since this has neither + nor --force, none of these will be force-updated—though in most cases that makes no difference anyway.)


1, 2 A reference is a generic term that covers both branches and tags (and more things as well). Branch names like master are just short-hand for references that begin with refs/heads/. Remote-tracking branch names like origin/master are just short-hand for references that begin with refs/remotes/. Note that the origin/ part comes from the fetch = line—but for all this to work the way it is supposed to, that line must match the name of the remote in the square brackets.


Settings under a [branch "..."] section

There are many possible settings. In general, you do not have to set any of them with git config directly—almost all of them have wrapper commands to set them in a more "user friendly" manner. That includes both of the settings you see here. It's not that rare to want to change one or both of them, using a command we will see in a moment.

The remote part is pretty clear on its own, though: it means that if you are on branch master and run git fetch without giving a remote name at all, Git should fetch from the remote named origin.

The merge part is the tricky one. It lists the name of a branch as seen on the remote. Note that when we run git fetch origin, we tell our Git to call up another Git, find the other Git's master, and copy that into our repository but call it origin/master. And yet ... this merge line says merge = refs/heads/master. Shouldn't it say: merge = refs/remotes/origin/master?

It probably should—but this setting predates the very invention of remotes in the first place. So it doesn't; instead, it lists the full name of the reference as it appears on the remote.

This setting is what gets used if you run git merge or git rebase without providing a branch name to merge or rebase-upon. Git runs the name through the mappings provided by the fetch = line for the remote, to figure out that it should merge with origin/master, for instance.

This setting is also used by the git pull convenience command, which is effectively3 the same as running git fetch followed by running git merge.

You might want to change one or both of these. For instance, if you create a new local branch feature/tall, it may have no branch.feature/tall.remote and branch.feature/tall.merge settings at all.

Since you just created this branch, there is no origin/feature/tall. The Git over at origin does not have feature/tall yet, so you do not have a copy of it.

Then you git push origin feature/tall:feature/tall to have your Git call up origin's Git and have their Git create that branch, so that you now do have origin/feature/tall. You might want your Git to remember that.

You could run two git config commands, but instead, you can run one higher level wrapper command:

git branch --set-upstream-to=origin/feature/tall feature/tall

This tells your Git to set branch.feature/tall.remote to origin, and branch.feature/tall.merge to refs/heads/feature/tall (that being the name on origin).

You can combine the git push and the git branch --set-upstream-to steps using git push -u, which is even better, but the point here remains: you use a wrapper to get both values set at once, since setting only one value is not that useful.4

The special remote name . means this repository (as opposed to some remote repository). If the [branch "xyzzy"] section says remote = . and branch = refs/heads/whatever, then branch xyzzy has local branch whatever as its upstream, rather than having, e.g., origin/whatever as its upstream.


3This deliberately glosses over a lot of fiddly details.

4Setting just the remote part does affect a future git push, but git merge and git rebase won't be able to do the remote-tracking branch mapping without both entries.

torek
  • 448,244
  • 59
  • 642
  • 775
  • The settings under the `branch` section have no effect on a `fetch`; only on `merge` and `pull`, correct? – Evan Aad Apr 10 '17 at 09:18
  • 1
    Right. (And they only affect `git pull` because it runs `git merge`, or if you configure it to, `git rebase`, although the thing that configures `git pull` *to* run `git rebase` is yet another `branch`-section entry!) – torek Apr 10 '17 at 09:22
  • @torek But yet you write: "The `remote` part is pretty clear on its own, though: [...] Git should fetch from the remote named origin"... It would be nice if you could clear that up. – philipp2100 Jun 26 '20 at 14:37
  • @philipp2100: just so I get it right: what's the confusion here? Would it help if I included the actual text, `remote = origin`, here? – torek Jun 26 '20 at 18:41
  • No, the confusing part is that you describe `remote`, which is under the `branch` section, to affect `git fetch` in a certain way, but in the comments you both agree that settings under the `branch` section *wouldn't* affect `git fetch`. – philipp2100 Jun 27 '20 at 06:15
  • @philipp2100: ah. The issue here is that the `remote = ` tells `git fetch` *which* remote to fetch *from*. It has no effect on the fetching action, though, just on which URL and such get used. To control the fetching *action* from that particular remote—including the URL itself—you have to look in the `[remote]` section. I'll see if I can make that clearer. – torek Jun 28 '20 at 02:59
  • @torek Your latest comment and edit cleared it up, thanks! – philipp2100 Jun 28 '20 at 07:20
13

Its called refspec. Its the mechmism that git is using to "talk" to the remote server and to map local branches to remote branches.

Refspecs

A refspec maps a branch in the local repository to a branch in a remote repository.
This makes it possible to manage remote branches using local Git commands and to configure some advanced git push and git fetch behavior.

A refspec is specified as [+]<src>:<dst>. The <src> parameter is the source branch in the local repository, and the <dst> parameter is the destination branch in the remote repository.
The optional + sign is for forcing the remote repository to perform a non-fast-forward update.

Refspecs can be used with the git push command to give a different name to the remote branch. For example, the following command pushes the master branch to the origin remote repo like an ordinary git push, but it uses qa-master as the name for the branch in the origin repo. This is useful for QA teams that need to push their own branches to a remote repo.

git push origin master:refs/heads/qa-master

By adding a few lines to the Git configuration file, you can use refspecs to alter the behavior of git fetch.

By default, git fetch fetches all of the branches in the remote repository. The reason for this is the following section of the .git/config file:

[remote "origin"]
    url = https://git@github.com:mary/example-repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*

The fetch line tells git fetch to download all of the branches from the origin repo.
But, some workflows don’t need all of them. For example, many continuous integration workflows only care about the master branch. To fetch only the master branch, change the fetch line to match the following:

[remote "origin"]
    url = https://git@github.com:mary/example-repo.git
    fetch = +refs/heads/master:refs/remotes/origin/master

You can also configure git push in a similar manner. For instance, if you want to always push the master branch to qa-master in the origin remote (as we did above), you would change the config file to:

[remote "origin"]
    url = https://git@github.com:mary/example-repo.git
    fetch = +refs/heads/master:refs/remotes/origin/master
    push = refs/heads/master:refs/heads/qa-master

Refspecs give you complete control over how various Git commands transfer branches between repositories.

They let you rename and delete branches from your local repository, fetch/push to branches with different names, and configure git push and git fetch to work with only the branches that you want.

Community
  • 1
  • 1
CodeWizard
  • 128,036
  • 21
  • 144
  • 167
  • Suppose the `fetch` ref spec is `fetch = +refs/heads/*:refs/remotes/origin/*`. If the remote repository has a branch named `mybranch`, and my local repository does not have a remote tracking branch by the same name, will a remote tracking branch by this name be created, or will an error be reported? – Evan Aad Apr 10 '17 at 09:24
  • 1
    It will create new branch if it does not exist but if you are using git v2.X and you did not changed the default fetch refspec you will be able to **push** only to the same branch. – CodeWizard Apr 10 '17 at 09:29
  • And what if, conversely, my local repository has a remote tracking branch by the name `origin/mybranch` that was created by a previous `fetch`, but in the meanwhile the branch `mybranch` has been deleted on the remote. What will happen in the wake of a `fetch`, given the refspec mentioned in my previous comment? Will the local `origin/mybranch` be deleted? – Evan Aad Apr 10 '17 at 09:32
  • 1
    If a branch is deleted you can update your local repo with `git fetch --all --prune` it will delete the local undeleted branches. if you try to push deleted branch to the remote it will be created on the remote again – CodeWizard Apr 10 '17 at 09:34
  • @CodeWizard could you please format this line - `fetch = +refs/heads/master:refs/remotes/origin/master`? I can't as it's not enough characters. – hIpPy Apr 19 '17 at 16:09