-
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” 协议和 “smart” 协议。 本节将快速介绍这两种主要协议的运作方式。
Dumb 协议
如果你正在设置一个通过 HTTP 以只读方式提供的仓库,那么很可能会使用 dumb 协议。 这种协议被称为 “dumb”,因为它在传输过程中不需要服务器端有任何 Git 特定的代码; fetch 过程是一系列的 HTTP GET
请求,客户端可以假设服务器上 Git 仓库的布局。
注意
|
如今很少使用 dumb 协议。 它很难保护或私有化,因此大多数 Git 主机(无论是基于云的还是本地的)都会拒绝使用它。 通常建议使用 smart 协议,我们将在后面进一步描述。 |
让我们跟踪 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
分支。 此时,你已准备好开始 walking 过程。 因为你的起点是你在 info/refs
文件中看到的 ca82a6
提交对象,所以你首先获取它
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)
你得到一个对象 – 该对象在服务器上采用 loose 格式,你通过静态 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)
哎呀 – 看起来该树对象在服务器上不是 loose 格式,因此你收到了 404 响应。 造成这种情况的原因有很多 – 该对象可能位于备用仓库中,也可能位于此仓库中的 packfile 中。 Git 首先检查任何列出的备用项
=> GET objects/info/http-alternates
(empty file)
如果返回一个备用 URL 列表,Git 会在那里检查 loose 文件和 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)
你拥有你的树对象,因此你可以继续 walking 你的提交。 它们也都在你刚刚下载的 packfile 中,因此你无需再向服务器发出任何请求。 Git 检出 master
分支的工作副本,该分支由你开始下载的 HEAD 引用指向。
Smart 协议
简单的协议虽然简单,但效率较低,并且无法处理客户端向服务器写入数据的情况。智能协议是一种更常见的数据传输方法,但它需要在远程端运行一个对 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 源代码。