祖先路径如何与git log一起使用?

时间:2016-04-05 17:52:58

标签: git

我已阅读git log documentation,但我仍然觉得很难理解--ancestry-path选项的作用。我看到了调用git log的不同方法:

$ git log origin/master..HEAD
$ git log --ancestry-path origin/master..HEAD

在第一个命令中,我得到了HEAD上但不在origin/master上的提交列表,基本上这显示了我的分支上没有合并的内容。

在第二个命令中,我什么都没得到。如果我更改为3个点(...),它会向我显示某些内容,但我不确定如何理解它。基本上,--ancestry-path的添加有何不同?它究竟是什么简化

2 个答案:

答案 0 :(得分:22)

Matthieu Moy's answer是正确的,但如果你没有接触到必要的图论,那可能对你没有多大帮助。

的DAG

首先,让我们快速了解 D 竖立的 A 循环 G raphs或DAG。 DAG只是一个图形(因此是g),即它们之间的节点和连接的集合 - 这些像铁路线上的火车站一样工作,例如,站点是节点 - 即&# 34;针对" (d:列车只运行一个方向)并且没有循环(a)。

线性链和树结构是有效的DAG:

o <- o <- o

或:

       o <- o
      /
o <- o
      \   o
       \ /
        o
         \
          o <- o

(想象一下对角连接有箭头,以便它们根据需要指向左上或左下)。

但是,非树图可以有合并回的节点(这些是git的合并):

       o <- o
      /      \
o <- o        \
      \   o    \
       \ /      \
        o        o
         \      /
          o <- o

或:

     o--o
    /    \
o--o      o--o
    \    /
     o--o

(我只是在这里进一步压缩符号,节点通常仍指向左侧)。

接下来,git的..符号并不代表大多数人通常认为的含义。特别是,让我们再看看这个图,添加另一个节点,并使用一些单个字母来标记特定节点:

     o---o
    /     \
A--o       \
    \   B   \
     \ /     \
      o       C--D
       \     /
        o---o

并且,让我们再做一件事,并且不再仅仅考虑git log,而是更常见的情况&#34;选择具有祖先的修订&#34;。

选择具有祖先的修订(提交)

如果我们选择修订版A,我们只会获得修订版A,因为它没有祖先(左边没有任何东西)。

如果我们选择修订版B,我们会得到图表的这一部分:

A--o
    \   B
     \ /
      o

这是因为select-with-ancestry意味着&#34;接受我识别的提交,以及我可以通过跟随箭头返回的所有提交。&#34;这里的结果有点有趣,但不是非常有趣,因为没有合并,并且跟随箭头会给我们一个四个提交的线性链,从B开始并返回{{1 }}

选择带有祖先的AC会让我们更进一步。让我们看一下D得到的结果:

D

事实上,除了提交 o---o / \ A--o \ \ \ \ \ o C--D \ / o---o 之外,其他一切都是。为什么我们没有B?因为箭头都指向左边:我们得到B,它指向D,指向两个非字母提交;剩下的那两点,依此类推,但是当我们点击C左右的节点时,我们不能向右箭头移动,所以我们不能到达B

双点表示法

现在,git中的双点表示法实际上只是set subtraction的简写语法。 1 也就是说,如果我们写B,例如,它意味着:&#34;选择带有祖先的B..D,然后选择带有祖先的D,然后从B中排除(减去)所有提交后,从D选项中提交一组提交。选择。&#34;

选择带有祖先的B会获得D提交的之外的整个图形。减去B选项将删除B,我们之前绘制的两个A节点以及o。如果B不在集合中,我们该如何删除?简单:我们只是假装删除它并说我们已经完成了!也就是说,set subtraction只是为了删除实际上在集合中的东西。

B的结果就是这张图:

B..D

三点符号

三点符号不同。它在一个简单的branch-y图中更有用,甚至可能是一棵直树。让我们这次从树状图开始,看看两点和三点符号。这是我们的树状图,其中包含一些用于放置节点的单个字母名称:

     o---o
          \
           \
            \
             \
              C--D
             /
        o---o

这次我添加了额外的信件,因为我们需要谈谈提交和加入的某些地方,特别是节点 o--I / G--H \ J \ / K \ o--L 和{ {1}}。

