前言

当我们在做 backport 时,常常会面临这种情况——难以找到一个 commit 的上下文。在 backport 时,我们往往会盯住某一个模块,去查看该模块的 commit history 中有哪些上游更新,从而忽略了某些逻辑上相关而路径上不相关的内容。举个例子:我在尝试上游更新 arch/arm64/lib/ 时,查看的是该目录的 commit history ,从而忽略了针对 lib/ 或者是 include/linux/ 目录修改的前置 commit 。这种忽略轻则导致编译失败,重则导致隐性的不生效。
所以,有没有什么办法能够查看一个 commit 相关的上下文呢?

另外,当我们在做 backport 时,有时还会面临这样一种情况:我明确的想要某个 feature 或者 fix ,但是上游中并没有,那么有没有什么比上游还新的地方,可能有我想要的东西呢?

↑ 为了解决上述问题,隆重请出 Linux Kernel Mailing List (LKML) :

Linux Kernel Mailing List(LKML)是 Linux 内核开发的主要邮件列表,它由 Linus Torvalds 于 1991 年创建,目的是协调 Linux 内核的开发和维护。

LKML 是一个公开的邮件列表,任何人都可以订阅并参与讨论。内核开发人员、Linux 用户、学生、研究人员等等都可以在这个列表上发帖,并且每个帖子都会被发送到订阅列表的所有人手中。订阅者可以回复这些帖子,对其中的问题提出自己的看法或建议,或者对代码进行评审和修复。

在 LKML 上,讨论的主题涉及 Linux 内核的各个方面,包括设计、开发、维护、测试和发布。邮件列表中的讨论非常活跃,许多内核开发者和爱好者都会在这里讨论 Linux 内核的各个方面。

LKML 是 Linux 内核开发的重要组成部分,也是 Linux 社区交流的重要平台之一。在这里,开发者们可以共同探讨 Linux 内核的发展方向和技术细节,使得 Linux 内核不断发展壮大。

(来自 ChatGPT)

↑ 呐,没错,Linux 内核开发的主战场还是在使用老掉牙的邮件系统。

接下来,我们将从 Mailing List 开始,简单的介绍一下该如何优雅的从内核最上游摘取补丁。

Mailing List 如何工作?

众所周知,Mailing List 是可以订阅的,订阅后可以收到大家的讨论邮件。它的订阅方式在网上有许多的介绍,这里就不再赘述了。

现在,让我们来关心一个最基本的问题:邮件,大家都知道,是一个人发给另一个人的,那么为什么我订阅了邮件列表我就可以收到大家的讨论邮件?难道大家在发送邮件时都手动指定了我为收件人吗?

并不是这样的,这个过程是这样实现的:每个人在发送邮件时,需要额外抄送给“邮件列表”,“邮件列表”则会把收到的邮件再转发给它的订阅者,于是这封邮件就被“广播”了出去。

常见的邮件列表可以在这里找到。
不同的邮件列表往往有不同的主题(不同子系统/平台),如果你只关心某些特定的内容,查看/订阅特定的邮件列表即可。
当然,还有一个主邮件列表,叫做 linux-kernel ,其邮件地址为 [email protected] ,它的流量非常大,一天往往有上百封邮件,因此除非你的邮箱容量足够大,否则千万不要订阅 xD 。

以下是一个发送邮件的样例:

git send-email \
[email protected] \
[email protected] \
[email protected] \
path/to/my/mail

这封邮件的主收件人是 [email protected] ,并同时抄送给了邮件列表 [email protected][email protected] ,于是这俩邮件列表的订阅者也都会收到这封邮件。(可以看到,不是所有邮件列表都是 host by kernel.org 的,也有许多“第三方”的邮件列表)

往往,在发送内核相关邮件时,除了会抄送给主题相关的“特定邮件列表”,也会抄送给“主邮件列表”一份,这也是“主邮件列表”流量巨大的原因之一。

按照我的理解,邮件列表其实是一种抽象的“论坛”组织形式。不同的邮件列表就像论坛中不同的版块,发送给邮件列表的邮件就像论坛中的帖子。既然是论坛,那它也应该要提供组织邮件之间关系的方法,就好比“主题帖”与“回帖”。接下来我们将会浅浅的看一下邮件协议,来理解一下邮件之间的关系是如何进行组织的。

邮件协议

