Git中的单一工作分支

19

Bounty短描述

有没有一种可移植的方法可以使用一个仓库并具有多个检出?作为多个克隆的替代方案,其中存在太多开销(推送/拉取/同步...)和覆盖.git/objects硬链接的风险(在Windows上甚至无法工作)。


对git新手而言,很好奇从有经验的git用户那里听到他们的想法。

是否有一个概念上的原因,为什么git一次只能处理一个分支?当我大多数时候需要同时处理至少两个不同的分支时,反复切换分支似乎绝对不切实际,例如构建、并行运行等。

好吧,也许开发人员不需要真正同时在两个分支上工作。但是检出另一个分支不会自动带上被忽略的内容,比如构建输出文件,所以需要重新构建等。

有这个脚本git-new-workdir,它应该允许多个工作分支,但首先,它不是git发布的一部分,尽管它已经存在了约3年,所以我不相信它能保持我的文件一致性。而且,我无法在Windows分发中找到它,这是我用于开发的机器之一。

因此,唯一的官方选项是为每个分支创建一个新的“克隆”,这似乎是不正确的,因为每个克隆都是一个完整的仓库。我甚至不知道该如何命名克隆目录——我使用仓库名称还是分支名称或两者兼有?如果我从该分支创建另一个分支,会怎么样等等。

实际用例更新(@Philip建议)

我通常会同时处理两个主要/次要版本,其中功能并行运行。还有一个偶尔的开发分支,在将来合并到某个发布版之前,实验性功能会进入该分支。

在功能开发期间,通常会出现行为下降的情况,因此将其与更改前的行为进行比较更有效。检查先前的分支/修订版并不足够好,因为很多时候这意味着需要同时在硬盘上检出两个版本进行调试。
因此,更方便和自然的方法是在各自的目录中保留几个所谓的“活动”分支,并检出它们各自的已编译二进制文件。这也可以节省编译时间和偶尔的本地设置(即需要在每次检出后更改的配置文件以使产品运行)。

1
你为什么认为需要在工作树中有两个分支?你想要做什么? - Mot
2
我想比较同一产品在不同版本中的行为,当回到另一个分支时,我想避免从头开始重建所有内容。我必须同时处理三个独立的功能。 - Nick
大多数情况下,目的是观察两个版本共同的变量和调用分支的变化。临时更改偶尔是必要的。我从未遇到过在单个比较会话中无法修复的故障。 - Nick
1
看起来Bazaar原生支持这个功能,使用共享仓库,对于那些感兴趣的人来说。更多信息 - netvope
从Git 2.5开始,git仓库可以拥有多个工作树。我已相应地编辑了我的答案 - VonC
显示剩余2条评论
7个回答

16
当检出一个分支时,实际上只是指向提交记录的指针(在提交记录图中)。因此,Git 不能同时检出该图中的两个提交记录
实际上,参见 "Multiple working directories with Git?"。使用 Git 2.5+(2015 年第二季度),您可以为一个 git 存储库拥有 多个工作树,并使用 git checkout --to=<path> 命令来实现。
这样,您就可以在 <path> 的不同工作目录中检出一个分支。新的工作目录与当前存储库相连。
该链接是“可移植”的(即在 Windows 上也能工作),因为它被记录在主存储库的 $GIT_DIR/worktrees 目录中。

原始答案(2011年):

git branches

(来源:Scott Chason,《ProGIT Book》,http://progit.org/book/ch3-1.html,CC-BY-NC-SA)

如果您需要同时处理两个不同的分支,请克隆您的存储库并在该克隆中选择(git checkout)另一个分支。您可以使用分支名称作为该克隆的根目录名称。
您可以在这些存储库中的任何一个创建分支。


作为 "Git/Mercurial/Bazaar的流行程度与推荐" 中提到的,Git 在其核心是一个内容管理工具。每次提交都代表了整个仓库的完整快照。你不能在同一个容器(工作目录)中查看两个内容,即使你可以保留任意多指针(分支)的引用。因此问题就在于为什么 Git 不能有两个指针,一个指向每个目录呢?

Git Local operations

你只需将工作目录填充一个内容。


1
那么问题是为什么Git不能有两个指针,一个用于每个目录呢? - Nick
@Nick,当切换分支几乎不需要成本时,同时拥有两个分支作为工作副本的好处在哪里?您会同时在两个分支上工作吗?还是只是想要“窥视” - 如果是这样,请使用gitweb。 - mbx
1
@Nick:当然,你可以在每个仓库中拥有任意多的分支,只是你只能有一个当前分支。当你使用git时,大部分工具处理的是你的工作副本(即当你输入ls时看到的内容)、索引(即暂存区,或者组成下一次提交的内容)和当前提交(即HEAD,通常是一个分支的末尾)之间的关系。如果最后一种状态不唯一,使用git将会是一场噩梦。 - Mark Longair
2
@Nick:当你执行 git checkout new-branch 命令时,只有被更改的文件的时间戳才会被更新。因此,如果你使用构建工具(如 make)来考虑修改时间,那么只有最小化的一组文件需要被重建。 - Mark Longair
请确保在重用其他来源的图片时使用正确的归属。除此之外:解释得很好! :) - Lars
显示剩余4条评论

