就在我认为 Git 不能再复杂的时候,我才发现了 git worktree。这要么是子树的同义词,要么是我从来不知道的特性。工作树与子树相同还是不同。如果它们不同,它们有何不同,worktree 解决了什么问题?
2 回答
这些是非常不同的。为了正确理解它们,让我们定义关于索引和提交的工作树(或“工作树”或“工作树”或这些拼写的几乎任何变体)。
您已经知道提交保存快照,并且每个提交都有一个唯一的哈希 ID,用于命名一个特定的提交。同一个提交可以有许多其他名称(例如,分支和/或标签名称),但只有一个哈希 ID。您可能还知道提交具有元数据:谁制作了它们(姓名和电子邮件地址),何时(时间戳)以及为什么(git log
要显示的消息)。每个提交也有一个父哈希 ID——或者更确切地说,一个父列表,通常只有一个条目。父级是在此之前的提交,因此 Git 可以通过一系列提交向后走,以随着时间的推移显示事物。(具有两个父哈希 ID 的提交是合并提交。没有父哈希 ID 是根提交,并且在任何非空存储库中至少有一个,因为第一次提交之前没有提交。)
提交中的所有内容(包括文件)一直都完全冻结。您不能更改任何内容,甚至不能更改,原因是哈希 ID 实际上是所有提交内容的加密校验和。如果你只改变一点,校验和就会不同,所以这将是一个具有不同哈希 ID 的不同提交。
这意味着存储在任何提交中的所有文件都被冻结。它们也被压缩成一种特殊的 Git-only 格式,只有 Git 可以读取。这对历史来说很棒,但是我们将如何完成任何工作呢?这是工作树进入图片的地方。
要处理文件,我们必须让 Git从提交中复制它们。这使文件恢复到它们的日常形式,所有东西都可以读取它们——编辑器、编译器,以及你计算机上的任何东西——当然还有可写/可更改的。您处理/处理文件的地方就是您的工作树。
在当前提交(但是选择)和工作树之间,因此每个文件都有两个副本:提交中的冻结副本和工作树中的有用副本。
Git 可以在这里停下来,而其他版本控制系统,例如 Mercurial(请参阅mercurial)就是这样做的。但出于各种原因——其中许多与“快速运行”有关——Git 为每个文件添加了第三个副本。第三个副本进入 Git 所称的各种不同的地方,即index、staging area或cache。(你看到的名字取决于谁或 Git 的哪个部分在执行调用。)索引中的文件与它们在提交中的形式几乎相同,除了在索引中它们没有被冻结。如果你愿意的话,它们更容易冷冻或“泥泞”。
索引还保留工作树上的选项卡,以便它们紧密配对:索引“知道”工作树中的内容,或者如果它不知道 - 如果索引的缓存方面已过时 - 它知道这一点,这有助于 Git 快速找出发生了什么变化(如果有的话)。此外,当您运行时git commit
,Git 甚至不会真正查看工作树(除了向您将为日志消息编辑的文件添加一些注释)。它只是将准备就绪的文件从索引中冻结出来,这是索引获得其名称staging area的地方,以进行新的提交。
最后,当您在 Git 中处理提交时,您始终拥有三个活动副本:
HEAD
提交副本被冻结且仅限 Git 。- 索引副本是泥泞的:仅 Git,但没有完全冻结。最初它与
HEAD
副本匹配,但您可以使用git add
. - 工作树副本是正常且流畅的,您可以用它做任何事情。
索引和工作树是配对的。此外,索引在合并冲突期间发挥了扩展的作用:它最终保存了来自三个提交的文件副本,这些是合并的三个输入。当它处于这种扩展模式时,您甚至无法git stash
或以其他方式摆脱修改后的索引和工作树状态,而无需完成或中止合并。
这给我们留下了一个需要解决的问题:如果在做某事的过程中,我们需要相当紧急地修复其他分支中的一些错误怎么办?我们可以制作另一个克隆,这是传统的答案。如果我们没有处于冲突合并的中间,我们可以使用git stash
; 那是另一个答案。一个不太令人满意,如果我们处于合并的中间,另一个就没用了。
所以,输入git worktree add
。使用git worktree add
,您可以将另一对索引和工作树添加到现有存储库。有一个非常强的约束(出于良好的实现特定的原因):每个添加的工作树都必须在其自己的分支上,否则使用“分离 HEAD”模式。也就是说,如果您的主工作树在 branch 上feature/short
,则没有添加的工作树可以使用此分支。他们可以使用master
orhotfix
或develop
,但不能使用feature/short
。(或者,他们可以在存储库中任何地方的任何提交中使用分离的 HEAD。)
当您完成任何添加的辅助工作树时,您可以简单地rm -rf
处理它,然后git worktree prune
从其他辅助工作树之一或主工作树运行,让 Git 搜索和不查找添加的工作树。这会“解锁”添加的工作树已签出的任何分支。
同时,该git subtree
命令是一个精美的 shell 脚本,可让您将现有存储库的某些部分提取到您将在其他地方使用的新存储库,或者将您在其他地方使用的现有存储库并尝试从中取回东西。所以这是一个存储库到存储库的传输——或者至少是它的设置,在某些情况下。
(RomainValeri 还提到了git-merge-subtree
合并策略,这有点相关git subtree
,因为它旨在处理合并的三个输入中的一个或两个中的子树重命名。)