这并不是一个确切的答案,但我需要访问格式和大量空间。我尝试描述我认为的两个最佳答案背后的理论:
被接受的答案和
当前排名最高的答案。但实际上,它们回答了不同的问题。
在Git中,提交往往同时“存在于”多个分支上。事实上,这正是这个问题的主要内容。给定:
...--F--G--H <-- master
\
I--J <-- develop
在实际的Git哈希ID中,大写字母代表了它们所表示的提交。因此,在我们的git log输出中,我们经常会寻找仅包含提交H或仅包含提交I-J的情况。提交从G开始都在两个分支上,因此我们想要排除它们。
(请注意,在这样绘制的图形中,新的提交位于右侧。名称选择该行最右边的单个提交。每个提交都有一个父提交,即其左侧的提交:H的父提交是G,J的父提交是I。I的父提交再次是G。G的父提交是F,而F的父提交在此处未显示:它是“...”部分的一部分。)
对于这种特别简单的情况,我们可以使用以下命令:
git log master..develop
查看I-J
,或者:
git log develop..master
仅查看H
。冒号后的右侧名称告诉Git:“是的,这些提交”。冒号前的左侧名称告诉Git:“不,不是这些提交”。Git从结尾——从提交H
或提交J
开始,并向后工作。有关此更多信息,请参见像Git一样思考。
原问题的表述方式是要查找可以从一个特定名称到达的提交,但不能从同一类别中的任何其他名称到达。也就是说,如果我们有一个更复杂的图形:
O--P <-- name5
/
N <-- name4
/
...--F--G--H--I---M <-- name1
\ /
J-----K <-- name2
\
L <-- name3
我们可以挑选其中一个名称,例如
name4
或
name3
,然后问:
哪些提交可以通过该名称找到,但不能通过任何其他名称找到?如果我们选择
name3
,答案是提交
L
。如果我们选择
name4
,则根本没有提交:名称
name4
指的提交是提交
N
,但是可以通过从
name5
开始向后工作来找到提交
N
。
接受的答案使用远程跟踪名称而不是分支名称,并允许您指定一个名称(拼写为
origin/merge-only
)作为所选名称,并查看该命名空间中的所有其他名称。它还避免显示合并:如果我们将
name1
作为“感兴趣的名称”,并说
显示可从name1
到达但不能从任何其他名称到达的提交,我们将看到合并提交
M
以及常规提交
I
。
最流行的答案与此大不相同。它完全是关于在提交图中遍历而不跟随合并的两个分支,以及不显示任何是合并的提交。例如,如果我们以
name1
开始,我们就不会显示
M
(它是一个合并),但是假设合并
M
的第一个父提交是提交
I
,我们甚至不会查看提交
J
和
K
。我们最终会显示提交
I
,以及提交
H
、
G
、
F
等,这些都不是合并提交,并且都可以通过从
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
说:在主分支上,但是:
git checkout origin/master
...
git status
说:在 origin/master 处分离了 HEAD。我更倾向于同意 git checkout / git switch:origin/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图中的顶点是提交,边缘是弧-单向链接-从每个子级到每个父级。(这就是
像Git一样思考的原因。弧的单向性意味着Git必须反向工作,从子级到父级。)
两个主要的Git命令用于图形遍历是
git log
和
git rev-list
。这些命令非常相似 - 实际上它们大部分都是由相同的源文件构建的 - 但它们的输出不同:
git log
生成人类可读的输出,而
git rev-list
生成供其他Git程序阅读的输出。
1这两个命令都执行这种类型的图形遍历。
他们执行的图形遍历具体来说是:给定一些起始点提交集(可能只有一个提交,也可能有一堆哈希ID,也可能有一堆解析为哈希ID的名称),遍历图形,访问提交。特定的指令,例如
--not
或前缀
^
,或
--ancestry-path
,或
--first-parent
,以某种方式修改了
图形遍历。
当他们进行图形遍历时,他们会访问每个提交。但是他们只会打印一些选择的走过的提交。例如
--no-merges
或
--before <date>
指令告诉图形遍历代码要打印哪些提交。
为了一个接一个地访问这些提交,这两个命令使用
优先队列。你运行
git log
或
git rev-list
并给它一些起始提交点。然后将这些提交放入优先队列中。例如,一个简单的例子:
git log master
将名称
master
转换为原始哈希ID,并将该哈希ID放入队列中。 或者:
git log master develop
将这两个名称转换为哈希ID,并假定这是两个不同的哈希ID,然后将两者都放入队列中。
此队列中提交的优先级由更多参数确定。例如,参数--author-date-order
告诉git log
或git rev-list
使用作者时间戳,而不是提交者时间戳。默认情况下,使用提交者时间戳并选择最新日期的提交:具有最高数字日期的提交。因此,在master develop
的情况下,假设它们解析为两个不同的提交,则Git将首先显示稍后到达的提交,因为它将位于队列的前面。
无论如何,现在修订步进代码在循环中运行:
- 当队列中有提交时:
- 移除第一个队列条目。
- 决定是否打印此提交。例如,
--no-merges
: 如果是合并提交,则不打印任何内容; --before
: 如果其日期不早于指定时间,则不打印任何内容。如果没有被抑制打印,则打印提交:对于git log
,显示其日志;对于git rev-list
,打印其哈希ID。
- 将此提交的一些或全部父提交放入队列中(只要它现在不在那里,并且尚未被访问过2)。正常默认值是放入所有父提交。使用
--first-parent
抑制除每个合并的第一个父提交之外的所有父提交。
(现在,git log
和git rev-list
都可以进行历史简化,无论是否进行了父重写,但我们将跳过这部分内容。)
对于简单的链式结构,例如在没有合并提交时从
HEAD
开始向后工作,队列始终在循环顶部有一个提交。有一个提交,所以我们将其弹出并打印它,并将其(唯一的)父提交放入队列中再次循环,我们反向跟踪链直到达到第一个提交,或者用户厌倦了
git log
输出并退出程序。在这种情况下,任何排序选项都无关紧要:只有一个提交需要显示。
当存在合并且我们同时跟随两个父提交 - 合并的两个“分支”,或者当您给
git log
或
git rev-list
提供多个起始提交时,排序选项就很重要了。
最后,考虑在提交指定符前面加上
--not
或
^
的影响。这些有几种编写方式:
git log master --not develop
或者:
git log ^develop master
或者:
git log develop..master
所有的意思都是相同的。 --not
就像前缀 ^
,只不过它适用于多个名称:
git log ^branch1 ^branch2 branch3
表示 不是分支1,不是分支2,是分支3; 但是:
git log --not branch1 branch2 branch3
意思是不是分支1,不是分支2,不是分支3,你必须使用第二个--not
来关闭它:
git log --not branch1 branch2 --not branch3
这有点棘手。两个“not”指令通过异或组合在一起,所以如果你真的想要,你可以写成:
git log --not branch1 branch2 ^branch3
如果你想要表示不是branch1,不是branch2,是branch3,可以使用这个语法,同时进行混淆。
所有这些语法都通过影响图形遍历来实现。当git log
或git rev-list
遍历图形时,它确保不把任何可从任何否定引用到达的提交放入优先级队列中。(实际上,它们也影响起始设置:否定的提交不能直接从命令行进入优先级队列,因此git log master ^master
不显示任何内容。)
所有在gitrevisions文档中描述的花式语法都利用了这一点,你可以通过简单调用git rev-parse
来暴露它。例如:
$ git rev-parse origin/pu...origin/master
b34789c0b0d3b137f0bb516b417bd8d75e0cb306
fc307aa3771ece59e174157510c6db6f0d4b40ec
^b34789c0b0d3b137f0bb516b417bd8d75e0cb306
三个点的语法表示
从左侧或右侧可达的提交,但不包括从两边都可达的提交。在这种情况下,
origin/master
提交本身可从
origin/pu
(
fc307aa37...
)到达,因此
origin/master
哈希值会出现两次,一次是否定形式,但实际上,Git通过放置两个非否定的哈希ID和一个负数来实现三个点的语法, represented by the
^
prefix.
类似地:
$ git rev-parse master^^@
2c42fb76531f4565b5434e46102e6d85a0861738
2f0a093dd640e0dad0b261dae2427f2541b5426c
“^@”语法表示“给定提交的所有父级”,而“master^”本身——由分支名称“master”选择的提交的第一个父级——是合并提交,因此它有两个父级。这些是两个父级。
$ git rev-parse master^^!
0b07eecf6ed9334f09d6624732a4af2da03e38eb
^2c42fb76531f4565b5434e46102e6d85a0861738
^2f0a093dd640e0dad0b261dae2427f2541b5426c
< p >
^!
后缀表示
提交本身,但不包括其父提交 。在这种情况下,
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
,从一开始就完全抑制了访问图形的某些部分。
通过使用这些功能,我们得到了两个不同问题的答案。
git log ^branch1 ^branch2 merge-only-branch
语法呢? - Karl Bielefeldtgit log ^branch1 ^branch2 merge-only-branch
需要列出每个分支。可以通过巧妙地使用bash/grep(请参见我的答案)避免这种情况,但我希望git有一些内置的支持。您正确地假设所有合并自分支都是远程的(仅本地的分支对其他开发人员来说就像不存在一样)。使用--no-merges
将省略任何已合并并且原始合并自分支已删除的提交,因此这意味着合并自分支会保留到它们被合并到非仅合并分支(即主分支)。 - jimmyorr