使用Git,显示在一个特定分支上*仅*存在的所有提交,而不显示其他*任何*


86

给定一个分支,我想查看仅存在于该分支上的提交列表。在这个问题中,我们讨论了查看哪些提交在一个分支上但不在一个或多个指定的其他分支上的方法。

这略有不同。我想看看哪些提交在一个分支上,但不在任何其他分支上。

用例处于分支策略中,其中某些分支仅应合并到其中,而不能直接提交。这将用于检查是否直接在“仅合并”分支上进行了任何提交。

编辑:以下是设置虚拟git repo进行测试的步骤:

git init
echo foo1 >> foo.txt
git add foo.txt
git commit -am "initial valid commit"
git checkout -b merge-only
echo bar >> bar.txt
git add bar.txt
git commit -am "bad commit directly on merge-only"
git checkout master
echo foo2 >> foo.txt 
git commit -am "2nd valid commit on master"
git checkout merge-only 
git merge master

仅显示直接在“仅合并”分支上进行的带有消息“直接在仅合并中的错误提交”的提交。


1
这个问题假设所有合并的分支当前都在仓库中可用,一旦完全合并就永远不会删除,也可能永远不会与快进合并。让我知道是否丢失了某些内容,但是在我看来,这仅适用于相对较小的一组允许的合并自分支,所以为什么不只使用git log ^branch1 ^branch2 merge-only-branch语法呢?
Karl Bielefeldt

1
git log ^branch1 ^branch2 merge-only-branch要求列明每一个分支。可以通过巧妙地使用bash / grep来避免这种情况(请参阅下面的答案),但是我希望git对此提供一些内置支持。您是正确的,它假设所有merge-from分支都是远程的(仅本地本地和其他开发人员都不存在一样)。使用--no-merges忽略所有合并到的提交,然后删除它们的原始merge-from分支,因此假定合并from分支一直保留直到它们被合并到非合并分支(即master)。
jimmyorr 2011年

Answers:


75

我们刚刚找到了这种优雅的解决方案

git log --first-parent --no-merges

当然,在您的示例中,仍会显示初始提交。

这个答案不能完全回答问题,因为最初的提交仍然显示出来。另一方面,许多来这里的人似乎找到了他们想要的答案。


1
简单,有效,最佳。
Zabba 2012年

1
由于仍然显示对master的初始提交,因此无法回答问题。
jimmyorr

6
仅此一项就不能满足“仅在该分支上存在的提交”的条件,它显示initial valid commit,它是merge-onlymaster分支的一部分。但是,如果将最后一个当前分支名称放在puttin后面,再加上知道当前分支起源的^前缀分支名称,那么它可以解决一半的问题(不包括合并的事物)。例如:git log --first-parent --no-merges merge-only ^master
Slipp D. Thompson

13
我不知道为什么要这么大提倡,这似乎根本与问题无关。它当然不提供发布者想要的信息。
克里斯·拉西斯

2
这个答案可能并不完美。但这很简单,并且一定可以扩展。我发现添加分支名称很有用-即过滤所有提交都属于给定分支:git log --first-parent --no-merges | grep <branch_name>
artm

29

由我亲爱的朋友雷德姆巴(Redmumba)提供

git log --no-merges origin/merge-only \
    --not $(git for-each-ref --format="%(refname)" refs/remotes/origin |
    grep -Fv refs/remotes/origin/merge-only)

...origin/merge-only您的远程仅合并分支名称在哪里。如果仅本地混帐回购协议,替代工作refs/remotes/originrefs/heads,并替代远程分支名origin/merge-only当地分支机构名称merge-only,即:

git log --no-merges merge-only \
    --not $(git for-each-ref --format="%(refname)" refs/heads |
    grep -Fv refs/heads/merge-only)

2
我希望其他人可以仅使用git提供无grep的解决方案,但是如果没有,这感觉很优雅。
jimmyorr 2011年

1
是的,优雅。使用git for-each-ref在原籍列出每一个裁判的名字,并grep -v省略仅合并分支。 git log具有一个--not选项,我们将所有引用的列表传递给该选项(仅合并分支除外)。如果您对问题有更优雅的答案,让我们听听吧。
jimmyorr 2011年

