像单个文件一样使用Git merge-base

时间:2015-11-03 10:53:07

标签: git merge git-merge git-commit

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

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

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

1 个答案:

答案 0 :(得分:3)

  

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

不,或是,或者可能:这取决于你的意思。

  

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

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

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

让我们尝试思考实验。假设您有两个分支提示br1br2最终具有共同的祖先提交:

       o--o--o--Y   <-- br1
      /
...--X
      \
       o--o--o--Z   <-- br2

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

         o
        / \
       o   o--o--Y   <-- br1
      / \ /
...--X   o
      \
       o--o--o--Z   <-- br2

鉴于图表的方式和git merge的工作方式,&#34;常规&#34;合并(或使用git merge-base)将找到合并基础X,此时我认为大多数人会同意某个文件位于X并且已传播(可能已重命名) Y以及ZX中有一个共同的祖先。此共同祖先可能会在YZ(或甚至Y Z中)的不同路径名下显示,但它&#39 ; s仍然是共同的祖先,因此它被用作合并基础版本。

这里有一个问题:git不记录重命名。相反,它发现&#34;他们每次做出差异。为了发现generic/b.c中的文件X现在specific/b.c Y,{g}必须将X下的整个树对Y下的整个树进行区分{1}}。这意味着它必须找到提交X

这对于常规合并来说并不太难,因为它使用提交图:它从提交YZ开始,并向后遍历历史记录以查找最近的公共提交(属于当然X这里)。一旦我们知道(或git知道)使用X,就会产生两个差异,X - vs - YX - vs - Z,以及然后它可以将更改合并到公共文件的内容,而不管它在YZ中的路径。< / p>

(交叉合并存在次要问题,可能存在多个最近共同提交,但我们现在可以忽略它。)

如果我们(至少是暂时的)放弃了寻找重命名的想法,我们可以根据一些路径 p ,使用不同的方法,我认为< / em>就是你要问的:

  • 对于cyX之间的每次提交 Y (包括X并从Y向后工作),每个提交在czX之间提交 Z (同样从Z向后工作),比较cy/pcz/p
  • 当这两条路径出现时内容相等,声明提交相等。

请注意,这会将X的路径 p 的版本与X的版本进行比较(当然相同的),也反对任何一个提交链上的每个版本,同时也将每个版本与其他版本进行比较。

制作了这个完整的矩阵(我们可以在以后优化),我们现在可以找到许多有趣的&#34;承诺:

  1. cy - 至 - X链中的最后一次提交 Y ,其中 p 具有相同的内容它在X中的内容(这是该链中最新的提交 p 不变)
  2. cz - 至 - X链中的最后一次提交 Z ,其中 p 具有相同的内容它在X中的内容(另一条链中最新的未更改)
  3. 最早的 cy ,其中 p 与提交Y中的内容相同(这是最后一次路径 p 已在X - 至 - Y链中修改
  4. 最早的 cz ,其中 p 与提交Z
  5. 中的内容相同
  6. 任一链中与 p 具有相同内容的任何提交与其他链中的任何提交相同。
  7. 我想你可能正在考虑在这里找到第1项和第2项。但是,为什么并不清楚。如果您只关心路径 p 下存储的内容,我们已经确定(上文)这两个提交会在 p下存储相同的内容正如您在X中找到的那样。所以X:p是&#34;同样好的&#34;在识别这些内容时,您也可以使用提交X

    如果您正在谈论找到第3项和第4项,那么为什么并不是很清楚,因为我们已经确定这些内容与< 相同em> p 作为提交最多提交,因此Y:pZ:p对于识别这些内容同样有用。

    但也许您正在使用第5项:在两个链上提交,其中路径 p 下的内容相同(与另一个链上的其他提交相同) ,但不一定与tip-most提交中的内容相同。

    可能有很多这样的对。例如,假设在Xgit merge-base找到的绝对共同的祖先)中,路径 p 有五行。然后,在向Y前进时,该路径中的第一个提交将删除最后一行。同时在X - 到 - Z序列中,几个提交保留所有5行,然后一个删除最后一行。现在,此版本的 p 在两个开发行中都是相同的,直到修改 p 的下一个提交。让我们在X到 - Z序列中说出另一行被删除的情况。然后在X - 到 - Y序列中删除相同的行;然后,两个提交删除更多行,直到最后一个或两个分支提示文件完全为空。

    定义&#34;最近的&#34;还有另一个问题。让我们再看一下更为复杂的X - 到 - Y图片段,但会加入一些更有区别的字母:

             R
            / \
           P   T--o--Y   <-- br1
          / \ /
    ...--X   S
    

    假设路径 p 在提交RS中具有相同的内容,但在P和{{1}中都有所不同}。两者都与TX的图表距离相同。只要您关心路径 Y ,这可能无关紧要,但它确实表明不一定是唯一的提交。< / p>

    在我开始使用你想要使用的一些命令之前,这是很多措辞,以便解决你想要解决的任何问题。

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

    基本上,Y会发现可以从git rev-list X..Y -- path到达的提交,不包括Y可以修改的提交,修改X,其中&#34;修改&#34;表示&#34;对父母的差异显示对该路径的改变&#34;。 (有关此处理如何合并,请参阅文档。)列出提交的顺序取决于您选择的排序(有或没有拓扑约束;请参阅&#34;提交订购&#34;部分)。

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

    这两个X..Z基本上是从git rev-list到两个分支提示的整个修订链,但是因为它们允许你将输出限制为&#34;提交修改某些路径( s)&#34;,他们可以优化我在思想实验中概述的过程。

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

    要确定存储在特定路径下的内容在两个不同的提交中是否相同 - 显然,如果您在此处使用相同的提交ID两次,内容是相同的,但它仍然有效 - 您可以比较存储的blob& #39; s SHA-1 ID:

    -

    这些都不会检测到重命名;为此,你必须使用一个完整的path=dir/file ... rev_a=... # something from git rev-list, for instance rev_b=... if [ $(git rev-parse ${rev_a}:${path}) = $(git rev-parse ${rev_b}:${path} ]; then ... the contents match ... else ... the contents differ (at least slightly) ... fi (打开重命名检测)。