“之间”的概念在这里有点模糊,“分支”一词也没有很好的定义(通常是 git)。让我看看尽管有这些问题,我是否可以保持简短(对我来说很难:-)):
我将假设“之间”是指 git 提交图中的 path 的图论概念。例如,假设我们有这个图形片段(我不能为节点之间的弧线绘制箭头,但假设每个 -、/ 或 \ 在左侧都有一个箭头,以便提交指向直线左、下和左,或上和左,到他们的前任):
o - o
/ \
... - A - o - o - o - B <-- branch-x
\ /
o - o - C <-- branch-y
我已经确定了三个特定的提交——A、B 和 C——并提供了两个分支提示名称 branch-x 和 branch-y 分别指向提交 B 和 C。出于 git 实用性的目的,我们还假设有一个标签 A 指向提交 A,这样我们就不必拼出它的 SHA-1。
您可以通过三种可能的路径从提交B 到达提交A。一个从B 开始,上升到顶线,下降到中间,然后向左移动到A。一个从B 开始,直接向左走,最终到达A。最后一个从B开始,后退一步,向下向左,向上向左,再向左到达A。
Git 为您提供了特殊的两点语法 A..branch-x(如果您没有名为 A 的标签,则用实际的 SHA-1 替换节点 A),对于大多数命令,这表示他们应该访问所有从B回到A的所有可能路径上的节点,通常包括节点B本身,不包括节点A。这几乎是您想要的,但不完全是,因为您想排除在其他分支上进行的提交。
这带来了一个无法回答的(通常)问题“哪些提交是在哪个分支上进行的?”
Git 试图告诉你这个问题是无效的:你不应该关心;您应该只关心所有这些提交都可以从节点B 访问。 Git 实际上通常对此是正确的,但“通常”不是“总是”。不幸的是,我还没有找到任何好的方法来描述您何时应该(或应该)关心(实际的好例子对我正在尝试编写的文本很有帮助)。
与此同时,让我们继续前进。从上图中可以清楚地看出,下一行的提交都是“在branch-y 上进行的”。这里有一个问题,因为它看起来很清楚,但实际上可能不是正确。考虑一下如果我们重新绘制图表会发生什么:
o - o
/ \
... - A - o - o - o - B <-- branch-x
\ /
o
\
o - C <-- branch-y
这一次,branch-y 似乎只是为了保存两个最低的提交而创建的。 (如果有另一个分支名称指向唯一的第三行提交,则更有可能,尽管因为您最初的问题陈述说要排除 all 其他分支 - 而不仅仅是分支 branch-y - 那不会'在这种情况下真的很重要。)
不管怎样,虽然我不太清楚你所说的“分支”是什么意思,也不知道根据这张图你想要哪些提交,让我们来看看 git 实际提供的选择器。有一个重要的可能正是你的意思。
我之前提到“大多数” git 命令使用相同的语法说明符。事实上,大多数 git 命令要么包含来自 git rev-list 程序的代码,要么只是运行,该程序的工作是选择对象(通常是提交对象)以获得您想要使用的提交 ID 列表。它也是任何类型的脚本都需要的命令。
rev-list 命令has a dizzying number of options,许多用于协助各种图形遍历。我认为这里最有趣的两个是--first-parent 和--not。
使用--first-parent
让我们首先考虑--first-parent。检查上面的图表(两种布局:它们可能看起来不同,但在拓扑上它们是相同的)。请注意,它是在 merge 提交时,如节点 B 本身和 B 左边一步的节点,路径分叉。这是因为它只有合并提交具有多个传出弧(这实际上是合并提交的定义:它是一个具有两个或多个父节点的节点)。
当 git 进行合并提交时,它会将各个传出弧编号到多个父级。第一条弧很特别:它是提交时的当前分支。也就是说,当您执行git merge <sha-1-or-equivalent> 时,您当时在某个分支上,1 并且当前提交的 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 以包含“剪切点”(如果我可以这样称呼它们);添加--boundary 在原始图中将节点放回A 之后(但A 本身仍然被剪掉)。
(根据您修改后的问题,--not 可能是您想要的,您只需要获取所有分支的列表,其中git for-each-ref --format '%(refname:short)' refs/heads 是正确的脚本命令。分离出其节点的一个分支你想保留,把剩下的放在--not后面,然后运行git rev-list。)
1即使您在匿名分支上(换句话说,在“分离 HEAD”模式下),这实际上也是正确的。一些 git 命令会说您不在任何分支上,但您仍在使用与 build 分支相同的 git 内部结构。在这种情况下,您当前的分支根本没有名称。
2从技术上讲,--not 只是翻转了一点,将随后的 SHA-1-or-identifier 参数标记为被否定。如果它们已经有前缀^ 符号,它们将成为“正”引用,否则它们将成为负引用。例如,x ^y z 表示“是 x,没有 y,是 z”,而 x --not y z 表示“是 x,没有 y,没有 z”,x --not y ^z 表示“是 x,没有 y,是 z”。