git裸仓库,工作树和跟踪分支

时间:2019-01-25 14:13:41

标签: git git-branch git-bare git-worktree

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

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.

什么是使它正常工作的法宝?

2 个答案:

答案 0 :(得分:3)

我为此苦苦挣扎了很长时间,从没想起我为创建一个不像以前那样的裸仓库而采取的步骤。最终,我编写了以下名为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的分支,指向存储库中的第一个提交,并将其设置为HEAD,以确保所有“真实”分支都可以在工作树中安全地检出。除此之外,存储库将不包含任何本地分支,甚至不包含master,但是将完全按照正常的非裸克隆创建远程跟踪分支。因此,只需快速运行git worktree add ../master-worktree master,您就可以启动并运行。

答案 1 :(得分:1)

首先,请注意:如果您打算在非重要时期(一次超过两个星期)使用git worktree add,请确保您的Git版本至少为2.15。 1

出于特定目的,我建议使用git clone --bare 。相反,请使用常规克隆,然后使用您打算执行的git worktree add。您在a comment中注意到:

  

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

有几种简单的解决方法:

  • 选择要添加的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生成每个存储库中已经存在的empty tree的哈希ID。 git commit-tree进行一次提交以包装该空树-没有一棵树,因此我们必须做出一棵将其检出-并打印出此新提交的哈希ID,然后git checkout进行检查作为一个独立的头。结果是清空索引和工作树,因此存储库工作树中唯一的内容是.git目录。我们所做的空提交没有分支,也没有父提交(这是一个单独的根提交),我们永远不会将其推送到任何地方。


1 原因是git worktree首次出现的Git 2.5有一个我认为非常糟糕的错误:git gc从不扫描添加的 < / em>工作树的HEAD文件或它们的索引文件。如果添加的工作树始终位于某个分支上,并且从未进行过任何git add但未提交的工作,则这绝不会引起任何问题。如果未完成的工作至少闲置了14天,则默认的修剪保护时间足以防止其被销毁。但是,如果您git add进行一些工作或在独立的HEAD上进行提交,则请休假一个月,否则请不要干扰此添加的工作树,然后回到它, 和{ {1}}在为期两周的宽限期用尽之后,您保存的文件已被销毁!

此错误已在Git 2.15中修复。


为什么git gc --auto出错了

问题的根源在于--bare两件事

  • 它会创建一个没有工作树并且没有任何初始git clone --bare的裸仓库(core.bare设置为true);
  • 它将默认的git checkout refspec从fetch更改为+refs/heads/*:refs/remotes/origin/*

这第二项表示您没有发现+refs/heads/*:refs/heads/*名称。由于refmaps的概念(大部分是隐藏的/内部的),因此很难修复,该概念在refs/remotes/origin/文档中非常简要地显示(请参阅链接)。

更糟糕的是,这意味着git fetch将在每个refs/heads/*上进行更新。 git fetch拒绝创建第二个工作树是有原因的,该工作树引用了在任何现有工作树中检出的 same 分支,并且Git从根本上假设没有一个人会把在{em> this 工作树中附加了git worktree add的{​​{1}}引用弄乱了。因此,即使您确实解决了refmap问题,当您运行refs/heads/name时,由于HEAD和上游删除的同名git fetch,它也会更新(甚至删除) ,添加的工作树的--prune会中断,并且工作树本身会出现问题。 (请参见Why does Git allow pushing to a checked-out branch in an added worktree? How shall I recover?

您还可以尝试另一件事,我完全没有测试过,那就是:按照您的方式进行裸克隆,然后更改refspec 并重新获取,然后删除所有现有的分支名称,因为它们没有设置上游(或等效地设置其上游):

refs/heads/name

(或将HEAD替换为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 )。