如何使用Git拥有多个工作目录?

我不确定Git是否支持这一点,但理论上它对我来说应该是可行的。

我的工作流程经常涉及同时编辑多个分支中的文件。换句话说,我经常想在一个分支中打开几个文件,而在另一个分支中编辑另一个文件的内容。

我的典型解决方案是进行两次签出,但遗憾的是,我不能在它们之间共享分支和引用。我想要的是有两个工作目录由相同的.git文件夹管理。

我知道本地git clone解决方案(默认,这是硬链接共享对象,和--shared选项,这设置了一个替代对象存储与原始回购),但这些解决方案只削减磁盘空间的使用,特别是在--shared的情况下,似乎充满了危险。

是否有一种方法可以使用一个.git文件夹,并有两个工作目录支持它?或者Git是硬编码的,在任何时候只有一个工作目录签出?

116964 次浏览

我能想到的唯一解决方案是克隆两个目录,并将它们添加为彼此的远程存储库。然后,您可以继续将内容从更改后的一个存储库拉到另一个存储库,而无需实际将任何内容推到远程存储库。

我假设您希望有两个工作目录,而不是远程的两个克隆目录,因为您不想将一些分支推到远程。否则,两个克隆的遥控器就可以工作了——你只需要做一些推和拉来保持三个同步。

git分布带有名为git-new-workdir提供的脚本。 你可以这样使用它:

git-new-workdir project-dir new-workdir branch

,其中project-dir是包含.git存储库的目录的名称。 这个脚本创建了另一个.git目录,除了不能共享的文件(比如当前分支)之外,该目录有许多到原始目录的符号链接,允许你在两个不同的分支中工作

这听起来有点脆弱,但这是一个选择。

Git 2.5自2015年7月起提议替换contrib/workdir/git-new-workdir: < >强git worktree < / >强

参见提交68年a2e6a by juno C Hamano (gitster)

发行说明中提到:

contrib/workdir/git-new-workdir的替代品,它不依赖于符号链接,通过使借方和借方相互意识到对方,使对象和引用的共享更加安全。

参见提交799767 cc9 (Git 2.5rc2)

这意味着你现在可以do a git worktree add <path> [<branch>]

创建<path>并将<branch>签入其中。新的工作目录 链接到当前存储库,共享除工作之外的所有内容 目录特定的文件,如HEAD,索引等。 git worktree部分添加了:

git存储库可以支持多个工作树,允许你一次签出多个分支。
使用git worktree add,一个新的工作树与存储库相关联 < p > 这个新的工作树被称为“链接工作树”。相对于“主工作树”;由“;__ABC0"或“;git clone"
一个存储库有一个主工作树(如果它不是一个裸存储库)和零个或多个链接的工作树

细节:

每个链接的工作树在存储库的目录中都有一个私有子目录 $GIT_DIR/worktrees目录。
私有子目录的名称通常是链接工作树路径的基本名称,可能会附加一个数字以使其惟一。
例如,当$GIT_DIR=/path/main/.git时,命令git worktree add /path/other/test-next next创建:

  • /path/other/test-next和中的链接工作树
  • 也会创建一个$GIT_DIR/worktrees/test-next目录(如果test-next已经被占用,则创建$GIT_DIR/worktrees/test-next1目录)。

在一个链接的工作树中:

  • $GIT_DIR被设置为指向这个私有目录(例如示例中的/path/main/.git/worktrees/test-next)和
  • $GIT_COMMON_DIR被设置为指向主工作树的$GIT_DIR(例如/path/main/.git)。

这些设置是在位于链接工作树顶部目录的.git文件中进行的。

当你完成了一个链接的工作树,你可以简单地删除它。
存储库中工作树的管理文件最终将被自动删除(参见git config中的gc.pruneworktreesexpire),或者你可以在主库或任何链接的工作树中运行git worktree prune

.清除任何陈旧的管理文件

警告:仍然有git worktree“BUGS"部分需要注意。

< p > 子模块的支持不完整

.不建议对一个超级工程进行多次签出

