提交补丁的最佳实践

本文翻译自官方教程Git - MyFirstContribution,原文包含开发到提交的整个周期。但是想要提交的人应该都已经开发完代码了,所以本文用自己的实际例子重新写了一遍,省去了开发代码等流程,重点介绍如何使用 git send-email。

环境准备

下载 OpenSBI 仓库

git clone https://github.com/riscv-software-src/opensbi.git
cd opensbi

安装依赖

要从源代码构建 OpenSBI:

make

注:OpenSBI 的构建是可并行的。上面的命令可以添加-j#参数,如-j12

确认要解决的问题

在本文档中,我们将模拟提交一个简单的 Patch,.gitignore文件可以过滤不必要的文件,现在使用 VSCode 的用户越来越多,使用 VSCode 开发时常常会生成.vscode目录,但是这些文件不该被推送至远程,原仓库中的.gitignore文件中没有过滤该文件,我们给他加上。

为了能够模拟一次发送多个commit的场景,我们将再添加一个.so用来过滤编译过程中生成的.so文件。

建立工作空间

让我们先建立一个开发分支来进行我们的修改。

git checkout -b update_gitignore origin/master

我们将在这里做一些提交,以演示如何将一个带有多个补丁的主题同时送审。

实现代码

过滤 .vscode

打开文件.gitignore,为该文件添加/.vscode/

# Object files
*.o
*.a
*.dep

#Build & install directories
build/
install/

# Development friendly files
tags
cscope*

/.vscode/

为以上修改做一次提交:

$ git status
On branch update_gitignore
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   .gitignore
$ git add .gitignore
$ git commit -s

执行以上命令后将会弹出编辑框用来编写提交信息。主题行要少于 50 个字符,然后是一个空行(必须),然后是您的提交消息的正文。请记住要明确并提供更改的原因(理由),特别是如果无法从您的差异中轻松理解你的提交内容时。编辑提交消息时,不要删除上面 Signed-off-by 添加的 trailer。(由上面命令-s参数生成)。

其他规范请详细查阅目标社区的提交规范,如OpenSBI要求主题行需要以 lib:platform:, firmware:, docs:, utils: 或者 top:为前缀,修改.gitignore属于top范畴,所以我们需要将其加在主题行上。

top: filter .vscode folder

Filter the workspace's '.vscode' directory by adding '/.vscode/' to the.gitignore file.

Signed-off-by: Dominic Zhang <Dominic Zhang@gmail.com>

继续用 git show 检查您的新提交。尤其不要出现不需要在本次提交的内容。通常使用不同的 IDE 都可能会无意间生成一些配置文件等,请注意将其剔除。

commit 5dc340c29979d4c5d8c4d5a6e881348239714434 (HEAD -> update_gitignore)
Author: Dominic Zhang <Dominic Zhang@gmail.com>
Date:   Fri Nov 18 16:06:21 2022 +0800

    top: filter .vscode folder
    
    Filter the workspace's '.vscode' directory by adding '/.vscode/' to the.gitignore file.
    
    Signed-off-by: Dominic Zhang <Dominic Zhang@gmail.com>

diff --git a/.gitignore b/.gitignore
index 95692bb..90cf552 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,5 @@ install/
 # Development friendly files
 tags
 cscope*
+
+/.vscode/

过滤 .cache

与上一节步骤类似,我们在.gitignore文件中再添加一个/.cache/字段用来过滤.cache文件夹。

# Object files
*.o
*.a
*.dep

#Build & install directories
build/
install/

# Development friendly files
tags
cscope*

/.vscode/
/.cache/

添加完我们就即使保存工作进度,新生成一个commit

git add .gitignore
git commit -s
top: filter .cache folder

Filter the workspace's '.cache' directory by adding '/.cache/' to the.gitignore file.

Signed-off-by: Dominic Zhang <Dominic Zhang@gmail.com>

以上我们就已经准备好所有的代码了,在大部分场景下可能修改的是.c或者.h等源文件,这就需要我们能够使代码编译、运行并且测试通过后再提交。

这里为了演示提交流程,就没有涉及这些步骤。接下来我们就要准备提交的补丁文件了。

准备提交补丁

OpenSBI 项目是通过电子邮件发送补丁来进行代码审查的,当补丁准备好并得到社区认可后,维护者就会应用(Apply)这些补丁。OpenSBI 项目不接受来自 Pull Request 的贡献,而且通过电子邮件发送的补丁需要以指定的方式进行审核。

