7

So, we frequently optimize clones by effectively cloning with --single-branch. However, we are then unable to get additional branches later. What is the difference, plumbing-wise, between a git clone with and without --single-branch? How can we fetch down additional branches later?

A standard clone:

$ git clone -b branch-name https://repo.url standard
$ cd standard
$ git checkout remote-branch
Branch 'remote-branch' set up to track remote branch 'remote-branch' from 'origin'.
Switched to a new branch 'remote-branch'

A single-branch clone:

$ git clone -b branch-name --single-branch https://repo.url singlebranch
$ cd singlebranch
$ git checkout remote-branch
error: pathspec 'remote-branch' did not match any file(s) known to git

UPDATE

Per the answer from @AndrewMarshall, below, you need to update the default fetch refspec in the config. Even though you can hack your way around the fetch to pull down the right commits, your attempted checkout will absolutely deny knowing anything about that branch if you don't fix your config first:

$ git fetch origin +refs/heads/remote-branch:refs/remotes/origin/remote-branch
From https://gerrit.magicleap.com/a/platform/mlmanifest
 * [new branch]      remote-branch -> origin/remote-branch

$ git checkout remote-branch 
error: pathspec 'remote-branch' did not match any file(s) known to git

$ git remote set-branches origin --add remote-branch
$ git checkout remote-branch 
Branch 'remote-branch' set up to track remote branch 'remote-branch' from 'origin'.
Switched to a new branch 'remote-branch'

Notice we fetch it, then reconfigure, then checkout. The fetch can happen in any order (though you have to pass parameters if not in the config) but the checkout is gated by the config.

Dustin Oprea
  • 9,673
  • 13
  • 65
  • 105
  • I recommend avoiding `--single-branch` in general (also avoid `--shallow` in general). It's OK for very specific cases, but if you actually plan to use the repository, just go ahead and make a full clone. It's painful once, at the start, if the repository is big; after that, subsequent fetches are generally pretty light weight. – torek Mar 26 '19 at 05:38
  • That makes sense for developer systems but less sense for ephemeral build workspaces. – Dustin Oprea Mar 27 '19 at 06:52
  • Yes—although it turns out that when Jenkins, for instance, makes shallow clones (which are also single-branch clones), some sensible ideas, such as comparing `master` to some development branch, stop working. Using `--depth=50` makes for even more mysterious failures: almost all builds work, but then some don't, and it takes a long time to realize that it is long commit chains that are breaking! – torek Mar 27 '19 at 07:02

2 Answers2

12

--single-branch works by setting the remote’s fetch property to only be the single branch name, instead of a glob:

$ git config --get-all remote.origin.fetch
+refs/heads/master:refs/remotes/origin/master

So let’s add an entry with git remote set-branches:

$ git remote set-branches origin --add other-branch
$ git config --get-all remote.origin.fetch    
+refs/heads/master:refs/remotes/origin/master
+refs/heads/other-branch:refs/remotes/origin/other-branch

$ git fetch
From origin
 * [new branch]      other-branch        -> origin/other-branch

$ git checkout other-branch
Branch 'other-branch' set up to track remote branch 'other-branch' from 'origin'.
Switched to a new branch 'other-branch'

Or, alternatively, make it a glob so all branches may be fetched (the default, non-single-branch behavior) (note that the * is quoted to avoid shell expansion; the glob is for git, not the shell):

git remote set-branches origin '*'
Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
  • You're right. Didn't think about that. So, is the refspec provided to git-fetch just a filter on what we're already auditing for that remote? – Dustin Oprea Mar 26 '19 at 02:17
  • It seems two-fold: `git-fetch` uses the configured refspecs by default when asking the remote, and, when a refspec is given explicitly (e.g. `git fetch other-branch`), `git-fetch` will only create/update a local ref to match the remote if it’s configured (as otherwise it doesn’t know what local ref to create/update). – Andrew Marshall Mar 26 '19 at 02:22
  • Actually, it looks like the checkout is also constrained by it. You can pass the value you suggested directly, and fetch brings it down. However, checkout will still staunchly refuse to cooperate. See the example in my addendum above. – Dustin Oprea Mar 26 '19 at 02:39
  • It would probably work if you gave the ref itself (e.g. `git checkout other-branch origin/other-branch`), rather than letting `git-checkout` try and figure it out for itself (in which case it will use the fetch config ([docs](https://github.com/git/git/blob/v2.21.0/Documentation/technical/api-remote.txt#L76-L79))). – Andrew Marshall Mar 26 '19 at 02:51
  • Yeah, though with "-b" (e.g. "git checkout -b remote-branch origin/remote-branch"). – Dustin Oprea Mar 26 '19 at 02:59
1

Very short summary:

  • if you have used "single-branch"

  • you must do these TWO things

$ git remote set-branches origin --add other-branch

$ git fetch

  • you can then

$ git checkout other-branch

Fattie
  • 27,874
  • 70
  • 431
  • 719