2
哦,我敢肯定,这是最优雅的答案。我只是在说它有点“罗盘/复杂”,才是真正的优雅。:-)先生,并不是要贬低您的态度!
克里斯·K

1
尾随/*git for-each-refs命令依赖于不匹配的一些现有的文件和不具有failglobnullglob组(bash的选项,其他外壳会发生变化)。您应该引号/转义星号,也可以只保留结尾/*git for-each-ref模式可以匹配“从开始到斜线”)。也许使用grep -Fv refs/remotes/origin/foorefs/heads/foo)来更严格地消除哪些引用。
克里斯·约翰森

3
如果您只想查看一个分支而不是另一个分支中的提交,则可以简化此操作:git log --no-merges B1 --not B2,其中B1是您感兴趣的分支,而B2是您要与B1比较的分支。B1和B2都可以是本地或远程分支,因此您可以指定git log --no-merges master --not origin/master,甚至可以指定两个远程分支。
mr.b 2012年

21
git log origin/dev..HEAD

这将显示您在分支中所做的所有提交。


2
@Prakashorigin/branchName将指向远程分支的头,并且HEAD将指向该分支中最后一次本地提交的commitid。因此,当您使用git push时,将无法使用。
巴拉特(Bharat)2016年

您可以使用它来比较另一个本地分支。--no-merges标志也可以方便地解决OP的原始问题。
Paul Whipp

15

@Prakash答案有效。只是为了清楚...

git checkout feature-branch
git log master..HEAD

列出功能分支上的提交,但不列出上游分支(通常是您的主分支)上的提交。



7

试试这个:

git rev-list --all --not $(git rev-list --all ^branch)

基本上git rev-list --all ^branch获取所有修订版本不在分支中,然后将所有修订版本存储在仓库中,并减去之前的列表,该列表是分支中的修订版本。

@Brian发表评论后:

从git rev-list的文档中:

List commits that are reachable by following the parent links from the given commit(s)

因此,像git rev-list AA是提交的命令将列出从A可以到达的提交(包括A)。

考虑到这一点,类似

git rev-list --all ^A

将列出无法从A获得的提交

因此git rev-list --all ^branch将从分支的顶端列出所有无法到达的提交。这将删除分支中的所有提交,换句话说,仅删除其他分支中的提交。

现在让我们来 git rev-list --all --not $(git rev-list --all ^branch)

就像 git rev-list --all --not {commits only in other branches}

因此,我们要列出all无法访问的all commits only in other branches

这是仅在分支中的一组提交。让我们举一个简单的例子:

             master

             |

A------------B

  \

   \

    C--------D--------E

                      |

                      branch

这里的目标是获取D和E,而不是其他任何分支中的提交。

git rev-list --all ^branch 只给B

现在,git rev-list --all --not B我们要做的就是。这也是git rev-list -all ^B-我们希望所有提交都无法从B到达。在我们的情况下,这是D和E。这就是我们想要的。

希望这可以解释该命令如何正常工作。

评论后编辑:

git init
echo foo1 >> foo.txt
git add foo.txt
git commit -am "initial valid commit"
git checkout -b merge-only
echo bar >> bar.txt
git add bar.txt
git commit -am "bad commit directly on merge-only"
git checkout master
echo foo2 >> foo.txt 
git commit -am "2nd valid commit on master"

完成上述步骤后,如果执行a git rev-list --all --not $(git rev-list --all ^merge-only),则将得到您要查找的提交-"bad commit directly on merge-only"一个。

但是,一旦您完成了步骤的最后一步, git merge master该命令将不会给出预期的输出。因为到现在为止,仅合并中没有提交,因为master中的一个额外提交也已合并为仅合并。因此git rev-list --all ^branch给出空结果,因此git rev-list -all --not $(git rev-list --all ^branch)所有提交仅合并。


1
嗯...不知道为什么,但这不太起作用。将命令的输出xargs -L 1 -t git branch -a --contains显示为管道会显示大量误报(实际上是在其他分支上的提交)。我尝试了有没有--no-merges。感谢您的回答!
jimmyorr 2011年

就我在虚拟git repo中所见,似乎对我来说工作正常。
manojlds 2011年

我添加了创建虚拟git repo的步骤,以帮助演示您的答案存在的问题。
jimmyorr 2011年

啊,哎呀。在我一直认为它没有完成之前就提出了建议。git rev-list --all ^branch会给您所有不在中的提交branch。然后,您减去从该名单branch; 但根据定义,所有不在中的提交都不在branchbranch,因此您不会减去任何东西。jimmyorr寻找的是位于branch但不在的提交master,也没有任何其他分支。您不想减去不在其中的提交branch;您想减去其他任何分支中的提交。
布莱恩·坎贝尔

1
“ @manojlds”(所有修订)-(所有修订不在分支中)=分支中的修订。是的,可以获取所有修订branch,但是可以git rev-list branch。您只是git rev-list branch在以更复杂(且更慢)的方式编写。回答这个问题是不起作用的,即如何查找branch 不在任何其他分支中的所有提交。
布莱恩·坎贝尔

2

可接受答案的另一种变体,用于 master

git log origin/master --not $(git branch -a | grep -Fv master)

过滤除master之外的任何分支中发生的所有提交。


0

这不是一个真正的答案,但我需要使用格式和大量空间。我将尝试描述我认为两个最佳答案背后的理论:公认的答案和(至少目前)排名最高的答案。但是实际上,他们回答了不同的问题

Git中的提交通常一次“在”多个分支上。确实,这就是问题的实质。鉴于:

...--F--G--H   <-- master
         \
          I--J   <-- develop

大写字母代表实际的Git哈希ID,我们通常在输出中查找一个H或多个提交I-Jgit log。提交G都在两个分支上,因此我们希望将它们排除在外。

(请注意,在绘制这样的图表,较新提交是朝着正确的。名字中选择一个最右边的承诺在该行每个这些提交的有父提交,这是承诺他们的左:父HISG和母公司JI的父,IG再一次的家长。GF,并F有简单的这里没有显示父:它的一部分的...部分)。

对于这种特别简单的情况,我们可以使用:

git log master..develop    # note: two dots

查看I-J,或:

git log develop..master    # note: two dots

H仅查看。在两个点后面的右侧名称告诉Git:是的,这些提交。在两个点之前的左侧名称告诉Git:不,不是这些commits。Git从末尾开始,即commitH或commit J,然后向后工作。有关此的更多信息,请参见思考(a)Git

最初问题的表达方式是,希望找到可以从一个特定名称而不是同一通用类别中的任何其他名称到达的提交。也就是说,如果我们有一个更复杂的图:

               O--P   <-- name5
              /
             N   <-- name4
            /
...--F--G--H--I---M   <-- name1
         \       /
          J-----K   <-- name2
           \
            L   <-- name3

我们可以选择其中一个名称,例如name4name3,然后询问:可以通过该名称找到哪些提交,而不能通过其他任何名称找到? 如果我们选择name3答案,那就是commit L。如果我们选择name4,答案是根本没有提交:name4名称为commitNN可以通过从头开始name5并向后工作来找到的提交。

接受的答案使用远程跟踪名称而不是分支名称,并且允许您将一个(拼写的一个)指定origin/merge-only为所选名称,并查看该命名空间中的所有其他名称。它还避免了显示合并:如果我们选择name1“有趣的名称”,并说显示可从中访问name1但没有其他任何名称的提交,则将看到合并提交M和常规提交I

最受欢迎的答案是完全不同的。这是所有关于穿越提交图形,而不以下双腿合并,并没有表现出任何的是,提交的合并。name1例如,如果我们以开头,则不会显示M(这是一个合并),但是假设合并的第一个父对象M是commit I,我们甚至不会查看commitJK。我们最终会显示提交I,并且还承诺HGF,等等,这些都不是合并的提交和所有可以连在开始M和向后工作,参观只有第一各合并提交的母公司。

最受欢迎的答案非常适合例如查看master何时master打算成为仅合并分支。如果所有“实际工作”都是在随后合并到的分支中完成的master,则我们将具有以下模式:

I---------M---------N   <-- master
 \       / \       /
  o--o--o   o--o--o

其中所有未命名的o提交都是普通(非合并)提交,M并且N是合并提交。提交I是初始提交:有史以来第一次提交,并且应该是在主服务器上的唯一提交(不是合并提交)。如果git log --first-parent --no-merges master显示除以外的 任何提交I,我们将遇到以下情况:

I---------M----*----N   <-- master
 \       / \       /
  o--o--o   o--o--o

我们希望看到*直接在上进行的提交,而master不是通过合并某些功能分支。

简而言之,流行的答案非常适合查看master何时master只应进行合并,而不适用于其他情况。 接受的答案适用于其他情况。

远程跟踪名称是否像origin/master 分支名称?

Git的某些部分说它们不是:

git checkout master
...
git status

on branch master,但是:

git checkout origin/master
...
git status

HEAD detached at origin/master。我更愿意同意git checkout/ git switchorigin/master不是分支名称,因为您无法“使用”它。

接受的答案使用远程跟踪名称origin/*作为“分支名称”:

git log --no-merges origin/merge-only \
    --not $(git for-each-ref --format="%(refname)" refs/remotes/origin |
    grep -Fv refs/remotes/origin/merge-only)

中间的行调用git for-each-ref,对名为的远程对象的远程跟踪名称进行迭代origin

之所以可以很好地解决原始问题,是因为我们对其他人的分支名称感兴趣,而不是我们的分支名称。但这意味着我们已经将分支定义为分支名称以外东西。很好:请注意您在执行此操作。

git log 遍历提交图的某些部分

我们真正要在这里搜索的是所谓的daglet系列请参阅“分支”到底是什么意思? 也就是说,我们正在整体提交图的某些子集中寻找片段

每当我们让Git查看分支名称(例如)master,标签名称(例如v2.1)或远程跟踪名称(例如)时origin/master,我们往往希望让Git告诉我们该提交以及从该提交中可以得到的每个提交: ,然后向后工作。

在数学中,这称为走图。Git的提交图是有向无环图DAG,这种图特别适合步行。在浏览这样的图形时,将访问通过使用的路径可到达的每个图形顶点。Git图中的顶点是提交,边缘是从每个孩子到每个父对象的弧形(单向链接)。(这就是“想像(a)Git”出现的地方。弧线的单向性质意味着Git必须从子级到父级都向后工作。)

用于图形遍历的两个主要Git命令是git loggit rev-list。这些命令极为相似-实际上它们大多是从相同的源文件构建的-但它们的输出是不同的:git log产生供人类读取的git rev-list输出,而产生供其他Git程序读取的输出。1 这两个命令都执行这种图遍历。

他们具体执行的图形遍历是:给定一组起点提交(也许只有一个提交,也许一堆哈希ID,也许一堆解析为哈希ID的名称),遍历图形,访问commit。特殊指令(例如--not或前缀^,或--ancestry-path或)以某种方式--first-parent修改了图形遍历

在进行图行走时,他们会访问每个提交。但是它们仅打印部分已提交的选定子集。指令如--no-merges--before <date>告诉图形行走代码提交到打印

为了进行一次访问,一次提交一次,这两个命令使用优先级队列。您运行git loggit rev-list给它一些起点提交。他们将这些提交放入优先级队列。例如,一个简单的:

git log master

将名称master转换为原始哈希ID,并将该哈希ID放入队列。要么:

git log master develop

将两个名称都转换为哈希ID,并假设它们是两个不同的哈希ID,则将两者都放入队列。

此队列中提交的优先级由更多参数确定。例如,参数--author-date-order告诉git loggit rev-list使用作者时间戳,而不是提交者时间戳。默认设置是使用提交者时间戳,并选择最新的提交:具有最高数字日期的提交。因此,与master develop假设这些决心两种不同的提交,Git将显示哪一个来了以后第一个,因为这将是在队列的前面。

无论如何,修订版本遍历代码现在都在循环中运行:

  • 当队列中有提交时:
    • 删除第一个队列条目。
    • 确定是否完全打印此提交。例如--no-merges::如果是合并提交,则不打印任何内容;--before:如果日期不早于指定时间,则不打印任何内容。如果禁止打印,则打印commit:git log显示;为git rev-list,打印其哈希ID。
    • 将一些或所有此提交的提交提交到队列中(只要它现在不存在,并且尚未被访问2)。正常的默认值是放入所有父项。使用--first-parent禁止除每个合并的第一个父对象外的所有对象。

(两者git loggit rev-list可以做历史的简化带或不带父改写在这一点为好,但我们会跳过这里。)

对于简单的链,例如HEAD没有合并提交的情况下从头开始并向后工作,队列在循环的顶部始终始终有一个提交。有一个提交,因此我们将其弹出并打印,然后将其(单个)父级放入队列中,然后再次进行遍历,然后沿着链条向后移动,直到到达第一个提交,否则用户会厌倦git log输出并退出该程序。在这种情况下,排序选项都不重要:仅显示一次提交。

如果存在合并,并且我们遵循父母双方(合并的“两条腿”),或者当您给出一个git loggit rev-list多个起始提交时,排序选项就很重要。

最后,考虑提交说明符的作用--not^在提交说明符之前。这些有几种编写方式:

git log master --not develop

要么:

git log ^develop master

要么:

git log develop..master

都是同一件事。该--not像前缀^不同之处在于它适用于多个名称:

git log ^branch1 ^branch2 branch3

表示不是branch1,不是branch2,是branch3;但:

git log --not branch1 branch2 branch3

表示不是branch1,不是branch2,不是branch3,并且您必须使用一秒钟--not将其关闭:

git log --not branch1 branch2 --not branch3

这有点尴尬。这两个“非”指令通过XOR组合在一起,因此,如果您确实需要,可以编写:

git log --not branch1 branch2 ^branch3

如果要混淆,则表示不是branch1,不是branch2,是branch3

这些都通过影响图形游走而起作用。作为git loggit rev-list散步的图形,它确保投入的优先级队列中的任何承诺是从任何可达否定引用。(实际上,它们也影响启动设置:否定的提交不能直接从命令行进入优先级队列,因此git log master ^master什么也不显示。)

gitrevisions文档中描述的所有精美语法都使用了此语法,您可以通过简单调用暴露它git rev-parse。例如:

$ git rev-parse origin/pu...origin/master     # note: three dots
b34789c0b0d3b137f0bb516b417bd8d75e0cb306
fc307aa3771ece59e174157510c6db6f0d4b40ec
^b34789c0b0d3b137f0bb516b417bd8d75e0cb306

三点语法意味着可从左侧或右侧访问的提交,但不包括可从两者访问的提交。在这种情况下,origin/master提交 ()b34789c0b本身可从origin/pufc307aa37...)到达,因此origin/master散列出现两次,一次带有否定,但实际上Git通过放入两个正引用(两个非否定的哈希ID)来实现三点语法。一个负数,以^前缀表示。

相似:

$ git rev-parse master^^@
2c42fb76531f4565b5434e46102e6d85a0861738
2f0a093dd640e0dad0b261dae2427f2541b5426c

^@语法意味着所有给定的父母承诺,以及master^本身-在此提交的分支名称选定的第一个父master-is合并提交,所以它有两个家长。这是两个父母。和:

$ git rev-parse master^^!
0b07eecf6ed9334f09d6624732a4af2da03e38eb
^2c42fb76531f4565b5434e46102e6d85a0861738
^2f0a093dd640e0dad0b261dae2427f2541b5426c

^!后缀手段的承诺本身,但没有其父母。在这种情况下,master^0b07eecf6...。我们已经看到父母双方都有^@后缀。他们又来了,但是这次却被否定了。


1许多Git程序实际上git rev-list使用各种选项运行,并读取其输出,以了解要使用哪些提交和/或其他Git对象。

2因为该图是非循环的,所以可以保证没有访问过图,如果我们添加约束,则在将其所有子显示为优先级之前,从不显示父级。 --date-order,,--author-date-order--topo-order添加此约束。没有默认的排序顺序(没有名称)。如果提交时间戳很麻烦(例如,某些提交是由时钟关闭的计算机“将来”执行的),则在某些情况下可能会导致输出看起来很奇怪。


如果到目前为止,您已经了解很多 git log

概要:

  • git log 是关于在遍历图的某些或全部时显示一些选定的提交。
  • --no-merges在接受的答案和当前排名最高的答案中都存在该参数,它抑制显示某些已提交的提交。
  • --first-parent说法,从当前排名第一的回答,压制行走图形的某些部分,在图形的走本身。
  • --not前缀的命令行参数,在接受的答案是使用,压制曾经访问图形的某些部分在所有的,从一开始。

使用这些功能,我们可以针对两个不同的问题获得所需的答案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.