在研究如何将你的提交转化为电子邮件的补丁之前,让我们先分析一下最终的结果,即补丁系列(Patch Series)是什么样子。下面是 OpenSBI 邮件列表存档的网页界面上的补丁系列的摘要视图的一个例子。

Responsive Image

我们可以注意几点:

  • 每次提交都是以单独的邮件形式发送,提交信息的标题为主题,前缀为[PATCH i/n],代表n个提交系列中的第 i 个提交。
  • 每个补丁都是作为对cover letter的回复,cover letter的前缀为[PATCH 0/n],序号为 0 的标题。
  • 补丁系列的后续迭代被标记为 PATCH v2PATCH v3,等等,以代替 PATCH。例如,[PATCH v2 1/3]将是第二次迭代中三个补丁的第一个补丁。每次迭代都有一个新的cover letter(如上面的[PATCH v2 0/3]),本身就是对前一次迭代的cover letter的回复(下面会有更多介绍)。

注:单一补丁的主题是以[PATCH][PATCH v2]等发送的,没有 i/n 编号。如上图中的第四个 Patch,就是一个单一补丁。

什么是 cover letter

除了给每个补丁发一封邮件外,OpenSBI 社区还希望你的补丁能附带一封 cover letter。这是修改提交的一个重要组成部分,因为它概括了你想要做什么,以及为什么要这样做,比仅仅看你的补丁更明显。

你的 cover letter 的标题应该是能简洁地涵盖你整个主题分支的目的。就像我们的提交信息标题一样。下面是我们的系列标题。

Update gitignore ---

cover letter 的正文是用来给评审员提供额外的背景。一定要解释任何你的补丁自己没有说清楚的东西,但要记住,由于 cover letter 没有记录在提交历史中,任何可能对未来版本库历史的读者有用的东西也应该在你的提交信息中出现。

下文我们将介绍如何生成 cover letter 以及如何填写 cover letter。

用 git send-email 发送补丁

前提条件 - 设置 git send-email

send-email 的配置会根据你的操作系统和电子邮件供应商而有所不同,配置可以参考文档如何使用 git-send-mail 给开源社区提交 Patch - 如云泊

准备初始补丁集

在准备邮件本身之前,你需要准备补丁。

git format-patch --cover-letter -o update_gitignore/ --base=auto  update_gitignore@{u}..update_gitignore
  • --cover-letter 选项告诉 format-patch 为你创建一个 cover letter 模板。在你准备发送之前,你将需要填写该模板。
  • -o update_gitignore/ 选项告诉 format-patch 把补丁文件放到目录update_gitignore中。这样发送多个commit时就可以使用命令一次性发送,因为 git send-email 可以接收一个目录并从那里发送所有补丁。
  • --base=auto 选项告诉命令记录”基本提交”,接收者将在此基础上应用补丁系列。自动值将使 format-patch 自动计算基本提交,即远程跟踪分支的最新提交和指定修订范围的合并基数。
  • update_gitignore@{u}..update_gitignore 选项告诉 format-patch 为你在 update_gitignore 分支上创建的提交生成补丁,因为它是从上游分叉出来的。@{u}的意思就是从分叉开始到最新的提交。

执行完该命令我们看看生成了哪些内容。

$ git status
On branch update_gitignore
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        update_gitignore/

$ ls update_gitignore 
0000-cover-letter.patch  0001-top-filter-.vscode-folder.patch  0002-top-filter-.cache-folder.patch

该命令将为每次提交制作一个补丁文件。运行后,您可以用您喜欢的文本编辑器看一下每个补丁,确保一切正常。可以看到创建了一个-o参数中的update_gitignore文件夹,该文件夹下有三个文件,分别是 cover letter 和上文我们做的两次提交对应的补丁文件。

分别打开他们,结果如下:

From 30614e5469be4a2f930cca570836627a4e91f1d1 Mon Sep 17 00:00:00 2001
From: Dominic Zhang <Dominic Zhang@gmail.com>
Date: Fri, 18 Nov 2022 16:41:32 +0800
Subject: [PATCH 0/2] *** SUBJECT HERE ***

*** BLURB HERE ***

Dominic Zhang (2):
  top: filter .vscode folder
  top: filter .cache folder

 .gitignore | 3 +++
 1 file changed, 3 insertions(+)


base-commit: 880685586dcee950d209088a461443449a1693ce
-- 
2.17.1
From 5dc340c29979d4c5d8c4d5a6e881348239714434 Mon Sep 17 00:00:00 2001
From: Dominic Zhang <Dominic Zhang@gmail.com>
Date: Fri, 18 Nov 2022 16:06:21 +0800
Subject: [PATCH 1/2] top: filter .vscode folder

