Git:在特定分支上的两次提交之间获取提交 [英] Git: get commits between two commits on a specific branch

查看:111
本文介绍了Git:在特定分支上的两次提交之间获取提交的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经阅读了许多类似场景的答案,但并非如此。

我需要在git上的特定分支上的两个提交之间生成提交列表。所以对其他分支的任何提交都应该被忽略。任何想法如何做到这一点?

解决方案

之间的概念在这里有点模糊,术语分支也没有很好的定义(一般用git,就是这样)。让我看看,尽管存在这些问题,我是否能保持这个短暂(对我来说很难:-)):我会假设通过之间,你的意思是git commit图中的路径的图形理论概念。例如,假设我们有这个图片段(我不能为节点之间的弧线绘制箭头,但假装每个 - / \ 在左侧有一个箭头,这样,提交指向左上,左下或左上,直至他们的前任):

  o  -  o 
/ \
... - A - ○ - ○ - ○ - B < - 分支-x
\ /
o -o - C < - 分支-y
pre>

我已经确定了三个特定的提交 - 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 ,该路径分叉。这是因为它只有合并提交具有多个输出弧(这实际上是合并提交的定义:它是具有两个或更多个父项的节点)。

当git进行合并提交,它将个别传出弧线编号给多个父母。第一个弧是特殊的:它是提交时的当前分支。也就是说,当你做了 git merge< sha-1或等效的> 时,你当时在某个分支上, 1 和当前提交的SHA-1成为新合并提交的第一个父。额外的父母(合并ID,通常只有一个,但git允许更多)是第二,第三等。



使用 - first-parent 标志告诉git仅遍历第一个父弧。因此 git rev-list --first-parent branch-x 将以commit B 开头,然后找到它的第一个父对象(我们无法确定哪一个是我们上图中的第一个),请遵循第一个(也是唯一的)父级,等等,一直返回到根提交。



这可能是也可能不是你想要的(尽管它没有帮助之间的概念)。



使用 --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以外的每个分支 - 不是

如果我们最后一次查看我们的图,我们来看看哪些提交被选择了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 到达,但可从到达 x commit B ,所以得到的图是:

  $ \\ b $ b  -  o  -  o  -  B < - 分支x 

请注意, rev-list 含有 - boundary 以包含snip点(如果我可以称它们);在原始图中添加 - boundary A 之后放回节点(但是 A $ b

(根据您修改的问题, - c>可能是你想要的,你只需要得到所有分支的列表,其中 git for-each-ref --format'%(refname:short)'refs / heads 是正确的脚本命令,将你想要保存的节点的一个分支分出来,放在后面 - 而不是,并运行 git rev- )



1 即使是你在一个匿名分支上(换句话说,在分离头部模式下)。有些git命令会说你不在任何分支上,但你仍然在使用和 build 分支相同的git内部工作。在这种情况下,您当前的分店根本没有名字。



2 技术上 - 只是翻转一点,将随后的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

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 -, /, 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

I've identified three specific commits—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.

There are three possible paths by which you may reach commit 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.

Git gives you the special two-dot syntax, 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.

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 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).

Meanwhile, let's press on. It seems clear, from the diagram above, that the commits on the lower line were all "made on 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

This time it seems likely that 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.)

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 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.

The 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

Let's consider --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).

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 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.

Using the --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.

This may or may not be what you want (though it doesn't help with the notion of "between").

Using --not

Now let's look at the --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.

Thus, depending on what you mean by "exclude all commits on other branches", it may be the case that what you want is:

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

where you simply list every branch other than branch1 after the --not.

If we look at our diagram one last time, let's see which commits are selected by branch-x --not branch-y:

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

Commit 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

Note that 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).

(Based on your revised question, --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.)


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 --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.

这篇关于Git:在特定分支上的两次提交之间获取提交的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