如何判断一个提交是否是另一个提交的后代?

200
使用 Git,我如何确定我的分支中的一个提交是否是另一个提交的后代?

3
相反地问同一个问题:https://dev59.com/Z2Ml5IYBdhLWcg3wgnMF请问如何判断一个 commit 是否是另一个 commit 的祖先或后代?请用通俗易懂的语言进行翻译,不要改变原意。 - Chris Cleeland
12
你能否更改您接受的答案?大多数人喜欢“--is-ancestor”解决方案。 - Robert Siemer
8个回答

338

从 Git 1.8.0 开始,这被支持作为 merge-base 的一个选项:

git merge-base --is-ancestor <maybe-ancestor-commit> <descendant-commit>

从man手册中得知:

--is-ancestor

检查第一个提交是否为第二个提交的祖先,如果是则以状态0退出,否则以状态1退出。错误由非1的非零状态发出信号。

例如:

git merge-base --is-ancestor origin/master master; echo $?

6
不错!这里是一个包装了这个答案并输出人类可读结果的shell脚本: https://gist.github.com/simonwhitaker/6354592 - Simon Whitaker
13
换句话说: git merge-base THING --is-ancestor OF_THING && echo yes || echo no 例如: git merge-base my-feature-branch --is-ancestor master && echo yes || echo no - user1735594
2
@smarber git merge-base --is-ancestor -- commit commit 在我的电脑上使用 git 2.1.4 (Debian/Devuan 7.10 jessie) 和 1.9.1 (Ubuntu 14.04 trusty) 的哈希值都可以正常工作,这些版本已经相当古老了。即使在 Debian wheezy 上也可以使用,只需执行 sudo apt-get install git/wheezy-backports 即可。 - Tino
为什么Git Bash没有echo yes || echo no就不返回任何内容? - xr280xr
1
@xr280xr 它确实返回了一些东西,只是没有将任何内容打印到“stdout”。如果您熟悉C语言,那么这就像是“return 0”和“printf(“All good”)”之间的区别。第一个是为调用函数而设计的(因此您可以对其进行“if”操作),第二个是为用户而设计的。因为“git merge-base”首先是为脚本内部使用而设计的,所以用户需要将打印部分包装起来。 - user16785526

63
如果您想以编程方式(例如在脚本中)进行检查,可以检查git merge-base A B是否等于git rev-parse --verify A(然后A可从B到达),或者它是否为git rev-parse --verify B(则B可从A到达)。此处需要使用git rev-parse将提交名称转换为提交SHA-1 /提交ID。
VonC答案中的git rev-list也是一种可能性。 编辑:在现代Git中,有明确支持这个查询的形式: git merge-base --is-ancestor
如果您要询问的提交之一是分支尖端,那么git branch --contains <commit>git branch --merged <commit>可能是更好的非程序化解决方案。

1
可能最快的方法是 git checkout -b quickcheck <more-recent-commit-ID> 然后 git branch --contains <older-commit-ID> (然后使用 git branch -D quickcheck 来删除临时分支)。 - clee
2
两种可能的方法,都比@MattR答案中的方法更糟糕。 - jwg
7
@jwg:MattR的回答更好,但这个回答(很可能被接受),是在git 1.8.0和git merge-base --is-ancestor出现之前2年发布的。 该回答比较早,在Git 1.8.0以及使用git merge-base --is-ancestor命令之前2年就已经存在了。尽管如此,MattR的回答更好一些。 - Jakub Narębski
@JakubNarębski 好的,抱歉。 - jwg
4
在一个包含 200 万个提交的大型代码仓库中,我比较了 git branch --contains <commit>git merge-base --is-ancestor ... 的速度:前者需要 3 分 40 秒,而后者只需要 0.14 秒。 - hagello
@JakubNarębski 你说得完全正确,但现在我想人们可能希望发现更新的选项。也许你可以编辑你的答案以包含1.8.0+的解决方案? - Jelle Fresen

17

这种操作依赖于在SO问题中详细说明的“git log origin/master vs git log origin/master..”概念中的版本范围。

git rev-list 应该能够从一个提交开始向后遍历,直到达到另一个提交(如果可达)。

所以我会尝试:

git rev-list --boundary 85e54e2408..0815fcf18a
0815fcf18a19441c1c26fc3495c4047cf59a06b9
8a1658147a460a0230fb1990f0bc61130ab624b2
-85e54e240836e6efb46978e4a1780f0b45516b20