Filter the workspace's '.vscode' directory by adding '/.vscode/' to the.gitignore file.

Signed-off-by: Dominic Zhang <Dominic Zhang@gmail.com>
---
 .gitignore | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitignore b/.gitignore
index 95692bb..90cf552 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,5 @@ install/
 # Development friendly files
 tags
 cscope*
+
+/.vscode/
\ No newline at end of file
-- 
2.17.1
From 30614e5469be4a2f930cca570836627a4e91f1d1 Mon Sep 17 00:00:00 2001
From: Dominic Zhang <Dominic Zhang@gmail.com>
Date: Fri, 18 Nov 2022 16:20:37 +0800
Subject: [PATCH 2/2] top: filter .cache folder

Filter the workspace's '.cache' directory by adding '/.cache/' to the.gitignore file.

Signed-off-by: Dominic Zhang <Dominic Zhang@gmail.com>
---
 .gitignore | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index 90cf552..bf9d716 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,5 @@ install/
 tags
 cscope*
 
-/.vscode/
\ No newline at end of file
+/.vscode/
+/.cache/
\ No newline at end of file
-- 
2.17.1

注:另外,你也可以使用 --rfc 参数,在你的补丁主题前加上[RFC PATCH],而不是[PATCH]。RFC 是”请求评论”的意思,表示虽然你的代码还没有准备好提交,但你想开始代码审查过程。你也可能在列表中看到标有”WIP”的补丁,这意味着他们还没有完成,但希望审查者能看看他们目前的成果。你可以用--subject-prefix=WIP来添加这个标志。

检查并确保你的补丁和 cover letter 模板存在于你指定的目录中,这就完成所有准备了。

准备邮件

由于你在调用 format-patch 时使用了--cover-letter,你已经准备好了一个 cover letter 模板。在你喜欢的编辑器中打开它。

你应该看到已经有一些标题存在。检查你的From:标题是否正确。然后修改你的Subject:

确保保留[PATCH 0/X]的部分;这是向 Git 社区表明这封邮件是一个补丁系列的开始,许多审查者会根据这种类型的标记过滤他们的邮件。

接下来,你必须填写你的 cover letter 的正文。同样,关于应包括哪些内容,见上文。

最后,信中会包括用于生成补丁的 Git 的版本。你可以不用管这个字符串。

完善后的 cover letter 如下:

From 30614e5469be4a2f930cca570836627a4e91f1d1 Mon Sep 17 00:00:00 2001
From: Dominic Zhang <Dominic Zhang@gmail.com>
Date: Fri, 18 Nov 2022 16:41:32 +0800
Subject: [PATCH 0/2] Update gitignore

vscode is a very popular IDE, and it often needs to generate a.vscode. cache directory to hold workspace configuration files that should not be committed to a remote repository, so we made some modifications to the gitignore file to filter such directories.

Dominic Zhang (2):
  top: filter .vscode folder
  top: filter .cache folder

 .gitignore | 3 +++
 1 file changed, 3 insertions(+)


base-commit: 880685586dcee950d209088a461443449a1693ce
-- 
2.17.1

发送邮件

到这里,你应该有一个目录 update_gitignore/,里面包含你的补丁和一封 cover letter。是时候把它发出去了!你可以像这样发送。