9
如果您使用本地路径进行git clone,则.git/objects下的所有内容(即存储库中的大多数提交和数据)在尽可能多地与旧存储库硬链接,并且几乎不占用磁盘空间。因此,如果您想要两个不同的工作目录,只需在本地克隆存储库即可,成本不高。尝试从一个存储库管理两个不同的工作目录可能会起作用,但不值得麻烦。

基本上,运行git clone /path/to/old/repo new_repo,一切都会正常工作。


我听说过克隆硬链接,但是在 Windows(msysgit)上似乎无法使用它们,这很可能是由于 Cygwin 与硬/软链接的限制。 - Nick
我知道在cygwin中_hardlinks_是可能的,你说msysgit不使用它们也许是对的。尽管如此,硬盘空间很便宜。拥有两个repo的副本特别不可取吗? - Clueless
当然,磁盘空间不是问题。但是,如果要同时在两个分支上工作,就需要克隆整个存储库(包括所有不同的分支和修订版本),这似乎有些违反直觉。 - Nick
Windows硬链接只在Vista和W7中才有,使用mklink命令。我认为msysgit不区分XP和更高版本。 - Philip Oakley
@Nick,通常情况下,Git存储库不包含分支 - 因为克隆的存储库就是分支。这与SVN不同,其中分支、标记等都在同一个存储库中。 - laurent
@Laurent,每个存储库都包含所有分支和修订版本,因此理论上它可以允许对分支或修订版本进行多次检出。 - Nick

6
你需要的是称为alternates的东西,它允许一个仓库依赖于另一个仓库的对象。由于磁盘空间便宜且必须小心不要意外删除依赖仓库所需的对象,因此它的使用并不常见。

是的,这听起来像是这样。这意味着最接近多个工作目录的方法是重复使用.git/objects目录用于多个存储库。无法看到删除所需对象的任何风险。我想象.git/objects目录保存了整个历史记录,无论删除/添加什么,它都只会增长。或者也许您是指删除分支? - Nick
刚刚发现了一个关于 git clone -s 的参考,它使用替代品而不是硬链接到本地仓库。它谈到了删除分支的危险,涉及到删除分支和存在未引用提交的情况(虽然我不确定这是如何可能的)。 - Nick
1
如果您从不删除未合并的分支,那么您就不会有问题。只有在父存储库中删除包含克隆中仍在使用的提交的未合并分支时,才会出现此问题。 - Karl Bielefeldt
Alternates(备用库)与常规的git克隆几乎相同,唯一的区别是你需要共享一些磁盘空间。但由于原始问题中明确拒绝使用克隆“因为存在太多开销(推送/拉取/同步等)”,因此这种方法可能并没有太大帮助。 - sleske

5

如果您想要这样做,我强烈建议编写自定义应用程序1以一种可靠的方式管理此过程。

一个起点可能是

GIT_WORK_TREE=/worktrees/stable   git checkout -f origin/stable
GIT_WORK_TREE=/worktrees/unstable git checkout -f origin/unstable

我会特别确保您的工作副本看起来与普通存储库不同,以便正常的git命令不适用。使用通常的git(子)命令会导致灾难,因为有一天,您会遇到一个脚本,它不会像您期望的那样使用GIT_DIR、GIT_WORK_TREE、GIT_INDEX。1(可能基于NGit,或类似Python、Ruby等可在您的环境中移植的Git绑定)

4
实际上,git-new-workdir 就是为了解决这个问题而制作的。我也遇到过同样的问题,并且使用它没有任何问题。关于你的顾虑:

