设置和配置
获取和创建项目
基本快照
分支与合并
共享和更新项目
检查和比较
打补丁
调试
电子邮件
外部系统
服务器管理
指南
管理
底层命令
- 2.43.1 → 2.52.0 无更改
-
2.43.0
2023-11-20
- 2.38.1 → 2.42.4 无更改
-
2.38.0
2022-10-02
描述
Git 支持通过 ssh://, git://, http:// 和 file:// 传输协议传输 packfile 中的数据。存在两套协议,一套用于将数据从客户端推送到服务器,另一套用于从服务器获取数据到客户端。这三种传输协议(ssh、git、file)使用相同的协议传输数据。http 在 gitprotocol-http[5] 中进行了文档说明。
在标准的 Git 实现中,用于获取数据的服务器端进程是 upload-pack,客户端进程是 fetch-pack;用于推送数据的服务器端进程是 receive-pack,客户端进程是 send-pack。该协议的功能是让服务器告知客户端当前服务器上有什么,然后双方协商发送最少的数据量以完全更新其中一方。
pkt-line 格式
以下描述基于 gitprotocol-common[5] 中描述的 pkt-line 格式。当语法指示 PKT-LINE(...) 时,除非另有说明,通常的 pkt-line LF 规则适用:发送方 SHOULD 包含 LF,但接收方 MUST NOT 在其不存在时抱怨。
错误包是一个特殊的 pkt-line,包含一个错误字符串。
error-line = PKT-LINE("ERR" SP explanation-text)
在整个协议中,在预期 PKT-LINE(...) 的地方,MAY 发送错误包。一旦客户端或服务器发送了此包,本协议定义的数据传输过程将终止。
传输协议
packfile 协议通过三种传输协议启动。Git 传输协议是一个简单的、未认证的服务器,它接收客户端希望通信的命令(几乎总是 upload-pack,尽管 Git 服务器可以配置为全局可写,此时也允许 receive-pack 的启动)并执行它,然后将其连接到请求进程。
在 SSH 传输协议中,客户端只是通过 SSH 协议在服务器上运行 upload-pack 或 receive-pack 进程,然后通过 SSH 连接与该调用进程通信。
file:// 传输协议在本地运行 upload-pack 或 receive-pack 进程,并通过管道与其通信。
附加参数
该协议提供了一种机制,允许客户端在其发送给服务器的第一个消息中发送额外信息。这些被称为“附加参数”,并得到 Git、SSH 和 HTTP 协议的支持。
每个附加参数的形式为 <key>=<value> 或 <key>。
接收到任何此类附加参数的服务器 MUST 忽略所有未识别的键。当前,唯一识别的附加参数是“version”,其值为 1 或 2。有关协议版本 2 的更多信息,请参阅 gitprotocol-v2[5]。
Git 传输协议
Git 传输协议首先使用 pkt-line 格式在网络上传送命令和仓库,然后是一个 NUL 字节和一个主机名参数,最后以 NUL 字节终止。
0033git-upload-pack /project.git\0host=myserver.com\0
该传输协议可以通过添加额外的 NUL 字节,然后添加一个或多个以 NUL 终止的字符串来发送附加参数。
003egit-upload-pack /project.git\0host=myserver.com\0\0version=1\0
git-proto-request = request-command SP pathname NUL
[ host-parameter NUL ] [ NUL extra-parameters ]
request-command = "git-upload-pack" / "git-receive-pack" /
"git-upload-archive" ; case sensitive
pathname = *( %x01-ff ) ; exclude NUL
host-parameter = "host=" hostname [ ":" port ]
extra-parameters = 1*extra-parameter
extra-parameter = 1*( %x01-ff ) NUL
host-parameter 用于 git-daemon 基于名称的虚拟托管。请参阅 git daemon 的 --interpolated-path 选项,以及 %H/%CH 格式字符。
基本上,Git 客户端通过 Git 协议连接到服务器端的 upload-pack 进程所做的事情如下:
$ echo -e -n \ "003agit-upload-pack /schacon/gitbook.git\0host=example.com\0" | nc -v example.com 9418
SSH 传输协议
通过 SSH 启动 upload-pack 或 receive-pack 进程是通过 SSH 远程执行在服务器上执行二进制文件。它基本上等同于运行以下命令:
$ ssh git.example.com "git-upload-pack '/project.git'"
为了让服务器支持给定用户的 Git 推送和拉取,该用户需要能够通过其登录时提供的 SSH shell 执行一个或两个命令。在某些系统上,该 shell 访问被限制为只能运行这两个命令,甚至只能运行其中一个。
在 ssh:// 格式的 URI 中,它在 URI 中是绝对的,因此主机名(或端口号)之后的 / 作为参数发送,然后由远程 git-upload-pack 正确读取,因此它实际上是远程文件系统中的绝对路径。
git clone ssh://user@example.com/project.git | v ssh user@example.com "git-upload-pack '/project.git'"
在“user@host:path”格式的 URI 中,它相对于用户的主目录,因为 Git 客户端将运行:
git clone user@example.com:project.git
|
v
ssh user@example.com "git-upload-pack 'project.git'"
唯一的例外是当使用 ~ 时,在这种情况下,我们将不带前导 / 来执行它。
ssh://user@example.com/~alice/project.git,
|
v
ssh user@example.com "git-upload-pack '~alice/project.git'"
根据 protocol.version 配置变量的值,Git 可能会尝试在 GIT_PROTOCOL 环境变量中将附加参数作为冒号分隔的字符串发送。只有当 ssh.variant 配置变量指示 ssh 命令支持将环境变量作为参数传递时,才会执行此操作。
这里有几点需要记住:
-
“命令名称”用连字符拼写(例如 git-upload-pack),但这可以由客户端覆盖;
-
仓库路径始终用单引号括起来。
从服务器获取数据
当一个 Git 仓库想要获取另一个仓库拥有的数据时,第一个仓库可以从第二个仓库 fetch。此操作确定服务器拥有而客户端没有的数据,然后以 packfile 格式将该数据流式传输到客户端。
引用发现
当客户端首次连接时,服务器将立即响应一个版本号(如果“version=1”作为附加参数发送),以及它拥有的每个引用的列表(所有分支和标签)以及每个引用当前指向的对象名称。
$ echo -e -n "0045git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" |
nc -v example.com 9418
000eversion 1
00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack
side-band side-band-64k ofs-delta shallow no-progress include-tag
00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 refs/heads/master
003cb88d2441cac0977faf98efc80305012112238d9d refs/tags/v0.9
003c525128480b96c89e6418b1e40909bf6c5b2d580f refs/tags/v1.0
003fe92df48743b7bc7d26bcaabfddde0a1e20cae47c refs/tags/v1.0^{}
0000
返回的响应是描述每个 ref 及其当前值的 pkt-line 流。该流 MUST 按照 C 语言 locale 的排序顺序进行排序。
如果 HEAD 是一个有效的引用,HEAD MUST 出现在广告列表的第一个引用中。如果 HEAD 不是一个有效的引用,HEAD MUST 完全不出现在广告列表中,但其他引用仍然可能出现。
该流 MUST 在第一个引用后的 NUL 处包含能力声明。如果引用是一个注解标签,则该引用的剥离值(即“ref^{}”)MUST 出现在引用本身之后。符合规范的服务器 MUST 在引用是注解标签时对其进行剥离。
advertised-refs = *1("version 1")
(no-refs / list-of-refs)
*shallow
flush-pkt
no-refs = PKT-LINE(zero-id SP "capabilities^{}"
NUL capability-list)
list-of-refs = first-ref *other-ref
first-ref = PKT-LINE(obj-id SP refname
NUL capability-list)
other-ref = PKT-LINE(other-tip / other-peeled)
other-tip = obj-id SP refname
other-peeled = obj-id SP refname "^{}"
shallow = PKT-LINE("shallow" SP obj-id)
capability-list = capability *(SP capability)
capability = 1*(LC_ALPHA / DIGIT / "-" / "_")
LC_ALPHA = %x61-7A
服务器和客户端 MUST 对 obj-id 使用小写,两者都 MUST 将 obj-id 视为不区分大小写。
有关允许的服务器能力及其描述列表,请参阅 protocol-capabilities.txt。
Packfile 协商
在引用和能力发现之后,客户端可以通过发送 flush-pkt 来决定终止连接,告知服务器现在可以优雅地终止并断开连接,当它不需要任何 pack 数据时。这可以与 ls-remote 命令一起发生,也可以在客户端已更新时发生。
否则,它将进入协商阶段,客户端和服务器确定传输所需的最小 packfile,通过告知服务器它想要的对象、其浅层对象(如果有)以及它想要的提交最大深度(如果有)。客户端还将发送一个它想要生效的能力列表,从服务器在第一个 want 行中说它可以做什么中选择。
upload-request = want-list
*shallow-line
*1depth-request
[filter-request]
flush-pkt
want-list = first-want
*additional-want
shallow-line = PKT-LINE("shallow" SP obj-id)
depth-request = PKT-LINE("deepen" SP depth) /
PKT-LINE("deepen-since" SP timestamp) /
PKT-LINE("deepen-not" SP ref)
first-want = PKT-LINE("want" SP obj-id SP capability-list)
additional-want = PKT-LINE("want" SP obj-id)
depth = 1*DIGIT
filter-request = PKT-LINE("filter" SP filter-spec)
客户端 MUST 将引用发现阶段想要的所有 obj-id 作为 want 行发送。客户端在其请求正文中 MUST 至少发送一个 want 命令。客户端在 want 命令中 MUST NOT 提及未出现在通过 ref 发现获得的响应中的 obj-id。
客户端 MUST 将其仅拥有浅拷贝(意味着它没有提交的父提交)的所有 obj-id 作为 shallow 行发送,以便服务器了解客户端历史的限制。
客户端现在发送其在此事务中想要的提交历史的最大深度,即从历史尖端开始想要的提交数量(如果有),作为 deepen 行。深度 0 与不进行深度请求相同。客户端不希望接收超过此深度的任何提交,也不希望接收仅用于完成这些提交所需的对象。作为结果未接收到其父提交的提交被定义为浅层,并在服务器中标记为如此。此信息将在下一步返回给客户端。
客户端可以根据需要,使用几种过滤技术中的一种,选择性地请求 pack-objects 忽略 packfile 中的各种对象。这些用于部分克隆和部分获取操作。不满足 filter-spec 值(除非在 want 行中明确请求)的对象将被忽略。有关可能的 filter-spec 值,请参阅 rev-list。
一旦所有 want 和 shallow(以及可选的 'deepen')传输完毕,客户端 MUST 发送一个 flush-pkt,告知服务器端它已完成发送列表。
否则,如果客户端发送了正向深度请求,服务器将确定哪些提交将是浅层而哪些不是,并将此信息发送给客户端。如果客户端没有请求正向深度,则此步骤将被跳过。
shallow-update = *shallow-line
*unshallow-line
flush-pkt
shallow-line = PKT-LINE("shallow" SP obj-id)
unshallow-line = PKT-LINE("unshallow" SP obj-id)
如果客户端请求了正向深度,服务器将计算出深度不超过所需深度的提交集。提交集从客户端的 wants 开始。
服务器为每个提交写入 shallow 行,其父提交将不会被发送。服务器为每个客户端已指示为浅层但当前请求深度下不再是浅层的提交(即,其父提交现在将被发送)写入 unshallow 行。服务器 MUST NOT 标记客户端未指示为浅层的任何内容。
现在客户端发送它拥有的 obj-id 列表,使用 have 行,以便服务器可以制作一个只包含客户端所需对象的 packfile。在 multi_ack 模式下,标准的实现一次发送多达 32 个,然后发送一个 flush-pkt。标准的实现将跳到下一个并立即发送下一个 32 个,以便始终有 32 个“在网络上传输中”的块。
upload-haves = have-list
compute-end
have-list = *have-line
have-line = PKT-LINE("have" SP obj-id)
compute-end = flush-pkt / PKT-LINE("done")
如果服务器读取了 have 行,它将通过 ACK 客户端声称它拥有但服务器也拥有的任何 obj-id 来响应。服务器根据客户端选择的 ack 模式以不同的方式 ACK obj-id。
在 multi_ack 模式下:
-
服务器将对任何公共提交响应 ACK obj-id continue。
-
一旦服务器找到了一个可接受的公共基提交并准备好制作 packfile,它将盲目地将所有 have obj-id ACK 回给客户端。
-
然后服务器将发送一个 NAK,然后等待来自客户端的另一个响应 - 要么是 done,要么是另一组 have 行。
在 multi_ack_detailed 模式下:
-
服务器将区分 ACK,其中它会通过 ACK obj-id ready 行来指示它已准备好发送数据,并用 ACK obj-id common 行来指示已识别的公共提交。
在没有 multi_ack 或 multi_ack_detailed 的情况下:
-
upload-pack 在找到的第一个公共对象上发送“ACK obj-id”。之后,它不会发出任何声音,直到客户端给出“done”。
-
如果尚未找到公共对象,upload-pack 在 flush-pkt 上发送“NAK”。如果已找到,因此已发送 ACK,则 flush-pkt 保持静默。
在客户端收到足够的 ACK 响应,使其能够确定服务器拥有足够的信息来发送高效的 packfile(在标准的实现中,当它收到足够的 ACK 以至于可以将 --date-order 队列中剩余的所有内容着色为与服务器公共,或者 --date-order 队列为空时,就会确定),或者客户端决定放弃(在标准的实现中,当客户端发送 256 个 have 行但未收到任何一个被服务器 ACK 时,就确定了——这意味着没有共同点,服务器应该只发送其所有对象),然后客户端将发送一个 done 命令。done 命令向服务器发出信号,表明客户端已准备好接收其 packfile 数据。
但是,256 的限制 仅 在标准的客户端实现中生效,前提是我们在此前一轮中至少收到了一个“ACK %s continue”。这有助于确保在我们完全放弃之前至少找到一个共同祖先。
一旦从客户端读取了 done 行,服务器将发送一个最终的 ACK obj-id 或发送一个 NAK。obj-id 是确定的最后一个公共提交的对象名称。服务器只在 done 之后发送 ACK,前提是至少有一个公共基并且启用了 multi_ack 或 multi_ack_detailed。服务器在 done 之后总是发送 NAK,前提是未找到公共基。
代替 ACK 或 NAK,服务器可能会发送错误消息(例如,如果它不识别客户端收到的 want 行中的对象)。
然后服务器将开始发送其 packfile 数据。
server-response = *ack_multi ack / nak
ack_multi = PKT-LINE("ACK" SP obj-id ack_status)
ack_status = "continue" / "common" / "ready"
ack = PKT-LINE("ACK" SP obj-id)
nak = PKT-LINE("NAK")
一个简单的克隆可能看起来像这样(没有 have 行):
C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \
side-band-64k ofs-delta\n
C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
C: 0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n
C: 0032want 74730d410fcb6603ace96f1dc55ea6196122532d\n
C: 0000
C: 0009done\n
S: 0008NAK\n
S: [PACKFILE]
一个增量更新(fetch)响应可能看起来像这样:
C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \
side-band-64k ofs-delta\n
C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
C: 0000
C: 0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n
C: [30 more have lines]
C: 0032have 74730d410fcb6603ace96f1dc55ea6196122532d\n
C: 0000
S: 003aACK 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 continue\n
S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d continue\n
S: 0008NAK\n
C: 0009done\n
S: 0031ACK 74730d410fcb6603ace96f1dc55ea6196122532d\n
S: [PACKFILE]
Packfile 数据
现在客户端和服务器已经完成了关于需要发送到客户端的最小数据量的协商,服务器将构造并以 packfile 格式发送所需数据。
有关 packfile 本身外观的更多信息,请参阅 gitformat-pack[5]。
如果客户端指定了 side-band 或 side-band-64k 功能,服务器将以多路复用的方式发送 packfile 数据。
每个数据包都以数据包行的长度开头,表示后续数据的数量,后跟一个指定数据所在的 sideband 的单个字节。
在 side-band 模式下,它最多发送 999 个数据字节加上 1 个控制码,总共最多 1000 个字节在一个 pkt-line 中。在 side-band-64k 模式下,它最多发送 65519 个数据字节加上 1 个控制码,总共最多 65520 个字节在一个 pkt-line 中。
sideband 字节将是 1、2 或 3。Sideband 1 将包含 packfile 数据,sideband 2 将用于客户端通常会打印到 stderr 的进度信息,sideband 3 用于错误信息。
如果没有指定 side-band 功能,服务器将不进行多路复用,直接流式传输整个 packfile。
向服务器推送数据
向服务器推送数据将调用服务器上的 receive-pack 进程,该进程将允许客户端告知它应该更新哪些引用,然后发送服务器完成这些新引用所需的所有数据。一旦接收并验证了所有数据,服务器将更新其引用以匹配客户端指定的版本。
认证
协议本身不包含任何认证机制。这应由传输协议(如 SSH)在调用 receive-pack 进程之前处理。如果 receive-pack 通过 Git 传输协议配置,则可以向访问该端口(9418)的任何人写入,因为该传输协议未进行身份验证。
引用发现
引用发现阶段与获取协议中的过程几乎相同。服务器上的每个引用对象 ID 和名称都以 packet-line 格式发送到客户端,后跟一个 flush-pkt。唯一的实际区别在于能力列表不同 - 唯一可能的值是 report-status、report-status-v2、delete-refs、ofs-delta、atomic 和 push-options。
引用更新请求和 Packfile 传输
一旦客户端知道服务器上的引用版本,它就可以发送引用更新请求列表。对于服务器上它想要更新的每个引用,它发送一行,列出服务器上当前的 obj-id、客户端想要将其更新到的 obj-id 以及引用的名称。
此列表后跟一个 flush-pkt。
update-requests = *shallow ( command-list | push-cert )
shallow = PKT-LINE("shallow" SP obj-id)
command-list = PKT-LINE(command NUL capability-list)
*PKT-LINE(command)
flush-pkt
command = create / delete / update
create = zero-id SP new-id SP name
delete = old-id SP zero-id SP name
update = old-id SP new-id SP name
old-id = obj-id
new-id = obj-id
push-cert = PKT-LINE("push-cert" NUL capability-list LF)
PKT-LINE("certificate version 0.1" LF)
PKT-LINE("pusher" SP ident LF)
PKT-LINE("pushee" SP url LF)
PKT-LINE("nonce" SP nonce LF)
*PKT-LINE("push-option" SP push-option LF)
PKT-LINE(LF)
*PKT-LINE(command LF)
*PKT-LINE(gpg-signature-lines LF)
PKT-LINE("push-cert-end" LF)
push-option = 1*( VCHAR | SP )
如果服务器已宣布 push-options 功能,并且客户端已在上述功能列表中指定了 push-options,则客户端会发送其 push 选项,后跟一个 flush-pkt。
push-options = *PKT-LINE(push-option) flush-pkt
为了向后兼容旧的 Git 服务器,如果客户端发送推送证书和推送选项,它 MUST 将其推送选项同时嵌入到推送证书内和推送证书之后发送。(请注意,证书内的推送选项前面有前缀,而证书后的推送选项没有。)这两个列表 MUST 相同,除了前缀。
之后,将发送包含服务器完成新引用所需的所有对象的 packfile。
packfile = "PACK" 28*(OCTET)
如果接收端不支持 delete-refs,发送端 MUST NOT 请求 delete 命令。
如果接收端不支持 push-cert,发送端 MUST NOT 发送 push-cert 命令。当发送 push-cert 命令时,command-list MUST NOT 被发送;而是使用推送证书中记录的命令。
如果使用的唯一命令是 delete,则 packfile MUST NOT 被发送。
如果使用了 create 或 update 命令,则 packfile MUST 被发送,即使服务器已拥有所有必需的对象。在这种情况下,客户端 MUST 发送一个空的 packfile。唯一可能发生这种情况的情况是客户端正在创建一个指向现有 obj-id 的新分支或标签。
服务器将接收 packfile,解包,然后验证正在更新的每个引用(在请求处理过程中未发生更改,即 obj-id 仍然与 old-id 相同),并运行任何更新钩子以确保更新是可接受的。如果所有这些都正常,服务器将更新引用。
推送证书
推送证书以一组头部行开头。在头部和空行之后,是协议命令,每行一个。请注意,推送证书 PKT-LINE 中的尾部 LF 不是 可选的;它必须存在。
当前,已定义以下头部字段:
GPG 签名行是对推送证书内容(在签名块开始之前)的独立签名。独立签名用于证明命令是由推送者提供的,推送者必须是签名者。
报告状态
在接收发送者发送的 pack 数据后,如果 report-status 或 report-status-v2 功能处于生效状态,接收者会发送报告。它是一个简短的列表,说明更新中发生了什么。它首先列出 packfile 解包的状态,为 unpack ok 或 unpack [error]。然后它会列出它尝试更新的每个引用的状态。每行是 ok [refname](如果更新成功),或 ng [refname] [error](如果更新失败)。
report-status = unpack-status
1*(command-status)
flush-pkt
unpack-status = PKT-LINE("unpack" SP unpack-result)
unpack-result = "ok" / error-msg
command-status = command-ok / command-fail
command-ok = PKT-LINE("ok" SP refname)
command-fail = PKT-LINE("ng" SP refname SP error-msg)
error-msg = 1*(OCTET) ; where not "ok"
report-status-v2 功能通过添加新的选项行来扩展协议,以支持由 proc-receive 钩子重写引用的报告。proc-receive 钩子可以处理一个伪引用的命令,该命令可能会创建或更新一个或多个引用,并且每个引用可能具有不同的名称、不同的 new-oid 和不同的 old-oid。
report-status-v2 = unpack-status
1*(command-status-v2)
flush-pkt
unpack-status = PKT-LINE("unpack" SP unpack-result)
unpack-result = "ok" / error-msg
command-status-v2 = command-ok-v2 / command-fail
command-ok-v2 = command-ok
*option-line
command-ok = PKT-LINE("ok" SP refname)
command-fail = PKT-LINE("ng" SP refname SP error-msg)
error-msg = 1*(OCTET) ; where not "ok"
option-line = *1(option-refname)
*1(option-old-oid)
*1(option-new-oid)
*1(option-forced-update)
option-refname = PKT-LINE("option" SP "refname" SP refname)
option-old-oid = PKT-LINE("option" SP "old-oid" SP obj-id)
option-new-oid = PKT-LINE("option" SP "new-oid" SP obj-id)
option-force = PKT-LINE("option" SP "forced-update")
更新可能因多种原因而不成功。引用可能自最初发送引用发现阶段以来已更改,意味着有人在此期间推送了。推送的引用可能是非快进引用,并且更新钩子或配置被设置为不允许,等等。此外,某些引用可以被更新,而其他引用可能被拒绝。
一个典型的客户端/服务器通信可能看起来像这样:
S: 006274730d410fcb6603ace96f1dc55ea6196122532d refs/heads/local\0report-status delete-refs ofs-delta\n S: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug\n S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master\n S: 003d74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/team\n S: 0000 C: 00677d1665144a3a975c05f1f43902ddaf084e784dbe 74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/debug\n C: 006874730d410fcb6603ace96f1dc55ea6196122532d 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a refs/heads/master\n C: 0000 C: [PACKDATA] S: 000eunpack ok\n S: 0018ok refs/heads/debug\n S: 002ang refs/heads/master non-fast-forward\n