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

jim*_*orr 77 git

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

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

用例是一种分支策略,其中一些分支只应合并到,而不是直接在其上提交.这将用于检查是否已在"仅合并"分支上直接进行任何提交.

编辑:以下是设置虚拟git仓库测试的步骤:

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
Run Code Online (Sandbox Code Playgroud)

应该只显示直接在仅合并分支上创建的消息"直接在仅合并时提交错误"的提交.

wrt*_*prt 70

我的同事刚刚发现这个优雅的解决方案__CODE__.在您的示例中,当然初始提交仍会显示.

请仔细阅读:这个答案并没有完全回答这个问题,因为初始提交仍然显示出来.另一方面,许多来到这里的人似乎找到了他们正在寻找的答案.在投票前请考虑这一点.请参阅评论以供讨论.

  • 我不确定为什么这么受欢迎,这似乎与这个问题无关.它当然不提供海报正在寻找的信息. (10认同)
  • 仅此一项不满足_"仅存在于该分支上的提交"_条件 - 它显示"初始有效提交",它是"仅合并"和"主"分支的一部分.但是,如果一个人通过puttin的努力结束当前的分支名称,然后是^ -prefixed分支名称,其中一个人知道当前的分支,它解决了一半的问题(不包括合并的东西).例如:`git log --first-parent --no-merges merge-only ^ master` (6认同)
  • 这个答案可能并不完美.但它很简单,在某种程度上肯定有效.我发现添加分支名称很有用 - 即过滤所有提交属于给定分支:`git log --first-parent --no-merges | grep <branch_name>` (2认同)

jim*_*orr 26

礼貌我亲爱的朋友雷蒙巴:

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

... origin/merge-only您的远程仅合并分支名称在哪里.如果使用仅限本地的git仓库,替换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)
Run Code Online (Sandbox Code Playgroud)

  • 如果你只想看到一个分支中的提交而不是另一个分支中的提交可以简化:`git log --no-merges B1 - not B2`,其中B1是你感兴趣的分支,B2是分支你想比较B1到.B1和B2都可以是本地或远程分支,因此您可以指定`git log --no-merges master --not origin/master`,甚至可以指定两个远程分支. (3认同)
  • 我希望其他人可以只使用git提供一个无grep的解决方案,但如果没有,这感觉非常优雅. (2认同)
  • 哦,我确定这是最优雅的答案.我只是认为它对于真正的优雅有点"冗长/复杂".:-)并不意味着贬低你的方法,先生! (2认同)

Pra*_*ash 16

git log origin/dev..HEAD
Run Code Online (Sandbox Code Playgroud)

这将显示您分支中的所有提交.

  • 对我来说,这应该是正确的答案 (2认同)
  • @Prakash`origin / branchName`将指向远程分支的头部,`HEAD`将指向该分支中最后一次本地提交的commitid。因此,当您使用git push时,这将不起作用。 (2认同)

grz*_*zuy 12

也许这可以帮助:

