Git 在计算 cherry-pick、合并和变基的差异时会考虑哪些文件或提交?

Which files or commits does Git consider when calculating differences for cherry-pick, merge and rebase?

我读过 Git 内部原理 here and here 并且知道什么是提交,以及树和 blob。

我知道 Git 存储的是单个文件而不是文件差异(deltas),后面的差异是根据需要实时计算的。该文档还经常谈到 "difference between two commits"(无论它们是 parent 和 child、ancestor/descendant 还是两者都不是)。

但是,我不清楚 Git 如何在各种情况下计算这些增量(cherry-picking、合并、变基)。在每种情况下都考虑了哪些文件(即提交的文件)?

我读过,根据该结构,单个提交可以被视为整个分支(即导致该提交的提交历史),对于给定的文件,我可以通过以下方式访问其所有版本向后遍历分支(尽管我想不一定回到它的根;只是回到前一个文件版本可能就足够了)。如果我的假设是错误的,请澄清。

这些规则在概念上很简单,但在实践中却变得复杂。

  • 真正的git merge使用提交DAG来寻找合并基础。合并基础被定义为最低公共祖先(以明显的方式推广到可能有多个 LCA 的任意 DAG,与总是有唯一 LCA 的简单树相比)。 git merge-base 命令将在给定两个提交的情况下从 DAG 中找到一个(默认)或所有(--all)合并基础提交。

    如果有多个合并基础,则算法取决于-s(策略)参数。默认的 recursive 策略使用递归合并合并基(还有什么?:-))。目前这是以缓慢-简单-愚蠢的方式完成的:如果有 5 个合并基础,Git 合并其中两个(根据需要找到这两个的合并基础)并从结果中生成 "virtual commit" , 将结果与 list-of-5 中的下一个(第 3 个)合并,将 that 结果与第 4 个结果合并,并将 that 与5日获得最终的虚拟合并基地。 (为了使这一切正常工作,我相信 Git 实际上使 真正的 提交。没有理由不这样做:这些未引用的提交稍后将被自动垃圾收集。)

    resolve 策略只是选择多个合并基础之一并将其用作基础。

    在任何情况下,一旦我们有一个合并基础哈希 ID $base 和两个分支提示,合并的两个差异是来自:

    的输出
    git diff $base $tip1
    git diff $base $tip2
    

    (或多或少——如果需要,可以对 --rename-limit 值进行一些调整,具体取决于额外的合并命令参数,并且所有这些都假定没有特殊的合并驱动程序;实际合并是逐个文件进行的,但是每个文件的合并基础版本来自$base,任何重命名检测首先发生在两个提交范围的差异中。

  • git cherry-pick 命令将每个提交与其父提交进行比较,然后首先尝试将生成的增量作为补丁应用。如果失败,它会回退到 "three way merge",但合并基础是逐个文件而不是逐个提交的基础,因为它使用格式化补丁中的 Index: 信息.每个补丁中的文件有一行 Index: 行,给出了两个有问题的 blob 的 SHA-1 ID。

    因此,最初完全忽略合并基数:cherry-pick 只是将补丁用作补丁。只有当补丁不适用时(如 git apply 中),cherry-pick 才会退回到三向合并(如 git apply -3 中)。 blob 本身也必须存在于您的存储库中——对于 cherry-pick,它总是存在;对于电子邮件补丁的文字 git apply,它可能不会。

    此时要合并的两个差异是:

    git diff $indexbase $file1
    the diff in the patch # equivalent to git diff $indexbase $file2
    

    其中 $indexbase 是由 Index: 行中的哈希 ID 提取的文件,$file1 是工作树中的文件。 (此文件与 HEAD 提交匹配,除非您使用 git cherry-pick -n。)在任意(通过电子邮件发送的)补丁中,您根本不需要 $file2,只有差异;在精心挑选的补丁中,$file2 是提交中被精心挑选的文件的版本(但不需要它,因为我们已经有了差异!)。

    如果您挑选一个合并提交,您必须告诉 Git 该合并提交的哪个 父级将用于生成一个变更集作为补丁.此步骤完全手动。

  • 从功能上讲,rebase 由一系列 cherry-pick 操作组成。变基中省略了合并提交。 (Interactive rebase 的 --preserve-merges 操作使 new 合并,完全忽略原始合并。)交互式 rebase 字面上运行 git cherry-pick(每次提交一次一次复制), 而非交互式 rebase 尝试使用 git format-patch <args> | git am -3 如果它可以 (格式补丁省略 "empty" 提交所以这只有在没有 -k 的情况下才有可能).

    在某些情况下,要复制的提交是通过对称差异上的实际 git rev-list --cherry-pick 选择的,或者出于算法目的,是等效的东西。