Git 内部原理 - (5)引用规格 (6) 传输协议
引用规格
纵观全书,我们已经使用过一些诸如远程分支到本地引用的简单映射方式,但这种映射可以更复杂。 假设你添加了这样一个远程版本库:
$ git remote add origin https://github.com/schacon/simplegit-progit
上述命令会在你的 .git/config
文件中添加一个小节,并在其中指定远程版本库的名称(origin
)、URL 和一个用于获取操作的引用规格(refspec):
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*
引用规格的格式由一个可选的 +
号和紧随其后的 <src>:<dst>
组成,其中 <src>
是一个模式(pattern),代表远程版本库中的引用;<dst>
是那些远程引用在本地所对应的位置。 +
号告诉 Git 即使在不能快进的情况下也要(强制)更新引用。
默认情况下,引用规格由 git remote add
命令自动生成, Git 获取服务器中 refs/heads/
下面的所有引用,并将它写入到本地的 refs/remotes/origin/
中。 所以,如果服务器上有一个 master
分支,我们可以在本地通过下面这种方式来访问该分支上的提交记录:
$ git log origin/master
$ git log remotes/origin/master
$ git log refs/remotes/origin/master
上面的三个命令作用相同,因为 Git 会把它们都扩展成 refs/remotes/origin/master
。
如果想让 Git 每次只拉取远程的 master
分支,而不是所有分支,可以把(引用规格的)获取那一行修改为:
fetch = +refs/heads/master:refs/remotes/origin/master
这仅是针对该远程版本库的 git fetch
操作的默认引用规格。 如果有某些只希望被执行一次的操作,我们也可以在命令行指定引用规格。 若要将远程的 master
分支拉到本地的 origin/mymaster
分支,可以运行:
$ git fetch origin master:refs/remotes/origin/mymaster
你也可以指定多个引用规格。 在命令行中,你可以按照如下的方式拉取多个分支:
$ git fetch origin master:refs/remotes/origin/mymaster \
topic:refs/remotes/origin/topic
From git@github.com:schacon/simplegit
! [rejected] master -> origin/mymaster (non fast forward)
* [new branch] topic -> origin/topic
在这个例子中,对 master
分支的拉取操作被拒绝,因为它不是一个可以快进的引用。 我们可以通过在引用规格之前指定 +
号来覆盖该规则。
你也可以在配置文件中指定多个用于获取操作的引用规格。 如果想在每次获取时都包括 master
和 experiment
分支,添加如下两行:
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/experiment:refs/remotes/origin/experiment
我们不能在模式中使用部分通配符,所以像下面这样的引用规格是不合法的:
fetch = +refs/heads/qa*:refs/remotes/origin/qa*
但我们可以使用命名空间(或目录)来达到类似目的。 假设你有一个 QA 团队,他们推送了一系列分支,同时你只想要获取 master
和 QA 团队的所有分支而不关心其他任何分支,那么可以使用如下配置:
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/qa/*:refs/remotes/origin/qa/*
如果项目的工作流很复杂,有 QA 团队推送分支、开发人员推送分支、集成团队推送并且在远程分支上展开协作,你就可以像这样(在本地)为这些分支创建各自的命名空间,非常方便。
引用规格推送
像上面这样从远程版本库获取已在命名空间中的引用当然很棒,但 QA 团队最初应该如何将他们的分支放入远程的 qa/
命名空间呢? 我们可以通过引用规格推送来完成这个任务。
如果 QA 团队想把他们的 master
分支推送到远程服务器的 qa/master
分支上,可以运行:
$ git push origin master:refs/heads/qa/master
如果他们希望 Git 每次运行 git push origin
时都像上面这样推送,可以在他们的配置文件中添加一条 push
值:
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*
push = refs/heads/master:refs/heads/qa/master
正如刚才所指出的,这会让 git push origin
默认把本地 master
分支推送到远程 qa/master
分支。
删除引用
你还可以借助类似下面的命令通过引用规格从远程服务器上删除引用:
$ git push origin :topic
因为引用规格(的格式)是 <src>:<dst>
,所以上述命令把 <src>
留空,意味着把远程版本库的 topic
分支定义为空值,也就是删除它。
或者你可以使用更新的语法(自Git v1.7.0以后可用):
$ git push origin --delete topic
传输协议
Git 可以通过两种主要的方式在版本库之间传输数据:“哑(dumb)”协议和“智能(smart)”协议。 本节将会带你快速浏览这两种协议的运作方式。
哑协议
如果你正在架设一个基于 HTTP 协议的只读版本库,一般而言这种情况下使用的就是哑协议。 这个协议之所以被称为“哑”协议,是因为在传输过程中,服务端不需要有针对 Git 特有的代码;抓取过程是一系列 HTTP 的 GET
请求,这种情况下,客户端可以推断出服务端 Git 仓库的布局。
Note
|
现在已经很少使用哑协议了。 使用哑协议的版本库很难保证安全性和私有化,所以大多数 Git 服务器宿主(包括云端和本地)都会拒绝使用它。 一般情况下都建议使用智能协议,我们会在后面进行介绍。 |
让我们通过 simplegit 版本库来看看 http-fetch
的过程:
$ git clone http://server/simplegit-progit.git
它做的第一件事就是拉取 info/refs
文件。 这个文件是通过 update-server-info
命令生成的,这也解释了在使用HTTP传输时,必须把它设置为 post-receive
钩子的原因:
=> 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
changed the version number
接下来,你还要再获取两个对象,一个是树对象 cfda3b
,它包含有我们刚刚获取的提交对象所指向的内容,另一个是它的父提交 085bb3
:
=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)
这样就取得了你的下一个提交对象。 再抓取树对象:
=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)
噢——看起来这个树对象在服务端并不以松散格式对象存在,所以你得到了一个 404 响应,代表在 HTTP 服务端没有找到该对象。 这有好几个可能的原因——这个对象可能在替代版本库里面,或者在包文件里面。 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
和一些其它的,包括客户端的识别码)。
每一行以一个四位的十六进制值开始,用于指明本行的长度。 你看到第一行以 005b 开始,这在十六进制中表示 91,意味着第一行有 91 字节。 下一行以 003e 起始,也就是 62,所以下面需要读取 62 字节。 再下一行是 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 引用。 删除引用时,将会看到相反的情况:右边的 SHA-1 值全为 0。
接下来,客户端会发送一个包文件,它包含了所有服务端还没有的对象。 最后,服务端会以成功(或失败)响应:
000eunpack ok
HTTP(S)
HTTPS 与 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
请求,这个请求中包含了 git-upload-pack
提供的数据。
=> POST http://server/simplegit-progit.git/git-receive-pack
这个 POST
请求的内容是 send-pack
的输出和相应的包文件。 服务端在收到请求后相应地作出成功或失败的 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 的源代码。
Git 内部原理 - (5)引用规格 (6) 传输协议的更多相关文章
- git内部原理
Git 内部原理 无论是从之前的章节直接跳到本章,还是读完了其余章节一直到这——你都将在本章见识到 Git 的内部工作原理 和实现方式. 我们发现学习这部分内容对于理解 Git 的用途和强大至关重要. ...
- Git详解之九:Git内部原理
Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各章一直到这,你都将在本章见识 Git 的内部工作原理和实现方式.我个人发现学习这些内容对于理解 Git 的用处和强大是非常重要的, ...
- Git详解之九 Git内部原理
以下内容转载自:http://www.open-open.com/lib/view/open1328070620202.html Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各 ...
- Git 内部原理 - (7)维护与数据恢复 (8) 环境变量 (9)总结
维护与数据恢复 有的时候,你需要对仓库进行清理 - 使它的结构变得更紧凑,或是对导入的仓库进行清理,或是恢复丢失的内容. 这个小节将会介绍这些情况中的一部分. 维护 Git 会不定时地自动运行一个叫做 ...
- Git 内部原理--初探 .git
说到Git大家应该都非常熟悉,几乎每天都会用到它.在日常使用过程中,我们貌似并不需要关注其内部的原理,只需要记住那几个常用的命令,就可以说自己是会Git的人了.可是,事实真的是这样子的吗?今天我们就来 ...
- Git内部原理(1)
Git本质上是一套内容寻址文件系统,在此之上提供了VCS的用户界面. Git底层命令(plumbing) vs 高层命令(porcelain) Git的高层命令包括checkout.branch.re ...
- git内部原理-第一篇
本人计划写一些关于<git内部原理>的文章 计划每周一篇
- git原理:引用规格
引用规格(refspec):就是在 .git/config 里面那个配置远程仓库的东西 [remote "origin"]url = https://github.com/test ...
- Git 内部原理 - (3) Git 引用 (4)包文件
Git 引用 我们可以借助类似于 git log 1a410e 这样的命令来浏览完整的提交历史,但为了能遍历那段历史从而找到所有相关对象,你仍须记住 1a410e 是最后一个提交. 我们需要一个文件来 ...
随机推荐
- 前后端分离跨域 关于前后端分离开发环境下的跨域访问问题(angular proxy=>nginx )
前后端分离后遇到了跨域访问的问题: angular1中使用proxy很麻烦,最后还是失败结束:最后总结3种方法如下: 本人使用的第一种方法,只是开发环境下使用很方便! 1:禁掉谷歌的安全策略(Turn ...
- JavaScript中Math常用方法
title: JavaScript中Math常用方法 toc: false date: 2018-10-13 12:19:31 Math.E --2.718281828459045,算数常量e Mat ...
- 引用axiv文献的问题
首先找了一下对8种常见引用格式进行说明的文章 https://blog.csdn.net/wkd22775/article/details/51798927 然后就是水木社区大神们的记录http:// ...
- 你不知道的JavaScript(十)with关键字
with关键字在JavaScript中不太常用,用来定义一个和对象相关的作用域,在该作用域中可以访问对象的属性或方法而前面无需加上对象名,以达到简化代码的目的. <script type=&qu ...
- Linux下查看mysql错误日志
1.进入 mysql 安装目录 进入 data 目录(该目录存储的是数据库的数据) cd /usr/local/mysql ll 进入 mysql 目录 ,发现 文件后缀 .err,即是mysql ...
- HDU 2120 Ice_cream's world I【并查集】
解题思路:给出n对点的关系,求构成多少个环,如果对于点x和点y,它们本身就有一堵墙,即为它们本身就相连,如果find(x)=find(y),说明它们的根节点相同,它们之间肯定有直接或间接的相连,即形成 ...
- SQL中的union
在SQL中,如果我们查询一个班级的考试成绩数据,再统计考试成绩的总和,我们使用以下两条语句: select StudentName,Grade from Student select '总成绩',SU ...
- ZBrush雕刻生物小技巧
本教程主要学习如何使用ZBrush®3D图形绘制软件的工具和笔刷雕刻酷酷的生物造型,我们今天来看看在游戏.媒体和电视领域有着十几年丰富经验的3D角色艺术家Francis-Xavier Martins是 ...
- 计算a-b的差[返回BigDecimal 类型]
/*** * 返回 a-b 的差 [返回 BigDecimal 类型] * @param a 被减数 * @param b 减数 * @return */ public static BigDecim ...
- C# treeview绑定
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) ...