在这里,我想要基于常见的 RFC 5322 规范对邮件头部的部分字段进行介绍,并结合一下 git send-email 命令。

RFC 5322是一份Internet标准,它规定了电子邮件消息的格式和传输规范。该标准在2008年发布,取代了之前的RFC 2822标准。

RFC 5322描述了电子邮件的组成部分、消息头和消息体、邮件地址的格式、日期和时间格式、消息编码和传输规范等方面的细节。它还包括了对一些早期邮件标准的修订和更新,以适应现代电子邮件的需求。

RFC 5322规定了邮件格式的基本要求,包括了邮件头、主题、发件人和收件人等重要信息。邮件头包含了所有关于邮件的元信息,例如主题、发件人、收件人、抄送、密送、日期和时间等。邮件体则包含了实际的邮件内容。

RFC 5322为电子邮件的传输提供了一些规范,例如SMTP(Simple Mail Transfer Protocol)协议,以确保邮件能够在网络上正确地传输。它还指定了如何使用MIME(Multipurpose Internet Mail Extensions)来支持在电子邮件中包含各种类型的附件和多媒体内容。

总之,RFC 5322为电子邮件的格式和传输提供了一个详细的标准,使不同的电子邮件客户端和服务器能够互相兼容,确保电子邮件能够在互联网上稳定、高效地传输。

(来自 ChatGPT)

收件人 和 抄送

首先,我想先说说邮件头部的 To 字段和 Cc 字段,它们分别对应 git send-email 中的 --to--cc
在常见的网页版邮箱中,To 对应的是“收件人”,而 Cc 对应的是“抄送”。
这俩字段在实际效果上并没有什么区别,被指定在邮箱都能够收到这封邮件。它们之间的区别更多的是在邮件礼仪上。

To 更多的表示希望收件者能够处理和回应这封邮件;而 Cc 则表示这封邮件并非是针对收件者的,邮件内容仅供参考。

ToCc 字段中出现的邮箱,互相都可以看到。也就是说,当你收到一封邮件时,无论你是“收件人”还是“被抄送者”,你都可以看到这封邮件原始的收件人和抄送者有谁。

试想这样一种情况:甲要找乙办事,但是考虑到乙可能不会理他,于是甲就在发送邮件时 Cc 了乙的上级领导丙。于是乙在收到邮件时一看抄送者,“wok 这封邮件我的领导丙也能收到,看来这事不得不认真办”。(←借他人施压)

在 kernel mailing 中,To 一般被设置为直接相关人(比如对应模块的维护者),Cc 则被设置为“可能对此感兴趣的人”以及“邮件列表”。
ToCc 都可以有多个,完全没有问题。

唯一标识符

邮件头部还有一个名为 Message-Id 的字段。这个字段是邮件的唯一标识符,每一封邮件的这个字段内容都应该不同。
这个字段由发送方的客户端或是邮件服务器填入,其内容一般是随机字符串或是由时间戳、随机字符以及发送方邮箱地址拼接而成的一长串。

Message-Id 可能长这样:

[email protected]
[email protected]
30646027.20230425162525.6447fef5d41b97.31335891@mail128-83.atl41.mandrillapp.com

又或者干脆完全随机:

6050123042401634172

总之,它对这封邮件起到唯一标识作用,而且需要在邮件被发送时就准备好,因为接收方可以通过这个字段来判断一些意外情况 e.g. 由于网络原因重复收到了相同的邮件。

当然,Message-Id 的作用远大于此,将在下面继续展开。

回复与引用

手动的引用

先不讲邮件协议,先来讲一种约定俗成。

在回复一封邮件时,一般而言我们会在邮件的标题加上 Re: ,并在邮件内容中使用 >> 来引用想要回复的文本。

举个栗子。

原邮件:

标题:我觉得小明做错了

他把 A 错弄成了 B ,并造成了 C 的结果。这一切的责任都应该由他来承担。

回复邮件:

标题:Re: 我觉得小明做错了

>> 他把 A 错弄成了 B

不对,他是把 A 错弄成了 C 。

>> 并造成了 C 的结果

造成的结果是 D。

>> 这一切的责任都应该由他来承担

是小王叫他这么做的,小王也有责任。

回复邮件的回复邮件:

标题:Re: 我觉得小明做错了

