Git merge-base 就像单个文件一样

Gre*_*reg 2 git merge git-merge git-commit

是否有任何命令可以在两个分支中查找文件的共同祖先?

假设有一个文件在两个分支中独立修改。我想找到两个分支共有的该文件的最后一个版本。我相信这可以归结为在两个分支中找到该文件的单父提交。

但是,merge-base 只允许查找提交的父提交,而不是文件。我尝试指定最后两次提交修改各自分支中的文件,但我得到的父提交不在任一分支中该文件的更改历史记录中,这可能是由于提交通常包含的更改超过一个文件。

tor*_*rek 5

\n

是否有任何命令可以在两个分支中查找文件的共同祖先?

\n
\n\n

不,或者是,或者也许:这取决于你的意思。

\n\n
\n

假设有一个文件在两个分支中独立修改。我想找到两个分支共有的该文件的最后一个版本。我相信这可以归结为在两个分支中找到该文件的单父提交。

\n
\n\n

文件没有父提交。只有提交才有父提交。

\n\n

更糟糕的是,每次提交都会存储每个文件(即,提交时属于暂存区域一部分的每个文件)。因此,从某种意义上说,这要么是每次提交,要么是常规的普通合并基础。显然这不是你的意思,所以让我们看看我们还能说些什么。

\n\n

让我们尝试一个思想实验。假设您有两个分支提示br1,并且br2最终有一个共同的祖先提交:

\n\n
       o--o--o--Y   <-- br1\n      /\n...--X\n      \\\n       o--o--o--Z   <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n\n

还考虑一个更复杂的图,它仍然有一个共同的祖先和两个分支提示:

\n\n
         o\n        / \\\n       o   o--o--Y   <-- br1\n      / \\ /\n...--X   o\n      \\\n       o--o--o--Z   <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n\n

