如何参与 QEMU 邮件列表讨论¶
主要作者
环境与时效说明
文中命令在 Ubuntu 22.04/24.04 上验证。邮件列表协作的具体要求以目标项目当前的贡献指南为准;本节的 smtp 配置示例仅用于本地验证,请勿在文档中记录真实密码或应用专用密码。
在参与开源项目开发的过程中,向社区提交代码补丁(Patch)是常见的协作方式。许多成熟的开源项目 (如 Linux Kernel、QEMU、Git 等)采用邮件列表(Mailing List)作为主要的代码审查与交流平台。 与 GitHub Pull Request 不同,这类社区通常要求开发者通过电子邮件发送补丁。
git send-email 是 Git 提供的一个强大工具,允许你将 Git 提交直接转换为符合邮件格式的补丁并 发送到指定的邮件列表。虽然配置过程稍显复杂,但一旦设置完成,即可高效、规范地参与主流开源社区的 协作。
本文将详细介绍如何在 Ubuntu 系统下安装、配置 git send-email,并使用它向 QEMU 上游提交补丁, 以及参加邮件讨论。
安装 Git Email¶
Ubuntu 默认不会安装完整版的 git(git 本身通常已随系统安装,但 git-email 需要单独安装),
因此还需要再装一次 git-email:
如果 apt update 报 Release file 404,通常是当前发行版已经 EOL(例如 Ubuntu 23.10
mantic),仓库已被官方下线:请升级系统,或把镜像源换成仍在维护的版本,而不要继续使用
已下线的仓库。
典型的 apt 404 报错(已 EOL 的发行版)
Err:9 https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports mantic-security Release
404 Not Found [IP: 101.6.15.130 443]
...
E: The repository 'https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports mantic Release' no longer has a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
E: The repository 'https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports mantic-updates Release' no longer has a Release file.
如果需要更新的 Git 版本,也可以手动添加 Git 官方的 PPA,再次安装:
该 PPA 由 Git 社区维护,提供较新的稳定版 Git 及其扩展组件,推荐长期使用。
配置 Git Email¶
首先我们需要准备一个支持 SMTP 的邮箱。国内环境下推荐 QQ 邮箱 / 163 邮箱 / 腾讯企业邮: 它们在国内网络下连通性好,也都提供"授权码 / 应用专用密码"机制。
需要注意 Gmail:它在国内网络环境下不稳定,而且已经不支持"低安全性应用密码"(Less Secure Apps)。如果确实要用 Gmail 发补丁,必须先在 Google 账号里开启两步验证,再生成一个 应用专用密码(App Password),用它代替登录密码。
选定邮箱后,需要在邮箱网页端的设置里开启 SMTP 服务,并获取应用专用密码 / 授权码 (不是邮箱的登录密码)。各家的 SMTP 主机与端口如下,具体以服务商当前的帮助文档为准:
| 邮箱服务商 | SMTP 主机 | 端口与加密方式 | 备注 |
|---|---|---|---|
| QQ 邮箱 | smtp.qq.com |
465(ssl)/ 587(tls) |
需在设置中开启 SMTP 服务并获取授权码 |
| 163 邮箱 | smtp.163.com |
465(ssl) |
需开启 SMTP 服务并使用客户端授权码 |
| 腾讯企业邮 | smtp.exmail.qq.com |
465(ssl) |
使用客户端专用密码或登录密码 |
| Gmail | smtp.gmail.com |
465(ssl)/ 587(tls) |
需先开启两步验证并生成 App Password |
sendemail.smtpEncryption 只接受 ssl、tls、none 三个值(注意:写成 stl 是拼写错误,
Git 会直接报错拒绝)。端口与取值的对应关系如下:
| 端口 | 加密方式 | sendemail.smtpEncryption |
|---|---|---|
| 465 | 隐式 SSL/TLS(SMTPS,连接建立即加密) | ssl |
| 587 | STARTTLS(先明文建连,再升级为 TLS,提交端口) | tls |
| 25 | STARTTLS(传统端口,服务商是否支持需确认) | tls |
配置 git email 绑定自己的邮箱,这里推荐使用命令行,而不是直接修改 .gitconfig,避免配置出错:
git config --global sendemail.smtpEncryption ssl
git config --global sendemail.smtpServer smtp.qq.com
git config --global sendemail.smtpServerPort 465
git config --global sendemail.smtpUser '<your email>'
git config --global sendemail.smtpPass '<应用专用密码>'
为什么密码要用单引号,且不能多写一个 =
密码 / 授权码里常含 !、$、\ 等会被 shell 解释的字符,用单引号包住可以避免被
shell 展开。另外 git config 的写法是 key value,中间没有等号:写成
git config --global sendemail.smtpPass = 'xxx' 会把密码真的设成 = xxx,发送时认证必然失败。
上面的 <> 只是文档里的占位约定,实际执行时请替换成你的真实信息,并去掉尖括号(在 shell
里 <、> 是重定向符号,保留它们会被 shell 当成重定向而不是参数)。配置完毕以后,可以
cat ~/.gitconfig 检查一下:
[sendemail]
smtpEncryption = ssl
smtpServer = smtp.qq.com
smtpServerPort = 465
smtpUser = <your email>
smtpPass = *****
不要把真实密码写进文档或仓库
.gitconfig 中明文存储密码存在安全风险,也不要把含密码的配置粘贴到 Issue、文档或
聊天记录里。若担心泄露,可考虑使用凭据助手,或在每次发送时用 --smtp-pass 参数交互输入。
编辑补丁与发送¶
生成补丁¶
生成补丁系列时,把提交范围、版本号与输出目录一次性写清楚:
# 以 HEAD~3..HEAD 这 3 个提交生成 v2 补丁系列,输出到 outgoing/
git format-patch -v2 --cover-letter --thread --subject-prefix='PATCH' -o outgoing/ HEAD~3
参数说明:
-v2:生成第二版补丁(Subject 前缀为[PATCH v2 ...])。初次提交时可以不写;--subject-prefix='PATCH':为补丁插入统一的 Subject 前缀,常见取值还有RFC(征求意见稿) 或PATCH RFC这类组合,注意用引号包住;--cover-letter:额外生成序号为0000的封面信(cover letter),用于写整个补丁系列的背景、 设计取舍与测试方法。它是审查者读到的第一封邮件,发送前请把模板里的占位内容替换成真实说明;--thread:让补丁系列以同一个邮件线程发送(用--in-reply-to串起来),方便审查者按顺序阅读;-o outgoing/:把生成的文件输出到outgoing/目录,避免污染工作区;
不要照抄 HEAD~<number>
< 和 > 在 shell 中是重定向符号,git format-patch HEAD~<number> 会被 shell 当成
重定向而不是参数,命令直接失败。请写具体数字(如 HEAD~3),或使用 main..HEAD 这类范围
表达式。同理,下面命令示例里所有 <...> 占位符都必须用单引号包住,否则 shell 会把尖括号
当成重定向;唯一例外是 --in-reply-to 的 Message-Id——那里需要保留两侧尖括号(示例中已经用
单引号包好)。
关于 DCO 与 Signed-off-by¶
Signed-off-by: 对应 DCO(Developer Certificate of Origin,开发者源证书),表示你有权提交
这份代码,并且愿意以项目许可证分发它。它必须和你自己的署名一致,一般有三种落地方式:
- 提交时签名(推荐):
git commit -s,Git 会自动在 commit message 末尾追加Signed-off-by: 你的名字 <邮箱>,把签名固化在提交里,后续每次format-patch都会带上; - 让 format-patch 默认补签名:
git config --global format.signoff true,此后git format-patch会自动加上该行;这是"忘记手动签名"的兜底; - 单次补签名:
git format-patch -s ...。注意-s只对当次生成的补丁生效,如果你在多 个版本之间来回切换、或者忘了写,很容易出现某一版缺少Signed-off-by:的情况——所以更推荐 直接用git commit -s把签名固化在提交里。
如果补丁缺少 Signed-off-by:,checkpatch.pl 会报错,邮件列表也可能直接拒收。此时应回到提交
阶段用 git commit --amend -s 补签名,再重新生成补丁。
预演、自测与正式发送¶
邮件一旦发出就无法撤回,请按以下顺序操作:
--dry-run预演:只打印邮件与收件人列表,不真正投递;- 真正发给自己:不带
--dry-run,用--annotate逐封检查 Subject、收件人与正文,确认后发到自己的邮箱; - 确认收信后再正式发送:先在自己的收件箱检查邮件、补丁内容与线程关系,再向维护者和邮件列表投递。
先完成预演和自测,下面两条命令的收件人都应替换为自己的真实邮箱:
# 1. 预演:只显示将要发送的邮件,不实际投递
git send-email --dry-run \
--no-to --no-cc --no-bcc --suppress-cc=all \
--to='<your own email>' \
outgoing/*.patch
# 2. 真正发给自己:检查每封邮件,确认最终收件人列表中只有自己的邮箱
git send-email --annotate --confirm=always \
--no-to --no-cc --no-bcc --suppress-cc=all \
--to='<your own email>' \
outgoing/*.patch
自测时检查最终收件人列表
按 Git 官方文档,--dry-run 不会实际发送邮件,
不能验证 SMTP 投递或收件箱中的显示效果。上面的 --no-to、--no-cc、--no-bcc 清空配置中的
默认收件人,--suppress-cc=all 关闭自动抄送。补丁头部或自定义收件人命令仍可能带入其他地址,
因此必须逐封核对 --confirm=always 显示的最终列表;若出现他人地址,先取消并检查来源,修正后重新预演。
停在这里检查自己的收件箱:确认所有邮件已经收到、正文与补丁没有乱码、线程关系正确。
自测成功后,再通过 ./scripts/get_maintainer.pl PATCH_FILE 获取 QEMU 维护者和抄送对象,执行正式投递:
# 3. 仅在自测收信确认后,逐封检查并正式发送
git send-email --annotate --confirm=always \
--to='<maintainer email>' \
--cc='<mailing list / reviewer email>' \
outgoing/*.patch
发送成功以后,终端会输出:
如果发送失败,可以在 git send-email 后面的参数选项里增加 --smtp-debug 1 排查失败原因。
首次发送与列表审核
首次发送邮件,可以先发送给自己的邮箱,检查能否正常发送。有些开源社区邮件列表,第一次向其 发邮件需要审核;如果没有立即在归档中看到自己的邮件,请耐心等待一下。
发 v2:新版本要另开线程¶
重发补丁系列时不要用 --in-reply-to 把它挂到上一版下面。QEMU 官方提交指南的原文是:
Patches are easier to find if they start a new top-level thread, rather than being buried in-reply-to another existing thread. (补丁如果开启新的顶层线程,会比埋在另一个已有线程里更容易被找到。)
版本的区分靠标题里的版本号和 cover letter 中的变更日志,而不是靠回信关系:
# 先用 format-patch 生成 v2:-v2 是 format-patch 的选项,
# 它把版本号写进标题([PATCH v2 0/3] ...)与文件名
git format-patch -v2 --cover-letter -o outgoing/ HEAD~3
# 再发送生成好的文件:版本号已经在文件里,发送命令不必再传 -v2
git send-email \
--to='<maintainer email>' \
--cc=qemu-devel@nongnu.org \
outgoing/v2-*.patch
-v2 属于 format-patch,也可以直接交给 send-email
git send-email 的用法是 git send-email [<options>] (<file>|<directory>)... 或
git send-email [<options>] <format-patch-options>——也就是说,当参数是提交范围时,
它可以接受 git format-patch 的选项(例如直接 git send-email -v2 HEAD~3
让它内部调用 format-patch)。但本节这种"先把补丁生成到 outgoing/、再逐个发送文件"的
用法中,版本号已经写进文件和标题,发送时再传 -v2 只是多余,容易被误读为"发送阶段才决定版本"。
要点:
- 每个版本都是独立的顶层线程。若把 v2 挂在 v1 下面,不同版本会混在同一线程里,Patchwork 与审查者都难以判断哪一封才是当前版本;
- 变更日志(v1 → v2 改了什么)写在 cover letter 里,通常在
---之后、diffstat 之前; --in-reply-to的正确用途是回复某封具体邮件(例如回答审查意见、在某个补丁下追问), 此时它的值取自被回复邮件的Message-Id,建议连两侧尖括号一起给(形如--in-reply-to='<20240101.123456.abc@host>')。
补丁 Tag 规范与自动化工具¶
在开源社区协作中,补丁(patch)的 commit message 末尾通常会附带一组特殊的“标签” (Trailers / Tags),用于记录补丁在审查、测试、合并过程中的参与者与状态变化。这些标签是社区 协作的“签名链”,对于追溯责任、维护补丁质量至关重要。
常见 Tag 规则¶
以下是 Linux Kernel、QEMU 等社区常用的补丁标签:
| Tag | 含义 | 使用场景 |
|---|---|---|
Signed-off-by: |
开发者源证书担保(Developer Certificate of Origin,简称 DCO),表示你有权提交此代码 | 必须由作者和每个转发者添加 |
Reviewed-by: |
代码审查者认为该补丁正确 | 审查者 review 通过后在邮件中明确提供 |
Acked-by: |
维护者/子系统负责人认可合并 | 由相关子系统 maintainer 在回复中提供 |
Tested-by: |
测试者验证该补丁有效 | 他人测试通过后在邮件中明确提供 |
Reported-by: |
问题最初的报告者 | 用于修复 Bug 时致谢报告者 |
Suggested-by: |
方案建议者 | 若思路源自他人讨论 |
Co-developed-by: |
共同开发者 | 必须与对应的 Signed-off-by: 配对出现 |
Fixes: |
修复的旧补丁 commit | 格式 Fixes: SHA12 ("subject") |
Cc: |
邮件抄送对象 | 希望其关注的人员 |
Link: |
相关讨论链接 | 例如 lore.kernel.org 的讨论链接 |
这些标签放在 commit message 末尾的 trailer 区域,每个标签独占一行,标签之间不能有空行分隔。 例如:
e1000e: Prevent crash from legacy interrupt firing after MSI-X enable
When MSI-X is enabled after legacy interrupt ...
Reported-by: Alice <alice@example.com>
Suggested-by: Bob <bob@example.com>
Signed-off-by: You <you@example.com>
Reviewed-by: Charlie <charlie@example.com>
Tested-by: Dave <dave@example.com>
顺序约定
一般约定 Signed-off-by: 按补丁流转顺序排列(作者在最前),其他 tag(如 Reviewed-by、
Tested-by)放在对应 Signed-off-by 之后。Fixes: 通常放在正文说明之后、trailer 区域
的开头。
手动添加标签¶
最直接的方式是使用 git commit --amend 或 git rebase -i 手动编辑 commit message,把
他人在邮件列表中回复的 tag 粘贴进去。但当一个补丁系列(patch series)有几十封邮件、几十个
Reviewed-by 时,手动整理非常繁琐且易出错。
自动化工具:b4¶
b4 是 Linux Kernel 社区官方推荐的补丁管理工具。它可以从 lore.kernel.org 等公开存档中抓取补丁系列与讨论线程,并围绕“准备补丁 — 汇总 tag — 发送补丁” 的工作流,提供多条便捷命令。
安装:
常用命令:
# 【应用他人补丁】从 lore 拉取某个 message-id 对应的补丁系列,输出可供 git am 使用的 mbox
# 占位符必须用单引号包住:< > 在 shell 里是重定向符号,不加引号会在 b4 启动前就报语法错误
b4 am '<message-id-or-lore-url>'
# 【收录他人回复中的 tag】准备 v2 之前,切回该补丁对应的本地分支执行
b4 trailers -u
# 【准备并发送自己的补丁系列】按顺序执行下面五步
b4 prep -n '<branch-name>' # 创建补丁系列工作分支(占位符同样要加引号)
b4 prep --edit-cover # 编辑 cover letter,替换 EDITME 占位内容
b4 prep --auto-to-cc # 自动填充 To/Cc 收件人
b4 prep --check # 送检:checkpatch.pl 等检查
b4 send # 通过 git send-email 正式发送
如何获取 Message-Id
上面多条命令都依赖 MESSAGE_ID。Message-Id 是每封邮件在 email 头部的唯一标识,
形如 20240101.123456.abc@host(以下几种获取方式中,使用时一般去掉两侧的 <>)。
常见的获取方式:
- 从 lore 页面 URL 中截取:例如
https://lore.kernel.org/qemu-devel/20240101.123456.abc@host/,其中qemu-devel/之后、结尾斜杠之前的部分即为 Message-Id。b4也支持直接把整个 lore URL 传给它(如b4 am https://lore.kernel.org/qemu-devel/.../),效果等价。 - 从邮件原文头部读取:在 lore 页面点击
raw查看纯文本邮件,或者在邮件客户端里 选择“查看源码 / Show source”,在头部字段中找到Message-Id:,尖括号中的内容就是 该邮件的 Message-Id。
两种使用场景区分
b4 am作用于 mbox 输出,适合 maintainer 把他人投递的补丁应用到自己的分支,它 不会修改你当前分支上已有的 commit;- 作为 贡献者准备下一版补丁(如 v2)时,应使用
b4 trailers -u把他人回复中的 tag 合并到 本地已有的 commit 上,而不是重新b4 am。
发送前务必完成预检
b4 send 之前必须依次完成 --edit-cover、--auto-to-cc、--check,否则会把带着
EDITME 占位符和空收件人列表的补丁发出去。
自动化工具:patman¶
patman 源自 U-Boot 社区,也被
其他项目采用。它从 commit message 中解析特殊标记(如 Series-to:、Cc: 等),自动生成
补丁、cover letter,并调用 git send-email 发送。
常用命令:
patman 还能抓取之前版本补丁收到的 review tag 自动延续到新版本补丁中,减少重复劳动。
重要礼仪:不要替他人添加 tag
Reviewed-by:、Tested-by:、Acked-by: 等 tag 必须由本人在邮件列表中明确回复
(即对方亲自写出 Reviewed-by: Name <email> 这一行)之后,作者才能将其加入 commit
message。
未经同意替别人加 tag,在社区属于严重失礼,甚至可能被视为伪造背书。正确做法是等对方在邮件
中明确签名,再收录到下一版补丁。使用 b4 / patman 这类工具的好处之一,就是它们只会
识别真实邮件里出现过的 tag,既避免遗漏、也避免“代签”。
例外:Signed-off-by: 是唯一可以(且必须)由你自己为自己添加的 tag,代表你对所提交代码
的 DCO(Developer Certificate of Origin)声明。
小结¶
- 对于 Linux Kernel、QEMU 等使用 lore 存档的社区,优先推荐
b4; - 对于 U-Boot 及其他项目,
patman是成熟选择; - 对于少量补丁,手动维护 trailer 亦可,但务必保证格式正确、顺序合理,且绝不替他人加 tag。
回复邮件¶
在 QEMU、Linux Kernel 这类邮件列表社区里,回复邮件的格式要求非常明确:
- 一律采用 inline reply(引用在上、回复在下):先贴出被回复的那几行引用,紧跟其后写你的回复;这既不是"全部回复写在最前"的 top-post,也不是把回复堆在整封信末尾;
- 不要 top-post(把自己要说的全部写在引用前面)——top-post 在这些社区明确不受欢迎,审查者 需要反复上下滚动才能对上上下文;
- 不要发送 HTML 邮件:邮件列表的过滤器通常会直接拒收,必须使用"纯文本"(plain text)格式;
- 不要重排引用层级:保持
>的层级原样,只裁剪掉与本次回复无关的部分,不要重写别人的引用。
一个合规的回复长这样:
> This is a sample email.
> It changes a behavior of API x, ...
blabla ...
> API y has an issue that ...
blabla ...
方式一:直接用 lore 页面给出的命令(推荐)¶
以 Linux 内核官方的邮件列表存档服务 lore 为例。在 lore 页面上搜索你想要的邮件列表(比如键入
qemu),进入归档后搜索想回复的邮件标题,例如:
打开某封邮件后,拉到页面底部,lore 会把回复所需的命令和收件人直接列出来:
Reply instructions:
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CACGkMEsYDPjPBNmAd=AmZQ2AY46weFC_u8PK=+CSCuUD6W9zYg@mail.gmail.com' \
--to=jasowang@redhat.com \
--cc=dmitry.fleytman@gmail.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
把 /path/to/YOUR_REPLY 换成你自己的回复正文文件,原样执行即可。其中 --in-reply-to 的值就是
被回复邮件的 Message-Id,它保证你的回复挂进原线程,而不是新开一个孤立线程。
发送成功后,终端会输出:
这就表示邮件已经成功发出去了。
b4 send 用于发送补丁系列
b4 send 的官方命令说明
没有 --reply-to 选项。即使用 b4 管理补丁,回复某封审查邮件时也可以使用上面的
git send-email --in-reply-to 方法,或下面的邮件客户端方法。
方式二:用邮件客户端回复(Thunderbird)¶
不想订阅邮件列表、也不想手动拼 git send-email 命令时,可以借助邮件客户端的
Reply to List(回复到邮件列表) 功能。以 Thunderbird 为例:
- lore 邮件页面底部提供
mailto:链接("If your mail client supports setting the In-Reply-To header via mailto: links" 那一行的 mailto 链接),点击它会唤起本地邮件客户端, 并自动带上In-Reply-To头与原始收件人; - 在 Thunderbird 里也可以直接使用
Reply to List(回复到列表)按钮,按列表地址回复; - 无论走哪条路,发送前都必须把撰写格式切回纯文本。
对于中文版的 Thunderbird:
对于英文版的 Thunderbird:
Tools -> Account Settings -> [Account Name] -> Composition & Addressing -> 取消勾选 Compose messages in HTML format
当用纯文本格式发送邮件时取消勾选此项即可。判断正在撰写的邮件是否为纯文本格式很简单: 看【主题】下面是否出现 HTML 格式工具栏。
另外我们可以设置纯文本邮件的自动换行,方便网页端显示,以英文版为例:
再次强调:不要 top-post、不要发 HTML
邮件列表过滤器会拒收 HTML 邮件,top-post 在 QEMU / kernel 风格社区也不受欢迎;请始终使用 纯文本 + inline reply。
应急方案:手动下载 raw 邮件再回复
只有在无法使用上述任何一种方式时,才考虑手动拼装回复。步骤:
- 在 lore 邮件页面点击
raw,保存得到纯文本格式的原始邮件; - 删掉最上面一大段邮件头信息,但保留
Subject:那一行,并在原标题前加上Re:(Subject: 原标题->Subject: Re: 原标题); - 用
>标记引用原文,把自己的回复穿插在引用内容之间。批量加引用符号时可以用:
# 只引用"头部之后"的正文:1,/^$/ 覆盖从第 1 行到第一个空行(含)的范围,
# 因此 Subject 行与它后面那个分隔空行都保持原样,只有正文被加上 >
sed -i -e '1,/^$/!s/^/> /' /path/to/the-patch-email
- 回到 lore 的邮件页面,向下滚动,页面底部会列出用
git send-email回复这封邮件的完整 命令(含--in-reply-to),把/path/to/YOUR_REPLY替换为你整理好的正文文件后执行。
手动回复方法虽然麻烦,但不要求使用者订阅邮件列表。另外注意:mailto: 是给邮件客户端用的
URI scheme,不要把它写进 git send-email 的 --to= 参数里。