簡體中文 ▾ 主題 ▾ 最新版本 ▾ 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.tw/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