Git有多个工作目录?


241

我不确定这是否受Git支持,但从理论上讲,它似乎应该对我有用。

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

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

我知道本地git clone解决方案(默认设置是硬链接共享对象,而--shared选项使用原始存储库设置备用对象存储),但是这些解决方案仅减少了磁盘空间使用量,尤其是在--shared的情况下,似乎充满了危险。

有没有一种方法可以使用一个.git文件夹,并支持两个工作目录?还是Git硬编码为可以随时检出一个工作目录?


1
git-new-workdirgit checkout --to=<path>在Git 2.5中被替换。请在下面
VonC 2015年

3
实际上,该命令将是git worktree add <path> [<branch>](Git 2.5 rc2)。请参阅下面我编辑的答案
VonC 2015年

您应该更改接受的答案VonC的答案,因为自您最初提出问题以来,事情已经发生了变化。
xaxxon

感谢您提供最新的答案!
jtolds

Answers:


292

自2015年7月以来,Git 2.5提议替代contrib/workdir/git-new-workdirgit worktree

看看提交68a2e6a通过JUNIOÇ滨野(gitster

发行说明中提到

替代方法contrib/workdir/git-new-workdir不依赖于符号链接,而是通过使借方和借方彼此了解来使对象和引用的共享更加安全。

参见提交799767cc9(Git 2.5rc2)

这意味着您现在可以执行git worktree add <path> [<branch>]

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

一个git仓库可以支持多个工作树,允许您一次签出多个分支。
使用git worktree add,新的工作树与存储库关联。

与“ git init”或“ 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目录(或者$GIT_DIR/worktrees/test-next1如果test-next已经被占用)。

在链接的工作树中:

  • $GIT_DIR设置为指向此私有目录(例如/path/main/.git/worktrees/test-next,在示例中),并且
  • $GIT_COMMON_DIR设置为指向主工作树$GIT_DIR(例如/path/main/.git)。

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

完成链接的工作树后,您只需删除它即可。
信息库中工作树的管理文件最终将被自动删除(请参阅gc.pruneworktreesexpire中的git config),或者您可以git worktree prune在主工作树或任何链接的工作树中运行以清理所有过时的管理文件。


警告:还有一个git worktree“ BUGS”部分需要注意。

对子模块的支持不完整
不建议对一个超级项目进行多次检出。


注意:使用git 2.7rc1(2015年11月),您可以列出工作树。
提交bb9c03b提交92718b7提交5193490提交1ceb7f9提交1ceb7f9提交5193490提交1ceb7f9提交1ceb7f9(2015年10月8日),提交92718b7提交5193490提交1ceb7f9提交1ceb7f9(2015年10月8日),提交5193490提交1ceb7f9(2015年10月8日),提交1ceb7f9(2015年10月8日)和提交ac6c561(2015年10月2日),作者:Michael Rappazzo(rappazzo
(由Junio C gitsterHamano合并--commit a46dcfb中,2015年10月26日)

worktree:添加' list'命令

git worktree list”遍历工作树列表,并输出工作树的详细信息,包括工作树的路径,当前签出的修订和分支以及工作树是否裸露。

$ git worktree list
/path/to/bare-source            (bare)
/path/to/linked-worktree        abcd1234 [master]
/path/to/other-linked-worktree  1234abc  (detached HEAD)

也提供瓷器格式选项。

瓷器格式每个属性都有一行。

  • 属性用标签和值列出,并用单个空格分隔。
  • 布尔属性(如“裸露”和“分离”)仅作为标签列出,并且仅当且仅当该值为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文件。

提交618244e(2016年1月22日),以及提交d4cddd6(2016年1月18日)由阮泰玉维战(pclouds
协作者:Eric Sunshine(sunshineco
(由Junio C gitsterHamano合并--commit d0a1cbc中,2016年2月10日)

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月)之前,您可以删除另一个工作树中正在使用的分支。

当“ git worktree”的特点是在使用,“ git branch -d”允许了在另一个worktree签出一个分支删除。

参见Yamaguchi Kazuki(提交的f292244(2016年3月29日。 协作者:Eric Sunshine((由Junio C Hamano合并--commit 4fca4e3中,2016年4月13日)rhenium
sunshineco
gitster

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

当当前工作树检出分支时,禁止删除该分支。
但是,当仅由其他工作树检出分支时,错误地删除会成功。
使用find_shared_symref()来检查分支正在使用中,不仅与当前工作树的HEAD比较。


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

提交18eb3a9(2016年4月8日),并提交70999e9提交2233066(2016年3月27日),由山口和树(rhenium
(通过合并JUNIOÇ滨野- gitster-提交741a694,2016年4月18日)

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

重命名分支时,当前仅更新当前工作树的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]

此修补程序通过在重命名分支时更新所有相关的工作树HEAD来解决此问题。


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

提交080739b提交6d30862提交58142c0提交346ef53提交346ef53提交58142c0提交346ef53提交346ef53(2016年6月13日),以及提交984ad9e提交6835314由(2016年6月3日)阮泰玉维战(pclouds
建议提供者:Eric Sunshine(sunshineco
(由Junio C gitsterHamano合并--commit 2c608e0中,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)添加一个lock选项提交507e6e9(2017年4月12日)由阮泰玉维战(pclouds
提出者:David Taylor(dt
帮助:Jeff King(peff
(通过合并JUNIOÇ滨野- gitster-提交e311597 4月26日2017)

创建工作树后,允许立即锁定它。
这有助于防止“ git worktree add; git worktree lock”和“ git worktree prune” 之间的竞争。

所以git worktree add' --lock 是相当于git worktree lockgit worktree add,但没有竞争条件。


Git 2.17+(Q2 2018)添加git worktree move/ git worktree remove请参阅此答案


Git 2.19(Q3 2018)添加了一个“ --quiet”选项,以使“ git worktree add”不再那么冗长。

参见Elia Pinto(提交371979c(2018年8月15日。 帮助:MartinÅgren,Duy Nguyen(Eric Sunshine((由Junio C Hamano合并--commit a988ce9中,2018年8月27日)devzero2000
pcloudssunshineco
gitster

worktree:添加--quiet选项

与其他命令一样,将' --quiet'选项添加到。 “ ”是唯一受其影响的命令,因为除“ ” 以外的所有其他命令当前默认为静默。git worktreegit
addlist


请注意,“ git worktree add”曾经用来“先在stat中找到一个可用名称,然后再进行mkdir”,这很容易引起比赛。
Git 2.22(Q2 2019)已通过循环使用mkdir和响应来解决EEXIST此问题。

参见Michal Suchanek()的commit 7af01f2(2019年2月20日(通过合并JUNIOÇ滨野- -提交20fe798,2019年4月9日)hramrach
gitster

worktree:修复worktree add种族

Git运行一个stat循环以查找可用的工作树名称,然后mkdir对找到的名称进行处理。
将其mkdir循环,以避免再次调用工作树,添加找到相同的免费名称并首先创建目录。


Git 2.22(2019年第二季度)修复了逻辑,以告知Git存储库是否具有工作树,以防止git branch -D删除当前被错误检出的分支。
对于名称不寻常的存储库,此逻辑的实现已被破坏,不幸的是,如今这已成为子模块的规范。

请参阅Jonathan Tan()的commit f3534c9(2019年4月19日(通过合并JUNIOÇ滨野- -提交ec2642a,2019年5月8日)jhowtan
gitster

代码提取请求178见解

worktree:更新is_bare启发式

git branch -D <name>运行“ ”时,Git通常首先检查该分支当前是否已签出。
但是,如果该存储库的Git目录不在“ <repo>/.git”时,则不会执行此检查,例如,如果该存储库是其Git目录存储为“ super/.git/modules/<repo>” 的子模块,就是这种情况。
即使分支已签出,这也会导致该分支被删除。

这是因为get_main_worktree()在工作树上的worktree.c集合中is_bare,仅使用试探法,即如果工作树的路径不以“ /.git” 结尾,则仓库是裸露的,否则就不是裸露的。遵循启发式,
is_bare代码在92718b7中进行了介绍(“ worktree:向工作树结构添加细节”,2015-10-08,Git v2.7.0-rc0)pre-core.bare

此补丁程序执行以下两项操作:

  • get_main_worktree()使用is_bare_repository(),而不是在介绍7d1864c( “引进is_bare_repository()和core.bare配置变量”,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所引用的辅助工作树中删除分支。

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


1
这正是他们一直在寻找的最酷的东西。感谢那!

如何仅删除工作树并仍保留分支
Randeep Singh 2015年

@DotnetRocks您可以删除任何对分支没有任何影响的工作树(计算机上的本地文件夹):主.git存储库仍将包括完整的提交历史记录及其所有分支,无论是否工作树已被删除。
VonC 2015年

是的,但是如果只是通过转到系统上的该文件夹来删除工作树并删除,则git不会让我签出到该分支,并说已经在<path>(<path>工作树路径)处签出。但是,如果我执行以下操作,则看起来像:rm -rf <path> git worktree prune然后它起作用。那正确吗 ?
Randeep Singh 2015年

1
@Jayan谢谢。我已经更清楚地表明2.5和2.7已经发布了。
VonC

113

git发行版附带一个名为的贡献脚本git-new-workdir。您将按以下方式使用它:

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

其中project-dir是包含您的.git存储库的目录的名称。.git除了无法共享的文件(例如当前分支)之外,此脚本创建了一个目录,该目录具有指向原始目录的许多符号链接,从而使您可以在两个不同的分支中工作。

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


3
+1我的立场是正确的,这非常棒。它看起来可以立即在两个不同的已签出存储库之间共享历史记录和分支,而无需推/拉,而仅进行符号链接。我完全不知道git可以解决这个问题。las,它不包括在我的发行物中。
meagar

2
对于使用msysgit(windows)的用户,可以使用此脚本的移植版本:github.com/joero74/git-new-workdir
amos 2012年

9
通常,它工作得很好,但是如果您不小心在不同位置编辑了同一分支,则将其修复的很简单。
grep 2012年

对于卡在Git <2.5上且具有子模块的用户,请尝试git-new-workdir-recursive使用git-new-workdir
沃尔夫,

13

我遇到了这个问题,希望能找到在这里找不到的解决方案。因此,既然我确实找到了需要的东西,我决定将其发布在这里供其他人使用。

注意:如果您需要同时编辑多个分支(例如OP状态),那么这可能不是一个好的解决方案。 它用于让多个分支同时签出,而您不想进行编辑。(由一个.git文件夹支持的多个工作目录。)

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

  1. 什么是“ 裸仓库 ”。它本质上是.git目录的内容,而不位于工作树中。

  2. 您可以使用.git以下git选项在命令行上指定正在使用的存储库的位置(目录的位置)--git-dir=

  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。(这就是为什么我使用-f选项to 的原因checkout,因为我实际上根本没有计划在本地进行任何更改。只要使用此-f选项,我就可以签出任何工作树的任何标签或分支。)

对于我的用例,在同一台计算机上同时存在多个结帐无需对其进行编辑,这是完美的。我不知道是否有办法为多个工作树提供多个HEAD,而没有其他答案中涉及的脚本,但是我希望这对其他人还是有帮助的。


这正是我在寻找的东西,但是我无法使它正常工作……我得到的是“致命的:不是git存储库:'<localgitdir>'”。有任何想法吗?
Dan R

没关系,事实证明我在目录名中使用“〜”,而git不喜欢这样。当我使用完整路径时,它工作正常。
丹·R

@DanR,很高兴能为您提供帮助。:)您也可以使用$HOME。我后来发现了有关上述方法的其他警告,这与一个或另一个分支中不存在的文件有关。如果将A检入dir1,然后将B检入dir2,然后将C强制检入dir1,如果A中存在文件,但B或C中不存在该文件,即使通过强制检出也不会从dir1中删除该文件。 。因此,您可能需要git clean在这种情况下进行试验-或做我所做的事情,仅使用此方法填充新创建的目录即可。
通配符

谢谢你的提示。无论如何,我将把它作为CLI工具的一部分包装在ruby中,所以我将确保它始终从头开始。
丹·R

@DanR,您可能有兴趣查看我当时正在编写的代码。除CFEngine语法检查外,大多数代码通常适用于任何git登台脚本。(也许您可以将其替换为Ruby语法检查。):)
通配符

3

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

我假设您想要有两个工作目录,而不是远程服务器的两个副本,因为您不想将某些分支推送到远程服务器。否则,您的遥控器的两个克隆就可以正常工作-您只需要进行一些推入和拉出操作,即可使所有三个克隆保持同步。


你好 上面的人分享了一个不错的解决方案 git worktree命令。它比克隆相同的存储库好几次,效果更好。试试看,您会喜欢这个新功能。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.