特定分支上的git提交

3

我想查找特定分支的提交记录。我的树形结构如下:

feature/X     C--E--F
             /       \
master  -A--B--D---G--H--I--J->

如何获取提交记录 C、E 和 F? 我尝试过的方法是:
git rev-list feature/X ^master

但是这不会产生任何提交。我认为在这种特殊情况下,问题在于将 feature/X 回合并到主分支。这就是为什么提交 C、E 和 F 也可以从主分支访问,对吗?那么 - 如何处理这种情况?有任何想法吗?
谢谢 Thomas

git rev-list sha1_of_B..feature/X是什么意思? - rudimeier
很酷,谢谢 - 这个有效 - 但是如何自动识别B?为了弄清楚这一点,我必须检查哪些feature/X的提交可以在任何其他引用中访问,对吧?而且 - 据我所知,C、E和F可以使用master访问。 - user3592527
3个回答

1
这里有一行代码,有点hackish,但是能够工作:
git rev-list "$(git rev-list feature/X..master | tail -n 1)^"..feature/X

谢谢 - 看起来这是我的解决方案。只是为了我自己的理解:sha后面的^具体是什么意思? - user3592527
@user3592527 sha^ 表示 "sha 的第一个父节点"。 - qzb
@rudimeier 我已经改成了你的版本的答案。 - qzb

1
一般来说,一旦合并完成,提交来自哪个分支的详细信息就会丢失。您只知道这些提交是由当前分支覆盖的。 但是,我可以找到一种方式。首先,您需要找到合并发生的位置。可以使用“git log --merges -1”查找最接近主分支(在您的情况下为“H”)的合并。我假设“featureX”分支位于此之后的“F”。此提交具有2个父级。由于“featureX”已合并到主分支中,因此可以使用“H^”获取目标分支父级。 然后,您可以通过以下方式找到“H^”和“F”之间的差异:“git log H^..featureX”,这应该会给出所有可从“featureX”访问的提交,并省略了可从“H^”访问的提交,例如“C”,“E”和“F”。
作为一个例子,这里有一个存储库。 example 如果做得正确,我应该得到所有的“更新X”提交。
首先,我们获取合并提交。
% git log --merges -1 --oneline
e8928b9 Merge branch 'X'

然后我们得到了所讨论的日志。在我的repo中,这个特性分支被称为X
% git log e8928\^..X --oneline
92a1f58 Updates x 10
f56306d Updates x 9
54d2253 Updates x 8
a8ba58b Updates x 7
10d08c5 Updates x 6
625d267 Updates x 5
96671d4 Updates x 4
5031498 Updates x 3
41770ea Updates x 2
442033b Updates x 1

这种方法充其量只是一种权宜之计。我非常希望能找到一个真正的解决方案。


1
这可能有助于重新绘制这个图形:

feature/X     C--E--F
             /       \
master  -A--B--D---G--H--I--J->

as this:

        C--E--F           <-- feature/X
       /       \
<--A--B--D---G--H--I--J   <-- master

原因在于箭头确实指向后面,feature/X指向分支feature/X的尖端提交F,而master指向master分支的尖端提交(我假设这里是J,但也许根据您的原始图可能有更多)。
正如您所指出的,feature/X ^master(也可以拼写为master..feature/X)失败是因为从master开始,通过向后工作,可以到达提交F。当我们到达提交H时,同时向后遍历两个父提交,因此请求消除所有可从master到达的提交也消除了C--E--F序列。
为了防止这种情况发生,我们必须从某个点开始消除提交,即在将feature/X的末端合并到master之前的第一次合并之前的点。任何一个提交GDB都可以。也就是说,如果我们有任何一个这些提交的哈希值,那么:
git rev-list feature/X ^$hash

qzb's method 找到提交 D,然后使用后缀 ^ 来标识它的第一个且唯一的父提交。它的工作原理是列出从 Jmaster 的末尾)可达但从 Ffeature/X 的末尾)不可达的每个提交。但需要注意的是:git rev-list 可能会对提交进行排序,因此 D 实际上可能并不是最后一个被列出的提交,但是 | tail -1 假设列表以提交 D 的哈希值结尾。

因此,这取决于提交中存储的时间戳。如果它们按顺序进行(因此,随着提交向前移动,日期都会增加),那么这就不是问题。通常情况下确实如此。但有时您可能会按“错误”的日期顺序添加提交,这是由于时钟设置不正确或在不同计算机上执行提交时存在时间差异等原因造成的。

我们可以通过告诉git rev-list使用--topo-order来修复日期假设,这将强制它按照图形顺序(使用图形拓扑的偏序)列出提交。因此,在使用此方法时,请添加--topo-orderNoufal Ibrahim的方法是通过使用git log查找提交H来实现的。最好使用git rev-list,它与git log使用相同的选项,但只打印哈希值(这就是我们想要的)。
H=$(git rev-list --merges -1 master)
# H stands for Hash, and also for "commit H" :-)

