Git
英文 ▾ 主題 ▾ 最新版本 ▾ MyFirstContribution 最後更新於 2.46.0

摘要

這是一個教學,示範了建立 Git 樹變更、發送以供審查,以及根據評論進行變更的端對端工作流程。

先決條件

本教學假設你已相當熟悉使用 Git 管理原始碼。 Git 工作流程步驟將大部分不會解釋。

本教學旨在總結以下文件,但讀者可能會發現有用的額外內容

  • Documentation/SubmittingPatches

  • Documentation/howto/new-command.txt

取得協助

如果你遇到困難,可以在以下地方尋求協助。

git@vger.kernel.org

這是主要的 Git 專案郵件列表,程式碼審查、版本公告、設計討論等都會在此進行。有興趣貢獻的人歡迎在此發問。Git 列表要求純文字電子郵件,並偏好在回覆郵件時使用內文和底部張貼;你會在所有給你的回覆中被抄送。或者,你可以傳送電子郵件至 <git+subscribe@vger.kernel.org> 來訂閱列表(詳情請參閱https://subspace.kernel.org/subscribing.html)。此郵件列表的封存可在瀏覽器中檢視。

git-mentoring@googlegroups.com

此郵件列表的目標是新的貢獻者,並被建立為在主要列表的公開視野之外發問和接收答案的地方。特別有興趣協助指導新手的資深貢獻者會在此列表上。為了避免搜尋索引器,需要群組成員資格才能檢視訊息;任何人都可以加入,無需批准。

#git-devel 在 Libera Chat 上

此 IRC 頻道用於 Git 貢獻者之間的對話。如果有人目前在線上且知道你問題的答案,你可以即時獲得協助。否則,你可以閱讀回溯,看看是否有人回答你。IRC 不允許離線私人訊息傳遞,因此如果你嘗試向某人傳送私人訊息,然後登出 IRC,他們將無法回覆你。最好在頻道中提出你的問題,這樣如果你斷線也能獲得回答,其他人也能從對話中學習。

開始使用

複製 Git 儲存庫

Git 會鏡像到多個位置。從其中一個位置複製儲存庫;https://git.dev.org.tw/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 作為工作基礎是可行的。然而,未來若為錯誤修正等,你應該檢查該文件並以適當的分支作為基礎。

在本文件中,我們將以主要專案的 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, const char **argv, const char *prefix)

我們還需要新增 psuh 的宣告;開啟 builtin.h,找到 cmd_pull 的宣告,並為了保持宣告按字母順序排序,在其正前方為 psuh 新增一行

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

請務必在你的 psuh.c#include "builtin.h"。你還需要 #include "gettext.h" 才能使用與列印輸出文字相關的函數。

繼續將一些隨意的 printf 新增至 cmd_psuh 函數。這是一個不錯的起點,因為我們現在可以新增建置規則和註冊指令。

