【问题标题】:git bare repositories, worktrees and tracking branchesgit 裸仓库、工作树和跟踪分支
【发布时间】:2019-06-19 09:22:44
【问题描述】:

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

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.

什么是让它起作用的正确魔法?

【问题讨论】:

  • 将工作树添加到--bare 存储库不是一个好主意。我建议您将裸存储库保持裸露并制作一个非裸克隆来制作工作树。 (特别是 --bare 更改了 fetch refspec,使得任何 fetch 都可能严重干扰您正在进行的工作。)
  • 好的,谢谢您的建议。我曾假设这是应该使用工作树的方式;如果存储库不是裸露的,那么您最终必须创建一个虚拟分支来放置存储库本身,因为不可能同时在同一个分支上同时拥有存储库和工作树。你想把它写成我能接受的答案吗?

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


【解决方案1】:

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

出于您的特定目的,我建议不要使用git clone --bare。相反,请使用常规克隆,然后使用您打算执行的 git worktree adds。您在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 2.5,git worktree 首次出现的地方,有一个我认为非常糟糕的错误:git gc 从不扫描 添加的 工作-trees 的HEAD 文件,也不是它们的索引文件。如果添加的工作树总是在某个分支上并且从来没有任何git added 但未提交的工作,这永远不会导致任何问题。如果未提交的工作至少 14 天没有闲置,则默认的修剪保护时间足以防止其被破坏。但是,如果您 git add 一些工作或承诺在一个独立的 HEAD 上,去度假一个月或以其他方式保持这个添加的工作树不受干扰,然后再回到它,git gc --auto在两周的宽限期结束后运行,您保存的文件已被销毁!

此错误已在 Git 2.15 中修复。


为什么--bare 出错了

问题的根源在于git clone --bare 做了两件事

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

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

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

还有一件事你可以尝试,我根本没有测试过,那就是:像你一样做裸克隆,然后更改 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

(或将xargs 替换为xargs -n1 -I{} git branch --set-upstream-to=origin/{} {})。

【讨论】:

  • 我喜欢你的第三个选项,但你知道那个提交的 windows 等效项(使用 /dev/null)吗?我用 git bash 运行它,但我正在为 Windows 开发人员开发一个构建环境,并使用批处理文件自动执行这些命令。
  • 我不知道你如何在 Windows 上模拟/dev/null。古代 DOS 有 NUL: 像 CON: 之类的(我不确定我是否拼写正确),也许这行得通;无论如何,有人告诉我 WIndows 有一些可以工作的东西。
  • 我试过 NUL。没用。我可以解决它。当我无法运行替换 NUL 的命令时,我感到很惊讶。
【解决方案2】:

我已经为此苦苦挣扎了很长时间,从不记得我为创建一个不像一个行为的裸仓库所采取的步骤。最终,我编写了以下名为 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,您就应该可以正常运行了。

【讨论】:

  • 这很有趣也很有用。就个人而言,我创建了一个git-make-bare 脚​​本,它在接受的答案中执行@torek 的第三个要点。克隆时我的工作流程是git clone &lt;repo&gt;; cd repo; git make-bare; git worktree add master master
  • @tom,你对裸仓库中的 HEAD 做了什么?我从第一次提交创建一个虚拟分支的技术非常蹩脚,但这是我迄今为止发现的最好的方法,可以防止裸仓库“占用”一个分支。
  • 我的git-make-bare 基本上是这样做的:git checkout $(git commit-tree $(git hash-object -t tree /dev/null) &lt; /dev/null)
  • @tom,我记得尝试在裸仓库中使用分离的 HEAD,但如果我没记错的话,如果是这种情况,一些工作树操作会抱怨(即使我不明白为什么操作需要一个分支)。不过,这可能是git worktree 的更早版本,所以我应该再试一次。
  • 它适用于我,但我只使用addprune
【解决方案3】:

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

1.克隆 repo 而不结帐(没有浪费空间)

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

2.去回购

cd repo.git

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

git switch -c dummy

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

git worktree add branch-1

git worktree add pathtobr1 branch-1

就是这样。干净,方便,不浪费空间

希望这会有所帮助;-)

【讨论】:

  • 谢谢,我不知道--no-checkout。是新的吗?
  • 只使用git checkout $(git rev-parse HEAD) 让克隆使用分离的HEAD而不是创建一个虚拟分支不是吗?
  • 使用--no-checkout,HEAD 不存在。我不喜欢这种情况,因为对于某些工具,没有 HEAD 的 .git 存储库不会被识别为存储库。因此,虚拟分支价格低廉(实际上,它只是在 HEAD 中放了一个文本行 ;-))。因此,每个分支可以使用一个工作树。
猜你喜欢
  • 2011-11-26
  • 2017-02-14
  • 2016-06-14
  • 2013-05-09
  • 1970-01-01
  • 2011-05-26
  • 1970-01-01
  • 2021-05-27
  • 1970-01-01
相关资源
最近更新 更多