假设提交A1是提交A2的父级。它真正告诉我什么?
为了澄清我的问题,这里有两个不正确的解释:
1)提交A2是基于提交A1创建的,意思是用户签出了A1,做了一些编辑,并且提交了A2(没有任何介入的git命令)。由于变基,这是错误的。
2)每个git commit都存储相对于其父级的delta,因此您必须反向按箭头并应用每个delta来重建提交的内容。这是错误的,因为与许多其他VCS不同,git提交存储完整的快照而不是增量。
这是一个似乎差不多正确的解释的例子,但非常模糊:
3)提交A2包含提交A1表示的所有工作以及一些额外的工作。 “工作”用于简单地添加,删除和编辑文件。
答案 0 :(得分:2)
解释2是完全错误的,但它包含一个正确的项目:你做(或Git确实)必须遵循Git存储的向后箭头,以构建图形。每个提交“指向”其父提交(通过存储它们的真实名称哈希ID),使每个提交充当单个顶点(或节点)加上一组传出弧,一旦收集,形成有向无环图或DAG。在CS或信息学的大多数图表中,我们都有从父母到孩子的传出弧线,但在Git中,箭头都是向后的。 (这样父母在孩子存在之前不需要知道他们的子ID,同时也允许父提交一旦创建就是只读的。因为每个哈希ID仅由每个对象的内容决定,并且他们故意难以为了计算,在知道内容之前不能知道任何哈希ID。因此,父提交必须是只读的:你不能更新它们以添加它们的子项;这将改变它们的哈希ID。 1 )
解释1大多是正确的,但缺少一些关键项目。 As Jim Deville said in his answer,Git的各种管道命令允许您构造几乎任意的提交图节点(即提交对象)。命令git commit-tree
特别使用任意数量的有效父提交ID(-p
选项),一个有效的树ID和提交消息,并使用您的配置和计算机构建新的提交。设置作者和提交者名称,电子邮件和时间戳字段的当前时间的概念(或者如果设置了环境变量,则使用环境变量覆盖)。新的提交对象存储在数据库中,没有指向它的任何内容,因此您必须快速 2 设置引用(例如分支或标记名称)以保留它。 (或者,您可以创建另一个提交以保留刚刚创建的提交,但 提交需要一个名称或另一个需要某些提交的提交,等等。)
这意味着父信息取决于创建提交的命令。
当您使用git rebase
时,创建新提交的步骤通常 - 或者可能是 - git commit
本身,git commit
根据结果设置新提交的父级阅读HEAD
(然后立即更新HEAD
,或更常见的是HEAD
命名的分支。 rebase操作通常与“分离的HEAD”一起使用,其中HEAD
包含现有提交的原始哈希ID,而不是包含分支名称的更常见的HEAD
。
因此,rebase的工作方式是分离HEAD
,使其指向--onto
目标(默认为<upstream>
参数),然后一次提交一个提交。它通过将原始提交转换为增量,将增量应用于当前索引和工作树,并使每个新提交,以及提交la git commit
。 (rebase的实际机制是使用git cherry-pick
或git am
实现的,两者都是用C语言编写的,并使用git commit
中的代码。在某些情况下,交互式rebase可能会用于壁球步骤或使用--root
时,而不是运行git commit
而不是运行git cherry-pick
。--preserve-merge
git merge
rebase使用交互式机制并逐字地运行git diff
来创建新的合并。细节变得相当复杂。)
请注意,从快照到更改集/ delta的转换是通过对提交的已记录父级运行git commit-tree
来完成的。因此,设置一个奇怪的父ID不是有用的。您可以这样做(使用git show
)但除非您永远不会挑选或重新定位或git gc --auto
提交,所有这些都使用父ID将快照更改为delta,这将是糟糕的计划。
1 当然,可以将每个提交对象分成参与散列的只读部分和不参与散列的读/写部分。这将允许Git向父母添加子ID。但这会使Git 更少稳定且更少安全:只读对象不会像读/写对象那样被破坏,并且部分提交不参与在它的散列中意味着该部分不受保护散列。
2 默认情况下,git gc
,其他Git命令不时运行,为您提供两周的时间来完成此任务。如果花费的时间超过了这个时间,则自动{{1}}可能会删除您尚未引用的提交。
答案 1 :(得分:1)
我想说所有A1都是A2的父级意味着在给定分支的git tree-ish中,A1是A2之前的立即提交。
我不确定,但我相信您可以使用git plumbing直接编写提交和树,从而进行与前一次提交完全无关的提交。但是,即使在这种情况下,它也会像两者之间的步骤一样删除所有文件并添加新文件。