:
A
T / \
i B C
m : :
e D E
\ /
| F
V :
git merge-base B E
允许查找两个提交的共同祖先A
的位置。有没有办法找到再次合并两个分支的提交F
?
答案 0 :(得分:1)
糟糕。没有仔细阅读。
提交中的唯一信息是其父(或父项)的id。您无法从父提交中获取子项(这是存储库的定向部分是DAG)。
再看一下 - 看起来git log的--ancestry-path
选项可以做到这一点。例如:
* 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。
答案 1 :(得分:1)
此问题不一定有唯一的答案,因此您必须决定一些约束和/或启发式方法,或者接受不止一个“下游”合并的可能性。问题的核心与多个合并基础候选者的问题相同-使用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
或 branch2
的提交,也属于提交B
或的后代E
。
要真正获得正确的答案,我们要添加--children
。这样,我们将获得每个提交的哈希ID,以及沿着相同方向提交的子代。 Git在遍历链接时通过反转从孩子到父母的反向连接来实现--children
,这已经足够了;但我们看不到B
或E
的提交。这是一个问题。要显示它们,我们可以添加--boundary
。这是不理想的:--boundary
有时包含一些我们不希望的提交。幸运的是,它们都标记有-
,因此我们可以通过剔除不是我们关心的提交来排除额外的边界提交。
我将不显示任何内容,但是如果您这样做了,您现在将获得一个列表,每行一个条目,每个节点(顶点)及其边缘连接到其子节点。您现在可以问这些(V,E)集形成的DAG的LCA是什么?
如果我们只使用Git的LCA算法,那就太好了,但是Git没有办法在任意图上调用它-我们只能在提交上调用它,而实际的提交有父级,而不是子级。因此,您将必须自己编写。请参见Algorithm to find lowest common ancestor in directed acyclic graph?(很遗憾,该答案没有可接受的答案)。 This algorithm looks correct at first blush;它在图中具有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
和G
来命名提交B和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
如果我现在运行git rev-list --topo-order --merges --ancestry-path ^B ^H master
,我将列举提交P
,然后依次列出G
和M
。因此, last 行要么是提交G
要么是提交M
,并且当这两个是合并时,它们并没有达到正确的准则:它们不合并B
和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
答案 2 :(得分:0)
从this answer调整all.awk
以同时携带每个参考号的行号,然后当您遇到父母时,请查看他们共同参考的参考号。