git show-branch

  • 虽然这在理论上可以回答这个问题,[最好](// meta.stackoverflow.com/q/8259)在这里包含答案的基本部分,并提供参考链接. (2认同)

Bry*_*yan 10

@Prakash回答有效.为了清楚起见......

git checkout feature-branch
git log master..HEAD
Run Code Online (Sandbox Code Playgroud)

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


man*_*lds 7

试试这个:

git rev-list --all --not $(git rev-list --all ^branch)
Run Code Online (Sandbox Code Playgroud)

基本上git rev-list --all ^branch不是在分支中获取所有修订,然后是repo中的所有修订,并减去前面的列表,这是在分支中的修订.

在@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
Run Code Online (Sandbox Code Playgroud)

这里的目标是得到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"
Run Code Online (Sandbox Code Playgroud)

完成上述步骤后,如果你这样做,git rev-list --all --not $(git rev-list --all ^merge-only)你将得到你正在寻找的提交 - 那个"bad commit directly on merge-only".

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


tor*_*rek 6

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

\n\n

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

\n\n
...--F--G--H   <-- master\n         \\\n          I--J   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

(请注意,在这样绘制的图表中,较新的提交位于右侧。名称选择该行上最右侧的单个提交。每个提交都有一个父提交,即其左侧的提交HG,并且 的父级J是。再次是I的父级。 的父级是,并且有一个此处未显示的父级:它是该部分的一部分。)IGGFF...

\n\n

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

\n\n
git log master..develop    # note: two dots\n
Run Code Online (Sandbox Code Playgroud)\n\n

查看I-J,或:

\n\n
git log develop..master    # note: two dots\n
Run Code Online (Sandbox Code Playgroud)\n\n

仅供查看H。右侧的名称,在两个点之后,告诉 Git:是的,这些提交。两个点之前的左侧名称告诉 Git:不,不是这些提交Git 从末尾\xe2\x80\x94at开始提交H或提交J\xe2\x80\x94并向后工作。有关此内容的(更多)更多信息,请参阅Think Like (a) Git

\n\n

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

\n\n
               O--P   <-- name5\n              /\n             N   <-- name4\n            /\n...--F--G--H--I---M   <-- name1\n         \\       /\n          J-----K   <-- name2\n           \\\n            L   <-- name3\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们可以选出这些名称之一,例如name4name3,并询问:哪些提交可以通过该名称找到,但不能通过任何其他名称找到? 如果我们选择name3答案是 commit L。如果我们选择name4,答案是根本没有提交:名称为name4commit 的提交NN可以通过从 at 开始name5并向后工作来找到提交。

\n\n

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

\n\n

最受欢迎的答案却截然不同。这都是关于遍历提交图而不跟踪合并的两条腿,并且不显示任何合并的提交。例如,如果我们从 开始name1,我们不会显示M(它是合并),但假设合并的第一个M父级是 commit I,我们甚至不会查看提交JK。我们最终将显示 commit I,以及 commit HGF等,\xe2\x80\x94 这些都不是合并提交,并且所有这些都可以通过从 开始M并向后工作来访问,仅访问每个合并提交的第一个父级。

\n\n

例如,最流行的答案非常适合查看master何时master打算成为仅合并分支。如果所有“实际工作”都是在随后合并到的侧分支上完成的master,我们将有这样的模式:

\n\n
I---------M---------N   <-- master\n \\       / \\       /\n  o--o--o   o--o--o\n
Run Code Online (Sandbox Code Playgroud)\n\n

其中所有非字母命名的o提交都是普通(非合并)提交,并且MN是合并提交。提交I是初始提交:有史以来的第一次提交,也是唯一应该master 上而不是合并提交的提交。如果显示除 之外的git log --first-parent --no-merges master任何提交,我们会遇到这样的情况: I

\n\n
I---------M----*----N   <-- master\n \\       / \\       /\n  o--o--o   o--o--o\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

简而言之,流行的答案非常适合查看master何时master仅合并,但对于其他情况则不那么好。 接受的答案适用于这些其他情况。

\n\n

远程跟踪名称是否类似于origin/master 分支名称?

\n\n

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

\n\n
git checkout master\n...\ngit status\n
Run Code Online (Sandbox Code Playgroud)\n\n

on branch master,但是:

\n\n
git checkout origin/master\n...\ngit status\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

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

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

\n\n

这是对原始问题的一个很好的解决方案的原因是我们在这里对其他人的分支名称感兴趣,而不是我们的分支名称。但这意味着我们已将分支定义为分支名称之外的其他内容。没关系:当你做这件事的时候,要意识到你正在做这件事。

\n\n

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

\n\n

我们在这里真正寻找的是一系列我称之为“ daglet”的东西:请参阅“分支”到底是什么意思? 也就是说,我们正在寻找整个提交图的某个子集中的片段

\n\n

每当我们让 Git 查看分支名称(如 )master、标签名称(如v2.1)或远程跟踪名称(如 )时origin/master,我们倾向于让 Git 告诉我们有关该提交以及我们可以从该提交获得的每个提交:从那里开始,并向后工作。

\n\n

在数学中,这称为遍历图。Git 的提交图是有向无环图DAG,这种图特别适合步行。当遍历这样的图时,人们将访问通过所使用的路径可到达的每个图顶点。Git 图中的顶点是提交,边是从每个子项到每个父项的 arcs\xe2\x80\x94 单向链接\xe2\x80\x94。(这就是Think Like (a) Git发挥作用的地方。弧线的单向性质意味着 Git 必须向后工作,从子级到父级。)

\n\n

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

\n\n

他们所做的图遍历具体是:给定一组起始点提交(可能只是一个提交,可能是一堆哈希 ID,可能是一堆解析为哈希 ID 的名称),遍历图,访问提交。特定的指令,例如--notor 前缀^、 or --ancestry-path、 or ,以某种方式--first-parent修改图形遍历。

\n\n

当他们进行图形遍历时,他们会访问每个提交。但他们只打印步行提交的一些选定子集。诸如--no-merges或 之类的指令--before <date>告诉图形遍历代码承诺print

\n\n

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

\n\n
git log master\n
Run Code Online (Sandbox Code Playgroud)\n\n

将名称转换master为原始哈希 ID 并将该哈希 ID 放入队列中。或者:

\n\n
git log master develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

将两个名称都转换为哈希 ID,\xe2\x80\x94 假设这是两个不同的哈希 ID\xe2\x80\x94 将两者放入队列中。

\n\n

该队列中提交的优先级由更多参数决定。例如,参数--author-date-order告诉git loggit rev-list使用作者时间戳,而不是提交者时间戳。默认情况下是使用提交者时间戳并选择最新的提交:具有最高数字日期的提交。因此,假设这些解析为两个不同的提交,Git 将显示master develop出现的那个,因为它将位于队列的前面。

\n\n

无论如何,修订行走代码现在循环运行:

\n\n
    \n
  • 当队列中有提交时:\n\n
      \n
    • 删除第一个队列条目。
    • \n
    • 决定是否打印此提交。例如,--no-merges:如果是合并提交,则不打印任何内容;--before:如果日期没有早于指定时间,则不打印任何内容。如果打印未被抑制,则打印提交: for git log, 显示其日志; 对于git rev-list,打印其哈希 ID。
    • \n
    • 将此提交的部分或全部父提交放入队列中(只要它现在不存在,并且尚未被访问2)。正常的默认设置是放入所有父项。使用会抑制除每个合并的第一个--first-parent父级之外的所有合并。
    • \n
  • \n
\n\n

(此时, 和git loggit rev-list可以在有或没有父重写的情况下进行历史简化,但我们将在这里跳过它。)

\n\n

对于一个简单的链,例如从 at 开始HEAD并在没有合并提交时向后工作,队列中始终在循环顶部有一个提交。有一个提交,因此我们将其弹出并打印它,并将其(单个)父级放入队列中并再次循环,然后我们向后跟踪链,直到到达第一个提交,或者用户厌倦了git log输出并退出程序。在这种情况下,任何排序选项都不重要:只有一个提交要显示。

\n\n

当存在合并并且我们遵循父级\xe2\x80\x94合并的两条“腿”\xe2\x80\x94时,或者当您给出一个git loggit rev-list多个起始提交时,排序选项很重要。

\n\n

--not最后,考虑提交说明符或^前面的效果。它们有几种写法:

\n\n
git log master --not develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者:

\n\n
git log ^develop master\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者:

\n\n
git log develop..master\n
Run Code Online (Sandbox Code Playgroud)\n\n

都是同一个意思。The--not类似于前缀,^只不过它适用于多个名称:

\n\n
git log ^branch1 ^branch2 branch3\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
git log --not branch1 branch2 branch3\n
Run Code Online (Sandbox Code Playgroud)\n\n

意味着不是branch1,不是branch2,不是branch3,你必须用一秒钟--not才能将其关闭:

\n\n
git log --not branch1 branch2 --not branch3\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
git log --not branch1 branch2 ^branch3\n
Run Code Online (Sandbox Code Playgroud)\n\n

意思是不是branch1,不是branch2,是branch3,如果你想混淆的话

\n\n

这些都是通过影响图形行走来起作用的。当git loggit rev-list遍历图表时,它确保不将从任何否定引用可到达的任何提交放入优先级队列。git log master ^master(事实上​​,它们也会影响启动设置:例如,否定的提交无法直接从命令行进入优先级队列,因此不会显示任何内容。)

\n\n

gitrevisions 文档中描述的所有奇特语法都使用了它,您可以通过简单的调用来公开它git rev-parse。例如:

\n\n
$ git rev-parse origin/pu...origin/master     # note: three dots\nb34789c0b0d3b137f0bb516b417bd8d75e0cb306\nfc307aa3771ece59e174157510c6db6f0d4b40ec\n^b34789c0b0d3b137f0bb516b417bd8d75e0cb306\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

同样:

\n\n
$ git rev-parse master^^@\n2c42fb76531f4565b5434e46102e6d85a0861738\n2f0a093dd640e0dad0b261dae2427f2541b5426c\n
Run Code Online (Sandbox Code Playgroud)\n\n

语法^@意味着给定提交的所有父级,而master^\xe2\x80\x94 本身(由分支名称master\xe2\x80\x94 选择的提交的第一个父级)是合并提交,因此它有两个父级。这是两个父母。和:

\n\n
$ git rev-parse master^^!\n0b07eecf6ed9334f09d6624732a4af2da03e38eb\n^2c42fb76531f4565b5434e46102e6d85a0861738\n^2f0a093dd640e0dad0b261dae2427f2541b5426c\n
Run Code Online (Sandbox Code Playgroud)\n\n

后缀^!表示提交本身,但不表示其父级。在这种情况下,master^0b07eecf6...。我们已经看到父母双方都有后缀^@;他们又来了,但这一次,被否定了。

\n\n
\n\n

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

\n\n

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

\n\n
\n\n

如果您已经做到了这一步,那么您现在已经了解了很多git log

\n\n

概括:

\n\n
    \n
  • git log是关于在遍历图表的部分或全部部分时显示一些选定的提交。
  • \n
  • --no-merges在已接受的答案和当前排名最高的答案中都发现了这一论点,它禁止显示一些执行的提交。
  • \n
  • --first-parent来自当前排名最高的答案的论点在图行走本身期间抑制了图的某些部分的行走。
  • \n
  • 命令行参数的前缀--not(如接受的答案中所使用的那样)从一开始就完全禁止访问图表的某些部分。
  • \n
\n\n

使用这些功能,我们可以得到两个不同问题的我们喜欢的答案。

\n