简体中文 ▾ 主题 ▾ 最新版本 ▾ MyFirstContribution 最近更新于 2.52.0

此信息专用于 Git 项目

请注意,如果您打算为 Git 项目本身做贡献,此信息才与您相关。它绝不是普通 Git 用户的必读内容。

摘要

这是一个教程,演示了创建 Git 提交、发送以供审查以及根据反馈进行修改的端到端工作流程。

先决条件

本教程假设您已经相当熟悉使用 Git 管理源代码。Git 的工作流程步骤将大部分不作解释。

本教程旨在总结以下文档,但读者可能会发现其他有用的附加背景信息

  • Documentation/SubmittingPatches

  • Documentation/howto/new-command.adoc

获取帮助

如果您遇到困难,可以在以下地方寻求帮助。

git@vger.kernel.org

这是主要的 Git 项目邮件列表,代码审查、版本公告、设计讨论等都在这里进行。有兴趣贡献的人欢迎在此处发布问题。Git 邮件列表要求纯文本邮件,回复邮件时偏好内联和底部回复;您将在所有回复中被抄送。可选地,您可以通过发送电子邮件至 <git+subscribe@vger.kernel.org> 订阅该列表(有关详细信息,请参阅 https://subspace.kernel.org/subscribing.html)。该邮件列表的 存档 可在浏览器中查看。

Libera Chat 上的 #git-devel

此 IRC 频道用于 Git 贡献者之间的交流。如果有人在线并且知道您问题的答案,您可以实时获得帮助。否则,您可以阅读 scrollback,看看是否有人回复了您。IRC 不允许离线私信,因此如果您尝试私信某人然后退出 IRC,他们无法回复您。最好在频道中提问,这样即使您断开连接也能得到答复,并且其他人也可以从对话中学习。

Discord 上的 #discord

这是一个非官方的 Git Discord 服务器,面向所有人,从刚开始使用 Git 的用户到开发 Git 的人员。这是一个提问、分享技巧以及实时与更广泛的 Git 社区联系的好地方。

该服务器设有用于一般讨论的频道,以及专门为 Git 用户和 Git 开发人员准备的频道。服务器的搜索功能还可以帮助您查找之前的对话和常见问题的答案。

入门

克隆 Git 仓库

Git 在多个位置进行了镜像。从其中一个位置克隆仓库;https://git-scm.cn/downloads 建议从 GitHub 上的镜像克隆是最佳选择之一。

$ git clone https://github.com/git/git git
$ cd git

安装依赖项

要从源代码构建 Git,您需要在系统上安装一些依赖项。有关所需内容的提示,您可以查看 INSTALL 文件,并特别注意关于 Git 对外部程序和库的依赖项的部分。该文档提到了一个方法,可以在不安装的情况下“试用”我们刚构建好的 Git;本教程将使用此方法。

确保您的环境拥有您所需的一切,方法是构建上面步骤中您刚刚克隆的 Git

$ make
注意
Git 的构建是并行化的。上面未包含 -j#,但您可以根据自己的喜好在此处及其他地方使用它。

确定要解决的问题

在本教程中,我们将添加一个新命令,名为 git psuh,它是“Pony Saying ‘Um, Hello’”的缩写——这是一个尽管在用户日常工作流程中被频繁调用但尚未实现的功能。

(我们已经在实现像 sl 这样流行的命令时看到了其他类似的工作。)

设置工作区

让我们首先创建一个开发分支来处理我们的更改。根据 Documentation/SubmittingPatches,由于这是一个全新的命令,因此基于 master 分支是没问题的。但是,将来进行 bug 修复等操作时,您应该查阅该文档并基于适当的分支。

在本教程中,我们将所有工作都基于上游项目的 master 分支。创建用于开发的 psuh 分支,如下所示:

$ git checkout -b psuh origin/master

我们将在此创建多个提交,以演示如何一次性将一个主题(包含多个补丁)发送以供审查。

编写代码!

注意
可以在 https://github.com/nasamuffin/git/tree/psuh 找到参考实现。

添加新命令

许多子命令都写成内置命令,这意味着它们是用 C 实现的,并编译到主 git 可执行文件中。将非常简单的 psuh 命令实现为内置命令将演示代码库的结构、内部 API,以及作为贡献者与审查者和维护者合作将此更改集成到系统中的过程。

内置子命令通常实现为一个名为“cmd_”后跟子命令名的函数,位于以子命令命名的源文件(位于 builtin/ 目录下)中。因此,将您的命令实现到 builtin/psuh.c 文件中是合理的。创建该文件,并在其中,按照样式和签名编写命令的入口点函数:

int cmd_psuh(int argc UNUSED, const char **argv UNUSED,
	     const char *prefix UNUSED, struct repository *repo UNUSED)

有几点需要注意:

  • 子命令实现接收其命令行参数,如同 main() 函数一样,通过 int argc + const char **argv

  • 它还接受两个额外的参数,prefixrepo。它们的确切含义将在稍后讨论。

  • 由于第一个示例不会使用任何参数,编译器将给出关于未使用参数的警告。由于添加新内置命令的 API 要求必须有这四个参数,因此您不能省略它们。相反,您可以在每个参数前添加 UNUSED,以告知编译器您 **知道** 您(还)没有使用它。

我们还需要添加 psuh 的声明;打开 builtin.h 文件,找到 cmd_pull 的声明,并在它前面添加一行用于 psuh 的声明,以保持声明按字母顺序排序。

int cmd_psuh(int argc, const char **argv, const char *prefix, struct repository *repo);

确保在您的 psuh.c 中包含 "builtin.h"。您还需要包含 "gettext.h" 来使用与打印输出文本相关的函数。

继续在 cmd_psuh 函数中添加一些临时的 printf。这是一个不错的起点,因为现在我们可以添加构建规则并注册该命令。

注意
您的临时文本,以及在本教程过程中您将添加的许多文本,都是面向用户的。这意味着它需要是可本地化的。查看 po/README 文件中“标记需要翻译的字符串”部分。在本教程中,我们将根据需要标记字符串以供翻译;将来在编写用户可见的命令时,您也应该这样做。
int cmd_psuh(int argc UNUSED, const char **argv UNUSED,
	     const char *prefix UNUSED, struct repository *repo UNUSED)
{
	printf(_("Pony saying hello goes here.\n"));
	return 0;
}

让我们尝试构建它。打开 Makefile 文件,找到 builtin/pull.o 被添加到 BUILTIN_OBJS 的位置,并以相同的方式在它旁边按字母顺序添加 builtin/psuh.o。完成后,移至顶层目录,仅使用 make 进行构建。另外,添加 DEVELOPER=1 变量以启用一些额外的警告:

$ echo DEVELOPER=1 >config.mak
$ make
注意
当您开发 Git 项目时,首选使用 DEVELOPER 标志;如果您因为某些原因无法使用它,可以关闭它,但最好将问题告知邮件列表。

太好了,现在您的新命令可以独立构建了。但没人调用它。让我们来改变这一点。

命令列表位于 git.c 文件中。我们可以通过向 commands[] 数组添加一个 cmd_struct 来注册一个新命令。 struct cmd_struct 包含一个命令名称字符串、一个指向命令实现的函数指针以及一个设置选项标志。目前,让我们继续模仿 push。找到注册 cmd_push 的行,复制它,并为 cmd_psuh 修改它,将新行放置在字母顺序上(紧随 cmd_pull 之前)。

选项记录在 builtin.h 的“添加新内置命令”部分。由于我们希望稍后打印有关用户当前工作区上下文的数据,因此我们需要 Git 目录,所以选择 RUN_SETUP 作为我们唯一的选项。

再次进行构建。您应该会看到一个干净的构建,所以让我们来试用一下,看看它是否有效。在 bin-wrappers 目录中有一个可以用于测试的二进制文件。

$ ./bin-wrappers/git psuh

看!您有了一个命令!干得好!让我们提交这个。

git status 显示了修改过的 Makefilebuiltin.hgit.c,以及未跟踪的 builtin/psuh.cgit-psuh。首先,让我们处理二进制文件,它应该被忽略。在编辑器中打开 .gitignore 文件,找到 /git-pull,并在其中按字母顺序添加您新命令的条目。

...
/git-prune-packed
/git-psuh
/git-pull
/git-push
/git-quiltimport
/git-range-diff
...

再次检查 git status 应该显示 git-psuh 已从未跟踪列表中移除,并且 .gitignore 已添加到修改列表。现在我们可以暂存并提交:

$ git add Makefile builtin.h builtin/psuh.c git.c .gitignore
$ git commit -s

您将看到编辑器弹出,以便您编写提交消息。提交应以 50 列或更短的标题行开始,包括您正在处理的组件的名称,然后是一个空行(始终必需),接着是提交消息的主体,该主体应提供大部分上下文。请记住要明确并提供您更改的“原因”,尤其是当从 diff 中不易理解时。编辑提交消息时,请勿删除上面使用 -s 添加的 Signed-off-by 标记。

psuh: add a built-in by popular demand

Internal metrics indicate this is a command many users expect to be
present. So here's an implementation to help drive customer
satisfaction and engagement: a pony which doubtfully greets the user,
or, a Pony Saying "Um, Hello" (PSUH).

This commit message is intentionally formatted to 72 columns per line,
starts with a single line as "commit message subject" that is written as
if to command the codebase to do something (add this, teach a command
that). The body of the message is designed to add information about the
commit that is not readily deduced from reading the associated diff,
such as answering the question "why?".

Signed-off-by: A U Thor <author@example.com>

继续使用 git show 检查您的新提交。“psuh:”表明您主要修改了 psuh 命令。标题行让读者了解您所做的更改。签名行(-s)表示您同意《开发者证书》(Developer’s Certificate of Origin 1.1)(请参阅 Documentation/SubmittingPatches [[dco]] 部分)。

在教程的其余部分,为了简洁起见,仅列出标题行。然而,完整的示例提交消息可在本文档顶部链接的参考实现中找到。

实现

除了打印字符串之外,至少做些其他事情可能很有用。让我们先看看我们得到的所有内容。

修改您的 cmd_psuh 实现,以转储您收到的参数,保留现有的 printf() 调用;由于参数现在已被使用,请从它们中删除 UNUSED 宏:

	int i;

	...

	printf(Q_("Your args (there is %d):\n",
		  "Your args (there are %d):\n",
		  argc),
	       argc);
	for (i = 0; i < argc; i++)
		printf("%d: %s\n", i, argv[i]);

	printf(_("Your current working directory:\n<top-level>%s%s\n"),
	       prefix ? "/" : "", prefix ? prefix : "");

构建并尝试运行。正如您所料,基本上只有我们在命令行上传递的任何内容,包括我们的命令名称。(如果您的 prefix 为空,请尝试 cd Documentation/ && ../bin-wrappers/git psuh)。这没什么用。那么我们还能获得什么其他上下文呢?

添加一行以包含 "config.h""repository.h"。然后,在函数体中添加以下内容:函数体

	const char *cfg_name;

...

	repo_config(repo, git_default_config, NULL);
	if (repo_config_get_string_tmp(repo, "user.name", &cfg_name))
		printf(_("No name is found in config\n"));
	else
		printf(_("Your name: %s\n"), cfg_name);

repo_config() 将从 Git 已知的配置文件中获取配置,并应用标准的优先级规则。 repo_config_get_string_tmp() 将查找特定键(“user.name”)并返回其值。存在许多类似的单键查找函数;您可以在 Documentation/technical/api-config.adoc 中查看所有这些函数(以及有关如何使用 repo_config() 的更多信息)。

您应该看到打印的名称与您运行以下命令时看到的名称匹配:

$ git config --get user.name

太好了!现在我们知道如何检查 Git 配置中的值了。让我们也提交这个,以免丢失我们的进度。

$ git add builtin/psuh.c
$ git commit -sm "psuh: show parameters & config opts"
注意
同样,以上是为了在本教程中简洁起见。在实际更改中,您不应使用 -m,而应使用编辑器编写有意义的消息。

然而,了解用户的上下文是什么样的还是挺好的。让我们看看是否能打印出用户当前分支的名称。我们可以模仿 git status 的实现;打印程序位于 wt-status.c 文件中,我们可以看到分支保存在一个 struct wt_status 中。

wt_status_print()cmd_status()builtin/commit.c 中调用。查看该实现,我们看到状态配置如下填充:

status_init_config(&s, git_status_config);

但当我们深入研究时,可以发现 status_init_config() 封装了对 repo_config() 的调用。让我们修改我们在上一个提交中所写的代码。

确保包含该头文件,以便您可以使用 struct wt_status

#include "wt-status.h"

然后,修改您的 cmd_psuh 实现,以声明您的 struct wt_status,准备它,并打印其内容:

	struct wt_status status;

...

	wt_status_prepare(repo, &status);
	repo_config(repo, git_default_config, &status);

...

	printf(_("Your current branch: %s\n"), status.branch);

再次运行它。看看——这是您当前分支的(详细)名称!

让我们也提交这个。

$ git add builtin/psuh.c
$ git commit -sm "psuh: print the current branch"

现在让我们看看是否能获取有关特定提交的一些信息。

幸运的是,这里有一些辅助函数。 commit.h 有一个名为 lookup_commit_reference_by_name 的函数,我们可以简单地提供一个硬编码的字符串; pretty.h 有一个非常方便的 pp_commit_easy() 调用,它不需要传递完整的格式对象。

添加以下包含项:

#include "commit.h"
#include "pretty.h"

然后,在您的 cmd_psuh() 实现的声明和逻辑附近分别添加以下行:

	struct commit *c = NULL;
	struct strbuf commitline = STRBUF_INIT;

...

	c = lookup_commit_reference_by_name("origin/master");

	if (c != NULL) {
		pp_commit_easy(CMIT_FMT_ONELINE, c, &commitline);
		printf(_("Current commit: %s\n"), commitline.buf);
	}

struct strbuf 为基本的 char* 提供了一些安全措施,其中之一是长度成员,以防止缓冲区溢出。它需要使用 STRBUF_INIT 进行良好初始化。当您需要传递 char* 时,请记住这一点。

lookup_commit_reference_by_name 会解析您传递给它的名称,因此您可以尝试更改那里使用的值,看看您能发现什么。

pp_commit_easypretty.h 中的一个方便的包装函数,它接受一个单独的格式枚举简写,而不是一个完整的格式结构。然后它会根据该简写格式化打印提交。这些类似于许多 Git 命令中 --pretty=FOO 可用的格式。

构建并运行它,如果您使用的名称与示例中的相同,您应该会看到您所知的 origin/master 中最近一次提交的主题行。不错!让我们也提交这个。

$ git add builtin/psuh.c
$ git commit -sm "psuh: display the top of origin/master"

添加文档

太棒了!您有了一个很棒的新命令,可以分享给社区了。但是等等——这不是很用户友好。运行以下命令:

$ ./bin-wrappers/git help psuh

您的新命令没有文档!让我们来修复它。

查看 Documentation/git-*.adoc 文件。这些是 Git 已知子命令的手册页。您可以打开它们查看格式,然后创建一个新文件 Documentation/git-psuh.adoc。与 Git 项目中的大多数文档一样,帮助页面使用 AsciiDoc 编写(请参阅 CodingGuidelines,“Writing Documentation”部分)。使用以下模板填写您自己的手册页:

git-psuh(1)
===========

NAME
----
git-psuh - Delight users' typo with a shy horse


SYNOPSIS
--------
[verse]
'git-psuh [<arg>...]'

DESCRIPTION
-----------
...

OPTIONS[[OPTIONS]]
------------------
...

OUTPUT
------
...

GIT
---
Part of the git[1] suite

关于此内容最重要的一点是文件头,用 = 划线;NAME 部分;以及 SYNOPSIS,其中通常包含您的命令接收参数时的语法。尝试使用成熟的手册页标题,以使您的文档与其他 Git 和 UNIX 手册页保持一致;这可以为您的用户节省时间,因为他们可以直接跳到包含所需信息的章节。

注意
在尝试构建文档之前,请确保已安装 asciidoc 包。

现在您已经编写了手册页,您需要显式地构建它。我们像这样将 AsciiDoc 转换为 troff,后者是人类可读的格式:

$ make all doc
$ man Documentation/git-psuh.1

$ make -C Documentation/ git-psuh.1
$ man Documentation/git-psuh.1

虽然这不像运行 git help 那样令人满意,但至少您可以检查您的帮助页面是否看起来正确。

您还可以通过在顶层目录运行 make check-docs 来检查文档覆盖率是否良好(即,项目是否已识别您的命令已实现并已记录)。

继续提交您的新文档更改。

添加用法文本

尝试运行 ./bin-wrappers/git psuh -h。您的命令在结束时应该会崩溃。这是因为 -h 是一个特殊情况,您的命令应该通过打印用法来处理它。

请查看 Documentation/technical/api-parse-options.adoc。这是一个方便的工具,用于提取您需要处理的选项,它接受一个用法字符串。

为了使用它,我们需要准备一个 NULL 结尾的用法字符串数组和一个 builtin_psuh_options 数组。

#include "parse-options.h" 添加一行。

在全局作用域,添加您的用法字符串数组:

static const char * const psuh_usage[] = {
	N_("git psuh [<arg>...]"),
	NULL,
};

然后,在您的 cmd_psuh() 实现中,我们可以声明并填充我们的 option 结构。我们的非常简单,但如果您想更详细地探索 parse_options(),可以添加更多内容:

	struct option options[] = {
		OPT_END()
	};

最后,在打印您的参数和前缀之前,添加对 parse-options() 的调用:

	argc = parse_options(argc, argv, prefix, options, psuh_usage, 0);

此调用将修改您的 argv 参数。它将从 argv 中删除您在 options 中指定的选项,并更新 options 条目指向的位置。请务必将您的 argc 替换为 parse_options() 的结果,否则如果您以后尝试解析 argv,您将感到困惑。

值得注意的是特殊参数 --。正如您可能知道的,许多 Unix 命令使用 -- 来表示“命名参数结束”—— -- 之后的所有参数仅被解释为位置参数。(如果您想将一个通常被解释为标志的参数传递给某个东西,这会很有用。) parse_options() 将在遇到 -- 时停止解析,并将剩余的选项原样返回。

现在您有了用法提示,您可以教 Git 如何在 git help gitgit help -a 显示的通用命令列表中显示它,该列表是从 command-list.txt 生成的。找到 git-pull 行,以便您可以在其上方按字母顺序添加 git-psuh 行。现在,我们可以添加一些关于命令的属性,这些属性会影响它在上述帮助命令中的显示位置。 command-list.txt 的顶部共享有关每个属性含义的信息;在这些帮助页面中,命令是根据这些属性排序的。 git psuh 是面向用户的(porcelain),所以我们将它标记为“mainporcelain”。对于“mainporcelain”命令,command-list.txt 顶部的注释表明我们还可以选择性地添加另一个列表中的属性;由于 git psuh 显示了一些关于用户工作区的信息,但并不修改任何内容,因此我们将其标记为“info”。确保保持您的属性与 command-list.txt 中的其余内容风格一致,使用空格对齐和分隔它们。

git-prune-packed                        plumbingmanipulators
git-psuh                                mainporcelain		info
git-pull                                mainporcelain           remote
git-push                                mainporcelain           remote

再次构建。现在,当您使用 -h 运行时,您应该会看到您的用法被打印出来,并且您的命令在任何其他有趣的事情发生之前终止。太棒了!

继续提交这个。

测试

测试代码非常重要——即使是一个像这样的小玩具命令也是如此。此外,没有测试,您的补丁将不会被接受到 Git 树中。您的测试应该:

  • 说明当前功能的行为

  • 证明当前行为符合预期行为

  • 确保外部可见的行为在后续更改中不会被破坏

所以让我们写一些测试。

相关阅读:t/README

测试结构概述

Git 中的测试位于 t/ 目录下,并使用 t/README 文件中“命名测试”部分所示的模式,以 4 位十进制数字命名。

编写您的测试

由于这是一个玩具命令,让我们将其命名为 t9999。然而,由于许多系列/子命令组合已经满了,最好的做法似乎是找到一个与您添加的命令足够接近的命令,并共享其命名空间。

创建一个新文件 t/t9999-psuh-tutorial.sh。使用以下头部开始(请参阅 t/README 中的“编写测试”和“Source test-lib.sh”):

#!/bin/sh

test_description='git-psuh test

This test runs git-psuh and makes sure it does not crash.'

. ./test-lib.sh

测试被封装在 test_expect_success 中,以输出 TAP 格式的结果。让我们确保 git psuh 不会糟糕地退出,并且确实提到了正确的动物:

test_expect_success 'runs correctly with no args and good output' '
	git psuh >actual &&
	grep Pony actual
'

通过在脚本底部添加以下内容来指示您已运行完所有想运行的内容:

test_done

确保将您的测试脚本标记为可执行文件:

$ chmod +x t/t9999-psuh-tutorial.sh

您可以通过运行 make -C t test-lint 来了解您是否成功创建了新的测试脚本,该命令会检查诸如测试编号唯一性、可执行位等内容。

本地运行

让我们在本地尝试运行:

$ make
$ cd t/ && prove t9999-psuh-tutorial.sh

您可以运行完整的测试套件,并确保 git-psuh 没有破坏任何东西:

$ cd t/
$ prove -j$(nproc) --shuffle t[0-9]*.sh
注意
您也可以使用 make test 来执行此操作,或使用任何可以处理 TAP 的测试工具。 prove 可以并行运行。 shuffle 会随机化测试的运行顺序,这使得它们能够抵抗不希望的测试间依赖。 prove 还会使输出更美观。

继续将此更改提交。

准备分享:补丁系列(Patch Series)的构成

您可能已经注意到,Git 项目通过电子邮件补丁进行代码审查,当补丁准备好并获得社区批准后,维护者会将其应用。Git 项目不接受来自 pull requests 的贡献,并且发送用于审查的补丁需要以特定的方式格式化。

在查看如何将您的提交转换为电子邮件补丁之前,让我们分析一下最终结果“补丁系列”的样子。以下是在 Git 邮件列表存档 的 Web 界面上补丁系列的摘要视图的 示例

2022-02-18 18:40 [PATCH 0/3] libify reflog John Cai via GitGitGadget
2022-02-18 18:40 ` [PATCH 1/3] reflog: libify delete reflog function and helpers John Cai via GitGitGadget
2022-02-18 19:10   ` Ævar Arnfjörð Bjarmason [this message]
2022-02-18 19:39     ` Taylor Blau
2022-02-18 19:48       ` Ævar Arnfjörð Bjarmason
2022-02-18 19:35   ` Taylor Blau
2022-02-21  1:43     ` John Cai
2022-02-21  1:50       ` Taylor Blau
2022-02-23 19:50         ` John Cai
2022-02-18 20:00   ` // other replies elided
2022-02-18 18:40 ` [PATCH 2/3] reflog: call reflog_delete from reflog.c John Cai via GitGitGadget
2022-02-18 19:15   ` Ævar Arnfjörð Bjarmason
2022-02-18 20:26     ` Junio C Hamano
2022-02-18 18:40 ` [PATCH 3/3] stash: call reflog_delete from reflog.c John Cai via GitGitGadget
2022-02-18 19:20   ` Ævar Arnfjörð Bjarmason
2022-02-19  0:21     ` Taylor Blau
2022-02-22  2:36     ` John Cai
2022-02-22 10:51       ` Ævar Arnfjörð Bjarmason
2022-02-18 19:29 ` [PATCH 0/3] libify reflog Ævar Arnfjörð Bjarmason
2022-02-22 18:30 ` [PATCH v2 0/3] libify reflog John Cai via GitGitGadget
2022-02-22 18:30   ` [PATCH v2 1/3] stash: add test to ensure reflog --rewrite --updatref behavior John Cai via GitGitGadget
2022-02-23  8:54     ` Ævar Arnfjörð Bjarmason
2022-02-23 21:27       ` Junio C Hamano
// continued

我们可以注意几点:

  • 每个提交都作为单独的电子邮件发送,以提交消息标题作为主题,前面加上“[PATCH i/n]”,表示一个 n 个提交的系列中的第 i 个提交。

  • 每个补丁都作为对系列介绍性电子邮件(称为封面信)的回复发送,前面加上“[PATCH 0/n]”。

  • 补丁系列的后续迭代将用“PATCH v2”、“PATCH v3”等替换“PATCH”。例如,“[PATCH v2 1/3]”将是第二个迭代中三个补丁中的第一个。每次迭代都附带一封新的封面信(如上面的“[PATCH v2 0/3]”),它本身是前一次迭代封面信的回复(稍后会详细介绍)。

注意
单个补丁主题的发送格式为“[PATCH]”、“[PATCH v2]”等,不带 i/n 编号(在上方的线程概览中,没有显示单个补丁主题)。

封面信

除了每份补丁一个电子邮件外,Git 社区还希望您的补丁附带一封封面信。这是提交更改的重要组成部分,因为它能从高层次向社区解释您试图做什么,以及为什么这样做,其清晰度远超仅仅查看补丁本身。

您的封面信标题应简洁地概括您整个主题分支的目的。通常使用祈使语气,就像我们的提交消息标题一样。以下是我们为补丁系列命名的规则:


添加 psuh 命令 ---

封面信正文用于为审阅者提供额外背景信息。务必解释您的补丁本身未能阐明的任何内容,但请记住,由于封面信不会记录在提交历史中,任何对存储库历史的未来读者有用的信息都应包含在您的提交消息中。

这是 psuh 的示例正文

Our internal metrics indicate widespread interest in the command
git-psuh - that is, many users are trying to use it, but finding it is
unavailable, using some unknown workaround instead.

The following handful of patches add the psuh command and implement some
handy features on top of it.

This patchset is part of the MyFirstContribution tutorial and should not
be merged.

此时,教程将分岔,以演示两种不同的格式化补丁集和获取审阅的方法。

要介绍的第一种方法是 GitGitGadget,它对于熟悉 GitHub 通用拉取请求工作流程的人很有用。此方法需要 GitHub 帐户。

要介绍的第二种方法是 git send-email,它可以提供更精细地控制要发送的电子邮件。此方法需要一些设置,具体取决于您的系统,并且在本教程中不涵盖。

无论您选择哪种方法,您与审阅者的互动方式都是相同的;审阅过程将在 GitGitGadget 和 git send-email 部分之后介绍。

通过 GitGitGadget 发送补丁

发送补丁的一种选择是遵循典型的拉取请求工作流程,并通过 GitGitGadget 发送您的补丁。GitGitGadget 是 Johannes Schindelin 创建的一个工具,旨在让习惯于 GitHub PR 工作流程的 Git 贡献者生活更轻松。它允许贡献者针对 Git 项目的镜像打开拉取请求,并做一些魔法操作,将 PR 转换为一组电子邮件并为您发送出去。它还会为您运行 Git 持续集成套件。其文档位于 https://gitgitgadget.github.io/

在 GitHub 上 Fork git/git

在您可以使用 GitGitGadget 发送补丁进行审阅之前,您需要 Fork Git 项目并上传您的更改。首先,请确保您拥有一个 GitHub 帐户。

前往 GitHub 镜像 并找到 Fork 按钮。将您的 Fork 放在您认为合适的位置并创建它。

上传到您自己的 Fork

要将您的分支上传到您自己的 Fork,您需要将新的 Fork 添加为远程。您可以使用 git remote -v 来显示您已添加的远程。从 GitHub 上的新 Fork 页面,您可以按“Clone or download”来获取 URL;然后您需要运行以下命令来添加,将您自己的 URL 和远程名称替换为提供的示例。

$ git remote add remotename git@github.com:remotename/git.git

或使用 HTTPS URL

$ git remote add remotename https://github.com/remotename/git/.git

再次运行 git remote -v,您应该会看到新的远程显示出来。运行 git fetch remotename(替换为您远程的实际名称)以准备推送。

接下来,通过运行 git branch 再次仔细检查您是否一直在新的分支上进行开发。如果您没有,现在是时候将您的新提交移到自己的分支上了。

正如本文档开头简要提到的,我们的工作基于 master,所以请继续并按如下所示更新,或使用您首选的工作流程。

$ git checkout master
$ git pull -r
$ git rebase master psuh

最后,您已准备好推送新的主题分支!(由于我们分支和命令的命名选择,请谨慎输入以下命令。)

$ git push remotename psuh

现在您应该可以在 GitHub 上查看新创建的分支了。

通过 GitGitGadget 发送 PR

为了让代码得到测试和格式化以便审阅,您需要先针对 gitgitgadget/gitgit/git 打开一个拉取请求。前往 https://gitgitgadget.github.io/https://github.com/git/git,然后使用“New pull request”按钮或可能出现的、带有您新推送分支名称的“Compare & pull request”按钮来打开 PR。

使用 gitgitgadget/gitgit/git 作为基础之间的区别可以在 [此处](https://gitgitgadget.github.io/#should-i-use-gitgitgadget-on-gitgitgadgets-git-fork-or-on-gits-github-mirror) 找到。

审阅 PR 的标题和描述,因为 GitGitGadget 分别将它们用作您更改的封面信的主题和正文。有关如何为您的提交命名以及在描述中包含哪些内容,请参阅上面的 “封面信”

注意
对于单补丁贡献,您的提交消息应已具有意义,并从高层次解释补丁的目的(正在发生什么以及为什么),因此通常不需要额外上下文。在这种情况下,请删除 GitHub 自动从您的提交消息中生成的 PR 描述(您的 PR 描述应为空)。如果您确实需要提供更多上下文,可以在该空间中进行,它将被附加到 GitGitGadget 将要发送的电子邮件中,位于三虚线行和 diffstat 之间(有关提交后外观,请参阅 附录:单补丁更改)。

当您满意后,提交您的拉取请求。

运行 CI 并准备发送

如果您是第一次使用 GitGitGadget(很可能,因为您正在使用此教程),那么需要有人授予您使用该工具的权限。正如 GitGitGadget 文档中所述,您只需要让一个已经在使用它的人在您的 PR 上评论 /allow <username>。GitGitGadget 会在没有获得权限的情况下自动运行您的 PR 通过 CI,但直到有人允许您使用该工具,您才能 /submit 您的更改。

注意
您通常可以在 GitGitGadget 上找到可以 /allow 您的人,方法是查看最近有人获得 /allow 的拉取请求(搜索:is:pr is:open "/allow"),在这种情况下,作者和授予 /allow 的人都可以 /allow 您,或者通过在 #git-devel IRC 频道上询问 Libera Chat,链接您的拉取请求并请求某人 /allow 您。

如果 CI 失败,您可以使用 git rebase -i 更新您的更改并再次推送您的分支。

$ git push -f remotename psuh

事实上,您应该一直以这种方式进行更改,直到您的补丁被接受到 next 为止。

发送您的补丁

现在您的 CI 已通过,并且有人已授予您使用 GitGitGadget 的权限(通过 /allow 命令),发送审阅就像在您的 PR 上评论 /submit 一样简单。

使用评论更新

跳到 响应审阅部分,了解如何回复您在邮件列表中收到的审阅评论。

一旦您的分支按照所有审阅评论进行了修改,您就可以再次提交。

$ git push -f remotename psuh

接下来,查看您针对 GitGitGadget 的拉取请求;您应该会看到 CI 已重新启动。现在,当 CI 运行时,是时候修改拉取请求线程顶部的描述了;它将再次用作封面信。您应该利用此空间描述自上一版本以来所做的更改,以便审阅者对他们正在查看的内容有所了解。CI 运行完成后,您可以再次评论 /submit - GitGitGadget 将自动为您的更改添加 v2 标记。

使用 git send-email 发送补丁

如果您不想使用 GitGitGadget,您也可以使用 Git 本身来邮寄您的补丁。以这种方式使用 Git 的一些好处包括对主题行的更精细控制(例如,能够在主题中使用 [RFC PATCH] 标签)以及能够发送“试运行”邮件给自己,以确保在发送到列表之前一切看起来都很好。

前提条件:设置 git send-email

send-email 的配置可能因您的操作系统和电子邮件提供商而异,因此本教程不予涵盖,仅说明在许多 Linux 发行版中,git-send-email 未与典型的 git 安装一起打包。您可能需要安装此附加包;网上有很多资源可以帮助您做到这一点。您还需要确定配置它以使用您的 SMTP 服务器的正确方法;同样,由于此配置可能因您的系统和电子邮件设置而有很大差异,因此超出了本教程的范围。

准备初始补丁集

使用 Git 发送电子邮件是一个两部分的过程;在准备电子邮件本身之前,您需要准备补丁。幸运的是,这很简单。

$ git format-patch --cover-letter -o psuh/ --base=auto psuh@{u}..psuh
  1. --cover-letter 选项告诉 format-patch 为您创建封面信模板。在准备发送之前,您需要填写该模板 - 但现在,该模板将与您的其他补丁放在一起。

  2. -o psuh/ 选项告诉 format-patch 将补丁文件放入一个目录中。这很有用,因为 git send-email 可以接受一个目录并从中发送所有补丁。

  3. --base=auto 选项告诉命令记录“基本提交”,收件人应在此提交上应用补丁系列。auto 值将导致 format-patch 自动计算基本提交,这是远程跟踪分支的尖端提交与指定的修订范围的合并基。

  4. psuh@{u}..psuh 选项告诉 format-patch 为您在 psuh 分支上创建的提交生成补丁,自从它从其上游分支(如果您遵循了“设置您的工作区”部分中的示例,则为 origin/master)分叉以来。如果您已经在 psuh 分支上,您可以只说 @{u},它的意思是“自当前分支与上游分支分叉以来的提交”,这与前者相同。

该命令将为每个提交创建一个补丁文件。运行后,您可以用您喜欢的文本编辑器查看每个补丁,并确保一切看起来都正常;但是,不建议通过补丁文件进行代码修复。最好使用 git rebase -i 以正常方式进行更改,或者添加一个新提交,而不是修改补丁。

注意
可选地,您还可以使用 --rfc 标志将补丁主题前缀设置为“[RFC PATCH]”而不是“[PATCH]”。RFC 代表“请求评论”,表示虽然您的代码尚未准备好提交,但您希望开始代码审阅过程。当您的补丁是某个提议,但您不确定社区是否要采用该方法来解决问题时,也可以使用此标志 - 进行一种设计评审。您可能还会在列表中看到标记为“WIP”(进行中)的补丁 - 这意味着它们不完整,但希望审阅者查看到目前为止的内容。您可以使用 --subject-prefix=WIP 添加此标志。

检查并确保您的补丁和封面信模板存在于您指定的目录中 - 您已基本准备好发送审阅!

准备电子邮件

由于您使用 --cover-letter 调用了 format-patch,您已经准备了一个封面信模板。用您喜欢的编辑器打开它。

您应该会看到一些已经存在的标题。检查您的 From: 标题是否正确。然后修改您的 Subject:(有关如何为您的补丁系列选择好的标题,请参阅上面的 “封面信”

Subject: [PATCH 0/7] Add the 'psuh' command

请务必保留“[PATCH 0/X]”部分;这表明 Git 社区这封电子邮件是一个补丁系列的开始,许多审阅者会根据此标志过滤他们的电子邮件。

当您调用 git send-email 来添加封面信时,您需要添加一些额外的参数。

接下来,您需要填写封面信的正文。同样,有关要包含的内容,请参阅上面的 “封面信”

git format-patch --cover-letter 创建的模板包含一个 diffstat。这为审阅者提供了一个关于他们的审阅主题的摘要。从示例实现生成的 psuh 的 diffstat 如下所示。

 Documentation/git-psuh.adoc | 40 +++++++++++++++++++++
 Makefile                    |  1 +
 builtin.h                   |  1 +
 builtin/psuh.c              | 73 ++++++++++++++++++++++++++++++++++++++
 git.c                       |  1 +
 t/t9999-psuh-tutorial.sh    | 12 +++++++
 6 files changed, 128 insertions(+)
 create mode 100644 Documentation/git-psuh.adoc
 create mode 100644 builtin/psuh.c
 create mode 100755 t/t9999-psuh-tutorial.sh

最后,信件将包含用于生成补丁的 Git 版本。您可以保留该字符串不变。

发送电子邮件

此时,您应该有一个名为 psuh/ 的目录,其中填充了您的补丁和封面信。是时候邮寄出去了!您可以像这样发送:

$ git send-email --to=target@example.com psuh/*.patch
注意
查看 git help send-email 以了解您可能觉得有用的其他选项,例如更改回复地址或添加更多 CC 和 BCC 行。
注意
如果您不确定要 CC 谁,运行 contrib/contacts/git-contacts 可以列出潜在的审阅者。此外,您可以运行 git send-email --cc-cmd='perl contrib/contacts/git-contacts' feature/*.patch[1] 来自动将此电子邮件列表传递给 send-email
注意
当您发送真实补丁时,它将发送到 git@vger.kernel.org - 但请不要将本教程的补丁集发送到真实的邮件列表!目前,您可以将其发送给自己,以确保您了解其外观。
注意
发送补丁后,您可以访问 https://lore.kernel.org/git/ 来确认它们是否已送达邮件列表。使用搜索栏查找您的姓名或补丁的主题。如果出现,则表示您的电子邮件已成功送达。

运行上述命令后,您将看到每个即将发送的补丁的交互式提示。这为您提供了最后一次编辑或退出发送的机会(但请再次注意,不要在此处编辑代码)。一旦您在这些提示符处按下 ya,您的电子邮件就会被发送!恭喜!

太棒了,现在社区将放下一切来审阅您的更改。(开玩笑的 - 请耐心等待!)

发送 v2

本节将重点介绍如何发送补丁集的 v2 版本。要了解 v2 版本应包含哪些内容,请跳至 响应审阅 部分,了解如何处理审阅者的评论。

我们将重用我们的 psuh 主题分支用于 v2。在进行任何更改之前,我们将标记 v1 分支的尖端以便于引用。

$ git checkout psuh
$ git branch psuh-v1

使用 git rebase -i 根据审阅者的评论调整提交,从而优化您的补丁系列。一旦补丁系列准备好提交,请再次生成您的补丁,但使用一些新标志。

$ git format-patch -v2 --cover-letter -o psuh/ --range-diff master..psuh-v1 master..

--range-diff master..psuh-v1 参数告诉 format-patchpsuh-v1psuh 之间包含一个范围差异,并将其包含在封面信中(请参阅 git-range-diff[1])。这有助于告知审阅者 v1 和 v2 补丁之间的区别。

-v2 参数告诉 format-patch 将您的补丁输出为版本“2”。例如,您可能会注意到您的 v2 补丁都命名为 v2-000n-my-commit-subject.patch-v2 还会通过在补丁前面加上“[PATCH v2]”而不是“[PATCH]”来格式化您的补丁,并且您的范围差异将以“Range-diff against v1”为前缀。

运行此命令后,format-patch 将把补丁输出到 psuh/ 目录,与 v1 补丁并列。使用单个目录可以方便地在校对 v2 补丁时引用旧的 v1 补丁,但您需要小心只发送 v2 补丁。我们将使用像 psuh/v2-*.patch 这样的模式(而不是 psuh/*.patch,后者将匹配 v1 和 v2 补丁)。

再次编辑您的封面信。现在是提及您上次版本和现在版本之间差异的好时机,如果这是非常重要的内容。您不必在第二封封面信中使用完全相同的正文;重点是向审阅者解释您所做的可能不那么显眼的更改。

您还需要找到前一封封面信的 Message-ID。您可以从 git send-email 的输出中发送第一个系列时记下它,或者可以在 邮件列表 上查找它。在存档中找到您的封面信,点击它,然后点击“permalink”或“raw”以显示 Message-ID 标题。它应该匹配

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

您的 Message-ID 是 <foo.12345.author@example.com>。此示例将在下面使用;请务必将其替换为您**上一个封面信**的正确 Message-ID - 也就是说,如果您发送 v2,请使用 v1 的 Message-ID;如果您发送 v3,请使用 v2 的 Message-ID。

在查看电子邮件时,您还应该注意谁被 CC 了,因为在邮件列表中,保持线程中的所有 CC 是常见做法。您可以直接在您的封面信中添加这些 CC 行,在主题行之前,使用如下一行:

CC: author@example.com, Othe R <other@example.com>

现在再次发送电子邮件,密切注意您传递给命令的消息。

$ git send-email --to=target@example.com
		 --in-reply-to="<foo.12345.author@example.com>"
		 psuh/v2-*.patch

附录:单补丁更改

在某些情况下,您非常小的更改可能只包含一个补丁。当这种情况发生时,您只需要发送一封电子邮件。您的提交消息应该已经具有意义,并从高层次解释您补丁的目的(发生了什么以及为什么),但如果您需要提供更多上下文,可以在补丁中的 --- 下方进行。看下面的例子,它是在单个提交上使用 git format-patch 生成的,然后编辑以添加 --- 和 diffstat 之间的内容。

From 1345bbb3f7ac74abde040c12e737204689a72723 Mon Sep 17 00:00:00 2001
From: A U Thor <author@example.com>
Date: Thu, 18 Apr 2019 15:11:02 -0700
Subject: [PATCH] README: change the grammar

I think it looks better this way. This part of the commit message will
end up in the commit-log.

Signed-off-by: A U Thor <author@example.com>
---
Let's have a wild discussion about grammar on the mailing list. This
part of my email will never end up in the commit log. Here is where I
can add additional context to the mailing list about my intent, outside
of the context of the commit log. This section was added after `git
format-patch` was run, by editing the patch file in a text editor.

 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 88f126184c..38da593a60 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 Git - fast, scalable, distributed revision control system
 =========================================================

-Git is a fast, scalable, distributed revision control system with an
+Git is a fast, scalable, and distributed revision control system with an
 unusually rich command set that provides both high-level operations
 and full access to internals.

--
2.21.0.392.gf8f6787159e-goog

我的补丁已发送电子邮件 - 现在怎么办?

在发送更新版本之前,请给审阅者足够的时间来处理您的初始补丁。也就是说,请克制立即发送新版本的冲动,因为其他人可能已经开始审阅您的初始版本了。

在等待审阅评论时,您可能会在初始补丁中发现错误,或者可能意识到另一种更好的方法来实现补丁的目标。在这种情况下,您可以通过以下方式将您的发现传达给其他审阅者。

  • 如果您发现的错误很小,请回复您的补丁,就像您是审阅者一样,并提到您将在更新版本中修复它们。

  • 另一方面,如果您认为您想要改变方向的幅度如此之大,以至于对初始补丁的审阅将是浪费时间(对所有相关人员而言),请立即回复“我正在研究一种更好的方法,请忽略此补丁并等待更新版本。”来撤回该补丁。

好了,以上是如果您过早地发送了未经打磨的初始补丁,一种不错的做法。但当然,更好的方法是首先避免过早发送补丁。

请体谅审阅者检查您补丁每个新版本所需的时间。审阅者宁愿看到两天后出现一个经过打磨的、错误较少的版本,而不是现在看到初始版本(随后两天内出现多个“哎呀,我更喜欢这个版本而不是上一个版本”的补丁)。

响应审阅

几天后,您很可能会收到对您补丁集的回复,并附带一些评论。太棒了!现在您可以回去工作了。

礼貌的做法是回复每一条评论,通知审阅者您已做出建议的更改,认为原方案更好,或者该评论启发您以一种新的、优于原始方案和建议更改的方式来完成。这样,审阅者就不必检查您的 v2 来弄清楚您是否实现了他们的评论。

审阅者可能会询问您在补丁集中写的内容,无论是提议的提交日志消息还是代码本身。您应该在您的回复消息中回答这些问题,但审阅者之所以提出这些问题以理解您想写的内容,通常是因为您的补丁集需要澄清才能被理解。

不要仅仅满足于回答他们的问题并听他们说他们现在理解您想说什么。更新您的补丁以澄清审阅者遇到困难的要点,并准备您的 v2;您用来解释 v1 以回答审阅者问题的措辞可能会很有用。您的目标是使您的 v2 清晰到无需向下一个阅读它的人给出相同的解释。

如果您要反驳某个评论,请礼貌地说明您认为原方案更好的原因;请做好准备,审阅者仍可能不同意您的观点,而社区的其他人也可能在其中一方或另一方发表意见。与所有代码审阅一样,重要的是保持开放的心态,尝试以一种不同于您最初计划的方式行事;其他审阅者对项目的看法与您不同,他们可能正在考虑一个您没有想到的有效副作用。如果您不确定为什么会提出某项更改,或者审阅者要求您做什么,随时可以寻求澄清。

请确保您的电子邮件客户端具有纯文本电子邮件模式并已启用;Git 列表拒绝 HTML 电子邮件。请同时遵循 维护者说明 中概述的邮件列表礼仪,这类似于大多数开源社区中关于底部发帖和内联回复的礼仪规则。

当您对代码进行更改时,最干净(即,结果提交最容易查看)的方式是使用 git rebase -i(交互式 rebase)。看一下 O’Reilly 的这个 概述。总体的想法是修改每个需要更改的提交;这样,您就可以提交一个 v2,其中包含一个正确的补丁 A 和一个正确的补丁 B,而不是一个带有错误的补丁 A,一个在 v1 中没问题且无需上游审阅的补丁 B,以及一个修复补丁 A 的补丁 C。这改变了历史,但由于这是您尚未与任何人共享的本地历史,所以现在没问题!(稍后,这样做可能没有意义;看看它下面的部分以获取一些上下文。)

批准审阅后

Git 项目有四个集成分支:seennextmastermaint。您的更改将在审阅过程的早期由维护者放入 seen,然后,当它准备好进行更广泛的测试时,它将合并到 next。许多早期测试者使用 next,并可能会报告问题。最终,next 中的更改将进入 master,通常被认为是稳定的。最后,当一个新版本发布时,maint 用于基于 bug 修复。如本文档开头所述,您可以阅读 Documents/SubmittingPatches 以获取有关各种集成分支使用的更多信息。

现在回到当前:您的代码已获得上游审阅者的赞赏。它是完美的。它已准备好被接受。您无需做任何其他事情;维护者将把您的主题分支合并到 next,一切顺利。

但是,如果您在此之后发现它并不那么完美,您可能需要根据您在过程中的位置采取一些特殊步骤。

如果维护者在“git.git 中有什么新内容”电子邮件中宣布您的主题已标记为 next - 即,他们计划将其合并到 next 但尚未合并 - 您应该发送一封电子邮件请求维护者再等一会儿:“我发送了我的系列的 v4,您已将其标记为 next,但我需要更改这个和那个 - 请等待 v5 再合并。”

如果该主题已合并到 next,则不要使用 git rebase -i 修改您的补丁,而应增量地进行进一步更改 - 也就是说,添加另一个提交,基于维护者在 https://github.com/gitster/git 中详细介绍的主题分支。您的工作仍在同一主题中,但现在是增量的,而不是对主题分支进行 wholesale 重写。

维护者 GitHub 中的主题分支在 GitGitGadget 中有镜像,因此如果您通过这种方式发送审阅,您应该确保将 PR 打开到适当的 GitGitGadget/Git 分支。

如果您使用 git send-email,您可以像以前一样使用它,但您应该生成从 <topic>..<mybranch> 开始的 diffs,并将您的工作基于 <topic> 而不是 master


1. `contrib/` 下的脚本不是核心 `git` 二进制文件的一部分,必须直接调用。克隆 Git 代码库并运行 `perl contrib/contacts/git-contacts`。