Git
章節 ▾ 第二版

10.6 Git 內部原理 - 傳輸協定

傳輸協定

Git 可以透過兩種主要方式在兩個儲存庫之間傳輸資料:「笨」協定和「智慧型」協定。本節將快速介紹這兩種主要協定的運作方式。

「笨」協定

如果您要設定透過 HTTP 以唯讀方式提供的儲存庫,「笨」協定很可能就是會被使用的協定。這個協定之所以被稱為「笨」,是因為在傳輸過程中,伺服器端不需要任何特定的 Git 程式碼;提取過程是一連串的 HTTP GET 請求,客戶端可以假設伺服器上 Git 儲存庫的佈局。

注意

「笨」協定現在相當少用。它很難保護或設為私人,所以大多數 Git 主機(無論是雲端或本地)都會拒絕使用它。一般建議使用「智慧型」協定,我們會在稍後加以說明。

讓我們跟隨 simplegit 程式庫的 http-fetch 過程

$ git clone http://server/simplegit-progit.git

此命令的第一件事是拉取 info/refs 檔案。此檔案由 update-server-info 命令寫入,這就是為什麼您需要啟用它作為 post-receive hook,才能使 HTTP 傳輸正常運作

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

現在您有遠端參考和 SHA-1 的清單。接下來,您會尋找 HEAD 參考,以便知道完成時要檢出哪個版本

=> GET HEAD
ref: refs/heads/master

您需要在完成程序時檢出 master 分支。此時,您已準備好開始遍歷過程。因為您的起點是您在 info/refs 檔案中看到的 ca82a6 提交物件,所以您會從提取該物件開始

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)

您會收到一個物件,該物件在伺服器上是以鬆散格式存在,而且您是透過靜態 HTTP GET 請求提取它的。您可以對其進行 zlib 解壓縮、去除標頭,並查看提交內容

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700

Change version number

接下來,您還有兩個物件要擷取:cfda3b,這是我們剛擷取的提交指向的內容樹狀結構;以及 085bb3,這是父提交

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)

這會提供您下一個提交物件。抓取樹狀物件

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)

糟糕,看來該樹狀物件在伺服器上不是以鬆散格式存在,所以您會收到 404 回應。發生這種情況的原因有幾個:該物件可能在另一個儲存庫中,或可能在此儲存庫的 packfile 中。Git 會先檢查是否有任何列出的替代方案

=> GET objects/info/http-alternates
(empty file)

如果返回替代 URL 的清單,Git 會在那裡檢查鬆散檔案和 packfile,這是一個很好的機制,讓彼此分支的專案可以在磁碟上共享物件。不過,因為在此案例中沒有列出替代方案,您的物件必定在 packfile 中。若要查看此伺服器上有哪些 packfile,您需要取得 objects/info/packs 檔案,其中包含它們的清單(也由 update-server-info 產生)

=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

伺服器上只有一個 packfile,所以您的物件顯然在那裡,但您會檢查索引檔案以確認。如果伺服器上有數個 packfile,這也很有用,如此一來,您可以查看哪個 packfile 包含您需要的物件

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)

現在您有了 packfile 索引,您可以查看您的物件是否在其中,因為索引會列出 packfile 中包含的物件的 SHA-1 以及這些物件的偏移量。您的物件在那裡,所以請繼續擷取整個 packfile

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)

您有樹狀物件,所以會繼續遍歷您的提交。它們也都在您剛下載的 packfile 中,所以您不必再向伺服器發出任何請求。Git 會檢出由您在開頭下載的 HEAD 參考所指向的 master 分支的工作副本。

「智慧型」協定

「笨協定」雖然簡單,但效率較低,而且無法處理從用戶端寫入資料到伺服器的情況。「聰明協定」是一種更常見的資料傳輸方式,但它需要在遠端有一個了解 Git 的進程——它可以讀取本地資料,判斷用戶端擁有和需要的內容,並為其產生客製化的打包檔案。資料傳輸有兩組進程:一對用於上傳資料,另一對用於下載資料。

上傳資料

要將資料上傳到遠端進程,Git 使用 send-packreceive-pack 進程。send-pack 進程在用戶端執行,並連線到遠端的 receive-pack 進程。

SSH