注意
你的隨意文字以及你在本教學過程中將新增的大部分文字,都是面向使用者的。這表示它需要本地化。請查看「標記字串以進行翻譯」下的 po/README。在整個教學過程中,我們將視需要標記字串以進行翻譯;在未來撰寫面向使用者的指令時,你也應該這樣做。
int cmd_psuh(int argc, const char **argv, const char *prefix)
{
	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 中。我們可以透過將 cmd_struct 新增至 commands[] 陣列來註冊新的指令。 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() 呼叫。

	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" 中新增一行。然後,將以下程式碼片段新增至函式主體中

	const char *cfg_name;

...

	git_config(git_default_config, NULL);
	if (git_config_get_string_tmp("user.name", &cfg_name) > 0)
		printf(_("No name is found in config\n"));
	else
		printf(_("Your name: %s\n"), cfg_name);

git_config() 會從 Git 已知的設定檔中擷取設定,並套用標準優先順序規則。git_config_get_string_tmp() 會查找特定的索引鍵(「user.name」)並提供數值。有許多像這樣單一索引鍵查詢的函式;你可以在 Documentation/technical/api-config.txt 中查看所有這些函式(以及有關如何使用 git_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() 會包裝對 git_config() 的呼叫。讓我們修改我們在上一個提交中撰寫的程式碼。

請務必包含標頭,以便你使用 struct wt_status

#include "wt-status.h"

然後,修改你的 cmd_psuh 實作,以宣告你的 struct wt_status、準備好它並列印其內容

	struct wt_status status;

...

	wt_status_prepare(the_repository, &status);
	git_config(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-*.txt。這些是 Git 已知的子命令的手冊頁面。你可以開啟這些頁面並查看以熟悉格式,然後繼續建立一個新檔案 Documentation/git-psuh.txt。與 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.txt。這是一個方便的工具,可以擷取你需要能夠處理的選項,並且它會採用使用字串。

為了使用它,我們需要準備一個以 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。然而,由於許多 family/subcmd 組合已滿,最佳實踐似乎是找到一個與您新增的命令足夠接近的命令,並共用其命名空間。

建立一個新檔案 t/t9999-psuh-tutorial.sh。從標頭開始,如下所示(請參閱 t/README 中的「撰寫測試」和「來源 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

我們可以注意到幾件事

  • 每個提交都會以個別的電子郵件傳送,並以提交訊息標題作為主旨,前綴為「[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/git 開啟提取請求。前往 https://github.com/gitgitgadget/git 並使用「New pull request」按鈕或可能隨著您新推送的分支名稱出現的方便「Compare & pull request」按鈕來開啟 PR。

檢閱 PR 的標題和描述,因為 GitGitGadget 會將其分別用作變更的封面信函的主旨和正文。請參閱上面的 「封面信函」,以取得有關如何命名您的提交以及在描述中包含哪些內容的建議。

注意
對於單一修補程式貢獻,您的提交訊息應已具有意義,並從較高的層次解釋修補程式的目的(正在發生什麼以及為什麼),因此您通常不需要任何額外的背景資訊。在這種情況下,請移除 GitHub 自動從您的提交訊息產生的 PR 描述(您的 PR 描述應為空白)。如果您確實需要提供更多背景資訊,您可以在該空間中提供,並且它將附加到 GitGitGadget 將傳送的電子郵件中,介於三條虛線和 diffstat 之間(請參閱附錄章節:單一修補程式變更,以了解提交後的樣子)。

當您滿意時,請提交您的提取請求。

執行 CI 並準備傳送

如果您是第一次使用 GitGitGadget(這很可能,因為您正在使用本教學課程),那麼有人需要給予您使用該工具的權限。如 GitGitGadget 文件中所述,您只需要讓已經使用該工具的人在您的 PR 上評論 /allow <username>。GitGitGadget 會自動透過 CI 執行您的 PR,即使沒有授予權限,但您必須在有人允許您使用該工具後才能 /submit 您的變更。

注意
您通常可以透過檢查最近有人被授予 /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 的 Pull Request;你會看到 CI 再次啟動。現在當 CI 執行時,是個好時機去修改你 Pull Request 討論串頂部的描述;它將再次被用作封面信。你應該利用這個空間描述自上一個版本以來發生了哪些變化,以便你的審閱者知道他們正在看什麼。當 CI 執行完畢後,你可以再次評論 /submit - GitGitGadget 會自動為你的變更加上 v2 的標記。

使用 git send-email 發送 Patch

如果你不想使用 GitGitGadget,你也可以使用 Git 本身來郵寄你的 Patch。以這種方式使用 Git 的一些好處包括更精細地控制主旨行(例如,能夠在主旨中使用標籤 [RFC PATCH]),以及能夠將「試運行」郵件發送給自己,以確保在發送到列表之前一切看起來都沒問題。

先決條件:設定 git send-email

send-email 的設定會因你的作業系統和電子郵件提供商而異,因此本教程不會涵蓋,僅說明在許多 Linux 發行版中,git-send-email 並未與典型的 git 安裝一起打包。你可能需要安裝這個額外的套件;網路上有很多資源可以幫助你這樣做。你還需要確定正確的配置方式,以使用你的 SMTP 伺服器;同樣,由於這種配置會根據你的系統和電子郵件設定而有很大差異,因此超出本教程的範圍。

準備初始 Patchset

使用 Git 發送電子郵件是一個分為兩個部分的過程;在你可以準備電子郵件本身之前,你需要準備 Patch。幸運的是,這非常簡單。

$ git format-patch --cover-letter -o psuh/ --base=auto psuh@{u}..psuh
  1. --cover-letter 選項告訴 format-patch 為你建立一個封面信範本。在準備好發送之前,你需要填寫該範本 - 但現在,該範本將與你的其他 Patch 放在一起。

  2. -o psuh/ 選項告訴 format-patch 將 Patch 檔案放入一個目錄中。這很有用,因為 git send-email 可以接收一個目錄並從那裡發送所有 Patch。

  3. --base=auto 選項告訴命令記錄「基準提交 (base commit)」,預期收件人會在此基礎上應用 Patch 系列。auto 值會讓 format-patch 自動計算基準提交,它是遠端追蹤分支的尖端提交和指定修訂範圍的合併基準。

  4. psuh@{u}..psuh 選項告訴 format-patch 為你在 psuh 分支上建立的提交產生 Patch,因為它從其上游分支出來(如果你遵循「設定你的工作區」章節中的範例,則為 origin/master)。如果你已經在 psuh 分支上,你可以直接說 @{u},意思是「從目前分支從其上游分支出來後所做的提交」,意思是一樣的。

該命令將為每個提交建立一個 Patch 檔案。執行後,你可以使用你最喜歡的文字編輯器查看每個 Patch,並確保一切正常;但是,不建議透過 Patch 檔案進行程式碼修復。更好的做法是使用 git rebase -i 或新增一個提交,而不是修改 Patch。

注意
選擇性地,你也可以使用 --rfc 標誌在你的 Patch 主旨前面加上「[RFC PATCH]」而不是「[PATCH]」。RFC 代表「徵求意見 (request for comments)」,表示你的程式碼尚未完全準備好提交,但你希望開始程式碼審閱流程。當你的 Patch 是一個提案時,也可以使用它,但你不確定社群是否希望使用該方法解決問題 - 進行某種設計審閱。你可能還會在列表中看到標記為「WIP」的 Patch - 這表示它們不完整,但希望審閱者查看他們目前擁有的內容。你可以使用 --subject-prefix=WIP 來新增此標誌。

檢查並確保你的 Patch 和封面信範本存在於你指定的目錄中 - 你幾乎準備好發送你的審閱了!

準備電子郵件

由於你使用 --cover-letter 叫用了 format-patch,你已經準備好一個封面信範本。在你最喜歡的編輯器中打開它。

你應該看到一些標頭已經存在。檢查你的 From: 標頭是否正確。然後修改你的 Subject: (有關如何為你的 Patch 系列選擇好的標題,請參閱上方

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

請務必保留「[PATCH 0/X]」部分;這就是向 Git 社群表明此電子郵件是一個 Patch 系列的開頭的方式,並且許多審閱者會針對此類標記過濾他們的電子郵件。

當你叫用 git send-email 時,你需要新增一些額外的參數來新增封面信。

接下來,你必須填寫你的封面信正文。同樣,請參閱上方以了解要包含哪些內容。

git format-patch --cover-letter 建立的範本包含一個 diffstat。這讓審閱者了解在審閱你的主題時會遇到什麼。從範例實作產生的 psuh 的 diffstat 看起來像這樣

 Documentation/git-psuh.txt | 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.txt
 create mode 100644 builtin/psuh.c
 create mode 100755 t/t9999-psuh-tutorial.sh

最後,這封信將包含用於產生 Patch 的 Git 版本。你可以保留該字串不變。

發送電子郵件

此時,你應該有一個目錄 psuh/,其中填滿了你的 Patch 和封面信。是時候把它寄出去了!你可以這樣發送它

$ 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
注意
當你發送真正的 Patch 時,它會寄到 git@vger.kernel.org - 但請不要將本教學的 Patchset 發送到真正的郵寄清單!現在,你可以將其發送給自己,以確保你了解它的外觀。

執行上述命令後,你將看到一個互動式提示,針對即將發送的每個 Patch。這給你最後一次編輯或停止發送某些內容的機會(但同樣,不要以這種方式編輯程式碼)。當你在這些提示中按下 ya 時,你的電子郵件將被發送!恭喜你!

太棒了,現在社群將放下手邊的一切並審閱你的變更。(開玩笑的 - 請耐心等待!)

發送 v2

本節將重點介紹如何發送 Patchset 的 v2。若要了解 v2 中應包含哪些內容,請跳至回應審閱,以取得有關如何處理審閱者意見的資訊。

我們將重複使用我們的 psuh 主題分支來進行 v2。在我們進行任何變更之前,我們將標記 v1 分支的尖端,以便輕鬆參考

$ git checkout psuh
$ git branch psuh-v1

使用 git rebase -i 根據審閱者的意見調整提交,以改進你的 Patch 系列。一旦 Patch 系列準備好提交,請再次產生你的 Patch,但使用一些新的標誌

$ 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 Patch 之間的差異。

-v2 參數告訴 format-patch 將你的 Patch 輸出為版本「2」。例如,你可能會注意到你的 v2 Patch 都像 v2-000n-my-commit-subject.patch 這樣命名。-v2 還會透過在 Patch 前面加上「[PATCH v2]」而不是「[PATCH]」來格式化你的 Patch,並且你的 range-diff 前面會加上「Range-diff against v1」。

在你執行此命令後,format-patch 會將 Patch 輸出到 psuh/ 目錄,與 v1 Patch 一起。使用單個目錄可以讓你在校對 v2 Patch 時輕鬆參考舊的 v1 Patch,但你需要小心只發送 v2 Patch。我們將使用類似 psuh/v2-*.patch 的模式(而不是 psuh/*.patch,它會比對 v1 和 v2 Patch)。

再次編輯你的封面信。現在是個好時機來說明你上次版本和現在有何不同,如果它有任何重大意義。你的第二封封面信中不需要完全相同的主體;專注於向審閱者解釋你所做的變更,這些變更可能不太明顯。

你還需要去找你上一封封面信的 Message-ID。你可以在發送第一個系列時,從 git send-email 的輸出中記下它,或者你可以在郵寄清單上查詢它。在存檔中找到你的封面信,按一下它,然後按一下「永久連結」或「原始」以顯示 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

額外章節:單一 Patch 變更

在某些情況下,你非常小的變更可能只包含一個 Patch。當發生這種情況時,你只需要發送一封電子郵件。你的提交訊息應該已經很有意義,並從高層次解釋你的 Patch 的目的(正在發生什麼以及為什麼),但如果你需要提供更多內容,你可以在你的 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

我的 Patch 已寄出 - 接下來呢?

請給審閱者足夠的時間來處理您的初始補丁,然後再發送更新的版本。也就是說,請克制住立即發送新版本的衝動,因為其他人可能已經開始審閱您的初始版本了。

在等待審閱意見時,您可能會發現初始補丁中的錯誤,或者意識到有其他更好方法可以達成補丁的目的。在這種情況下,您可以如下方式將您的發現告知其他審閱者:

  • 如果發現的錯誤是小問題,請像審閱者一樣回覆您的補丁,並說明您將在更新的版本中修復這些問題。

  • 另一方面,如果您認為您想要大幅改變方向,導致審閱初始補丁會浪費時間(對所有相關人員而言),請立即撤回補丁,並回覆類似「我正在研究更好的方法,所以請忽略此補丁並等待更新的版本。」的訊息。

如果您的初始補丁在未經潤飾的情況下過早發送,上述作法是個好習慣。但更好的方法當然是避免在一開始就過早發送您的補丁。

請體諒審閱者檢查您的補丁每個新版本所需的時間。審閱者寧願在兩天後收到一個單一潤飾過的版本,而不是現在看到初始版本(然後在兩天內收到數個「糟糕,我比較喜歡這個版本」的補丁),而且這個版本犯的錯比較少,是他們唯一需要審閱的版本。

回覆審閱意見

幾天後,您應該會收到針對您的補丁集的評論回覆。太棒了!現在您可以繼續工作了。

禮貌上,應該回覆每則評論,告知審閱者您已做出建議的變更、覺得原始版本比較好,或是該評論啟發您以新的方式做改進,該方式比原始版本和建議的變更都更好。這樣審閱者就不需要檢查您的 v2 版本來判斷您是否實施了他們的評論。

審閱者可能會詢問您在補丁集中撰寫的內容,無論是在建議的提交日誌訊息中,還是在變更本身中。您應該在您的回覆訊息中回答這些問題,但審閱者之所以問這些問題,通常是因為您的補丁集需要澄清才能被理解,他們想了解您想表達的意思。

不要只滿足於在您的回覆中回答他們的問題,然後聽他們說他們現在了解您想表達的意思。更新您的補丁,以澄清審閱者難以理解的地方,並準備您的 v2 版本;您用來解釋 v1 版本以回答審閱者問題的文字可能會很有用。您的目標是使您的 v2 版本足夠清楚,讓下一位閱讀它的人不需要您再做相同的解釋。

如果您要反駁某個評論,請保持禮貌並解釋為什麼您覺得原始版本比較好;請做好審閱者可能仍然不同意您的觀點,並且社群中的其他人可能會傾向另一方的心理準備。與所有程式碼審閱一樣,保持開放的態度接受與您最初計劃不同的方式來做事是很重要的;其他審閱者對專案的觀點與您不同,並且可能正在考慮您沒有想到的有效副作用。如果您不確定為什麼建議某個變更,或審閱者要求您做什麼,隨時可以要求澄清。

請確保您的電子郵件用戶端具有純文字電子郵件模式,並且已開啟;Git 郵件列表拒絕 HTML 電子郵件。另請遵循 維護者注意事項 中概述的郵件列表禮儀,這與大多數開源社群中關於底部回覆和內嵌回覆的禮儀規則相似。

當您更改程式碼時,如果您使用 git rebase -i(互動式變基),產生的提交會最清晰,也就是說,最容易查看。請查看 O'Reilly 的這篇 概述。一般概念是修改每個需要變更的提交;這樣,您就不必讓補丁 A 有錯誤,補丁 B 沒問題,在 v1 版本中不需要任何上游審閱,以及補丁 C 在 v2 版本中修復補丁 A。您只需發送一個修正後的補丁 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