考虑到图表的方式和工作方式git merge,“常规”合并(或使用git merge-base)将找到 merge-base X,此时我认为大多数人会同意某些文件已存在X并被传播(可能通过重命名)到Y,也Z有共同的祖先XY这个共同祖先可能出现在或中的不同路径名下Z(甚至出现在 和 中Y Z,但它仍然是共同祖先,因此它被用作合并基础版本。

\n\n

但这里有一个问题:git 不记录重命名。相反,它每次进行差异时都会“发现”它们。为了发现文件generic/b.cinX现在位于specific/b.cY,git 必须将 下的整个树X与 下的整个树进行比较Y。这意味着它必须找到 commit X

\n\n

这对于常规合并来说并不太难,因为它使用提交图:它从两个提交开始Y,并向Z后遍历历史记录以找到最近的公共提交(当然就X在这里)。一旦我们知道(或 git 知道)要使用X,它就会生成两个差异,X-vs-YX-vs- Z,然后它就可以合并对公共文件内容的更改,无论它在和中具有什么路径YZ

\n\n

(十字合并还有一个第二个问题,其中可能有多个最近的共同提交,但我们现在可以忽略它。)

\n\n

但是,如果我们(至少暂时)放弃查找重命名的想法,那么我们可以在给定某些路径的情况下p使用不同的方法,我认为这就是您要问的问题:

\n\n
    \n
  • 对于和cy之间的每个提交(包括和 从 向后工作),以及和之间的每个提交(同样从 向后工作),比较和。XYXYczXZZcy/pcz/p
  • \n
  • 当这两个路径的内容相等时,声明提交相等。
  • \n
\n\n

请注意,这会将X\ 的路径版本pX\ 的版本(当然是相同的)进行比较,并且还将与任一提交链上的每个版本进行比较,同时还将每个版本与其他版本进行比较。

\n\n

制作了这个完整的矩阵(我们可以稍后优化),我们现在可以找到许多“有趣的”提交:

\n\n
    \n
  1. -to-链cy中的最后一次提交具有相同的内容(这是该链中未更改的最新提交)XYpXp
  2. \n
  3. 到链cz中的最后一次提交具有相同的内容(另一个链中最新的未更改)XZpX
  4. \n
  5. 最早的cy位置与p提交中的内容相同Y(这是最后一次在-to-链p中修改路径)XY
  6. \n
  7. 最早的cz位置p与提交中的内容相同Z
  8. \n
  9. p任一链中的任何提交与另一个链中的任何提交具有相同的内容。
  10. \n
\n\n

我想您可能正在考虑在这里查找第 1 项和第 2 项。但尚不清楚原因。如果您只关心路径下存储的内容p,我们已经(在上面)确定这两个提交存储的内容与p您在 中找到的内容相同X。因此,在识别这些内容方面“同样好”,您不妨使用 commit 。X:pX

\n\n

如果您正在谈论查找项目 3 和 4,那么也不太清楚为什么p,因为我们已经确定这些内容与它们的最尖端提交具有相同的内容,因此和对于识别这些内容。Y:pZ:p

\n\n

但也许您正在处理第 5 项:在两个链上提交,其中路径下的内容p相同(与另一个链上的其他提交相同),但不一定与最顶端提交中的内容相同。

\n\n

可以有很多这样的对。例如,假设在X(找到的绝对共同祖先git merge-base)中,路径p有五行。然后,在前进到 的过程中Y,该路径中的第一次提交将删除最后一行。同时,在X-to-Z序列中,多次提交保留所有 5 行,然后删除最后一行。现在,这个版本p在两条开发线中都是相同的,直到下一次提交修改p. 假设该序列位于X另一Z行被删除的位置。然后在X-to-Y序列中,同一行被删除;然后,两次提交都会删除更多行,直到最终文件在一个或两个分支提示处完全为空。

\n\n

定义“最近”还存在另一个问题。让我们再次看一下更复杂的X图形Y片段,但加入一些更明显的字母:

\n\n
         R\n        / \\\n       P   T--o--Y   <-- br1\n      / \\ /\n...--X   S\n
Run Code Online (Sandbox Code Playgroud)\n\n

假设该路径在 commits和p中具有相同的内容,但在 和 中具有不同的内容。两者与或 的图形距离相同。只要您关心 path ,这可能是无关紧要的,但它确实表明不一定有唯一的提交。RSPTXYp

\n\n
\n\n

在我开始讨论您想要使用的一些命令之前,已经有很多废话了,以便解决您想要解决的问题。

\n\n

将使您更接近解决方案的命令(甚至可能一直到那里,取决于您想要什么,尽管您似乎可能需要使用其他命令,有些甚至不需要 git 命令)是git rev-list。这可以找到修改了特定路径的提交(与这些提交的父提交相比;请注意,通常必须专门处理合并,因为它们有多个父提交)。如果您确实使用一个或多个路径来限制 列出的修订git rev-list,请注意,它将执行“历史简化”,以便从其输出中省略一些提交。根据您希望如何处理 DAG 级分支(如更复杂的链中的分支XY,这可能就是您想要的。

\n\n

基本上,git rev-list X..Y -- path会发现从 可以到达的提交Y,不包括从 可以到达的那些X修改path,其中“修改”意味着“与父级的差异显示对该路径的更改”。(有关如何处理合并,请参阅文档。)提交的列出顺序取决于您选择的排序(有或没有拓扑约束;请参阅“提交排序”部分)。

\n\n

如果您使用 重复此操作X..Z,您可以找到哪些提交修改了那里的路径。

\n\n

这两个git rev-list本质上是从整个修订链X到两个分支提示,但是因为它们让您将其输出限制为“修改某些路径的提交”,所以它们可以优化我在思想实验中概述的过程。

\n\n

您可能想X在此处包含提交。默认情况下,rev-list不会:您可以提前开始一次提交(在 的父级X),但如果X本身是合并,则可能会失败;或者您可以使用--boundary,它指示rev-list包含提交X的 SHA-1 (前缀为-)。

\n\n

找出特定路径下存储的内容在两次不同的提交中是否相同\xe2\x80\x94显然如果你在这里使用相同的提交ID两次内容是相同的,但它仍然有效\xe2\x80\x94you可以比较存储的 blob 的 SHA-1 ID:

\n\n
path=dir/file\n...\nrev_a=...   # something from git rev-list, for instance\nrev_b=...\nif [ $(git rev-parse ${rev_a}:${path}) = $(git rev-parse ${rev_b}:${path} ]; then\n    ... the contents match ...\nelse\n    ... the contents differ (at least slightly) ...\nfi\n
Run Code Online (Sandbox Code Playgroud)\n\n

这些都不会检测重命名;为此,您必须使用成熟的git diff(打开重命名检测)。

\n