(边界提交以-为前缀)

如果显示的最后一个提交与git rev-list命令中的第一个提交相同,则它是从第二个提交可达的提交。

如果第一个提交从第二个提交不可达,git rev-list应返回空。

git rev-list --boundary A..B

如果从B可达A,则以A结束。
与以下语句等价:

git rev-list --boundary B --not A

假设使用B作为正面参考,A是负面参考。
它将从B开始,并倒推回图形,直到遇到可从A到达的修订版本为止。
我认为如果A直接可达B,它会遇到(并显示,因为有--boundary选项)A本身。


这听起来像是一个常见的用例,我很惊讶git还没有发布一个完全满足这个需求的"porcelain"命令。 - Lawrence I. Siden
1
@lsiden: true。附注:不要忘记,为了以编程方式检查某些内容,您不应该使用外观命令(如https://dev59.com/r2w05IYBdhLWcg3w3lkl#6978402),而应该使用管道命令(如https://dev59.com/HW865IYBdhLWcg3wTc3h#3879077))。 - VonC
哦,看来我得回去好好练习我的Shell脚本技能了! - Lawrence I. Siden
1
问题:为什么片段中的“-85e54e2...”有一个负号?还可能是打字错误:“...与第一个提交相同…” - sdaau
1
@sdaau - 表示这是一个边界提交。我已经编辑了答案,以使其更清晰,并刷新了文档链接并纠正了这个五年前答案中的拼写错误。 - VonC
显示剩余2条评论

13

另一种方法是使用 git loggrep

git log --pretty=format:%H abc123 | grep def456
如果提交def456是提交abc123的祖先,则此命令将生成一行输出,否则不会输出任何内容。
通常情况下您可以省略--pretty参数,但如果您想确保只搜索实际提交哈希而不是日志注释等信息,则需要该参数。

我希望这个解决方案会比较慢,但实际上它非常快,即使是一个有20k+提交的项目。 - Renato Zannon
2
我使用--oneline代替--prettygit log --oneline ce2ee3d | grep ec219cc,效果很好。 - gens

3

https://dev59.com/aXA75IYBdhLWcg3w9-Sx#13526591提到了这个,现在让我们更人性化一些:

git-is-ancestor() (
  if git merge-base --is-ancestor "$1" "$2"; then
      echo 'ancestor'
  elif git merge-base --is-ancestor "$2" "$1"; then
      echo 'descendant'
  else
      echo 'unrelated'
  fi
)
alias giia='git-is-ancestor'

如果两个参数指向同一次提交,则返回“不相关”。 - mik

2
如果您正在使用 git merge-base --is-ancestor,请确保使用 Git 2.28(2020年第三季度)。
在Git 2.28(2020年第三季度)中,一些不必总是存在的“struct commit”字段已移至提交块。
查看 提交 c752ad0, 提交 c49c82a, 提交 4844812, 提交 6da43d9 (2020年6月17日)由Abhishek Kumar (abhishekkumar2718)提交。
(由Junio C Hamano -- gitster --于2020年7月6日合并至提交 d80bea4) commit-graph: 引入commit_graph_data_slab 签署者:Abhishek Kumar 结构体“commit”在许多上下文中使用。但是,成员“generation”和“graph_pos”仅用于与提交图形相关的操作,并且浪费内存。
随着我们过渡到使用64位代替当前32位的代数编号v2,这种浪费会更加明显。
由于它们经常一起访问,因此让我们引入结构体“commit_graph_data”并将它们移动到一个“commit_graph_data”内存池中。
虽然整个测试套件的运行速度与“master”一样快(系列:26m48s,“master”:27m34s,快了2.87%),但某些命令(如“git merge-base --is-ancestor”)的速度降低了40%由Szeder Gábor发现
在最小化提交内存池访问后,减速仍然存在,但接近20%。
Derrick Stolee认为减速归因于底层算法而不是提交内存池访问的缓慢(如此链接所述),我们将在以后的系列中跟进。

1
目前可用的最快速方式。特别是如果存在提交图! - Aktau

1

git show-branch branch-sha1 commit-sha1

其中:

  • branch-sha1: 你想要检查的分支中的sha1值
  • commit-sha1: 你想要与之比较的提交的sha1值

-1
在itub的回答基础上,如果您需要对存储库中的所有标签执行此操作:
for i in `git tag` ; do echo -ne $i "\t" ; git log --pretty=format:%H $i | (grep <commit to find> || echo ""); done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接