-
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 可以在兩個倉庫之間以兩種主要方式傳輸資料:“啞協議”(dumb protocol)和“智慧協議”(smart protocol)。本節將快速介紹這兩種主要協議的運作方式。
啞協議
如果你正在設定一個倉庫以 HTTP 方式提供只讀服務,那麼很可能會使用啞協議。這個協議之所以被稱為“啞協議”,是因為它在傳輸過程中不需要伺服器端有任何 Git 特定的程式碼;抓取(fetch)過程是一系列 HTTP GET
請求,客戶端可以假定伺服器上 Git 倉庫的佈局。
注意
|
啞協議如今已相當少用。它難以保障安全或實現私有化,因此大多數 Git 主機(無論是雲端還是本地部署)都會拒絕使用它。通常建議使用智慧協議,我們將在後面更詳細地描述它。 |
讓我們跟蹤 simplegit 庫的 http-fetch
過程。
$ git clone http://server/simplegit-progit.git
該命令做的第一件事是下載 info/refs
檔案。這個檔案是由 update-server-info
命令寫入的,這就是為什麼你需要將其作為 post-receive
鉤子啟用,以便 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 就會檢查那裡的鬆散檔案和打包檔案——這對於相互分支的專案來說,是一種很好的在磁碟上共享物件的機制。然而,在這種情況下,由於沒有列出備用倉庫,你的物件肯定在一個打包檔案中。要檢視此伺服器上有哪些打包檔案,你需要獲取 objects/info/packs
檔案,該檔案包含了它們的列表(也由 update-server-info
生成)。
=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
伺服器上只有一個打包檔案,所以你的物件顯然在那裡,但你會檢查索引檔案以確保。如果你在伺服器上有多個打包檔案,這也很實用,這樣你就可以看到哪個打包檔案包含你需要的物件。
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)
現在你有了打包檔案索引,你可以檢視你的物件是否在其中——因為索引列出了打包檔案中包含的物件的 SHA-1 值以及這些物件的偏移量。你的物件就在那裡,所以繼續獲取整個打包檔案吧。
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)
你已經有了你的樹物件,所以你可以繼續遍歷你的提交。它們也都在你剛剛下載的打包檔案中,因此你無需再向伺服器傳送任何請求。Git 會檢出 master
分支的一個工作副本,該分支由你一開始下載的 HEAD 引用所指向。
智慧協議
啞協議簡單但效率較低,並且無法處理從客戶端向伺服器寫入資料。智慧協議是一種更常見的資料傳輸方法,但它需要遠端端有一個瞭解 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
以及其他一些,包括客戶端識別符號)。
資料以塊(chunks)的形式傳輸。每個塊都以一個 4 字元的十六進位制值開頭,指定塊的長度(包括長度本身佔用的 4 位元組)。塊通常包含一行資料和一個尾隨的換行符。你的第一個塊以 00a5 開頭,這是 165 的十六進位制表示,意味著該塊長 165 位元組。下一個塊是 0000,表示伺服器已完成其引用列表。
現在,send-pack
程序知道伺服器的狀態了,它會確定自己有哪些提交是伺服器沒有的。對於本次推送將要更新的每個引用,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 協議可能會將這些資料進一步封裝在分塊傳輸編碼(chunked transfer encoding)中。
下載資料
當你下載資料時,會涉及 fetch-pack
和 upload-pack
程序。客戶端啟動一個 fetch-pack
程序,該程序連線到遠端端的 upload-pack
程序,以協商將要下載的資料。
SSH
如果你透過 SSH 執行抓取,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)
抓取操作的握手需要兩個 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 原始碼。