>>>> 他把 A 错弄成了 B
>>
>> 不对,他是把 A 错弄成了 C 。
>>
>>>> 并造成了 C 的结果
>>
>> 造成的结果是 D。
>>
>>>> 这一切的责任都应该由他来承担
>>
>> 是小王叫他这么做的,小王也有责任。

我同意你的观点。

总之,就是使用 >> 来引用之前的邮件内容,并针对性的发表你的意见,从而使对方能够更加高效的理解。
这一切是建立在自觉的基础上的,你完全可以不遵守这种格式,甚至可以捣乱去强行引用对方没说过的话(因为引用的内容你完全也可以自己输入)。
而且,在回复时,对方的一句话很有可能会被拆分为多次引用来加强针对性(比如上面的)。
再者,如果有多次来回回复,>> 可能会越堆越高,影响体验,说不定总有一次会被清掉。

说了这么多,我想表达的就是:这种约定俗成是无法被用来可靠的追踪邮件之间的关系的,邮件协议必须提供一种可靠的组织邮件之间关系的方法。
而这正是我接下来想要介绍的内容。

回复与引用字段

于是,邮件的头部中提供了 In-Reply-ToReferences 两个字段,来可靠的追踪邮件之间的关系。

Basically,它们是靠你在发送邮件时手动指定“回复的 Message-Id”来工作的(一般来说网页版邮箱或者邮件客户端会自动帮你做这件事)。
上面已经说到了,Message-Id 是唯一标识符,那么在回复时通过标识符来指定自己回复的是谁,就非常的可靠了。

举个例子,你收到了一封邮件 A ,假设其 Message-Idaaaaa
现在,你想要回复这封邮件,于是你使用 git send-email 发送邮件 B ,指定 --in-reply-to=aaaaa 来表示你回复的是 A 。
假设你所发送的邮件 B 的 Message-Idbbbbb ,那么对方在收到你的回复后,可以使用 --in-reply-to=bbbbb 来回复你的回复。

在上面的来回中,邮件头部的 In-Reply-To 会被设置为 --in-reply-to= 所指定的值,而 References 则会在先前的基础上 append 上新的 In-Reply-To 值,展开看看就是这样:

邮件 A :

Message-ID: <aaaaa>

(回复给 A )
邮件 B :

Message-ID: <bbbbb>
References: <aaaaa>
In-Reply-To: <aaaaa>

(假设对方继续回复 B )
邮件 C :

Message-ID: <ccccc>
References: <aaaaa>
 <bbbbb>
In-Reply-To: <bbbbb>

(假设你继续回复 C )
邮件 D :

Message-ID: <ddddd>
References: <aaaaa>
 <bbbbb>
 <ccccc>
In-Reply-To: <ccccc>

In-Reply-To 总是指定着想要回复的邮件标识符,而 References 则记录着整个邮件来往中涉及到的邮件标识符。
借助这两个字段,我们完全可以可靠的组织邮件,在邮件之间形成一种树状的关系。如果说那个最初的邮件是论坛的“主题帖”的话,我们就可以通过这两个字段轻松的追踪所有的“回帖”,并建立“回帖”之间的关系。具体的东西等下面介绍 lore.kernel.org 的时候再进一步介绍。

其它

邮件的头部还有一些别的有趣的字段,但是由于它们和接下来的内容关系不大,于是就懒得展开解释了。

比如 Received 能够提供邮件传输过程中的接收记录,你可以轻松的知晓这封邮件经过了哪些邮件服务器。

还有比如 Reply-ToMail-Reply-To 能够指定“如果想要回复这封邮件的话,应该回复给谁”,因为有时发件者的邮箱并不接受回复,想要回复需要发到别的邮箱地址。

当然,还有一些非常基础的字段,比如 FromDateSubject ,它们分别标识了邮件的发送者、发送日期和标题。它们都是属于一看就能明白含义的那种,故也没有什么解释的必要了(不过接下来的内容中它们会出现)。

邮件与补丁

接下来,我想要聊聊 git 生成的补丁。
这里所说的补丁,是指使用 git format-patch 命令生成的 .patch 文件。

从历史上来说,这种补丁从设计之初开始就是为了作为邮件发送的,甚至可以说是量身定制的。

我们可以随便生成两个补丁看看:

# 把我的仓库中最新的两条 commit 作为一组补丁进行生成
$ git format-patch -2
0001-oplus_charger-Initialize-works-before-using-them.patch
0002-dsi_panel-Make-sure-the-gpio-is-valid-before-directi.patch

