-
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 可以通过两种主要方式在两个仓库之间传输数据:“哑协议”(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 响应。这有几个原因——该对象可能在另一个仓库中,或者可能在该仓库的一个包文件中。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 检出了你在开始时下载的 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` 进程确定它有哪些提交是服务器没有的。对于将要更新的每个引用,`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-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 源代码。