Git:获取特定分支上两个提交之间的提交记录

9
我已经阅读了许多类似情况的答案,但没有这个精确的问题。
我需要生成一个特定分支上两个提交之间发生的提交列表。因此,其他分支上进行的任何提交都应该被忽略。有什么办法可以做到这一点吗?

1
如果您选择了一个合并提交和一个分支提交,您需要澄清您想要什么。另一方面,您是在寻找一些bash脚本来过滤git的输出,还是在寻找一些git命令? - orb
嗨@orb,谢谢!是的,我正在寻找一个bash命令。即使分支是在遥远的过去从主分支创建的,但非常不同,因此在我的情况下发生在其他分支上的任何提交都是无关紧要的。 - benjamin.keen
在你推送一个分支之后,指示它在分支中的提交签名会被移除,因此你(git)无法确定先前的提交是否在主分支中。它的分支活动仅由每个提交的父提交哈希信息记录。您可以通过命令 git rev-list --parents -n <要跟踪的提交数量> <提交哈希> 沿着父提交进行跟踪。 - orb
2个回答

12

“between”这个概念在这里有点模糊,“branch”这个术语也没有被很好地定义(通常与git有关)。尽管存在这些问题,我会尽量让解释简短一些(对我来说可能有点困难 :-) ):

我假设你所说的“between”,是指git提交图中的图论路径。例如,假设我们有以下图形片段(我无法为节点之间的弧线绘制箭头,但是假装每个-/\ 在左侧都有一个箭头,以使提交指向直线向左、向左下或向左上的前任):

              o - o
            /       \
... - A - o - o - o - B   <-- branch-x
            \   /
              o - o - C   <-- branch-y
我已确定了三个特定的提交-ABC,并提供了两个指向提交BC的分支顶点名称branch-xbranch-y。为了Git的实用性,假设有一个标签A指向提交A,这样我们就不必拼写它的SHA-1了。
从提交B到提交A有三条可能的路径。其中一条从B开始,向上走到顶线,再返回到中间,然后向左移动到A。另一条从B开始,直接向左走,最终到达A。最后一条从B开始,退回一步,向下向左,向上向左,再向左移动一次,才能到达A
Git给出了特殊的双点语法A..branch-x(如果没有名为A的标签,请替换节点A的实际SHA-1),对于大多数命令,它表示它们应该访问从BA的所有可能路径上的所有节点,通常包括节点B本身,但不包括节点A。这几乎是你想要的,但不完全是,因为你希望排除在其他分支上进行的提交。
这带来了一个无法回答的(通常情况下)问题:“哪些提交是在哪个分支上进行的?”Git试图告诉你这个问题是无效的:你不应该关心;你只需要关心所有这些提交是否可以从节点B到达。Git通常是正确的,但“通常”并不意味着“总是”。不幸的是,我没有找到任何好的方法来描述你应该(或实际上)关心的情况(实际的好例子对我正在尝试写的文本会有帮助)。
同时,让我们继续前进。从上面的图表中,似乎清楚地看出,所有的下行提交都是“在branch-y上进行的”。这里存在一个问题,因为它看起来很清楚,但它可能并不实际为真。考虑一下如果我们这样重新绘制图形会发生什么:
              o - o
            /       \
... - A - o - o - o - B   <-- branch-x
            \   /
              o
                \
                  o - C   <-- branch-y

这次似乎是创建了branch-y分支,只是为了容纳最下面的两个提交。如果有另一个分支名称指向单独的第三行提交,那么更有可能(尽管您原始问题陈述说要排除所有其他分支——不仅仅是分支branch-y——在这种情况下,这并不重要)。

无论如何,虽然我不太清楚您所说的“分支”是什么意思,也不确定您想要哪些提交,但让我们看一下git实际提供的选择器。有一个重要的选择器可能恰好是您所需的。

我之前提到过,“大多数”git命令都使用相同的语法规范。事实上,大多数git命令包含来自或简单运行git rev-list程序的代码,其工作是选择对象(通常是提交对象)以获取您想要处理的提交ID列表。它也是您想要进行任何类型脚本编写的命令。