注意:在git 2.7rc1(2015年11月)中,你可以列表你的工作树。
参见提交bb9c03b7的提交bb9c03b提交92718 b7提交5193490提交1 ceb7f9提交1 ceb7f9提交5193490提交1 ceb7f9提交1 ceb7f9提交1 ceb7f9(2015年10月08日),提交92718 b7提交5193490提交1 ceb7f9提交1 ceb7f9(2015年10月08日),提交5193490提交1 ceb7f9(2015年10月08日),提交1 ceb7f9(2015年10月08日)和提交bb9c03b6(2015年10月02日)。
(由Junio C Hamano—gitster提交a46dcfb中合并,26 Oct 2015)

worktree:添加'list'命令

` git worktree list `遍历工作树列表,并输出 工作树的详细信息,包括到工作树的路径、当前 检查修订和分支,如果工作树是空的 $ git工作树列表 /路径/ /裸源 ( 光) /path/to/linked-worktree [master] /path/to/other-linked-worktree 1234abc (detached HEAD)

也有瓷器格式可供选择。

瓷格式每个属性有一行。

  • 属性用一个标签和一个空格分隔的值列出。
  • 布尔属性(如'bare'和'detached')仅作为标签列出,仅当且仅当值为true时才会出现。
  • 空行表示工作树的结束

例如:

$ git worktree list --porcelain


worktree /path/to/bare-source
bare


worktree /path/to/linked-worktree
HEAD abcd1234abcd1234abcd1234abcd1234abcd1234
branch refs/heads/master


worktree /path/to/other-linked-worktree
HEAD 1234abc1234abc1234abc1234abc1234abc1234a
detached

注意:如果你移动工作树文件夹,你需要手动更新gitdir文件。

参见提交618244 e(2016年1月22日)和提交d4cddd6(2016年1月18日)by ngibmc Duy (pclouds)
得益于:Eric Sunshine (sunshineco)
(由Junio C Hamano—gitster提交d0a1cbc中合并,10 Feb 2016)

新文档在git 2.8(2016年3月)将包括:

如果你移动一个链接的工作树,你需要更新'gitdir'文件 在条目的目录中。
例如,如果一个链接的工作树被移动到/newpath/test-next,并且它的.git文件指向/path/main/.git/worktrees/test-next,则更新 /path/main/.git/worktrees/test-next/gitdir改为引用/newpath/test-next

删除分支时要小心:在git 2.9(2016年6月)之前,你可以在另一个工作树中删除一个正在使用的分支。

< p >当“git worktree"功能正在使用,"git branch -d"允许 删除在另一个工作树中签出的分支

参见提交f292244 (29 march 2016) by 山口一树(rhenium)
得益于:Eric Sunshine (sunshineco)
(由Junio C Hamano—gitster in 提交4 fca4e3合并,13 Apr 2016)

branch -d:拒绝删除当前签出的分支

当一个分支被当前工作树签出时,删除 禁止分支。
但是,当分支只被其他工作树签出时,错误地删除成功。
使用find_shared_symref()来检查分支是否正在使用,而不仅仅是 与当前工作树的HEAD比较。


类似地,在git 2.9(2016年6月)之前,重命名另一个工作树中签出的分支并不会调整该工作树中的符号HEAD。

参见提交18 eb3a9(2016年4月08日)和提交70999 e9提交2233066(2016年3月27日)by 山口一树(rhenium)
(由Junio C Hamano—gitster in 提交741年a694合并,18 Apr 2016)

branch -m:更新每个工作树的所有head

当重命名一个分支时,当前只有当前工作树的HEAD 更新,但它必须更新所有工作树的头指向

这是当前的行为,/path/to/wt的HEAD没有更新:

 % git worktree list
/path/to     2c3c5f2 [master]
/path/to/wt  2c3c5f2 [oldname]
% git branch -m master master2
% git worktree list
/path/to     2c3c5f2 [master2]
/path/to/wt  2c3c5f2 [oldname]
% git branch -m oldname newname
% git worktree list
/path/to     2c3c5f2 [master2]
/path/to/wt  0000000 [oldname]

这个补丁通过更新所有相关的工作树头修复了这个问题 当重命名分支时


git 2.10正式支持锁定机制(2016年第三季度)

参见提交080739 b1的提交080739 b提交6 d30862提交58142 c0提交346年ef53提交346年ef53提交58142 c0提交346年ef53提交346年ef53(2016年6月13日)和提交984年ad9e提交080739 b0(2016年6月03日)。
建议:Eric Sunshine (sunshineco)
(由Junio C Hamano—gitster提交2 c608e0中合并,2016年7月28日)