来看看它们的内容(只截取了部分):

......
From: LibXZR <[email protected]>
Date: Sun, 16 Apr 2023 20:45:31 +0800
Subject: [PATCH 1/2] oplus_charger: Initialize works before using them

......
......
From: LibXZR <[email protected]>
Date: Mon, 17 Apr 2023 12:15:05 +0800
Subject: [PATCH 2/2] dsi_panel: Make sure the gpio is valid before direction
 input

......

可以看到,补丁的头部字段是和邮件格式相对应的,当把补丁作为邮件发送时,这些字段也会自然而然的成为邮件头部的一部分。
再者,补丁文件本身就是纯文本,不包含任何不可识读的二进制数据,被塞进邮件中一点问题也没有(按照规范,补丁是作为邮件的内容(以及部分头部)来发送的,而不是附件,这很重要,不过 git send-email 会自动帮你处理好这一点)。

其实 git am 除了能够打补丁,还支持打邮件(即包含补丁的原始邮件数据),它会自动处理掉邮件格式中的补丁无关内容,从中提取出真正的补丁然后进行应用(下面会进一步介绍)。

小特性:前缀序号

在上面的例子中能够看到 git format-patch 为生成补丁的标题自动加上了如 [PATCH 1/2] 这样的前缀,这其实是一种便于邮件整理的特性。

补丁往往是一系列的,而且是具有前后依赖关系的,在经过了邮件这一中间介质后,这种顺序可能会是错乱或是丢失的。
(你可能会说,我们生成的补丁文件的文件名不是有 00010002 这样的前缀么?这不是标识着顺序么?那为何要多此一举?别忘了,发送邮件时补丁可不是作为附件的,这里的文件名压根就只是给你自己看的,邮件的接收方可看不到)

于是 git format-patch 会自动在你的 commit message 前面加上这种前缀,表示这是某个补丁序列中的第几个补丁(以及这个序列总共有多少个补丁)。别担心,这个前缀在 git am 时会自动被去掉,没什么不良影响。

小特性:前缀版本

有时候,我们会在改进了自己的补丁后重新发送。在这种情况下,又该如何快速区分哪个补丁更新呢?
emmm ,邮件的 Subject 可是由 commit message 生成的啊,难道我得重新写 commit message 吗?

并不需要,其实你只要在 git format-patch 加上选项 -v 即可。

举个例子:

$ git format-patch -2 -v2
v2-0001-oplus_charger-Initialize-works-before-using-them.patch
v2-0002-dsi_panel-Make-sure-the-gpio-is-valid-before-dire.patch
$ cat v2-0001-oplus_charger-Initialize-works-before-using-them.patch | grep Subject
Subject: [PATCH v2 1/2] oplus_charger: Initialize works before using them
cat v2-0002-dsi_panel-Make-sure-the-gpio-is-valid-before-dire.patch | grep Subject 
Subject: [PATCH v2 2/2] dsi_panel: Make sure the gpio is valid before

呐,是不是很方便?
v2 代表第二版补丁,v3 代表第三版,依此类推。当然,这些信息在 git am 时也会自动被去除掉。

小特性:邮件封面

这里的“邮件封面”是对 cover letter 的生硬翻译。

在我们发送一系列的补丁时,总需要总结一下这一系列补丁大概完成了什么样的工作吧?cover letter 起到的就是这样的“总结”作用。

我们可以在 git format-patch 时使用 --cover-letter 选项来让 git 帮我们(部分的)完成这个过程。

$ git format-patch -2 --cover-letter                                                
0000-cover-letter.patch
0001-oplus_charger-Initialize-works-before-using-them.patch
0002-dsi_panel-Make-sure-the-gpio-is-valid-before-directi.patch

生成出来的 cover letter 长这样:

From 86746dbc34d68fd8392064ead51e2ba0b7afc56a Mon Sep 17 00:00:00 2001
From: LibXZR <[email protected]>
Date: Thu, 27 Apr 2023 23:28:57 +0800
Subject: [PATCH 0/2] *** SUBJECT HERE ***

*** BLURB HERE ***

LibXZR (2):
  oplus_charger: Initialize works before using them
  dsi_panel: Make sure the gpio is valid before direction input

 oplus/kernel/charger/oplus_charger.c | 18 ++++++++++--------
 techpack/display/msm/dsi/dsi_panel.c |  2 +-
 2 files changed, 11 insertions(+), 9 deletions(-)

