考虑以下git历史记录:
* commit bfa39b2c6952295dbcfcf8b25667e1965401aed1
| Date: Wed Aug 24 15:50:14 2016 -0700
|
| Fix bug introduced by fb77497
|
* commit ed3d7d82e7c6e6771e0495e3588c5c3089664883
| Date: Wed Aug 24 15:50:09 2016 -0700
|
* commit fb77497e8d235d142fca2e18f74895e131b59978
| Date: Wed Aug 24 15:50:06 2016 -0700
|
| Massive refactor
|
* commit be56876ce707df48ca8df683d3f81a11244b9cef
Date: Wed Aug 24 15:50:01 2016 -0700
假设提交fb77497
引入了一个后来由bfa39b2
修复的错误。因此,fb77497
和ed3d7d8
都会受到此错误的影响,但bfa39b2
和be56876
不会。如果历史记录较长,您会说从fb77497
(包含)到bfa39b2
(不包括)的所有提交都会受到影响。
我可以使用什么命令来确定指定的提交是否在此范围内?如果命令直观地与多个分支一起工作也会很棒。
请注意,bisect命令不适用于此处 - 我已经知道错误的引入和修复位置。
答案 0 :(得分:3)
有很多方法可以做到这一点;哪一个使用,哪一个是最有效的",取决于其他项目。两种主要方法是使用--ancestry-path
,这是git rev-list
(因此也是git log
)和git merge-base --is-ancestor
的选项(自Git版本1.8起可用)。
所以:
git rev-list --ancestry-path A..B
将生成您想要的大部分内容,但它不包括commit A 并包含commit B (请参阅下面的角落案例部分)。这些都是完整的哈希值,因此您现在可以使用grep
直接匹配它们只要 grep为完整哈希值(您不希望缩写哈希值a123
匹配完整哈希98765a123999...
)。
首先,让我们注意这是一个图形问题。我们有一个DAG,例如:
o--o--o--o
/
o--o--o---o--o--o
\ / \
o--o o
/
o--o--o o
我们选择两个不同的节点 A 和 B 。如果我们选择来自断开连接的子图的节点,则从 A 到 B 的无路径:
o--o--o--o
/
o--o--o---o--B--o
\ / \
o--o o
/
A--o--o o
所以问题"是 A 和 B 之间某条路径上的任何其他节点"因为没有这样的道路,所以没有实际意义。即使没有断开连接的子图,我们仍然可以解决这个问题。如果我们选择A
和B
,请考虑会发生什么:
o--o--B--o
/
o--o--o---o--A--o
\ / \
o--o o
/
o
从A
到B
仍然没有路径,也没有从B
到A
的路径。但是,git rev-list A..B
或git rev-list B..A
将生成一个提交列表,特别是那些可从第二个提交ID但不能从第一个提交ID中获取的提交。例如,git rev-list A..B
将列出已加星标的提交:
*--*--B--o
/
o--o--o---o--A--o
\ / \
o--o o
/
o
加上提交B本身。
因此,我们必须选择 A 和 B ,以便 A 是 B 或 B 是 A 的祖先。 (使用 A != B 来约束它也是合理的,我们在说 distinct 节点时就这样做了。)失去一般性,我们可以假设 A 是 B 的祖先,只需交换 A 和 B 即使不是,如果合适的话:
o--o--o--o
/
o--B--o---o--A--o
\ / \
o--o o
/
o
这里我们简单地交换,以便 A 是左边的提交, B 是右边的那个(我假设历史从左到右移动)在这里,所有箭头指向这个特定的平面图中的向左,或向上和向左或向下和向左)。
现在, A 到 B 可能有多个路径,所以我们感兴趣的是是否任意选择节点 C 位于从 A 到 B 的任何祖先路径中:
o--o--o--o
/
o--A--*---*--B--o
\ / \
*--* o
/
o
如果我们为 C 选择一个现在已加星标的节点,答案是&#34;是的, C 是 B的祖先和 A &#34;的后代。如果我们为 C 选择任何其他节点,答案应为&#34; no&#34;: C 不是<的祖先em> B ( B 右侧的三个节点,或顶行的任何节点)或 C 不是 A的后代(任何底行节点,或 A 左侧的节点)。
使用git rev-list
:
git rev-list A..B # or: git log --format=%H A..B
我们将获得已加星标的提交,加上提交 B 本身(除非您使用--boundary
,否则将省略提交 A ,但添加--boundary
可以复杂的图形在所有边界提交中都会有点棘手,包括那些在不需要的路径上的提交,所以最好直接检查 A ,或将其添加到列表中。)
这里存在两个缺点:首先,如果 A B 的祖先,我们可能仍会得到一些提交!这是我们之前的示例,B
位于图表的顶行。但即使 A 是 B 的祖先,我们也可能太多提交:
o--o--o--o
/
o--o--A---*--B--o
\ / \
*--* o
/
o
这三个已加星标的提交,加上 B 本身就像往常一样git rev-list
(或git log
)将为简单的A..B
选择:提交可以从 B (包括 B 本身),无法从 A 访问。不幸的是,这包括我们不想要的两个提交。
一种解决方案是添加--ancestry-path
。 --ancestry-path
选项约束git rev-list
的输出以排除不是第一次提交的后代的提交。因此:
git rev-list --ancestry-path A..B
将只生成 B 本身以及以下一个星号提交:
o--o--o--o
/
o--o--A---*--B--o
\ / \
o--o o
/
o
当然,如果 A 本身不是 B 的祖先(或者只有 B <),它也不会产生任何效果。 em> A = B )。
git merge-base --is-ancestor
您还可以使用git merge-base --is-ancestor
一次测试一次提交。假设我们已经将 A 和 B 按正确顺序放置:
if git-merge-base is-ancestor $C $B && git merge-base is-ancestor $A $C; then
... $C is "between" $A and $B
fi
作为一个好的副作用,提交被认为是它自己的祖先,所以这匹配 C = A ,但你仍然需要测试 C = B 丢弃那个案子。
一直以来,我一直在假设提交 A 是引入错误的那个 B 是修复错误的那个 - 不完全相同你的榜样。您当然只是将一个节点从修复程序中移回到图形中 - 但是如果修复是由于合并而发生的,那么#34;将一个节点向后移动&#34;可能很棘手,因为可能有多个前任提交。例如,假设 B 是图中的错误修复,如下所示:
...--A--o--o--B--...
\ /
o----o
在这里,我们可能希望检查 A 之后的每个节点,包括两行中的节点,但B^
( B 的第一个父节点)可能在顶行或底行,将排除另一行。
(使用gitrevisions
语法有一个技巧,我没有彻底测试过:
git rev-list --ancestry-path ^A B^@
^@
后缀表示&#34; B 的所有父母,但不是 B 本身&#34;,^
前缀表示&#34;使用 A 作为排除ID&#34;,--ancestry-path
似乎使用&#34;所有排除ID;必须是后代&#34;约束。也值得尝试^A^@
来包含 A - 它似乎只是在我刚试过的一个太简单的测试中工作。)
使用--boundary
会将提交A
添加回由git rev-list
生成的列表(标有前导-
),但将--boundary
与{{{{1}组合在一起1}}无法正常工作。 --ancestry-path
标志告诉Git包含边界条件排除的提交,但有时这包括非祖先路径边界提交。所以它适用于提交--boundary
本身,但不适用于提交被剪切而不是非祖先路径。有点难以想出一个快速图表示例,而且这个示例未经过测试:
A
此处 o-----o
/ \
...--o--A--o--o--B
\ / \ /
o-o---o--o
尝试修剪底行的不需要的部分,但--ancestry-path
可能会放回其中一个不需要的提交。 (我已经看到了这一点,但不记得是什么引发了它,这就是为什么这可能是一个虚假的例子。)