在Perforce中,合并信息(例如合并历史记录)是每个文件的。 在 Subversion 中,合并信息是按目录进行的。 在 Git 中,合并信息是按文件还是按目录?
两者都不是。
Subversion 在这里有点奇特,因为它是建立在基本上处理目录的系统之上的。 这意味着你可以使用 Subversion 查看一个特定的目录——嗯,有点:你也可以得到它的子目录。 但它从一个目录开始:因此目录级合并信息。
Git 的基本单位既不是文件也不是目录,而是提交。 您不能少于整个提交。1当你运行git checkoutcommit-specifier
时,Git 将整个提交的树复制到你的索引/暂存区——这占用的空间相对较少;请参阅脚注 1 — 然后将所有这些文件从索引复制到工作树中,您可以在其中处理它们。
每个 Git 提交都是项目中所有文件的完整快照,或者更准确地说,是提交时索引中的所有文件。 由于索引从当前提交中的所有文件开始,因此在下一次提交中也将继续包含所有文件。 合并提交在这里与任何其他提交没有什么不同:它拥有所有文件,在其完整和完整的荣耀中,但压缩、冻结和 Git 化,就像任何提交中的所有文件被压缩、冻结和 Git 化一样。 由于它们被冻结了(您永远无法更改任何提交的任何部分),如果它们保持不变,它们就会被共享,因此,如果您提交 100 MB 的文件十次,甚至一百万次,该文件使用的磁盘空间量与您只提交 100 MB 文件一次相同 — 因为实际上, 你做到了。 (每次提交都会在文件顶部添加一点空间,用于提交的元数据,但是如果您熟悉Unix/Linux系统上链接的想法,则可以将每个提交视为具有指向一个底层文件的硬链接。
同时,提交是合并的事实通过存储在每个提交的父哈希 ID 或 ID 记录。 每次提交都会记住紧接在此特定提交之前的提交的原始哈希 ID(可以说是真实名称)。 对于大多数提交,只有一个这样的哈希 ID,我们最终会得到一个向后看的链,因此如果我们从最近的提交开始,其哈希是一些我们称之为H
的大丑陋哈希,我们可以找到它的父级,我们将其哈希称为G
。 然后我们可以用G
来查找G
的父级,我们称之为F
,然后用F
来找F
的父级,依此类推:
... <-F <-G <-H
我们只需要知道问题的答案:这个链中最后一个提交的哈希 ID 是多少?为了找到这个答案,我们查找一个分支名称,例如master
.名称包含最新的哈希 ID,仅此而已! 其他一切都来自提交。
合并提交的唯一特别之处在于它至少记录了两个父哈希:
...--F--G--H
M--...
/
...--J--K--L
合并信息在于M
有两个父级,H
和L
。 从M
我们可以倒退到H
,然后沿着那个链条回来,或者我们可以倒退到L
,然后沿着那个链条回来。 所有三个提交——M
、H
和L
——都是完整和完整的快照,所以如果我们想看看顶部链提交是如何通过合并底层链来修改H
L
提交到其中的,我们可以比较H
和M
。 如果我们想看看底层链提交是如何通过将顶链提交H
合并到其中来修改L
的,我们可以比较L
和M
。 例如,当我们想查看H
是如何被修改时,这些是我们所做的相同比较:我们比较G
和H
,这是两个快照,以查看从G
到H
的变化。
如果我们想看看两条链最初分道扬镳的位置,我们只需要一个更广阔的视野:
...--E---F---G----H
M--...
/
I--J--K--L
如果F
的父级是E
,而J
的父级是I
父级是E
的,那么我们可以看到E
是我们合并H
和L
时的合并基础。 由于所有提交都始终被冻结,如果E
是合并基础,那么E
现在仍然是合并基础。
这意味着您的问题的答案是:根本没有明确的合并信息。 存在合并的事实以及重复合并所需的信息由提交图暗示。提交图是您(或 Git)通过读取各个提交派生的数据结构。 由于提交是基本单元,因此您始终有一个完整的提交。阿拉伯数字在浅层克隆中,您可以缺少较早的提交,但您可以通过返回克隆的任何地方并"取消浅表"克隆(或者只是将其加深到足够远以查看您需要看到的内容)来填充这些提交。
1Git 确实支持稀疏结帐的概念,但您仍然可以获得整个提交。它的所有文件仍会复制到索引中。 稀疏签出仅限制然后将哪些文件从索引复制到工作树。 由于索引副本大部分是冻结的,可以直接共享提交中的副本,因此这减少了所需的磁盘空间量,因为某些文件永远不需要从冻结的提交中解压缩出来。 你做的下一个提交,如果你做了另一个,是根据索引中的内容而不是从工作树中的内容进行的,所以新的提交继续拥有所有文件,尽管签出很稀疏。
2Git 添加了允许某种占位符的想法,以promisor 包的形式,您可以在其中提交但缺少一些内部数据。 也就是说,提交本身仍然是基本单元,但是虽然你知道提交C有树 T,但你可能缺少对象T本身,直到有东西明确要求它,此时 Git 会尝试通过打电话回家来履行承诺谁做出承诺。 这实际上还没有在 Git 中,但它正在慢慢进入代码库。
请注意,这有点类似于浅层克隆。 在浅层克隆的情况下,你知道提交 C 有父 P,但你缺少对象P,因为有一个浅层移植,这使得 Git 的某些部分假装C根本没有父级。使用git fetch --deepen=number
,你可以让 Git 获得 P,但也许可以浅嫁接P的一些祖父母。 这只是用该祖父母的新移植点替换浅移植点,或者如果您已经获得了所有父母,例如,通过git fetch --unshallow
,则完全删除浅移植点。