Ste*_*enz 6 git git-history-graph
我正在尝试编写一种方法来判断给定的提交是否位于给定分支的第一个父链上。因此,例如,合并基不会飞,因为提交可能已被合并。我想知道确切的提交是否曾经是分支的尖端。
注意:相关分支采用非快进合并策略。
一个简单的“是祖先”测试显然是行不通的,因为第二个或之后的父链的提交也是祖先:
\n...o--o--A--o--o--o--T\n \\ /\n ...-o--*--B----o\n \\\n C\nRun Code Online (Sandbox Code Playgroud)\n和A都是B的祖先T,但是你想接受A而拒绝B和C。(认为--first-parent这里是顶行。)
git merge-base然而,使用实际上可以完成部分工作。不过,您不需要的--is-ancestor 模式git merge-base,并且确实需要一些额外的处理。
T请注意,无论和 某个祖先之间的路径如何,该祖先的合并基础T(例如A或B)要么是祖先本身(A或B分别在此处),要么是祖先的某个祖先,例如*如果我们查看T和 则提交C作为一对。(即使在多个合并基的情况下,这也成立,尽管我将构建证明留给您。)
如果测试提交的合并基础或所有集合中任意选择的一个,并且分支提示还不是测试提交,我们就会遇到类似的情况,并且C可以立即拒绝它。(或者,我们可以用来拒绝它,或者......好吧,见下文。)如果没有,我们必须枚举相关提交和分支提示之间的祖先路径--is-ancestor中的提交。为此:A
o--o--*--T\nRun Code Online (Sandbox Code Playgroud)\n对于 B 来说是:
\n *--T\n /\n o\nRun Code Online (Sandbox Code Playgroud)\n如果任何此类提交是合并提交,就像标记的那样*,我们需要确保第一个父级包含沿此路径列出的提交之一。最困难的情况是那些拓扑类似于:
o--o\n / \\\n...--A o--T\n \\ /\n o--o\nRun Code Online (Sandbox Code Playgroud)\n因为--ancestry-path它们之间包括合并和到达的两种A方式,其中一种是第一父路径,另一种不是。(如果T它本身也是合并,则也是如此。)
不过,我们实际上并不需要首先找到合并基础。我们仅使用合并基础来检查祖先路径。如果合并基础不是测试提交本身,则测试提交不是提示提交的祖先,并且testcommit..tipcommit不会包含testcommit其自身。此外,添加--ancestry-path\xe2\x80\x94 会丢弃所有不是左侧子项的提交,然后 \xe2\x80\x94 将丢弃输出中的所有提交git rev-list:类似的情况C没有作为祖先的后代T(如果它做了,C将是一个合并基地)。
因此,我们想要检查 中的提交git rev-list --ancestry-path testcommit..branchtip。如果此列表为空,则测试提交首先就不是分支提示的祖先。我们有一个像 commit 这样的案例C;所以我们有了答案。如果列表非空,则将其缩减为其合并组件(使用 再次运行--merges,或将列表提供给git rev-list --stdin --merges,以生成缩小的列表)。如果此列表非空,请通过查找其来检查每个合并--first-parentID 并确保结果位于第一个列表中来检查每个合并。
在实际(尽管未经测试)的 shell 脚本代码中:
\nTF=$(mktemp) || exit 1\ntrap "rm -f $TF" 0 1 2 3 15\ngit rev-list --ancestry-path $testcommit..$branch > $TF\ntest -s $TF || exit 1 # not ancestor\ngit rev-list --stdin --merges < $TF | while read hash; do\n parent1=$(git rev-parse ${hash}^1)\n grep "$parent1" $TF >/dev/null || exit 1 # on wrong path\ndone\nexit 0 # on correct path\nRun Code Online (Sandbox Code Playgroud)\n上面的测试尽可能少的提交,但从某种意义上说,只运行会更实用:
\ngit rev-list --first-parent ${testcommit}^@..$branch\nRun Code Online (Sandbox Code Playgroud)\n如果输出包含$testcommit其自身,则只能通过第一个父级从$testcommit到达。(我们用来排除 的所有父级,这样即使对于根提交也有效;对于其他提交,因为我们使用的是 ,所以就足够了。)此外,如果我们确保这是按拓扑顺序完成的,则从根提交发出的最后一个提交 ID当且仅当可从 到达时,命令将为自身。因此:branch^@$testcommit${testcommit}^--first-parentgit rev-list$testcommit$testcommit$branch
hash=$(git rev-parse "$testcommit") || exit 1\nt=$(git rev-list --first-parent --topo-order $branch --not ${hash}^@ | tail -1)\ntest $hash = "$t"\nRun Code Online (Sandbox Code Playgroud)\n应该可以解决问题。周围的报价$t是为了防止它扩展为空字符串。