[英]How to find whether a given commit is on the first-parent chain of a branch
我正在嘗試編寫一種方法來判斷給定的提交是否在給定分支的第一父鏈上。 因此,例如,merge-base 不會飛,因為提交可能已合並。我想知道確切的提交是否曾經是分支的尖端。
注意:有問題的分支受制於非快進合並策略。
no-fast-forward 策略意味着您可能可以在git log --first-parent
grep 。 您可能只需要哈希值,因此您可以使用git rev-list
代替
git rev-list --first-parent | grep <commit hash>
否則使用--format
和git log
來顯示你想要的數據。
編輯:這篇文章可以給你一些想法
一個簡單的“是祖先”測試顯然是行不通的,因為提交到第二個或更高版本的父鏈也是祖先:
...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
模式,並且確實需要一些額外的處理。
需要注意的是,無論之間的路徑的T
和一些祖先,的合並基礎T
和祖先(如A
或B
)要么是祖先本身( A
或B
分別這里),或祖先的一些祖先,如如果我們將T
和C
視為一對,則提交*
。 (即使在多個合並基礎的情況下也是如此,盡管我將構建證明留給您。)
如果測試提交和分支提示的合並基礎或所有任意選擇的一個集合還不是測試提交,我們有一個像C
這樣的案例,可以立即拒絕它。 (或者,我們可以使用--is-ancestor
來拒絕它,或者......好吧,見下文。)如果不是,我們必須在有問題的提交和分支提示之間的祖先路徑中枚舉提交。 對於A
這是:
o--o--*--T
對於 B,這是:
*--T
/
o
如果任何此類提交是合並提交,就像標記為*
提交一樣,我們需要確保第一個父項包含沿此路徑列出的提交之一。 最困難的情況是拓撲類似的情況:
o--o
/ \
...--A o--T
\ /
o--o
因為這些之間的--ancestry-path
包括合並和到達A
兩種方式,其中一種是第一父路徑,另一種不是。 (如果T
本身也是一個合並也是如此。)
不過,我們實際上並不需要首先找到合並基礎。 我們僅使用合並基礎來檢查祖先路徑。 如果合並基礎不是測試提交本身,則測試提交不是提示提交的祖先,並且testcommit..tipcommit
將不包括testcommit
本身。 此外,添加--ancestry-path
它會丟棄所有不是左手邊自己孩子的提交——然后將丟棄git rev-list
輸出中的所有提交:像C
這樣的情況沒有T
祖先的后代(如果確實如此, C
將成為合並基礎)。
因此,我們想要的是檢查git rev-list --ancestry-path testcommit..branchtip
。 如果此列表為空,則測試提交首先不是分支提示的祖先。 我們有一個像 commit C
這樣的案例; 所以我們有了答案。 如果列表非空,將其減少到它的合並組件(使用--merges
再次運行,或將列表提供給git rev-list --stdin --merges
,以生成縮小的列表)。 如果此列表非空,請通過查找其--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
上面的測試盡可能少提交,但從某種意義上說,只運行會更實用:
git rev-list --first-parent ${testcommit}^@..$branch
如果輸出包含$testcommit
本身,則$testcommit
可從branch
訪問,僅可由第一個父級訪問。 (我們使用^@
排除$testcommit
所有父項,這樣即使對於根提交也有效;對於其他提交, ${testcommit}^
就足夠了,因為我們使用的是--first-parent
。)此外,如果我們確保這是按拓撲順序完成的,當且僅當可以從$branch
訪問$testcommit
從git rev-list
命令發出的最后一個提交 ID 將是$testcommit
本身。 因此:
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
測量兩次提交之間的最短路徑,然后在第一父鏈中的這個位置獲取提交。 顯然,如果檢查的提交是第一父祖先,則這些必須相同。 被抑制的錯誤(例如第一父鏈太短)是無關緊要的。
為了使它更復雜,您可以創建一個由可讀性很強的 shell 腳本支持的 git 別名。
首先編寫腳本文件:
#!/bin/sh
ref="$1"
head="$2"
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.