使用双点表示法,我们可以获得H的内容?要找到答案,请从节点K开始并向后工作。您必须始终向左移动,即使您也向上或向下移动。这些是所选的提交。然后,从节点L..I开始并向后工作,找到 un -select的节点;如果你遇到任何早先选择的,扔掉它们。 (最后的清单留作练习,但我会把答案作为一个脚注。 2

现在让我们看看三点符号的实际效果。它的作用有点复杂,因为它必须在图中的两个分支之间找到合并基础。合并基础有一个正式的定义, 3 但是出于我们的目的,它只是:&#34;当我们向后追踪图表时,我们会在一些提交时见面。&# 34;

在这种情况下,例如,如果我们要求IL - 两者都产生相同的结果 - git会查找可从 提交到达的所有提交,但不是来自两者。也就是说,它排除了合并基础和所有早期提交,但保留了超出该点的提交。

L...II...L(或LI)的合并基础是提交I,因此我们会在L之后获取内容但不是H本身,我们无法从HH到达节点J,因为它不在其祖先中。因此,IL的结果是:

I...L

(请注意,这些历史记录没有加入,因为我们抛弃了节点L...I。)

o--I K \ o--L

现在,所有这些都是普通的选择操作。没有使用H修改任何内容。 documentation for git log and git rev-list - 这两个几乎是相同的命令,除了它们的输出格式 - 用这种方式描述--ancestry-path

  

当给出一系列要显示的提交时(例如--ancestry-path或              --ancestry-path),只显示直接存在于提交上的提交              commit1..commit2commit2 ^commit1之间的祖先链,即提交              既是commit1的后代,也是commit2的祖先。

我们在这里根据提交DAG定义祖先:如果第二个提交的箭头指向第一个,则第一个提交是直接祖先。如果第二个指向第一个通过某个提交链,那么间接祖先。 (出于选择目的,提交也被视为其自身的祖先。)

Descendants (有时也称为 children )的定义类似,但是与图中的箭头相反。提交是另一个提交的子(或后代),如果它们之间有路径。

请注意,commit1的描述是关于使用双点表示法而不是三点表示法,可能是因为三点表示法的实现在内部有点奇怪。如前所述,commit2将两个提交中的合并基础(或基数,如果有/多个)排除(就像使用前导--ancestry-path一样)合并基础是一个发挥&#34;必须是孩子的&#34;角色。我将提及B...D如何使用此功能,但我不确定它在现实世界中是否有用#34;实例

实际例子

这在实践中意味着什么?嗯,这取决于你给出的参数,以及实际的提交DAG。让我们再次看一下时髦的循环图:

^

假设我们在 --ancestry-path请求 o---o / \ A--o \ \ B \ \ / \ o C--D \ / o---o {em>}。这意味着我们接受提交B..D及其祖先,但排除--ancestry-path及其祖先,就像我们之前看到的那样。现在让我们添加D。我们之前的所有内容都是B的祖先,这仍然是正确的,但是这个新标志说我们必须抛弃--ancestry-path的孩子。

节点D有多少个孩子?好吧,没有!所以我们必须抛弃每个提交,给我们一个完全空列表

如果我们在没有特殊B符号的情况下请求B该怎么办?这为我们提供了从B...D --ancestry-path可以访问的所有内容,但排除了从D B可以访问的所有内容:

D

这与B相同,只是我们也获得了节点 o---o \ \ B \ \ C--D / o---o

[注意:关于B..DB混合的部分在2016年4月到2017年2月之间差不多有一年的时间是错误的。已经修复了注意到&#34;必须是子&#34; part从 merge base(s)开始,而不是从--ancestry-path表示法的左侧开始。]

假设我们在这里添加B...D。我们开始使用我们为B...D而不是--ancestry-path获得的相同图表,但是 discard 项目不是合并基础的子项。合并基数是B...D左侧的--ancestry-path。顶行o提交不是此节点的子节点,因此它们将被丢弃。同样,与祖先一样,我们认为一个节点是它自己的子节点,所以我们保留这个节点本身 - 给出这个部分结果:

B

但是,当我们(或o)丢弃此合并基节点的子节点时,合并基节点本身( B / o C--D \ / o---o 的左下方)不在{ {1}}首先是图表。因此,最终结果(实际在Git 2.10.1中测试)是:

--ancestry-path

(同样,我并不确定这在实践中有多么有用。起始图再次是B:从可以提交的所有内容,减去从两个提交的一切都可以提交:这可以通过从每个合并库开始丢弃,如果有两个或更多。子检查代码也处理提交列表。它保留了的所有内容如果有多个合并基础,则 任何合并基础的子项。请参阅revision.c中的函数B...D。)

因此,它取决于图表选择器

B C--D / o---o B...D的最终操作(包含或不包含limit_to_ancestry)取决于提交图。要预测它,您必须绘制图形。 (使用X..Y,也许使用X...Y,或使用为您绘制图表的查看器。)

1 --ancestry-path中有一个例外,它对git log --graph--oneline --decorate --all进行了自己的特殊处理。当您不使用git diff时,您应该忽略它的特殊处理。

2 我们从X..Y开始,左侧是X...Ygit diffI。然后,当我们从o返回时,我们会失去HG,因此结果只是H

3 正式的定义是合并基础是 L owest C ommon A ncestor,或者LCA,图中给定节点。在一些图中,可能存在多个LCA;对于Git,这些都是合并基础,G将排除所有这些基础。

为我绘制的图表运行L是有趣/有益的。这里的这些提交哈希不仅取决于图本身,提交,还取决于提交提交的时间戳,因此如果你构建同一个图,你会得到不同的哈希值,但是这里是我修改答案以修复o--IX...Y交互的说明时所得到的:

git rev-parse B...D

但我们可以看到这些是--ancestry-pathB...D和合并基础,使用更多命令:

$ git rev-parse B...D
3f0490d4996aecc6a17419f9cf5a4ab420c34cc2
7f0b666b4098282301a9f95e056a646483c2e5fc
^843eaf75d78520f9a569da35d4e561a036a7f107

D

B

确实会出现具有多个合并基础的图形,但它们构建起来有点困难 - 简单的方法是使用&#34; criss cross&#34;合并,运行$ git rev-parse B # this produces the middle hash 7f0b666b4098282301a9f95e056a646483c2e5fc 。如果你遇到这种情况并运行$ git rev-parse D # this produces the first hash 3f0490d4996aecc6a17419f9cf5a4ab420c34cc2 ,你会看到几个否定的哈希值,每个合并基数一个。运行$ git merge-base B D # this produces the last, negated, hash 843eaf75d78520f9a569da35d4e561a036a7f107 ,您将看到相同的合并基础集。

答案 1 :(得分:2)

正如文档所述,--ancestry-path删除了不属于origin/master后代的提交。如果您有一个本地的,未合并的分支,并且此分支基于早于origin/master的提交,则不会显示此分支中的提交,因为这些提交不是origin/master的后代。 / p>