git send-email --to=target@example.com update_gitignore/*.patch

注:请查看 git help send-email 中的一些其他选项,你可能会发现这些选项很有价值,比如改变回复地址或添加更多的抄送地址或密送地址。

注:当你发送一个真正的补丁时,它将被发送到 opensbi@lists.infradead.org - 但请不要把你的补丁集从教程中发送到真正的邮件列表中!现在你可以把它发送给你自己,以确保你了解它的形式。

在你运行上面的命令后,你会为每个即将发出的补丁看到一个交互提示。这给了你最后一次机会来编辑或放弃发送一些东西(但还是那句话,不要用这种方式编辑代码)。一旦你在这些提示下按下 ya,你的邮件就会被发送出去!Congratulation!

发送补丁的更新版本

本节将重点介绍如何发送你的补丁集的 v2 版。我们将在 v2 版中重新使用我们的 update_gitignore 分支。在我们做任何改动之前,我们先新建一个名为update_gitignore-v1的分支,这个分支是我们没有做新的改动的分支。这样在后面我们就可以方便的进行对比差异。

git checkout update_gitignore
git branch update_gitignore-v1

在更新补丁时,我们可能会遇到两种情况,一种是社区的意见只让修改最新的一个提交,一种是修改历史记录中的 commit。我们分别来处理这两种情况。

如何修改最新的提交

比如只需要修改top: filter .cache folder这个 commit。因为它在我们的修改中是最新的 commit,所以我们可以直接对代码修改。比如我们做一个简单的修改,给修改的内容/.cache加个注释。

# Object files
*.o
*.a
*.dep

#Build & install directories
build/
install/

# Development friendly files
tags
cscope*

/.vscode/

# Cache file
/.cache/
git add .gitignore
git commit --amend

注意!我们不需要生成新的commit,所以使用 --amend参数修改最新的commit message即可。执行这条命令会弹出编辑窗口,因为修改内容已经很明确,我们不需要在commit message里再做额外说明,直接保存退出即可。如果修改内容比较大,需要重新编写commit message

以上我们就完成了一次更新。

如何修改历史记录中的提交

如果很不巧,社区要求修改的是top: filter .vscode folder这个提交的内容,那怎么办,因为它不是最新的提交,而是上一个提交,我们无法使用git commit --amend来直接对他修改,好在 Git 十分强大,不需要我们reset就可以完成这样的工作。

同样我们也做一个简单的修改,为/.vscode/也添加一个注释。首先我们需要使用到git rebase这个强大的命令。本文只介绍使用到的功能,其他功能需要大家自行摸索。

git rebase -i

这条命令会弹出编辑窗口,-i参数表示以交互式方式进行变基(rebase)操作。弹出窗口内容如下:

pick 7175772 top: filter .vscode folder
pick 52b63f3 top: filter .cache folder

# Rebase 8806855..52b63f3 onto 8806855 (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

窗口会显示所有未提交到远程的 commit,下面的注释也告诉了我们该如何使用。我们找到edit的行,可以看到解释为使用当前的 commit,但是在变基过程中会停下来让我们修改。这正是我们想要的。我们编辑当前的内容如下:

edit 7175772 top: filter .vscode folder
pick 52b63f3 top: filter .cache folder

表示我们需要编辑历史记录中的top: filter .vscode folder提交,但是另一个 commit 我们不做改变。保存并退出当前窗口后,会有如下提示。

Stopped at 7175772...  top: filter .vscode folder
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue

根据提示,我们可以进行一系列修改了,修改完使用git commit --amend保存,如果一切符合自己要求了,再使用git rebase --continue完成变基操作。

我们先修改代码,可以看到代码已经回到了没有/.cache/的状态,我们添加一行注释:

# Object files
*.o
*.a
*.dep

#Build & install directories
build/
install/

# Development friendly files
tags
cscope*

# VSCode config file
/.vscode/
git add .gitignore
git commit --amend

同样弹出窗口后我们直接保存退出,如果修改幅度较大,可以进一步补充说明。然后使用以下命令继续完成变基。

git rebase --continue

此时我们可以看到我们不仅修改了历史记录中的 commit,还保证了最新的 commit 没有丢失或者更改。

准备更新版本的补丁集
 $ git format-patch -v2 --cover-letter -o update_gitignore/   master..update_gitignore-v1
update_gitignore/v2-0000-cover-letter.patch
update_gitignore/v2-0001-top-filter-.vscode-folder.patch
update_gitignore/v2-0002-top-filter-.cache-folder.patch

--range-diff master..update_gitignore-v1 参数告诉 format-patch 在 cover letter 中包括 update_gitignore-v1update_gitignore 两个分支之间的差异。这有助于告诉评审人你的 v1 和 v2 补丁之间的差异。

-v2 参数告诉 format-patch 将你的补丁输出为 v2 版本。例如,你可能注意到你的 v2 版补丁都被命名为 v2-000n-my-commit-subject.patch-v2 也会将你的补丁格式化,在前面加上[PATCH v2],而不是[PATCH]

运行此命令后,format-patch 会将补丁输出到 update_gitignore/ 目录,与 v1 版的补丁一起。使用一个目录可以方便在校对 v2 补丁时参考旧的 v1 补丁,但你需要注意只发送 v2 补丁。我们将使用 update_gitignore/v2-.patch这样的模式(而不是 update_gitignore/.patch,这将匹配 v1 和 v2 补丁)。

再次编辑你的 cover letter。现在是一个很好的时间来提及你的上一个版本和现在有什么不同,如果它是重要的东西。你不需要在你的第二封 cover letter 中使用完全相同的内容;重点是向审查人员解释你所做的可能不那么明显的变化。

我们就简单的写一下添加了注释。

From 30614e5469be4a2f930cca570836627a4e91f1d1 Mon Sep 17 00:00:00 2001
From: Dominic Zhang <Dominic Zhang@gmail.com>
Date: Fri, 18 Nov 2022 19:35:06 +0800
Subject: [PATCH v2 0/2] Update gitignore

Add a comment for the folder name.

Dominic Zhang (2):
  top: filter .vscode folder
  top: filter .cache folder

 .gitignore | 3 +++
 1 file changed, 3 insertions(+)

-- 
2.17.1

发送更新版本时你需要将新版本抄送给提出建议的人,你可以在你的 cover letter 中直接添加这些抄送行,在 Subject 行上面写上这样一行。

CC: Name <name@example.com>

例如,把更新的邮件抄送给我自己:

From 30614e5469be4a2f930cca570836627a4e91f1d1 Mon Sep 17 00:00:00 2001
From: Dominic Zhang <Dominic Zhang@gmail.com>
CC: Dominic Zhang <Dominic Zhang@gmail.com>
Date: Fri, 18 Nov 2022 19:35:06 +0800
Subject: [PATCH v2 0/2] Update gitignore

Add a comment for the folder name.

Dominic Zhang (2):
  top: filter .vscode folder
  top: filter .cache folder

 .gitignore | 3 +++
 1 file changed, 3 insertions(+)

-- 
2.17.1

现在再次发送电子邮件,注意你传入的参数。

git send-email --to target@example.com update_gitignore/v2-*.patch

恭喜你完成了一次补丁版本更新。


对于一些社区,要求更新的版本需要在同一个 thread 上进行。如下示例这样:

[PATCH 0/2] Here is what I did...
  [PATCH 1/2] Clean up and tests
  [PATCH 2/2] Implementation
  [PATCH v2 0/3] Here is a reroll
    [PATCH v2 1/3] Clean up
    [PATCH v2 2/3] New tests
    [PATCH v2 3/3] Implementation

就是更新的版本需要关联到之前的版本,而不能作为单独的一个列表。

你还需要去找到你之前的 cover letter 的 Message-Id。你可以在发送第一个补丁系列时,从 git send-email 的输出中记下它。 例如:

$ git send-email --to Dominic Zhang@gmail.com update_gitignore/v2-*.patch 

update_gitignore/v2-0000-cover-letter.patch
update_gitignore/v2-0001-top-filter-.vscode-folder.patch
update_gitignore/v2-0002-top-filter-.cache-folder.patch
(mbox) Adding cc: Dominic Zhang <Dominic Zhang@gmail.com> from line 'From: Dominic Zhang <Dominic Zhang@gmail.com>'
(mbox) Adding cc: Dominic Zhang <254758318@qq.com> from line 'CC: Dominic Zhang <254758318@qq.com>'

From: Dominic Zhang@gmail.com
To: Dominic Zhang@gmail.com
Cc: Dominic Zhang <254758318@qq.com>
Subject: [PATCH v2 0/2] Update gitignore
Date: Fri, 18 Nov 2022 19:54:54 +0800
Message-Id: <20221118115456.2242-1-Dominic Zhang@gmail.com>
X-Mailer: git-send-email 2.17.1

你也可以从社区的邮箱列表中找到 Message ID,因为 OpenSBI 不要求在同一个 thread 回复,所以没有相关信息,这里以Git 社区的邮箱列表为例。随便点击一个补丁主题,在页面中找到permalink或者raw,点击打开即可找到 Message ID 信息。

它的格式一般如下:

Message-Id: <foo.12345.author@example.com>

Responsive Image

Responsive Image

如果要发送更新版本,那么我们就需要找到上一版本的 Message ID。如发送的是 V3 版本,那么我们需要找到 V2 版本的 Message ID。并且在发送邮件时添加如下参数:

$ git send-email --to Dominic Zhang@gmail.com 
                 --in-reply-to="<foo.12345.author@example.com>" 
                 update_gitignore/v2-*.patch 

只有一个 Patch 的更改

在某些情况下,你的非常小的变化可能只包括一个补丁。这时,你只需要发送一封邮件。你的提交信息应该已经很有意义了,你只需要生成补丁文件就可以发送了。

git format-patch -o update_gitignore/  HEAD^
  • HEAD^参数表示生成与上一个提交之间的差异。