git 裸存储库、工作树和跟踪分支
git bare repositories, worktrees and tracking branches
我正在处理一个代码库,我需要同时在几个分支上工作以用于不同的目的。所以我克隆到一个裸存储库,然后设置一些工作树:
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.
使它起作用的正确魔法是什么?
首先,附注:如果您打算在非试用期(一次超过两周)使用 git worktree add
,请确保您的 Git 至少是 2.15 版。1
出于您的特定目的,我建议不要 使用git clone --bare
。相反,使用常规克隆,然后是您打算执行的 git worktree add
s。您在 中注意到:
... 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.
有几种简单的解决方法:
选择您要添加的 N 个工作树之一,并将其用作主工作树中的 b运行ch:
git checkout -b branch-1 ssh://git@git.example.com/project/repo branch-1
缺点是您现在有一个特殊的 "main" b运行ch,您不能随时将其删除 - 所有其他 b运行ch 都依赖于它。
或者,在克隆之后,在工作树中使用 git checkout --detach
,在默认 b运行ch:
上获得分离的 HEAD
git clone ssh://git@git.example.com/project/repo repo.git
cd repo.git
git checkout --detach
第二种方法的唯一缺点是工作树充满了文件,可能是 space 的浪费。还有一个解决方案:使用空树创建一个空白提交,然后检查它:
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
检查作为一个独立的 HEAD。效果是清空我们的索引和工作树,这样存储库工作树中唯一的东西就是 .git
目录。我们所做的空提交不在 b运行ch 上,也没有我们永远不会推送到任何地方的父提交(这是一个单独的根提交)。
1原因是Git2.5,git worktree
最先出现的地方,有一个我认为很严重的bug:git gc
从不扫描 added 工作树的 HEAD
文件,也不会扫描它们的索引文件。如果添加的工作树总是在一些 b运行ch 上并且从来没有任何 git add
ed 但未提交的工作,这永远不会导致任何问题。如果未提交的工作至少保留 14 天,默认的 p运行e 保护时间足以防止它被破坏。但是,如果你 git add
一些工作或在分离的 HEAD 上提交,请去度假一个月或以其他方式让这个添加的工作树不受干扰,然后再回来, 和 a git gc --auto
运行 在两周的宽限期 运行 之后,您保存的文件已被销毁!
此错误已在 Git 2.15 中修复。
为什么--bare
出错
这里问题的根源在于 git clone --bare
做了 两个 事情:
- 它创建了一个没有工作树且没有初始
git checkout
的裸存储库(core.bare
设置为 true
); 和
- 它从
+refs/heads/*:refs/remotes/<em>origin</em>/*[ 更改了默认的 <code>fetch
refspec =123=] 到 +refs/heads/*:refs/heads/*
.
第二项意味着没有 refs/remotes/origin/
名称,正如您发现的那样。由于 refmaps 的(主要是隐藏/内部)概念,这不容易修复,它在 git fetch
文档中非常简短地出现(参见 link)。
更糟糕的是,这意味着 refs/heads/*
将在每个 git fetch
上更新。 git worktree add
拒绝创建第二个工作树是有原因的,该工作树引用在任何现有工作树中检出的 same b运行ch,那就是 Git 从根本上假设没有人会弄乱 refs/heads/<em>name</em>
引用 HEAD
附加在 this 工作树中。因此,即使您确实解决了 refmap 问题,当您 运行 git fetch
并且它更新或什至删除时,由于 --prune
和上游删除了相同的名称 - refs/heads/<em>name</em>
名称,添加的工作树的 HEAD
中断,工作树本身也有问题。 (参见 )
还有一件事你可以尝试,我根本没有测试过,那就是:像你现在做的那样做裸克隆,然后 改变 refspec 和重新获取并删除所有现有的 b运行ch 名称,因为它们没有设置上游(或者,等效地,设置它们的上游):
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/{} {}
)。
我为此苦苦挣扎了很长时间,从来不记得我为创建一个不像一个的裸仓库所采取的步骤。最终,我编写了以下名为 git-clone-bare-for-worktrees
的脚本来创建用于工作树的裸存储库。它似乎工作得很好,但要注意它没有做很多错误处理。
#! /bin/env bash
set -e
url=
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
你应该起床 运行 宁。
我认为有一个非常简单的方法;
1.克隆 repo 而不结帐(不浪费 space)
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
就是这样。干净、简单又不浪费space
希望这对您有所帮助 ;-)
另一个解决方案是在克隆时使用 --separate-git-dir
参数:
$ mkdir project && cd project
$ git clone http://****/wt.git main --separate-git-dir=.git
Cloning into 'main'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (6/6), done.
$ git worktree add wt1 b1
Preparing worktree (new branch 'b1')
branch 'b1' set up to track 'origin/b1'.
HEAD is now at 23b2efc Added file
$ ls -a
. .. .git main wt1
此技术的主要缺点是根 project
目录的行为类似于 out-of-sync 工作树,如果您尝试在那里执行 运行 git 命令:
project$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
main/
wt1/
no changes added to commit (use "git add" and/or "git commit -a")
但其他一切看起来都完全按预期工作。
对我来说,这似乎是这里所有答案的最佳折衷方案。
我正在处理一个代码库,我需要同时在几个分支上工作以用于不同的目的。所以我克隆到一个裸存储库,然后设置一些工作树:
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.
使它起作用的正确魔法是什么?
首先,附注:如果您打算在非试用期(一次超过两周)使用 git worktree add
,请确保您的 Git 至少是 2.15 版。1
出于您的特定目的,我建议不要 使用git clone --bare
。相反,使用常规克隆,然后是您打算执行的 git worktree add
s。您在
... 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.
有几种简单的解决方法:
选择您要添加的 N 个工作树之一,并将其用作主工作树中的 b运行ch:
git checkout -b branch-1 ssh://git@git.example.com/project/repo branch-1
缺点是您现在有一个特殊的 "main" b运行ch,您不能随时将其删除 - 所有其他 b运行ch 都依赖于它。
或者,在克隆之后,在工作树中使用
上获得分离的 HEADgit checkout --detach
,在默认 b运行ch:git clone ssh://git@git.example.com/project/repo repo.git cd repo.git git checkout --detach
第二种方法的唯一缺点是工作树充满了文件,可能是 space 的浪费。还有一个解决方案:使用空树创建一个空白提交,然后检查它:
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
检查作为一个独立的 HEAD。效果是清空我们的索引和工作树,这样存储库工作树中唯一的东西就是 .git
目录。我们所做的空提交不在 b运行ch 上,也没有我们永远不会推送到任何地方的父提交(这是一个单独的根提交)。
1原因是Git2.5,git worktree
最先出现的地方,有一个我认为很严重的bug:git gc
从不扫描 added 工作树的 HEAD
文件,也不会扫描它们的索引文件。如果添加的工作树总是在一些 b运行ch 上并且从来没有任何 git add
ed 但未提交的工作,这永远不会导致任何问题。如果未提交的工作至少保留 14 天,默认的 p运行e 保护时间足以防止它被破坏。但是,如果你 git add
一些工作或在分离的 HEAD 上提交,请去度假一个月或以其他方式让这个添加的工作树不受干扰,然后再回来, 和 a git gc --auto
运行 在两周的宽限期 运行 之后,您保存的文件已被销毁!
此错误已在 Git 2.15 中修复。
为什么--bare
出错
这里问题的根源在于 git clone --bare
做了 两个 事情:
- 它创建了一个没有工作树且没有初始
git checkout
的裸存储库(core.bare
设置为true
); 和 - 它从
+refs/heads/*:refs/remotes/<em>origin</em>/*[ 更改了默认的 <code>fetch
refspec =123=] 到+refs/heads/*:refs/heads/*
.
第二项意味着没有 refs/remotes/origin/
名称,正如您发现的那样。由于 refmaps 的(主要是隐藏/内部)概念,这不容易修复,它在 git fetch
文档中非常简短地出现(参见 link)。
更糟糕的是,这意味着 refs/heads/*
将在每个 git fetch
上更新。 git worktree add
拒绝创建第二个工作树是有原因的,该工作树引用在任何现有工作树中检出的 same b运行ch,那就是 Git 从根本上假设没有人会弄乱 refs/heads/<em>name</em>
引用 HEAD
附加在 this 工作树中。因此,即使您确实解决了 refmap 问题,当您 运行 git fetch
并且它更新或什至删除时,由于 --prune
和上游删除了相同的名称 - refs/heads/<em>name</em>
名称,添加的工作树的 HEAD
中断,工作树本身也有问题。 (参见
还有一件事你可以尝试,我根本没有测试过,那就是:像你现在做的那样做裸克隆,然后 改变 refspec 和重新获取并删除所有现有的 b运行ch 名称,因为它们没有设置上游(或者,等效地,设置它们的上游):
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/{} {}
)。
我为此苦苦挣扎了很长时间,从来不记得我为创建一个不像一个的裸仓库所采取的步骤。最终,我编写了以下名为 git-clone-bare-for-worktrees
的脚本来创建用于工作树的裸存储库。它似乎工作得很好,但要注意它没有做很多错误处理。
#! /bin/env bash
set -e
url=
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
你应该起床 运行 宁。
我认为有一个非常简单的方法;
1.克隆 repo 而不结帐(不浪费 space)
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
就是这样。干净、简单又不浪费space
希望这对您有所帮助 ;-)
另一个解决方案是在克隆时使用 --separate-git-dir
参数:
$ mkdir project && cd project
$ git clone http://****/wt.git main --separate-git-dir=.git
Cloning into 'main'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (6/6), done.
$ git worktree add wt1 b1
Preparing worktree (new branch 'b1')
branch 'b1' set up to track 'origin/b1'.
HEAD is now at 23b2efc Added file
$ ls -a
. .. .git main wt1
此技术的主要缺点是根 project
目录的行为类似于 out-of-sync 工作树,如果您尝试在那里执行 运行 git 命令:
project$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
main/
wt1/
no changes added to commit (use "git add" and/or "git commit -a")
但其他一切看起来都完全按预期工作。
对我来说,这似乎是这里所有答案的最佳折衷方案。