舉例來說,假設您在專案中執行 git push origin master,而 origin 定義為使用 SSH 協定的 URL。Git 會啟動 send-pack 進程,該進程會透過 SSH 發起到伺服器的連線。它會嘗試透過類似這樣的 SSH 呼叫在遠端伺服器上執行命令

$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master□report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1+github-607-gfba4028 delete-refs
0000

git-receive-pack 命令會立即回應,為其目前擁有的每個參照回應一行——在這個例子中,只有 master 分支及其 SHA-1 值。第一行還包含伺服器的功能列表(這裡有 report-statusdelete-refs 以及其他一些功能,包括用戶端識別碼)。

資料以區塊形式傳輸。每個區塊都以 4 個字元的十六進位值開始,指定區塊的長度(包括長度本身的 4 個位元組)。區塊通常包含一行資料和一個尾隨的換行符號。您的第一個區塊以 00a5 開始,這是十六進位的 165,表示該區塊長 165 個位元組。下一個區塊是 0000,表示伺服器已完成其參照列表。

現在它知道伺服器的狀態,您的 send-pack 進程會判斷伺服器沒有的 commit。對於此推送將更新的每個參照,send-pack 進程會將該資訊告訴 receive-pack 進程。例如,如果您要更新 master 分支並新增一個 experiment 分支,則 send-pack 的回應可能如下所示

0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \
	refs/heads/master report-status
006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
	refs/heads/experiment
0000

Git 為您正在更新的每個參照傳送一行,該行包含行的長度、舊的 SHA-1 值、新的 SHA-1 值以及正在更新的參照。第一行還包含用戶端的功能。所有 '0' 的 SHA-1 值表示之前沒有任何東西——因為您正在新增 experiment 參照。如果您要刪除參照,您會看到相反的情況:右側全部為 '0'。

接下來,用戶端會傳送一個包含伺服器尚未擁有的所有物件的打包檔案。最後,伺服器會回應一個成功(或失敗)的指示。

000eunpack ok
HTTP(S)

這個過程在 HTTP 上大致相同,儘管交握的方式略有不同。連線是透過以下請求發起的

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master□report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000

這就是第一個用戶端-伺服器交換的結尾。然後用戶端會發出另一個請求,這次是 POST,其中包含 send-pack 提供的資料。

=> POST http://server/simplegit-progit.git/git-receive-pack

POST 請求包含 send-pack 的輸出和打包檔案作為其有效負載。然後伺服器會透過其 HTTP 回應指示成功或失敗。

請記住,HTTP 協定可能會將此資料進一步包裝在分塊傳輸編碼中。

下載資料

當您下載資料時,會涉及到 fetch-packupload-pack 進程。用戶端會啟動一個 fetch-pack 進程,該進程會連線到遠端的 upload-pack 進程,以協商將要下載的資料。

SSH

如果您要透過 SSH 執行 fetch 操作,fetch-pack 會執行類似這樣的操作

$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"

fetch-pack 連線後,upload-pack 會傳回類似這樣的內容

00dfca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master
0000

這與 receive-pack 回應的內容非常相似,但功能不同。此外,它還會傳回 HEAD 所指向的內容 (symref=HEAD:refs/heads/master),以便用戶端知道如果這是複製操作,應該簽出什麼。

此時,fetch-pack 進程會查看它擁有的物件,並透過傳送「want」和它想要的 SHA-1 值來回應它需要的物件。它會傳送它已擁有的所有物件,並使用「have」和 SHA-1 值。在此列表的末尾,它會寫入「done」以啟動 upload-pack 進程開始傳送它所需資料的打包檔案。

003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000
HTTP(S)

fetch 操作的交握會進行兩個 HTTP 請求。第一個是對與笨協定中使用的相同端點的 GET 請求

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed no-done symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

這與透過 SSH 連線調用 git-upload-pack 非常相似,但第二次交換是作為單獨的請求執行的

=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000

再次強調,這與上面的格式相同。對此請求的回應會指示成功或失敗,並包含打包檔案。

協定摘要

本節包含對傳輸協定的一個非常基本的概述。該協定包含許多其他功能,例如 multi_ackside-band 功能,但涵蓋這些功能超出了本書的範圍。我們嘗試讓您了解用戶端和伺服器之間的一般往返流程;如果您需要比這更多的知識,您可能需要查看 Git 原始碼。

scroll-to-top