Git:在特定分支上的两次提交之间获取提交 [英] Git: get commits between two commits on a specific branch
问题描述
我已经阅读了许多类似场景的答案,但并非如此。
我需要在git上的特定分支上的两个提交之间生成提交列表。所以对其他分支的任何提交都应该被忽略。任何想法如何做到这一点?
之间的概念在这里有点模糊,术语分支也没有很好的定义(一般用git,就是这样)。让我看看,尽管存在这些问题,我是否能保持这个短暂(对我来说很难:-)):我会假设通过之间,你的意思是git commit图中的路径的图形理论概念。例如,假设我们有这个图片段(我不能为节点之间的弧线绘制箭头,但假装每个 -
, /
或 \
在左侧有一个箭头,这样,提交指向左上,左下或左上,直至他们的前任):
o - o
pre>
/ \
... - A - ○ - ○ - ○ - B < - 分支-x
\ /
o -o - C < - 分支-y
我已经确定了三个特定的提交 -
A
,B
和C
- 并提供了两个分支名称branch-x
和分支-y
分别指向提交B
和C
。为了git-usefulness的目的,我们还假设有一个标记A
指向提交A
,所以我们不需要必须说明它的SHA-1。
有三种可能的途径可以通过它们到达
A
提交B
。一个以B
开头,上到顶端,下降到中间,然后左移到A
。一个从B
开始,直接向左,最后到达A
。最后一个从B
开始,返回一步,向下并离开,向上并离开,并再次离开,以达到A
Git为您提供特殊的双点语法,
A..branch-x
(或如果没有名为A
的标签,则在实际的SHA-1中替换节点A
),大多数命令都表示它们应该从B
返回到<$ c $ 所有所有可能路径中的节点 c> A ,通常包括节点B
本身以及节点A
。这是几乎你想要的,但不完全,因为你想排除在其他分支上提交的提交。
<这提出了无法回答的问题(一般情况下):在哪个分支上提交了哪些内容?
Git试图告诉你这个问题是无效的:你不应该在乎;你应该只关心所有这些提交都可以从节点
B
进行访问。实际上,Git通常是对的,但通常不是永远。不幸的是,我还没有找到任何好的方法来描述你什么时候应该(或者怎样)关心(实际的好例子会对我正在写的文本有帮助)。
同时,让我们继续。从上图中可以明显看出,下线的提交全部是在branch -y
上进行的。这里有一个问题,因为它看起来很清楚,但它可能并不是真正的 。考虑如果我们重新绘制图表会发生什么:o - o
/ \
... - A - o - o - o - B < - 分支-x
\ /
o
\
o - C < - 分支-y
这次可能是
branch-y
是为了保持两个最低的提交而创建的。 (更有可能的是,如果有另一个分支名称指向单独的第三行提交,但由于您的原始问题语句表示排除所有其他分支 - 不仅仅是分支分支-y
- 这在这种情况下并不重要。)
无论如何,虽然我不太清楚分支,也没有准确的提交你想要的图表,让我们来看看git实际提供的选择器。有一个重要的可能正是你的意思。
我之前提到过,大部分git命令使用相同的语法说明符。实际上,大多数git命令或者包含或者简单地运行
git rev-list
程序的代码,其工作是选择对象(通常是提交对象)来获取您要使用的提交ID列表。
$ b
rev-list
命令拥有令人眼花缭乱的选项,许多人可以协助各种图形遍历。我认为这两个最有趣的是- 第一父母
和- 而不是
。
使用
- 第一父母
让我们考虑
- 第一个父母
第一个。检查上面的图表(两种布局:它们可能看起来不同,但拓扑结构相同)。请注意,它在 merge 提交处,如节点B
本身,节点位于B $左边一步c $ c>,该路径分叉。这是因为它只有合并提交具有多个输出弧(这实际上是合并提交的定义:它是具有两个或更多个父项的节点)。
当git进行合并提交,它将个别传出弧线编号给多个父母。第一个弧是特殊的:它是提交时的当前分支。也就是说,当你做了
git merge< sha-1或等效的>
时,你当时在某个分支上, 1 和当前提交的SHA-1成为新合并提交的第一个父。额外的父母(合并ID,通常只有一个,但git允许更多)是第二,第三等。
使用
- first-parent
标志告诉git仅遍历第一个父弧。因此git rev-list --first-parent branch-x
将以commitB
开头,然后找到它的第一个父对象(我们无法确定哪一个是我们上图中的第一个),请遵循第一个(也是唯一的)父级,等等,一直返回到根提交。
这可能是也可能不是你想要的(尽管它没有帮助之间的概念)。
使用
--not
现在让我们看看
- 不是
标志。 2 通常,git rev-list< SHA-1-ID-or-name>
产生从给定的SHA-1到达的所有提交的集合(根据需要首先将名称解析为ID)。也就是说,它遵循所有路径回到所有根。结果是一组SHA-1 ID。使用- 而不是
会使rev-list
排除这些ID。就其本身而言,这个否定集并无用处,但当与正常(非否定)集相结合时,它 是有用的。事实上,这就是A..B
起作用的原因:rev-list
首先生成全部提交可从B
到达,然后从A
中减去可达到的所有提交集合。
因此,取决于排除其他分支上的所有提交的含义,它可能是您想要的内容:
git rev-list branch1 --not branch2 branch3 ... branchN
您可以在
之后列出除
branch1以外的每个分支
commit- 不是
。
如果我们最后一次查看我们的图,我们来看看哪些提交被
选择了branch-x --not branch-y code>:
o - o
/ \
... - A - o - o - o - B < - branch-x
\ /
o - o - C < - branch -y
提交
C
显然是re可以从branch-y
进行访问,就像最下面一行的所有提交一样。对于A
右侧的提交也可以访问,同样也是提交A
本身以及所有更早的提交。剩下的提交不可从branch-y
到达,但可从到达
xB
,所以得到的图是:
$ \\ b $ b - o - o - B < - 分支x
请注意, (根据您修改的问题, 1 即使是你在一个匿名分支上(换句话说,在分离头部模式下)。有些git命令会说你不在任何分支上,但你仍然在使用和 build 分支相同的git内部工作。在这种情况下,您当前的分店根本没有名字。 2 技术上 I've read a number of answers for similar scenarios, but not this precise one. I need to generate a list of commits that occurred between two commits on a specific branch on git. So any commits made to other branches should be ignored. Any idea how to do this? The notion of "between" is a little fuzzy here, and the term "branch" is not well defined either (in general with git, that is). Let me see if I can keep this short despite these issues (tough for me :-) ): I'm going to assume that by "between", you mean the graph-theoretic notion of a path within the git commit graph. For instance, suppose we have this graph fragment (I can't draw arrows for the arcs between nodes, but pretend each I've identified three specific commits— There are three possible paths by which you may reach commit Git gives you the special two-dot syntax, This brings up the unanswerable (in general) question "which commits were made on which branch?" Git tries to tell you that this question is invalid: that you should not care; that you should only care that all of those commits are reachable from node Meanwhile, let's press on. It seems clear, from the diagram above, that the commits on the lower line were all "made on This time it seems likely that Anyway, while I don't quite know what you mean by "branch", nor precisely which commits you want given this graph, let's take a look at the selectors git actually offers. There's one important one that might be exactly what you mean. I mentioned earlier that "most" git commands use the same syntax specifiers. In fact, most git commands either contain the code from, or simply run, the The Let's consider When git makes a merge commit, it numbers the individual outgoing arcs to the multiple parents. The first arc is special: it's the current branch at the time of the commit. That is, when you did Using the This may or may not be what you want (though it doesn't help with the notion of "between"). Now let's look at the Thus, depending on what you mean by "exclude all commits on other branches", it may be the case that what you want is: where you simply list every branch other than If we look at our diagram one last time, let's see which commits are selected by Commit Note that (Based on your revised question, 1This is effectively true even if you were on an anonymous branch (in "detached HEAD" mode, in other words). Some git commands will say that you're not on any branch, but you're still working with the same git internals that build branches. Your current branch simply has no name, in this case. 2Technically 这篇关于Git:在特定分支上的两次提交之间获取提交的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! rev-list
含有 - boundary
以包含snip点(如果我可以称它们);在原始图中添加 - boundary
在 A $ b
-
git for-each-ref --format'%(refname:short)'refs / heads
是正确的脚本命令,将你想要保存的节点的一个分支分出来,放在后面 - 而不是
,并运行 git rev- )
-
只是翻转一点,将随后的SHA-1或标识符参数标记为否定。如果它们已经有一个前缀 ^
符号,它们就变成了正向引用,否则它们变成负向引用。因此 x ^ yz
表示是x,否y,是z,而 x - 不是yz
表示是例如,x,no y,no z和 x --not y ^ z
表示yes x,no y,yes z。 $ b-
, /
, or \
has an arrow-head on the left so that commits point straight left, down-and-left, or up-and-left, to their predecessor(s)): o - o
/ \
... - A - o - o - o - B <-- branch-x
\ /
o - o - C <-- branch-y
A
, B
, and C
—and provided two branch-tip names branch-x
and branch-y
pointing to commits B
and C
respectively. For git-usefulness-purposes let's also assume there's a tag A
pointing to commit A
, so that we don't have to spell out its SHA-1.A
from commit B
. One starts at B
, goes up to the top line, descends back to the middle, and then moves left to A
. One starts at B
, goes directly left, and eventually reaches A
. The last starts at B
, goes back one step, goes down and left, up and left, and left once more to reach A
.A..branch-x
(or substitute in the actual SHA-1 for node A
if you don't have a tag named A
), which for most commands, indicates that they should visit all the nodes on all possible paths from B
back to A
, normally including node B
itself and excluding node A
. This is almost what you want but not quite, because you want to exclude commits that were made on other branches.B
. Git is actually usually right about this, but "usually" is not "always". Unfortunately, I haven't found any good ways to describe when you should (or do) care (actual good examples would be helpful for a text I'm attempting to write).branch-y
". There is a problem here, because it seems clear, but it may not actually be true. Consider what happens if we re-draw the graph thus: o - o
/ \
... - A - o - o - o - B <-- branch-x
\ /
o
\
o - C <-- branch-y
branch-y
was created just to hold the two lower-most commits. (It's even more likely if there is another branch name pointing to the lone third-row commit, although since your original problem statement said to exclude all other branches—not just branch branch-y
—that wouldn't really matter in this case.)git rev-list
program, whose job is to select objects (usually commit objects) to obtain the list of commit IDs that you want to work with. It's also the command you will want for any kind of scripting.rev-list
command has a dizzying number of options, many to assist in various kinds of graph traversals. The two most interesting here, I think, are --first-parent
and --not
.Using
--first-parent
--first-parent
first. Examine the graph above (either layout: they may look different but topologically, they're identical). Note that it's at merge commits, like node B
itself and the node one step to the left of B
, that paths fork. This is because it's only merge commits that have multiple outgoing arcs (this is in fact the definition of a merge commit: it's a node with two or more parents).git merge <sha-1-or-equivalent>
, you were on some branch at that time,1 and the SHA-1 of the then-current commit becomes the "first parent" of the new merge commit. The additional parents (the merged-in IDs, usually just one but git allows more) are the second, third, and so on.--first-parent
flag tells git to traverse only the first-parent arcs. So git rev-list --first-parent branch-x
will start with commit B
, then find its first parent (we can't tell which one is first from our diagrams above), follow that's first (and only) parent, and so on, all the way back to a root commit.Using
--not
--not
flag.2 Normally, git rev-list <SHA-1-ID-or-name>
produces the set of all commits reachable from the given SHA-1 (resolving a name to an ID first as needed). That is, it follows all paths back to all roots. The result is a set of SHA-1 IDs. Using --not
makes rev-list
exclude these IDs. By itself, this negated-set is not useful, but when combined with a normal (non-negated) set, it is useful. In fact, it's how A..B
works in the first place: rev-list
first generates the set of all commits reachable from B
, then subtracts away the set of all commits reachable from A
.git rev-list branch1 --not branch2 branch3 ... branchN
branch1
after the --not
.branch-x --not branch-y
: o - o
/ \
... - A - o - o - o - B <-- branch-x
\ /
o - o - C <-- branch-y
C
is obviously reachable from branch-y
, as are all the commits on the lowermost line. The commit just to the right of A
is reachable as well, as is commit A
itself and all earlier commits. The remaining commits are not reachable from branch-y
, but are reachable from branch-x
commit B
, so the resulting graph is: o - o
/ \
- o - o - B <-- branch-x
rev-list
has --boundary
to include the "snip points" (if I can call them that); adding --boundary
puts back the node just after A
in the original diagram (but A
itself remains snipped-away).--not
is probably what you want here, and you just need to get a list of all branches, for which git for-each-ref --format '%(refname:short)' refs/heads
is the proper scripting command. Separate out the one branch whose nodes you want kept, put the rest behind --not
, and run git rev-list
.)
--not
just flips a bit that marks subsequent SHA-1-or-identifier arguments as being negated. If they already have a prefix ^
symbol, they become "positive" references, otherwise they become negative references. Hence x ^y z
means "yes x, no y, yes z" while x --not y z
means "yes x, no y, no z" and x --not y ^z
means "yes x, no y, yes z", for instance.