简体中文 ▾ 主题 ▾ 最新版本 ▾ MyFirstContribution 最后更新于 2.50.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 贡献者之间交流。如果有人在线并知道您问题的答案,您可以实时获得帮助。否则,您可以阅读回滚日志以查看是否有人回复了您。IRC 不允许离线私信,因此如果您尝试私信某人然后退出 IRC,他们将无法回复您。最好在频道中提问,这样即使您断开连接也能得到回复,并且其他人也能从对话中学习。

入门

克隆 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,它是“小马驹说‘嗯,你好’”的缩写——尽管在用户日常工作流程中被频繁调用,但此功能尚未实现。

(我们已经看到在实现 sl 等流行命令方面的一些其他努力。)

设置您的工作区

让我们首先创建一个开发分支来处理我们的更改。根据 Documentation/SubmittingPatches,由于全新的命令是一个新功能,将其基于 master 分支是可以的。然而,未来对于错误修复等,您应该查阅该文档并将其基于适当的分支。

出于本文档的目的,我们将所有工作基于上游项目的 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)

几点注意事项

  • 子命令实现以 int argc + const char **argv 的形式接收其命令行参数,就像 main() 那样。

  • 它还接受两个额外参数: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#include "builtin.h"。您还需要 #include "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 列的主题行开头,其中包含您正在处理的组件名称,后跟一个空行(始终需要),然后是提交消息的正文,正文应提供大部分上下文。请记住明确表达并提供更改的“为什么”,特别是如果从您的差异中不容易理解。编辑提交消息时,请勿删除上方 -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)表示您同意开发者原始证明 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)。这并没有多大帮助。那么我们还能获得什么其他上下文呢?

添加一行 #include "config.h"#include "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()builtin/commit.c 中由 cmd_status() 调用。查看该实现,我们看到状态配置的填充方式如下

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);
	}

结构体 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,“编写文档”部分)。使用以下模板填写您自己的手册页

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 条目指向的位置将被更新。请务必用 parse_options() 的结果替换您的 argc,否则如果您稍后尝试解析 argv,将会感到困惑。

值得注意的是特殊参数 --。您可能知道,许多 Unix 命令使用 -- 来表示“命名参数的结束”—— -- 之后的所有参数都被解释为仅是位置参数。(如果您想传递通常被解释为标志的参数,这会很方便。)当 parse_options() 达到 -- 时,它将终止解析,并为您提供其余的选项,不做任何修改。

现在您有了用法提示,您可以教 Git 如何将其显示在由 git help gitgit help -a 显示的通用命令列表中,该列表是从 command-list.txt 生成的。找到 git-pull 的行,以便您可以按字母顺序在其上方添加您的 git-psuh 行。现在,我们可以添加一些关于命令的属性,这些属性会影响它在上述帮助命令中显示的位置。command-list.txt 的顶部共享了一些关于每个属性含义的信息;在那些帮助页面中,命令是根据这些属性排序的。git psuh 是面向用户的,或者说是“瓷器命令”(porcelain command)——所以我们将其标记为“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。然而,由于许多 family/subcmd 组合已满,最佳实践似乎是找到一个足够接近您已添加的命令并共享其命名空间。

创建一个新文件 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 还会使输出更美观。

继续,也提交此更改。

准备共享:补丁系列剖析

您可能已经注意到,Git 项目通过电子邮件补丁进行代码审查,这些补丁在准备好并获得社区批准后由维护者应用。Git 项目不接受来自拉取请求的贡献,用于审查的电子邮件补丁需要以特定方式格式化。

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

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

我们可以注意到几点

  • 每个提交都作为单独的电子邮件发送,以提交消息标题作为主题,对于 n 个提交系列中的第 i 个提交,标题前缀为 "[PATCH i/n]"。

  • 每个补丁都作为对系列介绍性电子邮件(称为封面信)的回复发送,前缀为 "[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 上派生 git/git

在使用 GitGitGadget 发送补丁进行审查之前,您需要派生 Git 项目并上传您的更改。第一件事——确保您有一个 GitHub 账户。

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

上传到您自己的派生仓库

要将您的分支上传到您自己的派生仓库,您需要将新派生仓库添加为远程。您可以使用 git remote -v 来显示您已添加的远程仓库。在 GitHub 上您新派生仓库的页面上,您可以点击“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(将 remotename 替换为您的实际远程名称)以准备推送。

接下来,通过运行 git branch 再次确认您一直在新分支上进行所有开发。如果没有,现在是将您的新提交移动到它们自己分支的好时机。

如本文档开头简要提及,我们将工作基于 master 分支,因此请按照以下所示或使用您偏好的工作流程进行更新。

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

最后,您已准备好推送您的新主题分支!(由于我们选择的分支和命令名称,在键入以下命令时请务必小心。)

$ git push remotename psuh