rev-list命令有大量的选项,可以帮助各种图形遍历。我认为这里最有趣的两个选项是--first-parent--not

使用--first-parent

首先考虑--first-parent。查看上面的图(任何一种布局:它们可能看起来不同,但在拓扑上相同)。请注意,只有在合并提交(如节点B本身和左侧一步的节点)处,路径才会分叉。这是因为只有合并提交具有多个出站弧(实际上这是合并提交的定义:它是具有两个或多个父节点的节点)。

当git进行合并提交时,它对多个父对象中的每个对象定了编号。第一个弧是特殊的:它是提交时的当前分支。也就是说,当您执行git merge <sha-1-or-equivalent>时,您在某个分支上,在该时间点上当前提交的SHA-1成为新合并提交的“第一个父级”。其他父节点(合并的ID通常只有一个,但git允许更多)是第二个、第三个等等。

使用--first-parent标志告诉git仅遍历第一个父级弧。因此git rev-list --first-parent branch-x将从提交B开始,然后找到它的第一个父对象(我们无法从上面的图表中确定哪个是第一个),跟随该父对象的第一个(仅有的)父节点等等,一直回溯到根提交。

这可能不是你想要的(虽然它并没有帮助理解“之间”的概念)。

使用--not

现在让我们看看--not标志。2通常,git rev-list <SHA-1-ID-or-name>会生成从给定SHA-1可达的所有提交集合(根据需要先将名称解析为ID)。也就是说,它跟随所有路径回到所有根。结果是一组SHA-1 ID。使用--not会使rev-list 排除这些ID。单独使用,这个否定集是没有用处的,但与正常的(非否定的)集合结合使用时,它是有用的。事实上,这就是A..B起作用的方式:首先rev-list生成从B可达的所有提交集,然后减去从A可达的所有提交集。

因此,取决于您对“排除其他分支上的所有提交”是什么意思,您可能需要的是:

git rev-list branch1 --not branch2 branch3 ... branchN

--not 后面列出除了 branch1 之外的每个分支。

如果我们最后再看一遍我们的图示,让我们看看哪些提交被 branch-x --not branch-y 选中:

              o - o
            /       \
... - A - o - o - o - B   <-- branch-x
            \   /
              o - o - C   <-- branch-y

显然,提交C可以从branch-y到达,包括最底部行的所有提交。位于A右侧的提交也是可以到达的,以及提交A本身和之前的所有提交。其余的提交无法从branch-y到达,但可以从branch-x提交B到达,因此结果图如下:

              o - o
            /       \
            - o - o - B   <-- branch-x
注意到rev-list--boundary选项,可以包括"snip点"(如果我这样称呼它们的话);添加--boundary会将节点放回原始图中 A 之后的节点(但是 A 本身被剪切掉了)。
(根据您修改后的问题,您可能想要的是--not,并且您只需要获取所有分支的列表,对于这一点,git for-each-ref --format '%(refname:short)' refs/heads是适当的脚本命令。将要保留的一个分支分开,将其余的放在--not后面,然后运行git rev-list。)
1即使你在匿名分支上(换句话说,在“分离头”模式下),这实际上仍然是正确的。一些git命令会说你没有任何分支,但你仍然在使用构建分支的相同git内部。在这种情况下,您当前的分支没有名称。 2在技术上,--not只是翻转了一个标记后面的SHA-1或标识符参数的位,使它们成为否定引用。如果它们已经有一个前缀^ 符号,它们将变成“正”引用,否则它们将成为负引用。因此,x ^y z意味着“是x,不是y,是z”,而x --not y z意味着“是x,不是y,不是z”,x --not y ^z则表示“是x,不是y,是z”。

1
我只想说谢谢你提供如此详细和全面的解释。这对于理解git的工作原理非常有用。 - Alex Skrypnyk

7
您可以使用以下命令轻松查找提交记录列表, git log branch_name commit_x..commit_y 例如, git log dev HEAD~20..HEAD~10 将为您显示分支 dev 的第 10 至 20 个提交记录列表。
您还可以通过 git log 的参数来筛选所需内容。
您还可以使用 git log dev HEAD~20..HEAD~10 >> logs.txt 将这些日志存储到文件中。

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