-- 
2.34.1

可以看到,其标题的前缀为 [PATCH 0/2] ,即这个补丁的序号为 0 ,同时这个补丁也没有什么实质上的内容。
没错,这是一个补丁,但是是一个空的补丁,其除了 commit message 以外什么都没有。

它里面的 *** SUBJECT HERE ****** BLURB HERE *** 是让你自己填写标题和内容的地方,就在那里对你的这一系列工作进行一个介绍和总结吧。

嗯,它也是可以通过 git send-email 进行发送的。

小结

在这一部分内容中,我们认识到了 git patch 和邮件之间的血缘关系,也了解到了 git 所提供的邮件标题前缀的一些约定。
接下来,我们将会打开邮件列表,来看看这些东西在实践中是如何被运用的。

lore.kernel.org

lore.kernel.org 是一个邮件列表的归档网站,说白了就是把邮件列表中出现的邮件们保存起来并提供如搜索、关系整理等附加功能。

接下来,我将用一个例子来对 lore 的页面以及 kernel mailing 中的一些规范进行进一步的解释。

这个例子的地址是 https://lore.kernel.org/lkml/[email protected]/ ,你也可以打开这个网址,和我的解释相互对照。

从网址开始

嗯,lore 的页面设计的非常阴间,非常容易让人晕头转向。但是,它的页面是静态的纯文本,因此我们可以简单的通过网址来判断当前处于一个什么样的状态下。

就比如:

这俩,只是在上面地址的基础上简单加上了一个后缀,其页面的内容就有大不同。
这后缀的具体含义将在下面再进一步解释,现在,让我们聚焦于最干净原始地址:

https://lore.kernel.org/lkml/[email protected]/

在这里,前面的 lore.kernel.org 就是这个站的域名,没什么好说的。
再往后,lkml 代表了处于“主邮件列表”中,对于不同的邮件列表,这里的值可能会发生变化,具体可用的邮件列表可以从 lore 的首页点进去然后看一看网址。
再往后,[email protected] 则是这一封邮件的 Message-Id

众所周知,Message-Id 是唯一的,因此 lore 可以使用 Message-Id 来轻松的定位邮件。
所以,我们在这个“原始地址”中看到的页面,其绝大部分内容都由这封邮件的内容组成。

看看页面

我们打开上面的,不带后缀的,干净的“原始地址”。
可以看到,页面主要由三大部分组成。

第一部分,邮件的内容:

↑ 这一部分对邮件的内容以及部分头部信息进行了完整的展示。
由于这个链接指向的邮件是一封 conver letter ,故其只有邮件内容,而没有代码的 diff 部分。

点击上图中的 raw 可以打开这封邮件的原始数据,点击 [thread overview] 则可以跳转到第二部分。

第二部分,“帖子概览”:

↑ 这一部分的内容是根据邮件头部的 In-Reply-ToReferences 字段自动生成的,它反映的是一系列邮件之间的关系,以及当前邮件在这一系列关系中的位置。

可能看着有点乱,但是,看到那些日期了吗?日期所对应的每一行都是一封邮件,无论右边的超链接是人名还是邮件标题,它们都是完整的邮件!
至于什么时候只显示人名,什么时候显示完整标题,下面再说。
你只需要点击右侧的超链接,就可以跳转到这封邮件,并查看其内容。

你可以按照论坛的“主题帖”与“回帖”来理解这种关系:

简单来说,你只需要观察缩进即可。没有缩进的是“主题帖”,也就是一封没有指定 In-Reply-To 的邮件。
具有一层缩进的是“一级回帖”,也就是 In-Reply-To 被设置为“主题帖”的邮件。
具有两层缩进的是“二级回帖”,也就是 In-Reply-To 被设置为“一级回帖”的邮件。
依此类推,缩进越多,所对应的回帖级数也越多。

于是,邮件之间的树形关系就被用这种形式展现出来了,简单而优雅。

那么,为什么这些邮件在显示的时候,有时候只显示发送者的名字,有时候又显示邮件的完整标题呢?
据我的观察,它的显示规律大概是这样的:

  • 当前所在的邮件,无论如何都会被显示为 发送者名字 [this message]
  • 回复与评论性质的邮件,一般只显示发送者名字。
  • 补丁性质的邮件,一般显示标题。(发送者名字作为非超链接放在旁边)

