-
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 源代码。