6

我正在使用一个代码库,我需要同时在多个分支上工作以用于不同的目的。所以我克隆到一个裸存储库,然后设置一些工作树:

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

现在branch-3工作树没有设置为跟踪远程树并试图让它这样做,我陷入了可怕的混乱。

$ 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.

让这个工作的正确魔法是什么?

4

3 回答 3

10

首先,附注:如果您打算使用git worktree add非平凡时期(一次超过两周),请确保您的 Git 至少是 2.15 版本。1

对于您的特定目的,我建议您不要使用git clone --bare. 相反,请使用常规克隆,后跟git worktree add您打算执行的 s。您在评论中指出:

...您最终不得不创建一个虚拟分支来放置存储库本身,因为不可能同时在同一个分支上同时拥有存储库和工作树。

有几个简单的解决方法:

  • 选择您将添加的 N 个工作树之一,并将其​​用作主工作树中的分支:

    git checkout -b branch-1 ssh://git@git.example.com/project/repo branch-1
    

    缺点是您现在有一个特殊的、可区分的“主”分支,您不能随时删除它——所有其他分支都依赖于它。

  • 或者,在克隆之后,git checkout --detach在工作树中使用,在默认分支上获得一个分离的 HEAD:

    git clone ssh://git@git.example.com/project/repo repo.git
    cd repo.git
    git checkout --detach
    
  • 第二种方法的唯一缺点是工作树充满了文件,可能会浪费空间。也有一个解决方案:使用空树创建一个空白提交,然后检查一下:

    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)
    

好的,最后一个不是很明显。不过这真的很简单。生成每个存储库中已经存在的空树git hash-object -t tree /dev/null哈希 ID 。进行提交以包装那棵空树——没有,所以我们必须创建一个来检查它——并打印出这个新提交的哈希 ID,并将其作为分离的 HEAD 进行检查。效果是清空我们的索引和工作树,因此存储库工作树中唯一的东西就是目录。我们所做的空提交不在分支上,也没有父提交(这是一个单独的根提交),我们永远不会推送到任何地方。git commit-treegit checkout.git


1其原因是git worktree首次出现的 Git 2.5 有一个我认为非常糟糕的错误:git gc从不扫描添加的工作树的HEAD文件,也不扫描它们的索引文件。如果添加的工作树总是在某个分支上,并且从来没有任何git add编辑但未提交的工作,这永远不会导致任何问题。如果未提交的工作至少 14 天没有闲置,则默认的修剪保护时间足以防止其被破坏。但是,如果您git add在分离的 HEAD 上进行一些工作或提交,请假一个月或以其他方式保持这个添加的工作树不受干扰,然后再回到它,并且git gc --auto在两周的宽限期用完后运行,文件你救的已经被毁了!

此错误已在 Git 2.15 中修复。


为什么--bare会出错

这里问题的根源在于它git clone --bare了两件事:

  • 它创建了一个没有工作树且没有初始值的裸存储库(core.bare设置为) ;truegit checkout
  • 它将默认fetchrefspec 从更改为.+refs/heads/*:refs/remotes/origin/*+refs/heads/*:refs/heads/*

正如您所发现的,第二项意味着没有refs/remotes/origin/名称。由于refmaps的(主要是隐藏的/内部的)概念,这不容易修复,它在文档中非常简短地显示git fetch(参见链接)。

更糟糕的是,这意味着refs/heads/*将在每个git fetch. git worktree add拒绝创建第二个工作树来引用在任何现有工作树中签出的同一分支是有原因的,那就是 Git 从根本上假设没有人会弄乱附加到的引用这个工作树。因此,即使您确实解决了 refmap 问题,当您运行它时,它会更新——甚至删除,由于上游删除了相同的名称——名称、添加的工作树的中断以及工作树本身变成有问题的。(请参阅为什么 Git 允许推送到添加的工作树中的签出分支?我应该如何恢复?refs/heads/nameHEADgit fetch--prunerefs/heads/nameHEAD

你可以尝试另一件事,我根本没有测试过,那就是:像你一样做裸克隆,然后更改 refspec并重新获取,并删除所有现有的分支名称,因为它们没有上游设置(或者,等效地,设置它们的上游):

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

(或替换为xargsxargs -n1 -I{} git branch --set-upstream-to=origin/{} {}

于 2019-01-28T18:29:57.363 回答
4

我已经为此苦苦挣扎了很长时间,从不记得我为创建一个不像一个裸仓库而采取的步骤。最终,我编写了以下脚本git-clone-bare-for-worktrees来创建用于工作树的裸存储库。它似乎工作得很好,但要注意它并没有做很多错误处理。

#! /bin/env bash
set -e

url=$1
name=${url##*/}

git init --bare "${name}"
cd "${name}"
git config remote.origin.url "$url"
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
git fetch

firstCommit=$(git rev-list --all --max-parents=0 --date-order --reverse | head -n1)
git branch bare-dummy $firstCommit
git symbolic-ref HEAD refs/heads/bare-dummy

它创建一个名为bare-dummy指向 repo 中的第一个提交的分支并将其设置为HEAD,确保所有“真实”分支都可以在工作树中安全地签出。除此之外,repo 将不包含任何本地分支,甚至不包含任何master远程跟踪分支,但将完全像使用普通的非裸克隆一样创建远程跟踪分支。因此,只需快速运行git worktree add ../master-worktree master,您就应该启动并运行。

于 2020-06-22T22:53:47.037 回答
4

我觉得有一个非常简单的方法;

1.克隆repo而不结账(不浪费空间)

git clone --no-checkout ssh://git@git.example.com/project/repo repo.git

2.去repo

cd repo.git

3. 创建一个虚拟分支,以便在每个现有分支上创建一个工作树

git switch -c dummy

4. 现在根据需要创建工作树

git worktree add branch-1

或者

git worktree add pathtobr1 branch-1

就这样。干净,简单,不浪费空间

希望这可以帮助 ;-)

于 2021-08-29T11:35:09.940 回答