-
1. 開始
-
2. Git 基礎
-
3. Git 分支
-
4. 伺服器上的 Git
- 4.1 協定
- 4.2 在伺服器上安裝 Git
- 4.3 產生 SSH 公開金鑰
- 4.4 設定伺服器
- 4.5 Git Daemon
- 4.6 智慧型 HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方託管選項
- 4.10 總結
-
5. 分散式 Git
-
A1. 附錄 A:其他環境中的 Git
- A1.1 圖形化介面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 總結
-
A2. 附錄 B:將 Git 嵌入您的應用程式
-
A3. 附錄 C: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-pack
和 receive-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-status
、delete-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-pack
和 upload-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_ack
或 side-band
功能,但涵蓋這些功能超出了本書的範圍。我們嘗試讓您了解用戶端和伺服器之間的一般往返流程;如果您需要比這更多的知識,您可能需要查看 Git 原始碼。