如何查找给定提交是否在分支的第一父链上
How to find whether a given commit is on the first-parent chain of a branch
我正在尝试编写一种方法来判断给定提交是否在给定分支的第一父链上。因此,例如,merge-base 不会运行,因为提交可能已被合并。我想知道确切的提交是否曾经是分支的尖端。
注意:相关分支采用非快进合并策略。
非快进策略意味着您可以在 git log --first-parent
中进行 grep。您可能只需要哈希值,因此可以使用 git rev-list
而不是
git rev-list --first-parent | grep <commit hash>
否则使用--format
和git log
来显示你想要的数据。
编辑:这个 post 可以给你一些想法
How can I tell if one commit is an ancestor of another commit (or vice-versa)?
花哨的方式
一个简单的“是祖先”测试显然是行不通的,因为在第二个或更高版本的父链下的提交也是祖先:
...o--o--A--o--o--o--T
\ /
...-o--*--B----o
\
C
A
和B
都是T
的祖先,但是您想接受A
而拒绝B
和C
。 (假设 --first-parent
是这里的第一行。)
使用git merge-base
,然而,将实际完成部分工作。不过,您不需要 git merge-base
--is-ancestor
mode,并且 do 需要一些额外的处理。
请注意,无论 T
和某个祖先之间的路径如何, 合并基础 T
和那个祖先(例如 A
或 B
) 要么是祖先本身(此处分别为 A
或 B
),要么是祖先的某些祖先,例如提交 *
如果我们查看 T
和 C
作为一对。 (即使在多个合并基础的情况下也是如此,尽管我将构建证明留给你。)
如果测试提交的合并基或任意选择的所有集合之一,并且分支提示还不是测试提交,我们有这样的情况 C
并且可以立即拒绝它。 (或者,我们可以使用 --is-ancestor
来拒绝它,或者......好吧,见下文。)如果不是,我们必须枚举提交之间 ancestry path 中的提交有问题和分支提示。 A
是:
o--o--*--T
B 是:
*--T
/
o
如果任何此类提交是 merge 提交,标记为 *
的提交也是如此,我们需要确保第一个父级包含其中一个提交( s) 沿此路径列出。最棘手的情况是那些在拓扑上类似于:
o--o
/ \
...--A o--T
\ /
o--o
因为它们之间的 --ancestry-path
包括合并和 两种 方式到达 A
,其中一种是第一父路径,另一种是这不是。 (如果 T
本身也是一个合并也是如此。)
不过,我们实际上并不需要首先找到合并基地。我们仅使用合并基础来检查祖先路径。如果合并基础不是测试提交本身,那么测试提交不是提示提交的祖先,并且 testcommit..tipcommit
将不包括 testcommit
本身。此外,添加 --ancestry-path
——丢弃所有本身不是左侧子项的提交——将丢弃 all 输出中的 git rev-list
提交:像 C
这样的情况没有 T
的祖先的后代(如果有,C
将是合并基础)。
因此,我们想要检查 git rev-list --ancestry-path testcommit..branchtip
中的提交。如果此列表为空,则测试提交首先不是分支提示的祖先。我们有一个像 commit C
; 这样的案例。所以我们有了答案。如果列表是非空的,将其减少到它的合并组件(运行 再次使用 --merges
,或者将列表提供给 git rev-list --stdin --merges
,以生成 sh运行ken 列表).如果此列表 非 -空,请通过查找其 --first-parent
ID 并确保结果在第一个列表中来检查每个合并。
实际(尽管未经测试)shell-脚本代码:
TF=$(mktemp) || exit 1
trap "rm -f $TF" 0 1 2 3 15
git rev-list --ancestry-path $testcommit..$branch > $TF
test -s $TF || exit 1 # not ancestor
git rev-list --stdin --merges < $TF | while read hash; do
parent1=$(git rev-parse ${hash}^1)
grep "$parent1" $TF >/dev/null || exit 1 # on wrong path
done
exit 0 # on correct path
暴力方式
上面的测试尽可能少 commits,但从某种意义上说,只是 运行:
会更实用
git rev-list --first-parent ${testcommit}^@..$branch
如果输出包含 $testcommit
本身,那么 $testcommit
是可达的,仅由第一个父级访问, 来自 branch
。 (我们使用 ^@
来排除 all 父 $testcommit
以便这甚至适用于根提交;对于其他提交,${testcommit}^
就足够了,因为我们正在使用 --first-parent
。)此外,如果我们确保这是按拓扑顺序完成的,则 git rev-list
命令发出的 last 提交 ID 将是 $testcommit
本身当且仅当 $testcommit
可以从 $branch
到达。因此:
hash=$(git rev-parse "$testcommit") || exit 1
t=$(git rev-list --first-parent --topo-order $branch --not ${hash}^@ | tail -1)
test $hash = "$t"
应该可以解决问题。 $t
周围的引号是为了防止它扩展为空字符串。
这是一种性能友好的单线:
git rev-parse HEAD~"$( git rev-list --count --first-parent --ancestry-path <commit>..HEAD )"
如果输出是你的<commit>
,那么它就是第一亲祖先。
我们的想法是,我们用rev-list --count --ancestry-path
测量两个提交之间的最短路径,然后在第一父链中的这个位置获取提交。显然,如果检查的提交是第一父祖先,则这些必须相同。抑制的错误(例如第一父链太短)无关紧要。
为了让它更复杂,您可以创建一个 git 别名,该别名由一个非常易读的 shell 脚本支持。
先写脚本文件:
#!/bin/sh
ref=""
head=""
if [ -z "$head" ]; then
head="HEAD"
fi
commit=$( git rev-parse "$ref"^{commit} )
distance="$( git rev-list --count --ancestry-path --first-parent "$commit".."$head" )"
found="$( git rev-parse HEAD~"$distance" )"
if [ "$commit" != "$found" ]; then
echo "${ref} is not a first-parent ancestor of ${head}"
exit 1
fi
echo "${ref} is a first-parent ancestor of ${head} at a distance of ${distance}"
exit 0
将其保存到系统上的适当位置,使其可执行,然后将其设置为 git 别名:
git config --global alias.fp '!<script-path>'
将 fp
替换为您觉得更舒服的任何内容。将 <script-path>
替换为您的脚本文件的位置,但保留 !
字符,有必要使用外部文件。
在此之后,您可以像正常 git 命令一样使用新别名:
$ git fp 66e339c
66e339c is a first-parent ancestor of HEAD at a distance of 45
我正在尝试编写一种方法来判断给定提交是否在给定分支的第一父链上。因此,例如,merge-base 不会运行,因为提交可能已被合并。我想知道确切的提交是否曾经是分支的尖端。
注意:相关分支采用非快进合并策略。
非快进策略意味着您可以在 git log --first-parent
中进行 grep。您可能只需要哈希值,因此可以使用 git rev-list
而不是
git rev-list --first-parent | grep <commit hash>
否则使用--format
和git log
来显示你想要的数据。
编辑:这个 post 可以给你一些想法
How can I tell if one commit is an ancestor of another commit (or vice-versa)?
花哨的方式
一个简单的“是祖先”测试显然是行不通的,因为在第二个或更高版本的父链下的提交也是祖先:
...o--o--A--o--o--o--T
\ /
...-o--*--B----o
\
C
A
和B
都是T
的祖先,但是您想接受A
而拒绝B
和C
。 (假设 --first-parent
是这里的第一行。)
使用git merge-base
,然而,将实际完成部分工作。不过,您不需要 git merge-base
--is-ancestor
mode,并且 do 需要一些额外的处理。
请注意,无论 T
和某个祖先之间的路径如何, 合并基础 T
和那个祖先(例如 A
或 B
) 要么是祖先本身(此处分别为 A
或 B
),要么是祖先的某些祖先,例如提交 *
如果我们查看 T
和 C
作为一对。 (即使在多个合并基础的情况下也是如此,尽管我将构建证明留给你。)
如果测试提交的合并基或任意选择的所有集合之一,并且分支提示还不是测试提交,我们有这样的情况 C
并且可以立即拒绝它。 (或者,我们可以使用 --is-ancestor
来拒绝它,或者......好吧,见下文。)如果不是,我们必须枚举提交之间 ancestry path 中的提交有问题和分支提示。 A
是:
o--o--*--T
B 是:
*--T
/
o
如果任何此类提交是 merge 提交,标记为 *
的提交也是如此,我们需要确保第一个父级包含其中一个提交( s) 沿此路径列出。最棘手的情况是那些在拓扑上类似于:
o--o
/ \
...--A o--T
\ /
o--o
因为它们之间的 --ancestry-path
包括合并和 两种 方式到达 A
,其中一种是第一父路径,另一种是这不是。 (如果 T
本身也是一个合并也是如此。)
不过,我们实际上并不需要首先找到合并基地。我们仅使用合并基础来检查祖先路径。如果合并基础不是测试提交本身,那么测试提交不是提示提交的祖先,并且 testcommit..tipcommit
将不包括 testcommit
本身。此外,添加 --ancestry-path
——丢弃所有本身不是左侧子项的提交——将丢弃 all 输出中的 git rev-list
提交:像 C
这样的情况没有 T
的祖先的后代(如果有,C
将是合并基础)。
因此,我们想要检查 git rev-list --ancestry-path testcommit..branchtip
中的提交。如果此列表为空,则测试提交首先不是分支提示的祖先。我们有一个像 commit C
; 这样的案例。所以我们有了答案。如果列表是非空的,将其减少到它的合并组件(运行 再次使用 --merges
,或者将列表提供给 git rev-list --stdin --merges
,以生成 sh运行ken 列表).如果此列表 非 -空,请通过查找其 --first-parent
ID 并确保结果在第一个列表中来检查每个合并。
实际(尽管未经测试)shell-脚本代码:
TF=$(mktemp) || exit 1
trap "rm -f $TF" 0 1 2 3 15
git rev-list --ancestry-path $testcommit..$branch > $TF
test -s $TF || exit 1 # not ancestor
git rev-list --stdin --merges < $TF | while read hash; do
parent1=$(git rev-parse ${hash}^1)
grep "$parent1" $TF >/dev/null || exit 1 # on wrong path
done
exit 0 # on correct path
暴力方式
上面的测试尽可能少 commits,但从某种意义上说,只是 运行:
会更实用git rev-list --first-parent ${testcommit}^@..$branch
如果输出包含 $testcommit
本身,那么 $testcommit
是可达的,仅由第一个父级访问, 来自 branch
。 (我们使用 ^@
来排除 all 父 $testcommit
以便这甚至适用于根提交;对于其他提交,${testcommit}^
就足够了,因为我们正在使用 --first-parent
。)此外,如果我们确保这是按拓扑顺序完成的,则 git rev-list
命令发出的 last 提交 ID 将是 $testcommit
本身当且仅当 $testcommit
可以从 $branch
到达。因此:
hash=$(git rev-parse "$testcommit") || exit 1
t=$(git rev-list --first-parent --topo-order $branch --not ${hash}^@ | tail -1)
test $hash = "$t"
应该可以解决问题。 $t
周围的引号是为了防止它扩展为空字符串。
这是一种性能友好的单线:
git rev-parse HEAD~"$( git rev-list --count --first-parent --ancestry-path <commit>..HEAD )"
如果输出是你的<commit>
,那么它就是第一亲祖先。
我们的想法是,我们用rev-list --count --ancestry-path
测量两个提交之间的最短路径,然后在第一父链中的这个位置获取提交。显然,如果检查的提交是第一父祖先,则这些必须相同。抑制的错误(例如第一父链太短)无关紧要。
为了让它更复杂,您可以创建一个 git 别名,该别名由一个非常易读的 shell 脚本支持。
先写脚本文件:
#!/bin/sh
ref=""
head=""
if [ -z "$head" ]; then
head="HEAD"
fi
commit=$( git rev-parse "$ref"^{commit} )
distance="$( git rev-list --count --ancestry-path --first-parent "$commit".."$head" )"
found="$( git rev-parse HEAD~"$distance" )"
if [ "$commit" != "$found" ]; then
echo "${ref} is not a first-parent ancestor of ${head}"
exit 1
fi
echo "${ref} is a first-parent ancestor of ${head} at a distance of ${distance}"
exit 0
将其保存到系统上的适当位置,使其可执行,然后将其设置为 git 别名:
git config --global alias.fp '!<script-path>'
将 fp
替换为您觉得更舒服的任何内容。将 <script-path>
替换为您的脚本文件的位置,但保留 !
字符,有必要使用外部文件。
在此之后,您可以像正常 git 命令一样使用新别名:
$ git fp 66e339c
66e339c is a first-parent ancestor of HEAD at a distance of 45