当只有一个否定提交时,git的git log
或git rev-list
的--ancestry-path
选项的行为相对简单,也就是说,如果您有:
git rev-list ^A B
# or it's shorthand form:
git rev-list A..B
那么结果是所有的提交都是A的后代和B的祖先(要获得更全面的解释,请参见此处(。
然而,我想知道如果你有两个(或更多(否定的修订,--ancestry-path
的表现如何——即:
git rev-list ^C ^A B
(注意-我将把未否定的修订,如上面的B
,称为"积极"提交,所有否定的修订称为"消极"提交。(
在检查了一些简单测试案例中的结果之后;后代";--ancestry-path
对负提交施加的限制似乎是OR’d/union’ed在一起的——也就是说,git似乎在做:
- 首先,查找至少一个正提交的祖先的所有提交
- 然后,筛选到仅是至少一个负提交的子提交
- 然后,筛选出至少一个负提交的祖先提交
或者,在更像集合的符号中:
IntersectionOf(
Union(ancestors(positive1), ...ancestors(positiveN))
AND
Union(descendents(negative1), ...descendents(negativeN))
)
- Union(ancestors(negative1), ...ancestors(negativeN))
然而,在一些更复杂的场景中,这被证明是不正确的——最终结果中排除的提交比上述逻辑所暗示的要多。
例如,假设您有一个完整的图:
ancestors(MA3)
-----MA3
/ |
/ MB3
A3 /|
/| B3 |
/ | /| |
R3-+--- | |
| | | |
| | --+--MA2
| | / | |
| |/ | MB2
| A2 | /|
| /| B2 |
|/ | /| |
R2-+--- | |
| | | |
| | --+--M1
| | / | /
| A1 | /
| / B1
|/ /
R1-----
这表示一个公共根分支(R(,特征分支a和B都从中继承,a和B依次合并为一个公共合并分支(M(。(这里有一个bash脚本,可以用这种结构建立回购!(
我们希望找到:
--ancestry-path ^R2 ^B3 MA3
如果我们做
Union(descendents(R2), Union(ancestors(R2),
descendents(B3)) ancestors(B3))
-----MA3
/ |
/ MB3
A3 /|
/| B3 | B3
/ | /| | /|
R3-+--- | | R3----- |
| | | | |
| --+--MA2 | |
| / | | | |
|/ | MB2 MINUS | |
A2 | / | |
B2 | B2
| /|
R2----- |
| |
| |
| |
| |
| B1
| /
R1-----
那么我们期待:
-----MA3
/ |
/ MB3
A3 |
| |
| |
| |
| |
| -----MA2
| / |
|/ MB2
A2
然而,我们从中实际得到的
git rev-list --ancestry-path ^R2 ^B3 MA3 | xargs -i git tag --points-at '{}'
是:
MA3
MB3
A3
MA2
A2
即:
-----MA3
/ |
/ MB3
A3 |
| |
| |
| |
| |
| -----MA2
| /
|/
A2
除了MB2被排除在外是相同的。
所以,很明显,我对--ancestry-path
如何处理多个^
否定的猜测是错误的。有人能解释git实际使用的逻辑吗?为什么MB2被排除在外?
编辑所以,只是为了直观地澄清下面jthill的答案(https://stackoverflow.com/a/65450624/920545),他说它是这样工作的:
- 首先,git计算
ancestors(MA3) - Union(ancestors(R2), ancestors(B3)
,因此我们得出:
ancestors(MA3) - Union(ancestors(R2),
ancestors(B3))
-----MA3
/ |
/ MB3
A3 |
| |
| |
| |
| |
| -----MA2
| / |
|/ MB2
A2 |
| |
| |
+ |
| |
| -----M1
| /
A1
然后,它尝试使用这个有限的节点集来查找从R2
或B3
到MA3
的路径上的所有节点。因此,它试图在这个图上找到从(x(到[y]的所有路径:
----[MA3]
/ |
/ MB3
A3 /|
| (B3) |
| |
| |
| |
| -----MA2
| / |
|/ MB2
A2 |
/| |
/ | |
(R2) | |
| |
| -----M1
| /
A1
这就是为什么我们最终得到了我上面展示的结果。谢谢你jthill!
MB2
位于R2
的祖先路径上,正如其名称所示,但当您将B3
的祖先排除在行走之外时,该祖先路径超出了您设置的界限。
Git正在执行您的要求:取MA3
及其所有祖先,排除B3
和R2
及其所有祖先,然后尝试追踪被排除提示的祖先路径。
得到的效果通常会产生与所有后代减去所有祖先追踪相同的结果,但你已经发现了差异很重要的情况。Git追溯祖先,而不是血统。