I'm working with a code base where I need to be working on several branches at once for different purposes. So I clone to a bare repository and then set up some worktrees:
git clone --bare ssh://git@git.example.com/project/repo repo.git
cd repo.git
git worktree add ../branch-1 branch-1
git worktree add ../branch-2 branch-2
... someone else creates branch-3 and pushes is ...
git fetch origin +refs/heads/*:refs/heads/* --prune
git worktree add ../branch-3 branch-3
Now the branch-3
worktree isn't set to track the remote tree and trying to make it do so, I get into a horrible mess.
$ cd ../branch-3
$ git branch -u origin/branch-3
error: the requested upstream branch 'origin/refs/heads/feature/SW-5884-move-database-container-to-alpine-base-2' does not exist
hint: ...<snip>
$ git fetch +refs/heads/*:refs/remotes/origin/* --prune
$ git branch -u origin/branch-3
fatal: Cannot setup tracking information; starting point 'origin/feature/SW-5884-move-database-container-to-alpine-base-2' is not a branch.
What's the right magic to get this to work?
First, a side note: if you are going to use git worktree add
for nontrival periods (more than two weeks at a time), be sure your Git is at least version 2.15.1
For your particular purpose I'd recommend not using git clone --bare
. Instead, use a regular clone, followed by the git worktree add
s that you intend to do. You note in a comment that:
... you end up having to create a dummy branch to sit the repository itself on, because it's not possible to have both the repository and a worktree on the same branch at the same time.
There are several simple workarounds for this:
Pick one of the N work-trees that you'll add, and use that as the branch in the main work-tree:
git checkout -b branch-1 ssh://git@git.example.com/project/repo branch-1
The drawback is that you now have a special distinguished "main" branch that you cannot just remove at any time—all the other branches depend on it.
Or, after the clone, use git checkout --detach
in the work-tree, to get a detached HEAD on the default branch:
git clone ssh://git@git.example.com/project/repo repo.git
cd repo.git
git checkout --detach
The only drawback to this second approach is that the work-tree is full of files, probably a waste of space. There's a solution to that as well: create a blank commit using the empty tree, and check that out:
git clone ssh://git@git.example.com/project/repo repo.git
cd repo.git
git checkout $(git commit-tree $(git hash-object -t tree /dev/null) < /dev/null)
OK, the last one is not exactly obvious. It really is pretty simple though. The git hash-object -t tree /dev/null
produces the hash ID of the empty tree that already exists in every repository. The git commit-tree
makes a commit to wrap that empty tree—there isn't one, so we must make one to check it out—and prints out the hash ID of this new commit, and the git checkout
checks that out as a detached HEAD. The effect is to empty out our index and work-tree, so that the only thing in the repository work-tree is the .git
directory. The empty commit we made is on no branch and has no parent commit (it's a lone root commit) that we will never push anywhere.
1The reason for this is that Git 2.5, where git worktree
first appeared, has a bug I consider very bad: git gc
never scans the added work-trees' HEAD
files, nor their index files. If the added work-trees are always on some branch and never have any git add
ed but uncommitted work, this never causes any problems. If uncommitted work does not sit around for at least 14 days, the default prune protection time suffices to keep it from being destroyed. But if you git add
some work or commit on a detached HEAD, go on holiday for a month or otherwise leave this added work-tree undisturbed, and then come back to it, and a git gc --auto
ran after that two-week grace period ran out, the files you saved have been destroyed!
This bug was fixed in Git 2.15.
--bare
goes wrongThe root of the problem here is that git clone --bare
does two things:
core.bare
set to true
) that has no work-tree and does no initial git checkout
; andfetch
refspec from +refs/heads/*:refs/remotes/origin/*
to +refs/heads/*:refs/heads/*
.This second item means that there are no refs/remotes/origin/
names, as you discovered. That's not easily repaired because of the (mostly hidden / internal) concept of refmaps, which show up very briefly in the git fetch
documentation (see link).
Worse, it means that refs/heads/*
will be updated on every git fetch
. There is a reason git worktree add
refuses to create a second work-tree that refers to the same branch that is checked out in any existing work-tree, and that is that Git fundamentally assumes that no one will mess with the refs/heads/name
reference that HEAD
is attached-to in this work-tree. So even if you did work around the refmap issue, when you run git fetch
and it updates—or even removes, due to --prune
and an upstream removal of the same name—the refs/heads/name
name, the added work-tree's HEAD
breaks, and the work-tree itself becomes problematic. (See Why does Git allow pushing to a checked-out branch in an added worktree? How shall I recover? Note that this bug was fixed in Git 2.26, released in March 2020.)
There's one other thing you could try, which I have not tested at all, and that is: do the bare clone as you are doing, but then change the refspec and re-fetch, and delete all the existing branch names since they have no upstreams set (or, equivalently, set their upstreams):
git clone --bare ssh://git@git.example.com/project/repo repo.git
cd repo.git
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
git fetch
git for-each-ref --format='%(refname:short)' refs/heads | xargs git branch -d
(or replace the xargs
with xargs -n1 -I{} git branch --set-upstream-to=origin/{} {}
).