:
A
T / \
i B C
m : :
e D E
\ /
| F
V :
git merge-base B E
允许找到A
两个提交的共同祖先在哪里。有没有办法找到F
两个分支再次合并的提交?
:
A
T / \
i B C
m : :
e D E
\ /
| F
V :
git merge-base B E
允许找到A
两个提交的共同祖先在哪里。有没有办法找到F
两个分支再次合并的提交?
这个问题不一定有唯一的答案,因此您必须决定一些约束和/或启发式方法,或者接受多个“下游”合并的可能性。问题的核心与多个合并基候选的问题相同——使用git merge-base --all
将它们全部列出,否则 Git 只会选择算法中最先弹出的那个。我们可以这样做,或者找到所有最好的合并候选者。
您已经绘制了我通常喜欢横向渲染的内容,例如:
B--...--D
/ \
A F--G--H <-- branch1
\ /
C--...--E <-- branch2
但我们可能有这个:
B--C---D--E--... <-- branch1
/ \ /
A X
\ / \
F--G---H--I--... <-- branch2
在这种情况下,如果您同时允许并考虑两者,则两者都合并D
并且H
同样是“分支重新合并的地方”的良好候选者。即使您不这样做,如果稍后合并回:branch1
branch2
branch2
branch1
B--C---D--E---J--... <-- branch1
/ \ / /
A X /
\ / \ /
F--G---H--I--... <-- branch2
然后只是从(或结束于)开始,branch1
两者都是同样好的候选人。D
H
无论如何,我们在这里需要的是枚举以您要考虑的一个或所有分支结尾的提交。为此,我们可以使用,例如:
git rev-list --ancestry-path ^B ^E branch1 branch2
这会找到作为branch1
or branch2
的祖先的提交,并且也是 commitB
或commit 的后代E
。
要真正得到正确答案,我们要添加--children
. 这样,我们将获得每个提交的哈希 ID,以及该提交的子节点。Git通过在--children
遍历链接时反转从孩子到父母的反向连接来实现这一点,这已经足够了;但我们不会看到提交B
或E
. 这是一个问题。为了让它们显示出来,我们可以添加--boundary
. 这并不理想:--boundary
有时会包含一些我们不想要的提交。幸运的是,它们都被标记了,-
因此我们可以通过剔除那些不是我们关心的提交来排除额外的边界提交。
我不打算展示这些,但如果你这样做了,你现在将有一个列表,每行一个条目,每个节点(顶点)及其连接到其子节点的边。你现在可以问这些 (V,E) 集合形成的 DAG 的 LCA 是多少?
如果我们可以只使用 Git 的 LCA 算法就好了,但是 Git 没有办法在任意图上调用它——我们只能在提交时调用它,而实际的提交有父节点,而不是子节点。所以你必须自己写。请参阅在有向无环图中找到最低共同祖先的算法?(不幸的是,没有公认的答案)。 这个算法乍一看是正确的;它在图中具有 LCA 的两个标准定义之一。
但是,如果我们愿意接受一个不太好的答案,我们可以通过添加--topo-order
(以确保父母在所有孩子之后出来)和--merges
(省略所有不是合并提交)。这将获得所有合并的列表。
我在这里制作了一个带有简单案例的测试存储库:
$ git log --all --decorate --oneline --graph
* 91fcef6 (HEAD -> master) J
* d1e5905 I
* 5bf18a0 merge
|\
| * 49b2ba7 (sidebr) D
| * 725e5ea C
| * 36b830d (tag: B) B
* | 198a982 (tag: G) G
* | 216bc01 F
* | e905e59 E
|/
* 5df9428 initial
所以我现在可以使用B
and命名提交 B 和 G G
,而我想要的“朝这个方向移动”的分支就是master
. 所以:
$ git rev-list --topo-order --merges --ancestry-path ^B ^G master
5bf18a0797dfd78107928a9a4095f357cfabe914
这里的最后一行是与两个提交“最接近”的合并。在这种情况下,这也是唯一的行,这就是我们想要的合并。
一旦我们绘制它,这里的缺陷就足够清楚了。假设我有一个更复杂的图表,例如:
I--J
/ \
H M--N
/ \ / \
/ K--L \
/ \
A P--Q <-- master
\ /
\ C--D /
\ / \ /
B G--O
\ /
E--F
如果我现在 run git rev-list --topo-order --merges --ancestry-path ^B ^H master
,我将枚举 commit P
,然后以某种顺序同时G
枚举M
。所以最后一行要么是 commit 要么是 commit G
,M
虽然这两个都是合并,但它们不符合正确的标准:它们不合并B
and H
。只有提交P
才能做到这一点。
因此,要检查您是否有正确的答案(不处理多个 LCA 问题),您应该从该git rev-list
命令获取每个输出行,可能以相反的顺序(考虑添加--reverse
),并查看两个提交是否是每个提交的祖先。“内部”合并就像G
并且M
将只有一个提交作为祖先。要进行 is-ancestor 测试,请使用git merge-base --is-ancestor
:
if git merge-base --is-ancestor $commit1 $mergecommit &&
git merge-base --is-ancestor $commit2 $mergecommit; then
... we've found a correct candidate
else
... move on to another candidate
fi
哎呀。读得不够仔细。
提交中的唯一信息是其父(或父)的 id。您无法从父提交获取子提交(这是存储库的定向部分,即 DAG)。
再看看这个——看起来--ancestry-path
git log 的选项可以做到这一点。例如给出:
* 85d26ab When compiling vim, also compile & install gvim
* 3146e5d Merge remote-tracking branch 'origin/devel' into deve
|\
| * 28d08e5 rebasing-merge: specify all commits explicitly
* | 006d11d Help 'file' find its magic file
|/
* e68531d (tag: Git-1.7.6-preview20110720) Update submodules
我们可以使用这两个提交的所有孩子
git log --oneline --ancestry-path B..E
如果您然后将其反转并选择第一个-那就是F。
git rev-list --reverse --ancestry-path 28d08e5..006d11d | head -1
在我的情况下,返回 3146e5d。
Adapt all.awk
from this answer to also carry the line number for each ref, then when you've encountered both parents look at the refs they have in common.