git worktree lock [--reason <string>] <worktree>
git worktree unlock <worktree>

如果链接的工作树存储在便携式设备或网络共享上 其中不总是挂载,您可以阻止其管理文件 通过发出git worktree lock命令(可选)进行修剪

.指定--reason来解释为什么工作树被锁定

<worktree>:如果工作树路径中的最后一个路径组件在工作树中是唯一的,它可以用来标识工作树。
例如,如果你只需要在“/abc/def/ghi"和“;/abc/def/ggg"”,然后“;ghi"或“;def/ghi"


Git 2.13 (Q2 2017)通过ngibmc Duy (pclouds)提交507年e6e9 (12 Apr 2017)中添加lock选项
建议:大卫·泰勒(dt)
得益于:杰夫·金(peff)
(由Junio C Hamano—gitster提交e311597中合并,26 Apr 2017)

允许在创建工作树后立即锁定它。
这有助于防止“git worktree add; git worktree lock"而且 “git worktree prune" . < / p >

因此,git worktree add' --lock相当于git worktree add之后的git worktree lock,但没有竞态条件。


Git 2.17+ (Q2 2018)添加了git worktree move / git worktree remove: 请看这个答案


Git 2.19(2018年Q3)添加一个"--quiet"选项使&;git worktree add"少 详细。< / p >

参见提交371979 c (15 Aug 2018) by Elia Pinto (devzero2000)
帮助:马丁Ågren martin.agren@gmail.com, Duy Nguyen (pclouds),和Eric Sunshine (sunshineco)
(由Junio C Hamano—gitster in 提交a988ce9合并,27 Aug 2018)

worktree:添加--quiet选项

将'--quiet'选项添加到git worktree,就像其他git命令一样。
'add'是唯一受它影响的命令,因为除了'list'之外的所有其他命令目前默认是沉默的

注意"git worktree add"用于用stat查找可用名称 然后是mkdir",有种族倾向。
Git 2.22 (Q2 2019)通过使用mkdir并在循环中对EEXIST做出反应来修复此问题

参见提交7 af01f2 (20 Feb 2019) by Michal Suchanek (hramrach)
(由Junio C Hamano—gitster in 提交20 fe798合并,09 Apr 2019)

worktree:修复worktree add比赛

Git运行一个stat循环来查找可用的工作树名称和 然后在找到的名称上执行mkdir
将其转换为mkdir循环,以避免再次调用工作树add查找相同的空闲名称并首先创建目录

Git 2.22 (Q2 2019)修复了判断Git存储库是否有工作树的逻辑保护"git branch -D"删除当前选中的分支 不小心出来了。
对于具有不寻常名称的存储库,这种逻辑的实现被破坏了,不幸的是,这是目前子模块的规范

参见提交f3534c9 (19 Apr 2019) by 谭恩美(jhowtan)
(由Junio C Hamano—gitster提交ec2642a中合并,08 May 2019)

worktree:更新is_bare启发式

< p >当“git branch -D <name>"是运行,Git通常首先检查如果 分支目前已签出。
但是如果该存储库的Git目录不在“<repo>/.git"”,则不会执行此检查,例如,如果该存储库是一个子模块,其Git目录存储为“super/.git/modules/<repo>"”。
这将导致分支被删除,即使它被签出 这是因为worktree.c中的get_main_worktree()在a上设置了is_bare 工作树只使用启发式,回购是裸露的,如果工作树 路径不以“__abc0”结尾,否则不为空。
is_bare代码在92718 b7(“worktree:向工作树结构添加细节”,2015-10-08,Git v2.7.0-rc0)中引入,遵循pre-core.bare启发式