这种总结只是针对大多数情况,并非是完全准确的,就以上面的例子来说,Theodore Y. Ts'o 所发送的“一级回帖”,是一封评论性质的邮件,但是它的标题被显示了(而且不是完整显示,因为它的标题前面本身还有个 Re: )。
↑ 我确实不太能够摸清楚它的显隐规则是怎样的,甚至我觉得它的显隐规则是非常 buggy 的。但是这无论是对于查找邮件还是浏览邮件都没有任何影响,你只需要点进去看一看内容就能清楚地知道这封邮件到底是个啥了。

从这里,我想你能够发现一个重要的规范:对于一系列的补丁,cover letter 一般会先被发送来创建一个“主题帖”,而系列中的其它补丁则会作为“一级回帖”来 In-Reply-To cover letter ,于是这个补丁集就被组织在了一起,查找起来也就方便了许多。 而这,也正是 cover letter 为何会被称为 cover letter 。

把上面的这个例子整理成树形图后,我想应该能够看的更加清楚:

总结:

  • 日期的每一行都是一封邮件。
  • 看缩进在脑子里构建树形图。
  • Cover letter 是主题帖。
  • 补丁集是回帖。
  • 对 cover letter 的评论可能和补丁集混在同一层(“一级回帖”)。
  • 别靠 发送者名字/邮件标题 的显隐来分辨内容,点进去看看才知道到底是啥。

对了,版本不同的补丁(比如 v2 和 v3 ),按照规范是要放在不同的“主题帖”中的。

第三部分,邮件回复

这一部分的内容就相当容易理解了,lore 会自动告诉你如果想要回复这封邮件的话该怎么做。
lore 会自动为你生成 git send-email 命令,并填入相关参数。

平铺的页面

上面的一切,针对的都是不带有后缀的,干净的“原始地址”。

接下来,介绍一下带有特殊后缀的地址,以及 lore 中的跳转操作。

在这里,有俩链接:

它们都可以对这个帖子下的所有内容进行展开,也就是说,把这个“主题帖”下的所有回帖全部铺开显示在一个网页上。

铺开模式有两种:

  • flat :只是将全部回帖的内容平铺开,等于在网址后面加上 /T
  • nested :平铺开并以缩进的形式展示回帖之间的树状关系,等于在网址后面加上 /t

嗯,铺开了就更像论坛了是吧 xD 。

无论是在哪种铺开模式下,你都可以通过点击这里的 permalink 来回到不带有后缀的“原始地址”。

从 lore 的列表中打开邮件 / 从谷歌搜索打开邮件时的页面一般都默认带有 /T 的后缀。
由于我不太喜欢这种铺开的模式,因为看着很乱,于是我从谷歌搜索打开的第一件事就是点击 permalink

有时候 lore 还会自动在链接后面加上页内定位符,比如 #u 。由于这种东西对页面内容没有影响,只是滚动网页罢了,于是这里就不展开介绍了。

打邮件(不推荐)

所谓的打邮件,其实就是用 git am 打补丁,不过呢,是把邮件当成补丁来打(上面提到过了)。

接下来讲一讲该如何下载和应用整个补丁集。

继续上面的例子,我们可以点击 mbox.gz 把整个帖子下载过来(包括“主题帖”以及其下面的所有“回帖”):

↑ 只要是在这个“主题”中,无论是从哪个邮件下载 mbox.gz ,得到的内容都是一样的,都是整个帖子的内容。
所谓整个帖子的内容,其实就是把帖子中的原始邮件文本全部连接在了一起。

↑ 从之前的分析中可以看到,所谓的补丁集,其实就是“主题帖”(cover letter)的“一级回帖”,此时我们下载了整个帖子,因此整个补丁集就已经被包含在这个邮件文件中了。

在解压缩后,我们可以直接应用这个邮件集合,git 会自动处理掉邮件协议中与补丁不相关的部分。

# 下载邮件集,相当于点击 `mbox.gz` 。
$ wget https://lore.kernel.org/lkml/[email protected]/t.mbox.gz
# 解压缩
$ gunzip t.mbox.gz
# 应用
$ git am t.mbox -3

这里的 -3 是在告诉 git am 要使用三方合并的冲突处理方式,也就是像 git cherry-pick 那样处理冲突。我想,没人喜欢用阴间的 --reject 来处理补丁冲突吧?

