这不是一个真正的答案,但我需要访问格式和大量空间。我将尝试描述我认为两个最佳答案背后的理论:the accepted one 和the (at least currently) top-ranked one。但事实上,他们回答了不同的问题。
Git 中的提交通常一次“开启”多个分支。事实上,这就是问题的大部分内容。给定:
...--F--G--H <-- master
\
I--J <-- develop
大写字母代表实际的 Git 哈希 ID,我们经常在 @987654334 中寻找 only commit H 或 only commits I-J @ 输出。通过G 向上提交在两个 分支上,所以我们想排除它们。
(请注意,在这样绘制的图表中,较新的提交位于右侧。names 选择该行上最右侧的单个提交。每个提交都有一个父提交,即他们左边的提交:H的父级是G,J的父级是I。I的父级是G。G的父级是@987654343 @ 和 F 有一个父级,这里根本没有显示:它是 ... 部分的一部分。)
对于这种特别简单的情况,我们可以使用:
git log master..develop # note: two dots
查看I-J,或:
git log develop..master # note: two dots
仅查看H。右侧的名称,在两个点之后,告诉 Git:是的,这些提交。两个点之前的左侧名称告诉 Git:不,不是这些提交。 Git 从 end 开始——在提交 H 或提交 J——并且工作向后。有关(更多)关于此的更多信息,请参阅Think Like (a) Git。
原始问题的表述方式是,希望找到可从一个特定名称访问的提交,但不能从同一通用类别中的任何其他名称访问.也就是说,如果我们有一个更复杂的图:
O--P <-- name5
/
N <-- name4
/
...--F--G--H--I---M <-- name1
\ /
J-----K <-- name2
\
L <-- name3
我们可以选择其中一个名称,例如name4 或name3,然后问:可以通过该名称找到哪些提交,但不能通过其他任何名称找到?如果我们选择name3,答案是提交L。如果我们选择name4,答案是根本没有提交:name4 命名的提交是提交 N 但提交 N 可以通过从 name5 开始并向后工作来找到。
接受的答案适用于远程跟踪名称,而不是分支名称,并允许您指定一个(拼写为 origin/merge-only)作为选定名称并查看该命名空间中的所有其他名称。它还避免了显示合并:如果我们选择name1 作为“有趣的名称”,并说显示可以从name1 访问但不能访问任何其他名称的提交,我们将看到合并提交M 以及定期提交 I。
最受欢迎的答案完全不同。这完全是关于遍历提交图不跟随合并的两条腿,并且不显示任何是的提交em> 合并。例如,如果我们以name1 开头,我们不会显示M(这是一个合并),但假设合并M 的first 父级是提交I,我们就赢了甚至不看提交J 和K。我们最终会显示提交I,并且还会提交H、G、F 等等——这些都不是合并提交,并且都可以通过从M 开始并向后工作来访问,仅访问每个合并提交的 first 父级。
最受欢迎的答案非常适合,例如,当 master 打算成为仅合并分支时,查看 master。如果所有“实际工作”都在随后合并到master 的分支上完成,我们将有这样的模式:
I---------M---------N <-- master
\ / \ /
o--o--o o--o--o
其中所有未命名的o 提交都是普通(非合并)提交,M 和N 是合并提交。提交I 是初始提交:有史以来第一次提交,也是唯一一个应该在master 上而不是合并提交的提交。如果git log --first-parent --no-merges master 显示任何提交除了 I,我们有这样的情况:
I---------M----*----N <-- master
\ / \ /
o--o--o o--o--o
我们希望看到直接在 master 上提交的提交 *,而不是通过合并某些功能分支。
简而言之,当 master 仅用于合并时,流行的答案非常适合查看 master,但不适用于其他情况。接受的答案适用于这些其他情况。
远程跟踪名称是否类似于origin/master branch 名称?
Git 的某些部分说它们不是:
git checkout master
...
git status
说on branch master,但是:
git checkout origin/master
...
git status
说HEAD detached at origin/master。我更愿意同意git checkout / git switch:origin/master 不是分支名称,因为你无法“使用”它。
The accepted answer 使用远程跟踪名称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 的远程的远程跟踪名称。
这是对原始问题的一个很好的解决方案的原因是我们在这里对其他人的分支名称感兴趣,而不是我们的分支名称。但这意味着我们将 branch 定义为 我们的分支名称 以外的东西。没关系:当你这样做时,请注意你正在这样做。
git log 遍历提交图的某些部分
我们在这里真正要搜索的是我称之为 daglets 的系列: 参见 What exactly do we mean by "branch"? 也就是说,我们正在整个提交的某个子集中寻找 片段图表。
每当我们让 Git 查看像 master 这样的分支名称、像 v2.1 这样的标签名称或像 origin/master 这样的远程跟踪名称时,我们倾向于让 Git 告诉我们关于该提交的信息 和我们可以从该提交中获得的每个提交:从那里开始,然后向后工作。
在数学中,这被称为walking a graph。 Git 的提交图是有向无环图或DAG,这种图特别适合行走。在遍历这样的图时,将通过所使用的路径访问每个 可到达 的图顶点。 Git 图中的顶点是提交,边是弧线——单向链接——从每个子节点到每个父节点。 (这就是Think Like (a) Git 的用武之地。弧的单向性质意味着 Git 必须从子级到父级向后工作。)
图形遍历的两个主要 Git 命令是 git log 和 git rev-list。这些命令非常相似——实际上它们大多是从相同的源文件构建的——但它们的输出不同:git log 产生供人类阅读的输出,而git rev-list 产生供其他 Git 程序阅读的输出。1 两个命令都执行这种图形遍历。
他们所做的图遍历具体是:给定一些起点提交(可能只是一个提交,可能是一堆哈希 ID,可能是一堆解析为哈希 ID 的名称), 遍历图表,访问提交。特定指令(例如 --not 或前缀 ^、--ancestry-path 或 --first-parent)会以某种方式修改 graph walk。
当他们进行图遍历时,他们会访问每个提交。但他们只 print 一些选定的 subset 步行提交。诸如--no-merges 或--before <date> 之类的指令告诉图形遍历代码哪个提交到print。
为了进行这种访问,一次提交一个,这两个命令使用priority queue。你运行 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 会抑制每个合并的 first 父级以外的所有父级。
(此时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
表示不是branch1,不是branch2,是branch3;但是:
git log --not branch1 branch2 branch3
表示不是branch1,不是branch2,不是branch3,你必须使用第二个--not来关闭它:
git log --not branch1 branch2 --not branch3
这有点尴尬。两个“not”指令通过异或组合,所以如果你真的想要,你可以这样写:
git log --not branch1 branch2 ^branch3
表示不是branch1,不是branch2,是branch3,如果你想obfuscate。
所有这些都是通过影响图形遍历来起作用的。当git log 或git rev-list 遍历图表时,它确保不 将任何可从任何否定 引用访问的提交放入优先级队列。 (事实上,它们也会影响启动设置:否定的提交不能直接从命令行进入优先级队列,因此git log master ^master 什么也不会显示。)
所有在the gitrevisions documentation 中描述的花哨语法都利用了这一点,您可以通过对git rev-parse 的简单调用来公开这一点。例如:
$ git rev-parse origin/pu...origin/master # note: three dots
b34789c0b0d3b137f0bb516b417bd8d75e0cb306
fc307aa3771ece59e174157510c6db6f0d4b40ec
^b34789c0b0d3b137f0bb516b417bd8d75e0cb306
三点语法意味着提交可从左侧或右侧访问,但不包括可从两侧访问的提交。在这种情况下,origin/master 提交 b34789c0b 本身可以从 origin/pu (fc307aa37...) 访问,因此 origin/master 哈希出现两次,一次带有否定,但实际上 Git 通过以下方式实现三点语法放入两个正引用(两个非否定哈希 ID)和一个负引用,由 ^ 前缀表示。
类似:
$ git rev-parse master^^@
2c42fb76531f4565b5434e46102e6d85a0861738
2f0a093dd640e0dad0b261dae2427f2541b5426c
^@ 语法意味着给定提交的所有父级,而master^ 本身——由分支名称master 选择的提交的第一个父级——是一个合并提交,所以它有两个父母。这是两个父母。并且:
$ git rev-parse master^^!
0b07eecf6ed9334f09d6624732a4af2da03e38eb
^2c42fb76531f4565b5434e46102e6d85a0861738
^2f0a093dd640e0dad0b261dae2427f2541b5426c
^! 后缀表示提交本身,但没有其父项。在这种情况下,master^ 是 0b07eecf6...。我们已经看到父母双方都带有^@ 后缀;他们又来了,但这次被否定了。
1许多 Git 程序实际上运行 git rev-list 并带有各种选项,并读取其输出,以了解要使用哪些提交和/或其他 Git 对象。
2因为图是非循环的,如果我们添加约束never show an parent before shows all,可以保证没有任何访问过其孩子的优先级。 --date-order、--author-date-order 和 --topo-order 添加此约束。默认排序顺序(没有名称)没有。如果提交时间戳有问题——例如,如果某些提交是由时钟关闭的计算机“在未来”进行的——这在某些情况下可能会导致输出看起来很奇怪。
如果你做到了这一步,你现在对 git log 了解很多
总结:
-
git log 是关于在遍历图表的部分或全部部分时显示一些选定的提交。
-
--no-merges 参数(在已接受的答案和当前排名靠前的答案中都可以找到)禁止显示 已执行的一些提交。
-
--first-parent 参数来自当前排名靠前的答案,在图遍历过程中抑制遍历图的某些部分。
- 命令行参数的
--not 前缀(在接受的答案中使用)从一开始就禁止访问图表的某些部分。
使用这些功能,我们可以得到我们喜欢的两个不同问题的答案。