我读过Git Internals这本书,并且大部分时间都了解Git如何将事物构造为Blob,树,提交以及分支是指向提交的轻量级指针。
我不太了解的部分是Git如何将这些更改跨分支/提交检出反映到文件系统上。
例如:
请考虑两个文件A.txt
和B.txt
,它们已提交给Commit 1
。除了这两个文件之外,文件C.txt
也被提交到Commit 2
。
据我了解,对象图将遵循以下内容:
Commit 1
指向Tree 1
,它对两个初始文件-BlobA
和BlobB
Commit 2
指向Tree 2
,其中有三个文件的blob。 BlobA
和BlobB
保持不变,因为它们的内容没有更改,而BlobC
也将位于Tree 2
下。现在,如果我目前在Commit 2
并结帐到Commit 1
,则HEAD
现在指向Commit 1
,我们可以遍历指示状态的有向图存储库的。现在,文件C.txt
不再在文件系统上。
Git如何在每次签出时将对象图的状态反映到文件系统上?
谢谢。
答案 0 :(得分:2)
Git的大多数工作树动作实际上都是通过 index 控制的。这意味着根本不需要遍历图!
索引的主要作用(至少在合并之外)是充当构建 next 提交对象的位置。这给它提供了许多人喜欢使用的名称,即“临时区域”。在索引中,诸如README.txt
之类的文件版本将开始与该文件的HEAD
版本相匹配。这两个文件实际上都作为 blob 对象存储在资源库中。
工作树将包含README.txt
的可用版本,代表文件的扩展版本。如果您已经建立了这样的过滤,那么它也经过了污点过滤和CRLF调整。如果更改工作树版本并希望提交更改,则必须运行git add README.txt
:这会将工作树文件复制回索引,应用任何干净的过滤器并进行CRLF到LF调整。如果启用了这些功能,则在存储库中创建一个新的Blob(如果新文件内容与某些现有内容匹配,则重新使用现有的Blob)并将新的哈希存储到索引中。实际上,这将替换文件的索引副本。
到目前为止还不错,但是当您签出某些提交(例如,由于git checkout master
并发出命令git checkout develop
时会发生什么?索引在这里扮演第二个角色,即跟踪工作树(即索引)并保持有关工作树的缓存信息。 (这也是其第三个名称 cache 的来源。)
Git已将master
转换为提交哈希以提取该提交,但在这一点上它再次这样做。此时,Git正在使用git read-tree
命令的所谓的两棵树合并模式。它还将develop
转换为提交哈希,因此现在有两个提交哈希,分别是master
(当前提交)和develop
(所需的提交)。在确保这些确实是提交之后, 1 Git将它们转换为树哈希ID:HEAD
树以及所需的树或目标树。
同时 index 列出工作树中每个跟踪文件的哈希ID。在理想情况下,对于索引和/或HEAD
提交中的每个文件 F , F 在两个{{1} }和索引。如果是这样,HEAD
的索引副本本身就是“干净的”(匹配)。工作树副本可能是干净的,也可能不是干净的(可能匹配索引副本,也可能不匹配)—在大多数情况下,索引作为 cache 的角色有助于使最后一次测试非常快。
对于两者 F
和中存在的每个文件 F ,的目标哈希F 与HEAD
的{{1}}哈希匹配,否则不匹配。对于目标树中没有 但在HEAD
中确实存在的文件, F 的索引和工作树副本是干净的,或者不是。如果文件是干净的,则可以安全地删除两个副本(如果文件不在目标文件中)或用目标树中的版本替换两个副本(如果文件在目标文件中)。但是,如果目标树的哈希值匹配 F
哈希值,则根本不需要触摸索引和工作树条目,因此Git不需要。
简而言之,只有HEAD
和目标树不匹配的地方,Git才需要更改工作树中的任何内容以实现检出。如果不匹配的部分是文件HEAD
在HEAD
中但不在目标中,则目标变为删除xyz.txt
-但仅允许这样做如果它是“干净的”,除非您将HEAD
添加到xyz.txt
中。如果不匹配的部分是文件既不在--force
也不在索引中,而不在在目标中,则目标变为创建 {{ 1}}中包含目标的内容,但是只有在文件不存在或者文件在ignore指令中列出时,才允许这样做。 2
1 分支名称来始终标识提交哈希。 (允许使用标签名称来标识其他类型的对象。)因此,从理论上讲,无需进行检查。 Git是否真的检查,取决于代码路径。
2 最后一部分有时会引起一些严重的疼痛。 Git确实应该(但不应)区分“忽略此文件,因为它很容易重新创建”和“忽略此文件,因为它不应该提交,但是永远不要破坏它”,因为它包含一些珍贵的东西例如用户配置数据。”