(请注意,我们必须为图形遍历指定起点,而git log默认从HEAD开始)。仅获取提交H的哈希值是不够的,因为我们必须向后爬一个父级。由于H两个父级,我们必须小心地从H爬到G(而不是F)。
幸运的是,每当我们使用git merge合并时,Git都会确保新合并提交的第一个父级是当前分支上的提交。也就是说,当我们通过运行git merge feature/X进行提交H时,我们在分支master上,名称master表示提交G。因此,H第一个父级是G,因此$H^1$H^仅标识提交G
H=$(git rev-list --merges -1 master)
git rev-list feature/X ^${H}^

花括号 H 周围的内容在技术上并不是必需的,只是为了清晰起见:我们展开 $H,然后在展开后面放置 ^(以标识提交 G),再在展开前面放置另一个 ^(告诉 git rev-list 我们将其用作排除限定符)。

由于 $yes ^$no 可以写成 $no..$yes,因此我们也可以这样写:

H=$(git rev-list --merges -1 master)
git rev-list ^${H}^..feature/X

这种方法更有效率(我们只枚举了一个提交 H,而不是使用 tail -1 获取可能很长的链的最后一个提交),并且不会受到日期顺序问题的影响(但我们在上面看到如何使用 --topo-order 来解决这些问题)。
顺便提一下,在找到提交 H 时,也应该像前面一样使用 --topo-order,原因相同:我们不希望 Git 对某些其他合并(例如在 A 之前)进行排序并放在 H 前面。

剩余的缺陷

qzb 指出其中一个:虽然 feature/X 指向提交 F,但如果过去有更多合并,则我们不一定“知道何时停止”。也就是说:
          o--o---o--o--o     <-- feature
         /    \ /       \
...--o--o--o---o--o--o---o   <-- master

通过以这种特定的方式绘制此特定图表,我们清楚地看到“顶部行”上的所有提交都是在feature上完成的,而feature被合并了两次,而master被合并回了一次feature。(顺便说一句,这种“交叉合并”可能会让你陷入麻烦。它不是错误的,但通常情况下,您应该小心将A合并到B中,然后将B合并回A。在某些情况下,这会产生多个合并基础,这可能会很棘手。)但对于Git来说并不清晰,并且有其他的绘制图表的方式也会模糊我们的视线。(此外,如果您允许“快进”合并(而不是非快进的实际合并提交),那么解开分支历史就变得不可能了。再次强调,这不是错误的,您只需要准备好处理它。)

如果在master分支的提交H之后进行合并,那么这两种方法都会出现一个更重要的问题。也就是说,假设我们到目前为止所画的字母图还有一点误导性,并且实际上应该像这样:

        C--E--F           <-- feature/X
       /       \
<--A--B--D---G--H--I--J   <-- master
                  /
         <-o--o--o        <-- feature/Y

Now if we do:

H=$(git rev-list --topo-order --merges -1 master)

我们最终将把$H指向提交I,而不是提交H。原因很简单:我们要求从master开始向后工作,找到最近(拓扑上)的合并提交。这就是提交I。但I^是提交H,排除H将使随后的git rev-list排除提交C-E-F
这似乎注定了这种方法要失败;我们能否回到查找提交D的位置?不行,因为qzb的技巧:
$(git rev-list feature/X..master | tail -n 1)

当Git沿着I的第二个父节点,即feature/Y,并开始列出所有这些提交时停止工作。如果没有使用--topo-order,我们会得到最旧的提交。使用--topo-order仍然无法告诉我们哪个链(I^1 vs I^2)首先处理。如果该链在提交A或更早的提交处连接回来,则可能得到提交A-或-更早提交的哈希值,而不是提交D的哈希值。

我们可以通过注意到带来feature/Y的附加合并I,并排除feature/Y,以便Git不会沿着该链路竞争来解决这个问题。但这开始变得复杂。那么我们真正需要的不是最近的合并,而是带入提交F的合并(即,“找到提交H”)。有没有办法做到这一点呢?事实证明确实有。我们想要的是--ancestry-path

--ancestry-path选项剥离掉不是被排除提交的后代的提交。由于feature/X合并到了master中,我们可以确定,在F之后,有些提交(当然是H)是F的后代——即,F是其中一个父提交——也是master的祖先。所以:

git rev-list --ancestry-path --topo-order ^feature/X master

该命令告诉Git列出提交HIJ没有其他提交。也就是说,我们不会沿着合并I带来的其他提交飞奔而去:这些提交将被修剪。
如果我们然后丢弃除最后一次提交以外的所有提交(再次使用tail -1),并选择使用--merges来在使用tail之前丢弃任何非合并提交,那么即使IJ是一个合并提交,这将让我们定位到提交H
H=$(git rev-list --ancestry-path --topo-order \
    --merges ^feature/X master | tail -1)
git rev-list ^$H^..feature/X

这是两种方法的混合体:我们使用 --ancestry-pathH 开始查找提交,使用 tail -1 去掉除提交 H 以外的所有提交,然后使用 ^$H^ 排除提交 G 及之前的提交。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接