5

From Version Control with Git by Loeliger 2ed,

What if you don’t specify a refspec at all on a git push command? How does Git know what to do or where to send data?

First, without an explicit remote given to the command, Git assumes you want to use origin.

Without a refspec, git push will send your commits to the remote for all branches that are common between your repository and the upstream repository. Any local branch that is not already present in the upstream repository will not be sent upstream; branches must already exist and match names.

  • Are "all branches that are common between your repository and the upstream repository" the same as all the remote-tracking branches in the local repository and the corresponding tracked branches in the remote repository?

  • The quote is for git push. Does it (especially the part for the case without a refspec) apply to git fetch and to git pull?

Tim
  • 1
  • 141
  • 372
  • 590
  • No time to say more, but re "assumes ... `origin`": I think that *was* true at some point, but it was fairly long ago. Git now defaults to `branch..remote` unless (since 1.8.3 I think) you have set `branch..pushremote` and/or `remote.pushdefault`. – torek Dec 30 '15 at 07:17

1 Answers1

6

(I'm afraid this answer is once again rather long, because the questions make assumptions that are—at least today—incorrect. Jump to the boldface section for the actual answers, but everything that comes before that also matters.)

The book text you quote is outdated and (now) just plain wrong (this is a hazard with evolving software, the books get outdated):

First, without an explicit remote given to the command, Git assumes you want to use origin.

I have not, in a relatively quick scan through git's own git history, found precisely when the behavior changed, but the documentation changed in git 1.8.2.1, and now says:

When the command line does not specify where to push with the <repository> argument, branch.*.remote configuration for the current branch is consulted to determine where to push. If the configuration is missing, it defaults to origin.

(This behavior matches git fetch as well—unsurprisingly, since they use the same bit of source code to get that result.) This fails to mention the optional branch.$branch.pushremote setting, which overrides branch.$branch.remote. In all cases here $branch represents your current branch, as shown by the output of git symbolic-ref --short HEAD. (If you're in "detached HEAD" mode, so that git symbolic-ref fails, git push normally gives you a "fatal: You are not currently on a branch" error, I think since version 2.0 when the defaults changed.)

This is still not the whole story; the git-config documentation adds yet one more quirk:

remote.pushDefault
The remote to push to by default. Overrides branch.<name>.remote for all branches, and is overridden by branch.<name>.pushRemote for specific branches.

(Fortunately, as of today, I think that covers everything. That is, git starts by looking for a pushremote setting for the current branch. If there is no such setting, it next looks for remote.pushdefault. If that is also not set, it falls back on the remote setting for the current branch. The behavior in older versions of git may vary. Note also that configuration items are case-insensitive: pushremote and pushRemote are the same configuration name [this makes me wonder if the branch name parts are case-insensitive as well; I'll have to test that out sometime].)

The next part about refspecs is even more wrong, due to git having acquired several changes over time, some of which are not documented well even in git's built-in documentation. Your quote reads:

Without a refspec, git push will send your commits to the remote for all branches that are common between your repository and the upstream repository.

The new documentation says:

When the command line does not specify what to push with <refspec>... arguments or --all, --mirror, --tags options, the command finds the default <refspec> by consulting remote.*.push configuration, and if it is not found, honors push.default configuration to decide what to push (See git-config for the meaning of push.default).

The push.default setting was added around version 1.8 of git, but its default value changed in git 2.0. If you have not configured a push.default you get this warning:

warning: push.default is unset; ...

ever since the configuration item became available. Consulting the git-config documentation, you will now find this:

push.default
Defines the action git push should take if no refspec is explicitly given. Different values are well-suited for specific workflows ...

There are five options, which I will simply list here (see the linked documentation for details): nothing, current, simple, upstream, and matching. The book text you quote describes matching, which was the default behavior prior to git version 2.0. The new default is simple.

On git push, if you configure "matching" behavior

Are "all branches that are common between your repository and the upstream repository" the same as all the remote-tracking branches in the local repository and the corresponding tracked branches in the remote repository?

No.

I recommend that you try running git ls-remote origin (or replace origin with the name of any other remote):

$ git ls-remote origin
28274d02c489f4c7e68153056e9061a46f62d7a0        HEAD
1ff88560c8d22bcdb528a6629239d638f927cb96        refs/heads/maint
28274d02c489f4c7e68153056e9061a46f62d7a0        refs/heads/master
0ac5344e619fec2068de9ab2afdb99d1af8854be        refs/heads/next
5467631257c047b16d95929559dd1887d27b0ddc        refs/heads/pu
68a0f56b615b61afdbd86be01a3ca63dca70edc0        refs/heads/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86        refs/tags/gitgui-0.10.0
3d654be48f65545c4d3e35f5d3bbed5489820930        refs/tags/gitgui-0.10.0^{}
[snip -- lots more omitted]

Then, try git for-each-ref refs/heads:

$ git for-each-ref refs/heads

You'll see similar output, but with different SHA-1s, and the word commit sandwiched in the middle.

The main point here is that each "head" listed in refs/heads—in both outputs, from ls-remote and from for-each-ref—represent the (local) branches that git push considers when doing "matching". The heads that match are added to the push list, giving the set of refspecs (in internal form but easily expressed as name:name or, if you used the --force flag, +name:name).

This happens even if there is no upstream configured for those local branches. Thus, if they (the remote) have a refs/heads/bob that's meant for Bob Roberts to use, and you have a refs/heads/bob that you're using for information about your plumbing bobs or fishing bobbers or whatever, git will attempt to match those up even though your bob is not meant to go upstream.

On push vs fetch

The quote is for git push. Does it (especially the part for the case without a refspec) apply to git fetch and to git pull?

No.

The behavior of git fetch is easier to explain, because it doesn't do all the weird stuff on you that git pull does. The git pull command is supposed to be convenient, but git pull is trying to mash a fundamentally repository-wide "fetch" operation together with a fundamentally branch-constrained "merge" operation. These two do not play well together.1 (At best, I think git pull could at some point be modified to do a simpler, repository-wide "fetch all, then check out and merge, pairwise, some branch(es) based on tracking configs and arguments" sequence. I think this would make it behave the way most people seem to expect it to. It would also be mostly backwards-incompatible, and behave the way it does now only for the simplest—but default—case: fetch all, then merge current branch only. But that's all speculation anyway.)

If you omit all refspecs, git fetch reads your remote.$remote.fetch configuration lines. (Note that this "omit all refspecs" clause means you must also avoid the old "Named file in $GIT_DIR/branches" method, for instance.)

The default configuration line (singular) for a remote named origin usually looks like this (view your local repository configuration file to see it):

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

This is the actual mechanism by which "remote-tracking branches" come into existence.

The fetch process starts off with that same git ls-remote origin step, which gets a list of every reference—branch, tag, or what-have-you—from the remote. The fetch code goes on to match these up with each of the fetch = lines under [remote "origin"]. Asterisks on the left side of a refspec have the obvious meaning; asterisks on the right side are replaced with whatever the left-side asterisk matched. (In current versions of git the * must match a "whole part", loosely defined as "stuff between slashes", but in the upcoming git this constraint will be relaxed—though I'm not sure to what useful end.)

You may have as many fetch = lines as you like. Each original reference is run through each line to get a new (replacement) reference: if the left side matches, the right side provides the replacement. (If the right side is missing or empty, the behavior gets a bit weird for historical reasons, partly to keep git pull working, partly because the desired behavior changed around git 1.8 or so.)

The results of all these replacements form the renamed references, and determine which references are copied at all; but there must be at most one result for each original reference: you can't map "their" refs/heads/foo to "your" refs/remotes/origin/bar and your refs/remotes/origin/zoink, for instance.

To make a repository act as a mirror, you can use +refs/*:refs/* to bring over all references with no renaming (but this will clobber your local branches on each fetch so this mostly makes sense only with --bare).

("Pruning"—removing "dead" references based on what the other side sent during the startup negotiations for a fetch or push—is done separately and only if you set the --prune flag.)


1It didn't start out this way. In fact, the original pull script predated remotes and remote-tracking branches entirely. Commit 7ef76925d9c19ef74874e1735e2436e56d0c4897 split pull into pull and fetch and then the two evolved in different directions over time; once "remotes" and "remote-tracking branches" came into existence, they'd evolved to the point that trying to mix them was, I think, just a bad idea, and now it's even worse. :-)

torek
  • 448,244
  • 59
  • 642
  • 775
  • Well... 1.8.2.1 to be precise: https://github.com/git/git/commit/cfe1348da60b75f6093f8fb6741630b06693e57a – VonC Dec 30 '15 at 17:14
  • https://github.com/git/git/commit/135dadef712f0c4cf884940e751024c831937904, from git 1.7+, is also an interesting read. – VonC Dec 30 '15 at 17:19
  • The `pushremote` part reminds me that git 2.5 introduced the `@{push}` shorthand: http://stackoverflow.com/a/30720302/6309 – VonC Dec 30 '15 at 17:26
  • Thanks. Just to make sure if I understand your reply correctly. Without specifying `refspec`, (1) for `git push`, the default branches to push are those branches that appear in both the remote repository's `refs/heads/` and the local repository's `refs/heads/`? Are the matched branches in the local repo's `refs/heads/` not necessarily local tracking branches, e.g. the `refs/heads/bob` example? Must all the local tracking branches be matched, and/or pushed? – Tim Dec 30 '15 at 22:11
  • (2) for `git fetch`, the default branches to fetch are those branches that appear in both the remote repo's `refs/heads/` and the local repo's `refs/remotes/origin/`? – Tim Dec 30 '15 at 22:11
  • (3) The quote in my post is from p204 of the book. I found that your two boldface sections about `git push` and `git fetch` are similar to p203 of the book, which says `+refs/heads/*:refs/remotes/remote/` is a typical `refspec` for `git fetch`, and `+refs/heads/*:refs/heads/*` a typical one for `git pull`. Does your reply mean that the `refspec`s that are said to be typical in the book are actually the default when not specifying `refspec`? – Tim Dec 30 '15 at 22:25
  • (4) For `git push` and `git fetch`, in both the default case (i.e. the case without specifying `refspec`) and the non-default case (i.e. the case with specified `refspec`), is it correct that the source branches and the destination branches are determined by matching according to the source ref part and destination ref part of the default `refspec` and the specified `refspec`? And Is it correct that remote-tracking branch isn't involved in determining the source branches and the destination branches? – Tim Dec 30 '15 at 22:51
  • (1) Yes, provided you config "matching" (the other settings differ and git 2.0+ uses "simple" by default). (2) Not precisely: fetch acts on the `fetch=` specs in your config file, so you must check the config file (there were broken setup scripts creating weird fetch lines leading to a lot of SO questions recently). The normal default config takes everything the remote presents and renames tem to remote-tracking branches: e.g., if `origin` had `master` and `x` yesterday and has `master` + `y` today, and you fetch yesterday and today, you have `origin` + `x` + `y` now (assumes no `--prune`). – torek Dec 31 '15 at 02:36
  • (3) Again, fetch actually obeys the config lines; push has become complicated with the 5 settings. There's method behind this madness: fetch is normally configured to safely copy from a remote: no matter what changes in a remote-tracking (`refs/remotes/origin/...`) branch, none of your own work is lost, nor is any of your own messy and/or sensitive data given to anyone else. With push, that's often not the case: you push directly to something someone else is also using (`refs/heads/master` for instance) so the new (2.0+) default is to be more conservative about what goes out. – torek Dec 31 '15 at 02:42
  • (4) Yes. All the crazy foolery with `fetch=` lines and with push settings ultimately boils down to (internalized) refspecs. Unfortunately `git fetch` has weirdness across the git 1.8.2 version boundary that makes even this simple answer slightly wrong! :-) Plus, the default tag-obtaining behavior for fetch without either `--tags` or `--no-tags` is inexplicable in terms of refspecs (sigh). – torek Dec 31 '15 at 02:45
  • @torek, I asked [this](https://stackoverflow.com/questions/52390483/how-does-git-fetch-resolve-the-remote) and I was referred to your answer by phd. Can you please verify it once. According to him , source code for how git push and git fetch to resolve the remote is same ? Being unfamiliar with source code myself , I need a verification from you too , if any details are getting missed. Thnx. – Number945 Sep 18 '18 at 23:35
  • It's not clear to me what you mean by "resolve the remote", but once we get to the point where `git fetch` or `git push` is trying to find the string `origin` or some other string, both use the same code path to find the current branch and check for `branch.$branch.remote`. The answer above shows that `git push` and `git fetch` do different things *before* getting to this point. – torek Sep 18 '18 at 23:50
  • My apologies for not being clear. I meant that when some one type `git fetch` and does not mention the repository , then how does git fetch find the repository to fetch from. Is it just `branch.$CurrentBranch.remote` or something else too ? – Number945 Sep 19 '18 at 18:33
  • 1
    Ah, for `git fetch` it really is just "use the current branch's remote, or `origin` if that's not set". – torek Sep 19 '18 at 18:49
  • @torek Thnx. In [another question](https://stackoverflow.com/questions/52391011/resolution-of-git-push-origin-with-remote-repository-push) I asked , I was again referred to your answer. However, I still did not find the solution to my confusion. Can you please have a look at it ? The git doc language is not clear to me. – Number945 Sep 19 '18 at 19:47