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 <[email protected]> 1205815931 -0700
committer Scott Chacon <[email protected]> 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 的进程——它可以读取本地数据,找出客户端拥有和需要什么,并为其生成自定义的 packfile。有两组用于传输数据的进程:一对用于上传数据,另一对用于下载数据。

上传数据

要将数据上传到远程进程,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进程会确定它有哪些服务器没有的提交。对于此推送将更新的每个引用,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-packupload-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_ackside-band功能,但涵盖它们超出了本书的范围。我们试图让您了解客户端和服务器之间的一般来回情况;如果您需要比这更多的知识,您可能需要查看 Git 源代码。

scroll-to-top