-
1. 起步
-
2. Git 基礎
-
3. Git 分支
-
4. 伺服器上的 Git
- 4.1 協議
- 4.2 在伺服器上部署 Git
- 4.3 生成 SSH 公鑰
- 4.4 架設伺服器
- 4.5 Git Daemon
- 4.6 Smart 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 鉤子啟用,以便 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 中包含的物件及其偏移量。您的物件在那裡,所以繼續獲取整個 packfile
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)
您獲得了樹物件,所以您繼續遍歷您的提交。它們都包含在您剛剛下載的 packfile 中,因此您不必再向伺服器發出任何請求。Git 會檢出您在開始時下載的 HEAD 引用指向的 master 分支的工作副本。
智慧協議
啞協議簡單但效率不高,並且無法處理客戶端到伺服器的資料寫入。智慧協議是更常用的資料傳輸方法,但它需要在遠端端有一個瞭解 Git 的程序——它可以讀取本地資料,弄清楚客戶端有什麼以及需要什麼,併為其生成自定義的 packfile。有兩種資料傳輸流程:一對用於上傳資料,一對用於下載資料。
上傳資料
要將資料上傳到遠端程序,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 程序會確定它有什麼而伺服器沒有的提交。對於此推送將更新的每個引用,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”。
接下來,客戶端傳送一個 packfile,包含伺服器尚不擁有的所有物件。最後,伺服器會響應成功(或失敗)指示
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 的輸出和 packfile。然後,伺服器透過其 HTTP 響應指示成功或失敗。
請記住,HTTP 協議可能還會將此資料包裝在分塊傳輸編碼中。
下載資料
下載資料時,會涉及 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 程序,開始傳送所需資料 packfile
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
同樣,這與上面的格式相同。此請求的響應指示成功或失敗,幷包含 packfile。
協議摘要
本節提供了傳輸協議的非常基礎的概述。該協議包含許多其他功能,例如 multi_ack 或 side-band 功能,但涵蓋它們超出了本書的範圍。我們試圖讓您對客戶端和伺服器之間的通用往返通訊有所瞭解;如果您需要比這更多的知識,您可能需要檢視 Git 原始碼。