有一个名为git-new-workdir的脚本可以允许多个工作分支,但首先,它不是 git 发布的一部分,尽管它已经存在了约3年,所以我不信任它能保持我的文件一致。

好吧,我(以及很多其他人)使用它已经多年了,我自己没有遇到任何错误,也没有听说过任何真正的问题(除了一些小限制,见下文)。这听起来可能并不像什么,但即使是在 Git 本身(或者任何 -free- SW 项目中),你所获得的唯一真正的保证就是"还没有人发现问题"。我不知道为什么 git-new-workdir 仍然在 contrib 中,但我不认为这应该阻止你使用它。

其次,在 Windows 发行版中找不到它,而这是我用于开发的机器之一。

这可能只是因为您的发行版选择不包含 contrib/ 中的内容。
如果您在 Cygwin 下使用 Git,则可以下载并使用原始的 git-new-workdir 脚本。它很好用,我自己也使用它。如果您使用 msysGit,则有可用的端口:https://github.com/joero74/git-new-workdir/blob/master/git-new-workdir.cmdhttps://github.com/dansmith65/git/blob/master/contrib/workdir/git-new-workdir-win 。不过,我不知道它们的质量如何。 git-new-workdir 的限制
完整性起见,这里是我知道的限制:

2
如果我理解正确,您正在寻找一种能够将特定分支头或提交拷贝到独立目录的能力,以进行调查和比较,同时仍然保持当前开发/错误修复分支作为主要的git“参考”。您预期,独立“复制出”的目录不太可能被直接重新提交到git。
一个未经测试的方法是使用基本的git命令及其[--git-dir=] [--work-tree=]选项,并通过谨慎的脚本调整/更正您的HEAD / Index,当您执行CopyOut并回退到您的当前分支时。
通常的警告也适用。
1.创建新的work_dir 2.启动指向work_dir的git 3.保存当前HEAD 4.检出所需的分支/提交(应该进入work_dir) 5.将HEAD设置回保存的值(这是一种hack方法,可能需要采取一些措施来保持索引正确!) 6.关闭git以“忘记”work_dir设置 7.重新在旧目录和关联的分支中启动git
这绝对需要仔细测试,并且可能需要围绕第3步和第5步进行调整,以使第6-7步成立。
如果copyout目录有需要提交到相应分支的错误修复,则git工作树选项可以提供一种从中引入更改的机制。
更新--------------------------------
clunx说明基于Windows用户的视图。
# this is run from your main work dir (that contains .git)
Copy .git/HEAD MyBackup
# HEAD will contain the line similar to "ref: refs/heads/master"
mkdir <path>
# the path should be 'somewhere else', not in your existing directory
# If the path doesn't exist the following checkout will fail
Git --work-tree=<path> checkout <branch>
# this will extract the latest commit on that branch to the work tree path
# or use a commit sha1 instead of <branch>, if you want an exact commit
# Note: your index will be updated to reflect the checked out files, as will HEAD.
Copy MyBackup .git/HEAD
# set the HEAD back to where it was
Git reset
# reset the index appropriate for the previous Head.
# note we dropped the --work-tree parameter

类似的技术可以用来从提取的路径中获取任何修订版本,只需在正确的时间将 --work-tree 指向正确的位置,并确保索引设置正确即可。不要忘记常规警告 [备份、备份和备份]。

我在一个测试仓库上尝试过这种方法,其中一些操作是在 Windows 上进行的。


这听起来非常有趣--但是,我不确定某些步骤的含义。当您说“保存当前 HEAD”时,您是指保存“.git/HEAD”文件吗?另外,当您说“关闭 git”时,您可能是指 GUI 工具。如果您能详细说明一下类似于命令行实用程序的东西,那就太好了。 - Nick
@Nick:我已经更新了我的答案,并附上了我测试过的命令序列。 - Philip Oakley
谢谢 -- 以为你忘了。我正在试用它,感觉有些笨重,可能需要一些调整,等下会回来。 - Nick
我离开了一段时间,但并没有忘记。如果您已经有一个固定的目标和一个可以存储HEAD文件的地方,那么事情会更容易。我没有尝试为它创建别名。有人投了反对票,但我不确定为什么。 - Philip Oakley

2
也许子模块可以帮助你拥有独立的目录?

有趣的方法--假设将分支排列成子模块,可能会在某种程度上有所帮助。 - Nick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接