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
选择的,或者出于算法目的,是等效的东西。
我读过 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
选择的,或者出于算法目的,是等效的东西。