现在您应该能够去 GitHub 上查看您新创建的分支了。

向 GitGitGadget 发送 PR

为了让您的代码经过测试并格式化以供审查,您需要首先针对 gitgitgadget/git 打开一个拉取请求 (Pull Request)。前往 https://github.com/gitgitgadget/git,通过“New pull request”按钮或可能与您新推送的分支名称一起出现的便捷“Compare & pull request”按钮打开一个 PR。

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

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

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

运行 CI 并准备发送

如果这是您第一次使用 GitGitGadget(很可能,因为您正在使用本教程),那么需要有人授予您使用该工具的权限。正如 GitGitGadget 文档中提到的,您只需要一个已经使用它的人在您的 PR 上评论 /allow <username> 即可。GitGitGadget 会自动让您的 PR 通过 CI,即使没有授予权限,但在有人允许您使用该工具之前,您将无法 /submit 您的更改。

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

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

$ git push -f remotename psuh

事实上,您应该继续以这种方式进行更改,直到您的补丁被 next 分支接受。

发送您的补丁

现在您的 CI 已通过,并且有人已授予您使用 /allow 命令的 GitGitGadget 权限,发送审查就像在您的 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 代表“请求评论”(request for comments),表示您的代码尚未完全准备好提交,但您希望开始代码审查过程。这也可以在您的补丁是一个提案时使用,但您不确定社区是否希望通过该方法解决问题——以进行某种设计审查。您可能还会在列表中看到标记为“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 从示例实现生成的差异统计如下所示

 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 以获取您可能觉得有价值的其他选项,例如更改回复地址或添加更多抄送和密送行。
注意
如果您不确定要抄送给谁,运行 contrib/contacts/git-contacts 可以列出潜在的审阅者。此外,您还可以执行 git send-email --cc-cmd='perl contrib/contacts/git-contacts' feature/*.patch[1],以自动将此电子邮件列表传递给 send-email
注意
当您发送真正的补丁时,它将发送到 git@vger.kernel.org ——但请不要将您教程中的补丁集发送到真实的邮件列表!现在,您可以将其发送给自己,以确保您了解它会是什么样子。

运行上述命令后,系统将为即将发送的每个补丁显示一个交互式提示。这为您提供了最后一次编辑或取消发送的机会(但同样,不要以这种方式编辑代码)。一旦您在这些提示符处按下 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-patch 在封面信中包含 psuh-v1psuh 之间的范围差异(range-diff)(参见 git-range-diff[1])。这有助于向审阅者说明您的 v1 和 v2 补丁之间的差异。

-v2 参数告诉 format-patch 将您的补丁输出为版本“2”。例如,您可能会注意到您的 v2 补丁都命名为 v2-000n-my-commit-subject.patch-v2 还会通过将补丁前缀改为“[PATCH v2]”而不是“[PATCH]”来格式化您的补丁,并且您的范围差异(range-diff)将以“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: 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 在单个提交上生成的,然后编辑以在 --- 和差异统计之间添加内容。

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(交互式变基)是最干净的——也就是说,生成的提交最容易查看。请查看 O’Reilly 的这篇概述。大体思路是修改每个需要更改的提交;这样,您就不必拥有一个带有错误的补丁 A、一个在 v1 中没问题且不需要上游审查的补丁 B,以及一个在 v2 中修复补丁 A 的补丁 C,而只需发布一个带有正确补丁 A 和正确补丁 B 的 v2 版本。这是更改历史记录,但由于它是您尚未与任何人共享的本地历史记录,所以目前可以接受!(稍后,这样做可能没有意义;请查看本节下方的一节以获取一些上下文。)

审查批准后

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

回到现在:您的代码已获得上游审阅者的高度赞扬。它很完美。它已准备好被接受。您无需做任何其他事情;维护者会将您的主题分支合并到 next 分支,一切都很美好。

然而,如果在此之后您发现它并非如此完美,您可能需要根据您所处的流程阶段采取一些特殊步骤。

如果维护者已在“git.git最新动态”电子邮件中宣布你的主题被标记为next——也就是说,他们计划将其合并到next但尚未完成——你应该发送一封电子邮件,请求维护者再等等:“我已经发送了我的系列补丁的v4版本,您已将其标记为next,但我需要修改一些内容——请等待v5版本再合并。”

如果主题已经合并到next,你应该增量地进行后续更改,而不是使用git rebase -i修改你的补丁——也就是说,通过另一个提交,基于维护者主题分支的顶端,详情请参阅https://github.com/gitster/git。你的工作仍然在同一个主题中,但现在是增量的,而不是对主题分支的整体重写。

维护者GitHub上的主题分支已在GitGitGadget中镜像,因此,如果你通过这种方式发送你的评论,你应该确保针对相应的GitGitGadget/Git分支打开你的PR。

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


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