接下来,git 将会按顺序应用这个邮件集合中的所有补丁内容。

那个,上面也说过了,cover letter 本质上也是一个补丁,只不过这个补丁的内容是空的。
所以,最先被应用的补丁,会是空的 cover letter ?
没错,是这样的:

$ git am t.mbox -3
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

在“打邮件”时,假如你正在应用的是一个补丁集,那遇到这样的情况非常正常,只需要使用 git am --skip 跳过即可。
除了 cover letter 以外,回复和讨论相关的邮件(即没有 diff 内容的邮件)都会被当成空补丁处理,需要手动进行跳过。

哦对了,上面所说的“按顺序应用”,按的并不是 patch 的序号,而是它出现在这个 mbox 文件中的顺序。
这两者的顺序可能是不同的,因此可能会造成意外的问题,这也是我不推荐使用这种方法打补丁的重要原因。

另外,应用单个补丁(而不是整个帖子)也是可行的,你只需要将那个邮件的 raw 文件下载过来即可。

b4 工具

我想,这才是从 lore 拉取补丁的正统方法。

在过去几周中,我一直在开发一个工具,用于从 lore.kernel.org 中获取补丁并执行大多数维护者常见的后处理操作,包括:

- 将补丁重新排列为正确的顺序
- 统计各种后续跟踪信息,例如已审核、已确认等
- 检查是否存在更新的补丁系列修订版本,并自动获取它。
- 这个工具最初被称为 "get-lore-mbox" ,但现在已经成为一个名为 "b4" 的独立项目。你可以在 git.kernel.org 和 pypi 上找到它。

(来自 https://people.kernel.org/monsieuricon/introducing-b4-and-patch-attestation )

这个工具的源码可以在 git.kernel.orgpypi 找到。

安装它非常简单,首先安装 python 和 pip ,然后 pip install b4 即可。

这个工具带有许多内核维护向的功能,你可以打开上面的链接自己去具体阅读一下相关文档。在这里,我们只关心如何使用它来从 lore 上拉取和应用补丁。

非常简单:

b4 am -o - "补丁对应的 Message id " | git am -3

上面这一行命令做了以下几件事:

  • 从 lore 拉取指定的补丁。
  • 将补丁通过管道传递给 git 。
  • 在应用补丁时采用三方合并。

你甚至可以把命令封装成一个脚本:

~/bin/pw

#! /usr/bin/bash
b4 am -o - "$1" | git am -3

确保它在 PATH 里,给足权限,然后像 cherry-pick 一样简单的使用,比如:pw [email protected]

b4 工具在拉取补丁时会自动完成以下事情:

  • 查找所给 Message-Id 对应的“主题帖”。
  • 拉取“主题帖”下的所有补丁邮件(非补丁相关邮件会被直接去除掉)。
  • 去除邮件协议中的非补丁相关部分。
  • 还原补丁的正确顺序。
  • ......

总的来说,开箱即用,非常友好。

补丁的搜索

对于 lore 来说,搜索补丁只需要在其首页或对应 mailing list 的搜索框中输入关键词(比如补丁的标题),然后点击 search all inboxessearch 即可。
只不过,这个搜索体验实在是不怎么友好,经常会出现一大堆的无关内容。

所以,对于搜索补丁,我还是更倾向于使用谷歌。只要随便一个邮件列表归档网站收录了这个补丁,那么我都可以轻松的获得其 Message-Id ,然后反过来用拼凑网址的方法在 lore 上查看它或者直接使用 b4 工具拉取它。

后记

这篇文章粗略的介绍了 Linux Kernel Mailing List ,并展示了邮件与 git 补丁之间千丝万缕的联系。

现在,可以回答前言中的那个问题了:

有没有什么办法能够查看一个 commit 相关的上下文呢?

只需要使用任意方式搜索它的 commit message ,找到它对应的邮件 Message-Id ,然后利用像 lore 这样的邮件列表归档找到它所属的“主题帖”,自然而然的,上下文相关的补丁也都会展示在你的面前了。

当然,除了 lore ,还有许多界面更加友善的邮件列表归档,比如 https://patchwork.kernel.org/
不过它们收录的补丁似乎远不及 lore 来的齐全,而且界面有点太友善以至于没什么介绍的必要了...

参考资料