这个补丁做了2件事:

  • get_main_worktree()改用is_bare_repository(),在7 d1864c中引入("引入is_bare_repository()和core。裸配置变量,2007-01-07,Git v1.5.0-rc1),并在e90fdc3中更新(“清理工作树处理”,2007-08-01,Git v1.5.3-rc4)。
    这解决了&;git branch -D <name>"上面描述的问题。
    李不过…< / >
  • 如果一个存储库有core.bare=1,但是"git"命令从它的一个次要工作树运行时,is_bare_repository()返回false(这很好,因为有一个可用的工作树)。
    并且,当主工作树为裸时,将其视为非裸会导致问题:例如,无法从主工作树的HEAD引用的辅助工作树中删除分支,即使该主工作树是裸的

为了避免这种情况,在设置is_bare时也要检查core.bare
如果core.bare=1,信任它,否则,使用is_bare_repository().


在Git 2.29 (Q4 2020)中,“worktree"API提供了更好地确定工作树路径的方法。

参见提交918年d8ff提交1 c4854e提交246756 f提交62573 a5 (31 Jul 2020) by Eric Sunshine (sunshineco)
(由Junio C Hamano—gitster in 提交197253 e合并,10 Aug 2020)

worktree:删除虚假和不必要的路径

署名:Eric Sunshine

.git/worktrees/<id>/gitdir的内容必须是形式为"/path/to/worktree/.git"的路径。
任何其他内容都将表明腐败的“gitdir"文件。< / p >

要确定工作树本身的路径,只需去掉&;__abc0 &;后缀,这实际上是工作树路径从一开始就确定的方式。

然而,5193490442 ("worktree: add a function to get worktree details", 2015-10-08, Git v2.7.0-rc0——合并列在批# 7中)以一种神秘的方式扩展了路径操作。
如果它不能剥离"/.git"从路径,然后它报告当前工作目录作为链接工作树的路径:

if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
strbuf_reset(&worktree_path);
strbuf_add_absolute_path(&worktree_path, ".");
strbuf_strip_suffix(&worktree_path, "/.");
}

这种逻辑显然是虚假的;它永远不可能是一般正确的行为。它在5193490442中凭空出现,既没有解释也没有测试来说明在哪种情况下它是可取的。

有可能这个逻辑被引入以某种方式处理一个腐败的&;__abc0 &;文件,以便它返回一些某种有意义的值,但返回当前工作目录没有帮助。事实上,这很容易误导(除了当前目录是工作树的"gitdir"入口是腐败的)。
此外,向用户报告损坏值,而不是完全隐藏它,更有帮助,因为它可能有助于诊断问题

因此,放弃这种虚假的路径调整,将逻辑恢复到仅仅剥离“/.git"”的原始行为。

我偶然遇到这个问题,希望找到一个我在这里没有找到的解决方案。所以现在我做了找到了我需要的东西,我决定把它贴在这里给其他人。

警告:如果你需要同时edit多个分支,这可能不是一个好的解决方案,比如OP状态。用于同时拥有多个分支签出,你打算编辑。(由一个.git文件夹支持的多个工作目录。)

自从我第一次提出这个问题以来,我学到了一些东西:

  1. 什么是“bare repository"。它本质上是.git目录的内容,不位于工作树中。

  2. 事实上,你可以在命令行上使用git选项--git-dir=指定你正在使用的回购的位置(你的.git目录的位置)

  3. 你可以用--work-tree=指定工作副本的位置

  4. 什么是“镜像回购”?

最后一点是非常重要的区别。我实际上不想在回购上工作,我只需要同时签出不同分支和/或标记的副本。实际上,我需要保证分支最终不同于我的远程分支。所以镜子对我来说是完美的。

所以对于我的用例,我通过这样做得到了我所需要的:

git clone --mirror <remoteurl> <localgitdir> # Where localgitdir doesn't exist yet
mkdir firstcopy
mkdir secondcopy
git --git-dir=<localgitdir> --work-tree=firstcopy checkout -f branch1
git --git-dir=<localgitdir> --work-tree=secondcopy checkout -f branch2

关于这一点需要注意的是,两个副本没有单独的HEAD。因此,在上述操作之后,运行git --git-dir=<localgitdir> --work-tree=firstcopy status将显示从branch2到branch1的所有差异作为未提交的更改——因为HEAD指向branch2。(这就是为什么我对checkout使用-f选项,因为我实际上根本不打算在本地做任何更改。只要使用-f选项,我就可以为任何工作树签出任何标记或分支。)

对于我的在同一台计算机上共存多个签出,而不需要编辑它们用例,这是完美的。我不知道是否有任何方法可以在没有其他答案所涵盖的脚本的情况下为多个工作树提供多个head,但我希望这对其他人有所帮助。