Git的传输协议是如何工作的?

6

我已经使用Git超过一年,现在需要向我们小组中的其他人解释它。这就是为什么我需要更多的背景信息。 在过去的一年中,我阅读了大部分的Git书籍,最近我继续学习第10章。在第10.6节中,我完全卡住了:

Let’s follow the http-fetch process for the simplegit library:

$ git clone http://server/simplegit-progit.git

The first thing this command does is pull down the info/refs file. This file is written by the update-server-info command, which is why you need to enable that as a post-receive hook in order for the HTTP transport to work properly:

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

我有一个小的测试存储库 https://github.com/to_my/repogit clone 运行良好。但是:

  • info/refs 文件夹在哪里?在 clone 后面只找到了 /.git/info/exclude...
  • 我应该如何使用 update-server-info 命令?它是否与 git clone 有关?
  • 我完全不理解 "...这就是为什么您需要将其作为 post-receive hook 启用",尽管我了解 hooks(我以为)并使用 pre-commit hook 自动增加软件包版本。
  • 我无法让命令 GET info/refs 在 git bash 中工作。

如果问题很愚蠢,请原谅,但我只是不明白如何将这些文档片段组合在一起。


2
从2018年第二季度和Git 2.18开始,您将拥有Git传输协议v2:请参见下面的我的答案 - VonC
5个回答

7
注意:从 Git 2.18 开始(2018 年第二季度),Git 传输协议将使用实现了 v2 的新协议。
在 Git 2.26 中(2020 年第一季度),它是默认的。但在 2.27 中(2020 年第二季度,参见本答案结尾和 后续答案),它不再是默认的。在 2.28 中(2020 年第三季度)又变成了默认的。

请查看提交a4d78ce, 提交0f1dc53, 提交237ffed, 提交884e586, 提交8ff14ed, 提交49e85e9, 提交f08a5d4, 提交f1f4d8a, 提交edc9caf, 提交176e85c, 提交b1c2edf, 提交1aa8dde, 提交40fc51e, 提交f7e2050, 提交685fbd3, 提交3145ea9, 提交5b872ff, 提交230d7dd, 提交b4be741, 提交1af8ae1 (2018年3月15日) 由Brandon Williams (mbrandonw)进行。
(于2018年5月8日由Junio C Hamano -- gitster --合并至提交9bfa0f9)

完整的规范在Documentation/technical/protocol-v2.txt中:
协议v2将通过以下方式改进v1:
  • 一个服务支持多个命令,而不是多个服务名称
  • 随着功能被移动到协议的自己部分中,易于扩展,不再被隐藏在NUL字节后并受到pkt-line大小的限制
  • 将其他信息从NUL字节中分离出来(例如代理字符串作为能力和使用'ls-refs'可以请求symrefs)
  • 只有在明确请求时才会省略引用广告
  • ls-refs命令以明确请求一些ref
  • 设计时考虑了http和无状态rpc。带有清晰的刷新语义,http远程助手可以简单地充当代理
在协议v2中,通信是基于命令的。当首次联系服务器时,将发布一系列功能列表。其中一些功能将是客户端可以请求执行的命令。一旦命令完成,客户端可以重用连接并请求执行其他命令。

info/refs仍然是客户端查询的服务器端点,如HTTP传输部分所述:

当使用 http://https:// 传输时,客户端会根据 http-protocol.txt 中的描述进行“智能”的 info/refs 请求,并在 Git-Protocol 标头中提供 "version=2" 以请求使用 v2。

C: Git-Protocol: version=2
C:
C: GET $GIT_URL/info/refs?service=git-upload-pack HTTP/1.0

一个v2服务器会回复:
   S: 200 OK
   S: <Some headers>
   S: ...
   S:
   S: 000eversion 2\n
   S: <capability-advertisement>

随后的请求直接发送到服务$GIT_URL/git-upload-pack。(git-receive-pack同样适用此方法)。
目标是具有更多功能:
有两种不同类型的功能:
一般功能旨在传达信息或更改请求的行为,以及命令,即客户端想要执行的核心操作(提取、推送等)。
协议版本2默认为无状态。
这意味着除非客户端请求维护状态,否则所有命令必须仅持续一轮并且从服务器端的角度来看是无状态的。
为使客户端正常运行,服务器端不得要求维护状态管理。
这允许简单的轮询负载平衡在服务器端进行,而不需要担心状态管理。
最后:

ls-refs是v2中用于请求引用广告的命令。
与当前的引用广告不同,ls-refs需要输入参数,这些参数可用于限制从服务器发送的引用。

以及:

fetch是v2中用于获取packfile的命令。
可以将其视为v1 fetch的修改版本,其中去除了ref-advertisement(因为ls-refs命令扮演了该角色),并且调整了消息格式以消除冗余并允许轻松添加未来扩展。


自从那个提交(5月10日)以来,协议V2已经在Google博客文章“Introducing Git protocol version 2”中由Brandon Williams正式宣布(5月28日)。
在两种情况下:
不支持基础命令的其他功能将作为命令的值在能力广告中广告,以空格分隔的特征列表形式呈现:“<command>=<feature 1> <feature 2>

另请参见 提交 5e3548e, 提交 ff47322, 提交 ecc3e53 (2018年4月23日) 由 Brandon Williams (mbrandonw).
(于2018年5月23日由Junio C Hamano -- gitster --合并到提交 41267e9中)

serve: 引入server-option能力

在协议版本2中引入"server-option"能力。
这使得未来的客户端在使用协议版本2时能够在命令请求中发送特定于服务器的选项。

fetch: 在使用协议v2时发送服务器选项

通过在命令行上指定'-o'或'--server-option',教会fetch可选择接受服务器选项。
当使用协议版本2进行通信时,这些服务器选项将被发送到远程端。

如果使用的协议不是v2,则提供的选项将被忽略且不会发送到远程端。

git ls-remote同样如此。


传输协议v2学会支持部分克隆,此功能可在Git 2.16中于2017年12月看到。

请查看提交ba95710, 提交5459268 (2018年5月3日),以及提交7cc6ed2 (2018年5月2日) ,作者为Jonathan Tan (jhowtan)
(由Junio C Hamano -- gitster --合并于提交54db5c0,2018年5月30日)

{fetch,upload}-pack:在协议v2中支持过滤器

fetch-pack/upload-pack协议v2是独立开发的,与部分获取中使用的filter参数无关,因此它不包括对其的支持。添加对filter参数的支持。

与旧协议一样,只有在配置了uploadpack.allowfilter时,服务器才会广告并支持“filter”。

与旧协议一样,如果指定了“--filter”,但服务器没有广告,则客户端将继续警告。


Git 2.19(2018年第三季度)改进了 git 传输协议 v2 的获取部分:

请参见提交 ec06283, 提交 d093bc7, 提交 d30fe89, 提交 af1c90d, 提交 21bcf6e(2018年6月14日)以及提交 af00855, 提交 34c2903(2018年6月6日),作者为Jonathan Tan (jhowtan)
(由Junio C Hamano -- gitster --提交 af8ac73中合并,日期为2018年8月2日)

fetch-pack: 引入协商 API

引入新文件 fetch-negotiator.{h,c},其中包含抽象了协商细节的API。

fetch-pack: 使用参考广告来修剪"已发送"的内容

在使用协议 v2 进行协商时,fetch-pack 有时没有充分利用参考广告中获取的信息: 具体而言,如果服务器发布了客户端也拥有的提交,则客户端无需通知服务器它拥有提交的父级,因为它可以告诉服务器它拥有广告中的提交,并且它知道服务器可以并且会推断其余部分。


Git 2.20(2018年第四季度)修复了git ls-remotes问题:

请参见提交6a139cd提交631f0f8(2018年10月31日),由Jeff King(peff完成。
(由Junio C Hamano -- gitster --提交81c365b中合并,2018年11月13日)

最近v2协议的更新破坏了"git ls-remote $there foo"的功能,不再显示与'foo'匹配且不是refs/{heads,tags}/foo的引用,但已经修复。

Git 2.20修复了git fetch的问题,该问题在使用协议v2与另一方通信时解析响应有些宽松。

详见提交记录5400b2a(由Jonathan Tan (jhowtan)于2018年10月19日提交)
(由Junio C Hamano -- gitster --合并于提交记录67cf2fa,2018年11月13日)

fetch-pack:在解析v2响应时更加精确

协议v2响应中的每个部分都是由“DELIM”数据包(表示还有更多的部分要遵循)或“FLUSH”数据包(表示没有要遵循的部分)后跟的。

但是,在解析“acknowledgments”部分时,“do_fetch_pack_v2()”即使接受两者,也会根据“acknowledgments”部分的内容而不是读取了“DELIM”或“FLUSH”来确定是否继续读取。

对于符合协议的服务器没有问题,但与发送意外的其他部分的服务器通信可能会导致混淆的错误消息。考虑一个在“acknowledgments”之后发送“new-section”的服务器:

  • 客户端编写请求
    • 客户端读取包含无“ready”的“acknowledgments”部分,然后是 DELIM
    • 由于没有“ready”,客户端需要继续协商,并编写请求
    • 客户端读取“new-section”,并向最终用户报告“预期'acknowledgments',收到' new-section '”

对于调试所涉及的Git实现的人来说,错误消息令人困惑,因为“new-section”并不是响应最新请求的结果,而是第一个请求的结果。

一个解决方案是始终在 DELIM 之后继续读取,但在这种情况下,我们可以做得更好。

我们从协议中知道:

  • "ready"表示至少要来packfile部分(因此是 DELIM ),并且:
  • 没有“ready”表示不会有后续部分(因此是 FLUSH )。

因此,教 process_acks() 强制执行此操作。


Git 2.21将为fetch pack提供V2协议的实际官方支持:

请参见提交 e20b419(2018年12月18日)由Jeff King(peff进行。
(在2019年1月29日的提交d3b0178中由Junio C Hamano -- gitster --合并)

fetch-pack: support protocol version 2

When the scaffolding for protocol version 2 was initially added in 8f6982b ("protocol: introduce enum protocol_version value protocol_v2", 2018-03-14, Git v2.18). As seen in:

git log -p -G'support for protocol v2 not implemented yet' --full-diff --reverse v2.17.0..v2.20.0

Many of those scaffolding "die" placeholders were removed, but we hadn't gotten around to fetch-pack yet.

The test here for "fetch refs from cmdline" is very minimal. There's much better coverage when running the entire test suite under the WIP GIT_TEST_PROTOCOL_VERSION=2 mode, we should ideally have better coverage without needing to invoke a special test mode.


Git 2.22(2019年第二季度)新增:当使用协议版本2进行通信时,“git clone”学习了一个新的--server-option选项。

请参见提交6e98305提交35eb824(由Jonathan Tan(jhowtan于2019年4月12日提交)。
(由Junio C Hamano -- gitster --合并至提交6d3df8e,2019年5月8日)

clone: 在使用协议v2时发送服务器选项

提交5e3548e(“fetch: 在使用协议v2时发送服务器选项”,2018-04-24,Git v2.18.0-rc0)教会了“fetch”在使用协议v2时发送服务器选项,但是“clone”没有。
这个能力由“-o”或“--server-option”触发。

教会“clone”相同的能力,但由于“clone”已经有其他参数的“-o”,因此只教会“clone”接收“--server-option”。

在文档中解释,无论是对于克隆还是对于提取,服务器处理服务器选项都是特定于服务器的。
这类似于receive-pack处理推送选项 - 目前,它们只被发送到钩子以根据需要进行解释。


注意:Git 2.12通过commit ed10cb9Brandon Williams引入了git serve命令:

serve:引入git-serve

引入git-serve,作为协议版本2的基础服务器。

协议版本2旨在替代Git当前的传输协议。
目的是打造一个更简单、更节省的协议,能够随着时间的推移而不断发展。

协议版本2通过消除初始的引用广告来改进版本1。
取而代之的是,服务器将以功能广告的形式导出其支持的功能和命令列表。
然后,客户端可以通过提供多个功能和特定于命令的参数来请求执行特定命令。
在完成命令后,客户端可以请求执行另一个命令,也可以通过发送刷新数据包来终止连接。

但是...Git 2.22已经修复了这个问题,由Johannes Schindelin提交的commit b7ce24d实现了这一点。

git serve 转换为一个测试帮助工具

git serve 内置命令是在 ed10cb9serve: introduce git-serve, 2018-03-15, Git v2.18.0-rc0)中引入的,作为服务 Git protocol v2 的后端,最初可能是由 git upload-pack 生成。

然而,在协议 v2 补丁被纳入核心 Git 的版本中,git upload-pack 直接调用 serve() 函数,而不是生成 git servegit serve 在生存中唯一的原因是提供一种测试协议 v2 功能的方式,所以它甚至不需要成为安装在用户面前的 Git 内置命令,而可以成为测试帮助程序。

让我们来实现这个想法吧。


Git 2.23 (2019年第二季度)将使update-server-info更加高效,因为它学会了不用重写具有相同内容的文件。

请参见提交f4f476b(2019年5月13日),由Eric Wong (ele828)撰写。
(由Junio C Hamano -- gitster --合并于提交813a3a2,2019年6月13日)

update-server-info: 避免不必要的覆盖

如果现有的 info/refsobjects/info/packs 文件与文件系统上的现有内容匹配,则不要更改它们。
这旨在保留修改时间(mtime),并使愚蠢的HTTP轮询器能够依赖于 If-Modified-Since 标头。

结合stdio和内核缓冲,内核应该能够避免块层写入并减少小文件的磨损。

因此,不再需要使用 --force 选项。
因此停止记录它,但让它保持兼容性(如果需要,进行调试)。

另外,Git 2.22.1 还将修复服务器端对 "git fetch" 的支持,在使用命名空间功能时会显示错误的 HEAD 符号引用值。

请查看 提交 533e088(2019年5月23日),作者为Jeff King (peff)
(由Junio C Hamano -- gitster --提交 5ca0db3中合并,于2019年7月25日)

upload-pack: 去除符号引用数据中的命名空间

自从 7171d8c (2013-09-17, Git v1.8.4.3) 开始,我们向克隆和获取客户端发送特殊信息,以便他们不必根据匹配的提交 ID 来猜测 HEAD 指向哪个分支。

但是,此功能在 GIT_NAMESPACE 功能中从未正常工作。因为 upload-pack 使用 head_ref_namespaced(find_symref),因此我们会查找并报告 refs/namespaces/foo/HEAD 而不是仓库实际的 HEAD
这是有道理的,因为顶层 HEAD 指向的分支可能根本没有被公开宣传。

但我们有两个问题:

  1. 我们报告完整名称 refs/namespaces/foo/HEAD,而不是只有 HEAD。
    这意味着没有客户端会费心处理该符号引用,因为我们没有其他方式广告它。
  2. 我们使用全名报告符号引用目标(例如,refs/namespaces/foo/refs/heads/master)。这对客户端也同样无用,客户端只能在广告中看到 "refs/heads/master"。

我们应该从这两个位置去除命名空间前缀(这就是这个补丁修复的内容)

可能没有人注意到,因为我们通常会做正确的事情。
错误(1)意味着我们没有关于 HEAD 的任何信息(只有关于 refs/namespaces/foo/HEAD)。因此,代码的客户端部分,在 a45b5f0 (2013-09-17, Git v1.8.4.3) 中,在 get_remote_head() 中注释符号引用信息时未注释 HEAD,我们使用 guess_remote_head() 中的备用选项来匹配对象 ID。
这通常是正确的。它只会在存在歧义的情况下失败,例如在包含在测试中的情况下。

这也意味着我们不必担心打算在其命名空间符号引用中放置预剥离名称的任何人将会出现错误(2)。
因为错误(1),首先我们宣传的符号引用无人会使用(更不要说那些符号引用在非命名空间访问时会被破坏了)。

请注意,此处针对 v0 和 v2 协议有不同的修复。
对于 v2 协议,符号引用广告移动为 ls-refs 命令的一部分。
这实际上在部分(1)上做得正确,因为符号引用注释依附于现有的符号引用广告,在广告中被正确剥离。
但它仍然需要解决部分(2)这个问题。


在Git 2.25.1(2020年2月)中,通过无状态RPC机制运行"ls-remote"时不必要的往返次数减少了。

请参见讨论

A colleague (Jon Simons) today pointed out an interesting behavior of git ls-remote with protocol v2: it makes a second POST request and sends only a flush packet.
This can be demonstrated with the following:

GIT_CURL_VERBOSE=1 git -c protocol.version=2 ls-remote origin

The Content-Length header on the second request will be exactly 4 bytes.

查看 提交 4d8cab9 (于2020年1月8日) 由 Jeff King (peff) 提交。
(合并自 Junio C Hamano -- gitster --提交 45f47ff, 于2020年1月22日)

transport: 断开无状态 RPC 辅助程序时不要刷新

Signed-off-by: Jeff King

自从 ba227857d2("Reduce the number of connects when fetching", 2008-02-04, Git v1.5.5-rc0 -- merge)以来,当我们断开 git 传输时,我们会发送最后一个 flush 数据包。
这样可以干净地告诉对方我们已经完成了操作,避免了对方抱怨“远程终端意外挂起”(尽管我们只会在像 ssh 或本地主机等传输中传递服务器 stderr 的情况下看到这种情况)。

但是,在通过传输辅助程序启动 v2 无状态连接会话时,发送此 flush 数据包没有任何意义。我们执行的每个操作都是自包含的,对方也可以接受我们在操作之间挂起。

更糟糕的是,通过发送 flush 数据包,我们可能会导致辅助程序发出一个完全新的请求 _just_ 来发送 flush 数据包。因此,我们可能会多产生一个网络请求,仅仅是为了说“顺便说一下,我们没有更多要发送的内容”。

让我们放弃这个额外的 flush 数据包。如测试所示,这将把 v2 ls-remote 在 http 上所需的 POST 数量从 2 减少到 1。


在 Git 2.26(2020 年第一季度)中,test-lint 机制已经能够检查“VAR=VAL shell_function”结构,但未检查“VAR=shell_function”,这已得到纠正。

请查看提交d6509da提交a7fbf12提交c7973f2(2019年12月26日)由Jonathan Nieder (artagnon)进行。
(由Junio C Hamano -- gitster --合并于提交c7372c9,2020年1月30日)

fetch test: mark test of "skipping" haves as v0-only

Signed-off-by: Jonathan Nieder

Since 633a53179e (fetch test: avoid use of "VAR= cmd" with a shell function, 2019-12-26), t5552.5 (do not send "have" with ancestors of commits that server ACKed) fails when run with GIT_TEST_PROTOCOL_VERSION=2.

The cause:

The progression of "have"s sent in negotiation depends on whether we are using a stateless RPC based transport or a stateful bidirectional one (see for example 44d8dc54e7, "Fix potential local deadlock during fetch-pack", 2011-03-29, Git v1.7.5-rc0).

In protocol v2, all transports are stateless transports, while in protocol v0, transports such as local access and SSH are stateful.

In stateful transports, the number of "have"s to send multiplies by two each round until we reach PIPESAFE_FLUSH (that is, 32), and then it increases by PIPESAFE_FLUSH each round.

In stateless transport, the count multiplies by two each round until we reach LARGE_FLUSH (which is 16384) and then multiplies by 1.1 each round after that.

Moreover, in stateful transports, as fetch-pack.c explains:

We keep one window "ahead" of the other side, and will wait for an ACK only on the next one.

This affects t5552.5 because it looks for "have"s from the negotiator that appear in that second window.

With protocol version 2, the second window never arrives, and the test fails.

Until 633a53179e (2019-12-26), a previous test in the same file contained

GIT_TEST_PROTOCOL_VERSION= trace_fetch client origin to_fetch

In many common shells (e.g. bash when run as "sh"), the setting of GIT_TEST_PROTOCOL_VERSION to the empty string lasts beyond the intended duration of the trace_fetch invocation.

This causes it to override the GIT_TEST_PROTOCOL_VERSION setting that was passed in to the test during the remainder of the test script, so t5552.5 never got run using protocol v2 on those shells, regardless of the GIT_TEST_PROTOCOL_VERSION setting from the environment.

633a53179e fixed that, revealing the failing test.


4
好的,你要深入讨论一些细节问题;即使你必须向同事团队解释Git,我也对需要这种详细程度感到惊讶...无论如何,在远程意味着使用愚蠢服务器的HTTP上,info/refs文件只会存在于那里。你在本地repo中可能找不到它(也不需要)。 (在这种情况下,远程库很可能是一个裸库,因此info将位于repo根目录,因为裸库没有工作树,并且将您习惯于在.git中看到的文件放在根目录中。)如果我们的远程库在类似github、tfs等地方,则您不需要担心这些问题,因为服务器会处理好所有事情。我猜,如果您将repo作为静态内容从普通Web服务器提供,则这将很重要,并且您必须设置挂钩。大多数用户永远不会使用或看到update-server-info命令;正如其名称所示,它适用于位于服务器端的repo-remotes,以弥补缺少git-aware HTTP服务器的不足。post-receive挂钩在接收推送后被调用;因此,在愚蠢服务器方案中,您在远程设置此挂钩,以便在推送到它时,它通过更新某些信息(如refs文件)来响应。您正在查看的GET命令是一个HTTP命令,在执行fetch时由git客户端必要时运行。

3

info/refs在哪里?我只找到了一个克隆后的/.git/info/exclude...

这个文件夹(不是目录)不存在,但如果有该文件,它应该在.git/info/refs中。

如何使用update-server-info命令?它是否与git clone有关?

通常情况下,你不需要使用它:它只用于“愚蠢”的传输方式。而“智能”的双向传输方式不需要它。

对于“...这就是为什么您需要将其作为post-receive钩子启用”的部分,我完全迷失了,尽管我理解(我认为)钩子并使用pre-commit钩子自动增加包版本。

如果由于某种原因你想要启用愚蠢的传输方式,你需要运行一些东西来创建或更新每次需要创建或更新时的多个文件。当引用发生变化时,需要更新info/refs文件,所以运行“something”的好地方是在post-receive hook中。“something”是git update-server-info命令。

请注意,如果你不在服务器上运行只推送的裸库,让post-receive脚本运行git update-server-info是不够的,因为提交和其他对象可以通过其他方式添加(例如手动git commit)。在这种情况下,你可以使用cron job来基于时钟驱动的方式创建或更新愚蠢的传输信息。

我无法在git bash中运行GET info/refs命令。

如果该文件存在,你可以通过HTTP从浏览器或使用curl命令获取它。


这有点清楚了。你提供的larsks答案的链接坏了吗?能否更新一下?在阅读第10章的前几节之后,我想知道我可以从第10.6章中学到什么:要么我已经知道了所有内容,不需要这一章,要么我阅读它,即使有了你的额外解释,我的直觉是我只会获得很少的收获。我希望更好地理解https-server/masterorigin/mastermaster(本地)是如何协同工作的,以及为什么真正需要额外的origin/master... - Christoph
@Christoph - origin/mastermaster 之间的关系以及 origin/master 的目的与传输无关。建议查看第3.5章了解有关远程分支的信息。我不知道你所说的 https-server/master 是什么意思,你是否有一个名为 https-server 的远程分支或者这里表示其他内容? - Mark Adelsberger
啊,他不幸地删除了那个答案。虽然如此,我也不确定《Pro Git》书中关于传输协议的描述有多大用处。我开始写自己的书(不是关于Git的),但一直没有时间去完成它;不过在书中,我确实有一章关于分发存储库的内容……原始PDF可以在这里获取:http://web.torek.net/torek/tmp/book.pdf - torek
1
@Christoph - 好的,就像我说的,你会从第三章比第十章学到更多关于这个的知识。以origin/master为例,它是用来干什么的呢?考虑到只有fetchpushpull才能访问远程,那么git status如何知道你的分支相对于originn次提交落后/超前的呢? - Mark Adelsberger
@torek 非常感谢这本书。看起来值得一读,我已经在里面找到了好的解释!我在哪里可以找到类似于“GET info/refs”的东西?我找到了很多get请求,但不在这个上下文中。 - Christoph
显示剩余4条评论

1
另一个与git传输协议相关的方面是数据包管理,包括在请求“HAVE”时的ACKs:
在Git 2.27之前(2020年第二季度),v2协议的服务器端用于提供“git clone”和“git fetch”的服务未准备好在意外位置看到定界符数据包,导致崩溃。

请见提交 cacae43 (2020年3月29日),以及 提交 4845b77提交 88124ab (2020年3月27日) 作者是Jeff King (peff)
(由Junio C Hamano -- gitster --提交 5ee5788中合并,2020年4月22日)

upload-pack: handle unexpected delim packets

Signed-off-by: Jeff King

When processing the arguments list for a v2 ls-refs or fetch command, we loop like this:

while (packet_reader_read(request) != PACKET_READ_FLUSH) {
        const char *arg = request->line;
 ...handle arg...
}

to read and handle packets until we see a flush. The hidden assumption here is that anything except PACKET_READ_FLUSH will give us valid packet data to read. But that's not true; PACKET_READ_DELIM or PACKET_READ_EOF will leave >packet->line as NULL, and we'll segfault trying to look at it.

相反,我们应该遵循客户端上展示的更谨慎的模型(例如在process_capabilities_v2中):只要我们收到正常数据包,就继续循环,然后确保我们因为真正的刷新而跳出了循环。这样可以修复段错误并正确诊断来自客户端的任何意外输入。
在 Git 2.27(2020 年第二季度)之前,上传包协议 v2 在找到共同祖先之前放弃的太早,导致从项目的分叉处进行了浪费性抓取。现已更正为与 v0 协议的行为匹配。

请查看 提交 2f0a093, 提交 4fa3f00, 提交 d1185aa (2020年4月28日) ,作者是Jonathan Tan (jhowtan)
(由Junio C Hamano -- gitster --合并于提交 0b07eec, 2020年5月1日)

fetch-pack: 在协议v2中,只有在ACK之后才会出现in_vain

签名:Jonathan Tan
审阅者:Jonathan Nieder

在获取时,Git发送了至少256个“have”行而没有任何ACK时,Git会停止协商。
但是,根据pack-protocol.txt的说法,这应该仅在第一个ACK之后触发:

然而,在先前的一轮中,如果我们已经收到了至少一个“ACK%s continue”,则只有规范客户端实现才会打开256个限制。这有助于确保在完全放弃之前至少找到一个共同的祖先。

协议v0的代码路径遵循此规则,但不适用于协议v2,导致协商时间缩短但包文件显著变大。
请教授协议v2的代码路径,在收到至少一个ACK后才检查此标准。


由于在2.27版本中(其中v2不是默认值),v2现在又成为了2.28的默认值。
请参见提交3697caf

config:让feature.experimental意味着protocol.version=2

Git 2.26默认使用协议v2,但发布后,用户发现在从某些远程仓库获取时,协议v2的协商代码容易失败(例如linux-next.git与Linus的linux.git相比)。0b07eec已经修复了这个问题(合并分支'jt/v2-fetch-nego-fix',2020-05-01,Git v2.27.0-rc0),但为了谨慎起见,在2.27中我们将协议v0作为默认协议,以争取时间来发现任何其他未预料到的问题。
为此,让我们确保使用feature.experimental标志请求最新功能的用户确实获得协议v2。这样,我们可以获得更广泛的用户体验新协议版本,并在将来的某个Git版本中为所有用户启用它时更有信心。
实施说明:这不是在repo-settings.c的其余feature.experimental选项中,因为那些选项与存储库对象绑定,而这个代码路径用于像“git ls-remote”这样不需要存储库的操作。
自Git 2.28(2020年第三季度)起,“fetch/clone”协议已更新,允许服务器指示客户端在从网络接收打包对象数据的同时抓取预打包的包文件。

请看 commit cae2ee1 (2020年6月15日) 由 Ramsay Jones (``) 提交。
请看 commit dd4b732, commit 9da69a6, commit acaaca7, commit cd8402e, commit fd194dd, commit 8d5d2a3, commit 8e6adb6, commit eb05349, commit 9cb3cab (2020年6月10日) 由 Jonathan Tan (jhowtan) 提交。
(在 commit 34e849b 中由 Junio C Hamano -- gitster -- 合并,2020年6月25日)

fetch-pack: 支持多个pack lockfile

签名:Jonathan Tan

每当fetch结果导致packfile被下载时,就会生成一个.keep文件,以便保留packfile(例如,从正在运行的"git repack"中)直到写入引用来引用packfile的内容。

在随后的补丁中,使用协议v2成功fetch可能会导致生成多个.keep文件。因此,教授fetch_pack()和传输机制支持多个.keep文件。

实现说明:

  • builtin/fetch-pack.c通常不会生成.keep文件,因此不受此或未来更改的影响。
    但是,它具有一个未记录的"--lock-pack"功能,由remote-curl.c在实现"fetch"远程帮助器命令时使用。
    根据远程助手协议,只会写入一个“锁定”行;其余的将导致stderr警告。
    然而,在实践中,警告永远不会被写入,因为remote-curl.c的"fetch"仅用于协议v0/v1(不会生成多个.keep文件)。 (协议v2使用“无状态连接”命令,而不是“fetch”命令。)

  • connected.c具有一个优化,即如果目标对象在已知为自包含和连接的pack中,则不需要对ref进行连通性检查。如果存在多个packfile,则无法执行此优化。

参见 Packfile URIs

此功能允许服务器将其packfile响应的一部分作为URI提供。 这使得服务器设计在带宽和CPU使用方面可以提高可扩展性 (例如,通过CDN提供某些数据),并且(在未来)为客户端提供了 一定程度的可恢复性。

此功能仅在协议版本2中可用。


"

在无状态RPC /智能HTTP传输中,git fetch --depth = (man)处理客户端的EOF时服务器端表现不佳。

这个问题已经在Git 2.30(2021年第一季度)中作为传输协议的一部分得到解决。

"

请查看fb3d1a0提交(2020年10月30日)由Daniel Duvall (marxarelli)提交。
(由Junio C Hamano -- gitster --d1169be提交合并,2020年11月18日)

upload-pack:允许无状态客户端在haves之前发送EOF

签名作者:Daniel Duvall

在给定深度的无状态打包文件协商期间,无状态RPC客户端(例如git-remote-curl)将发送多个upload-pack请求,第一个仅包含wants / shallows / deepens / filters,随后包含haves / done。
upload-pack处理此类请求时,在未检查客户端是否挂起的情况下进入get_common_commits可能会导致在协商循环中出现意外的EOF,以及使用消息"fatal: the remote end hung up unexpectedly"的die()
现实世界的影响包括:
- 通过不检查CGI的退出代码的服务器(例如mod_cgi)与git-http-backend通信的客户端不知道也不关心此致命错误。它继续正常处理响应主体。 - 与检查退出代码并返回错误HTTP状态作为结果的服务器通信的客户端将失败,并显示消息“error: RPC failed; HTTP 500 curl 22 The requested URL returned error: 500.” - 运行显示故障的服务器的管理员必须通过修补处理git-http-backend执行的代码来忽略退出代码或采取其他启发式方法来解决此问题。 - 在即使退出代码未显示为HTTP服务器端错误状态的情况下,管理员也可能不得不处理与故障相关的“hung up unexpectedly”日志垃圾邮件。
为了避免这些EOF相关的致命错误,请让upload-pack在发送浅层/非浅层行(后跟flush)和读取客户端haves之间轻松地窥探EOF。 如果此时客户端挂起,则正常退出。

1
在 Git 2.30(2021 年第一季度)中,传输层被教授可选地交换由 trace2 子系统分配的会话 ID,在获取/推送事务期间。

请查看 提交 a2a066d, 提交 8c48700, 提交 8295946, 提交 1e905bb, 提交 23bf486, 提交 6b5b6e4, 提交 8073d75, 提交 791e1ad, 提交 e97e1cf, 提交 81bd549, 提交 f5cdbe4 (2020年11月11日) 由 Josh Steadmon (steadmon) 提交。
(由 Junio C Hamano -- gitster -- 合并于 提交 01b8886, 2020年12月8日)

serve: 在v2协议中通过新的session-id功能广告服务器的会话ID

签名作者:Josh Steadmon

当transfer.advertiseSID为true时,通过新的session-id功能在所有v2协议连接中广告服务器的会话ID。

以及:

docs: 广告会话ID的新功能

签名作者:Josh Steadmon

在未来的补丁中,我们将添加Git服务器和客户端通过协议功能广告唯一会话ID的能力。这使得在客户端和服务器日志都可用时更容易进行调试。

technical/protocol-capabilities现在已经在其man page中包含:

session-id=<会话ID>


服务器可以发布一个用于跨多个请求识别此进程的会话ID。客户端也可以将自己的会话ID发送回服务器。

会话ID应该对于给定的进程是唯一的。它们必须适合数据包行,并且不能包含不可打印或空白字符。

technical/protocol-v2现在已经在其man page中包含:

session-id=<session id>


The server may advertise a session ID that can be used to identify this process
across multiple requests. The client may advertise its own session ID back to
the server as well.

Session IDs should be unique to a given process. They must fit within a
packet-line, and must not contain non-printable or whitespace characters. 

Git 2.30(2021年第一季度)的其他新修复:

fetch-pack遇到无效文件名时,可能会将NULL指针传递给unlink;现在已经加强了错误检查,使这种情况不再可能发生。

请参见提交6031af3(由René Scharfe(rscharfe于2020年11月30日提交)。
(由Junio C Hamano -- gitster --于2020年12月8日合并至提交eae47db

fetch-pack:忽略无效的包锁文件

签名作者:René Scharfe
审核作者:Taylor Blau

9da69a6539(“fetch-pack: 支持多个包锁文件”,2020年6月10日,Git v2.28.0-rc0 -- 合并列在批次#5中)开始使用string_list来代替单个字符串指针作为包锁文件名。
它还从transport_unlock_pack()中删除了一个NULL检查,这是最终删除这些锁文件并释放其名称字符串的函数。
如果index_pack_lockfile()不喜欢从传递给它的文件描述符读取的内容,则可以返回NULL
unlink(2)声明不接受NULL指针(至少在glibc中)。
Undefined Behavior Sanitizer与Address Sanitizer一起检测到一个情况,在t1060中,transport_unlock_pack()NULL锁文件名传递给unlink(2)(make SANITIZE=address,undefined; cd t; ./t1060-object-corruption.sh)。
重新引入NULL检查以避免未定义行为,但将其放在源代码处,以便string_list中的项目数反映有效锁文件的数量。

传输层v2可能与原始引入于Git 2.18(2018年第二季度)的提交图不兼容(为祖先遍历所需的预计算信息在单独文件中以优化图行走)

Ævar Arnfjörð Bjarmason在这个线程中描述了您将看到的错误消息:

$ git status
    error: graph version 2 does not match version 1
$ ~/g/git/git --exec-path=$PWD status
    error: commit-graph version 2 does not match version 1
    On branch master
    [...]

随着 Git 2.31 (2021年第一季度) 的推出,提交图学会使用纠正后的提交日期而非生成数来帮助拓扑修订遍历,以便与协议 v1 区分开来。

请查看 提交 5a3b130, 提交 8d00d7c, 提交 1fdc383, 提交 e8b6300, 提交 c1a0911, 提交 d7f9278, 提交 72a2bfc, 提交 c0ef139, 提交 f90fca6, 提交 2f9bbb6, 提交 e30c5ee (2021年1月16日) 由 Abhishek Kumar (abhishekkumar2718) 提交。
(由 Junio C Hamano -- gitster -- 合并于 提交 8b4701a, 2021年2月17日)

commit-graph:实现生成数据块

签名作者:Abhishek Kumar
审核者:Taylor Blau
审核者:Derrick Stolee

如Ævar所发现的,我们无法将图形版本递增以区分生成编号v1和v2。
因此,在实现生成编号v2之前,必须以向后兼容的方式区分图形版本之一先决条件。
我们将引入一个名为“Generation DATa chunk”(或GDAT)的新块。
GDAT将存储已更正的提交者日期偏移量,而CDAT仍将存储拓扑级别。
旧版Git不了解GDAT块,并会忽略它,从CDAT中读取拓扑级别。
新版Git可以解析GDAT并利用更新的生成编号,当GDAT块丢失时回退到拓扑级别(与由旧版Git编写的提交图形发生的情况相同)。
为了最小化存储更正的提交日期所需的空间,Git将更正的提交日期偏移量存储到提交图形文件中,而不是更正的提交日期。
这使我们每个提交节省4个字节,将GDAT块大小减半,但偏移可能会溢出为存储分配的4个字节。
由于这样的溢出应该极其罕见,因此我们使用以下溢出管理方案:
我们引入一个名为“Generation Data OVerflow”('GDOV')的新提交图形块,用于存储偏移大于GENERATION_NUMBER_V2_OFFSET_MAX的提交的更正提交日期。
如果偏移量大于GENERATION_NUMBER_V2_OFFSET_MAX,则设置偏移量的MSB,其他位存储GDOV块中更正提交日期的位置,类似于如何维护额外的边缘列表。
我们使用以下存储库历史记录测试与溢出相关的代码:
          F - N - U
         /         \
U - N - U            N
         \          /
          N - F - N
其中:
  • 由U表示的提交具有自Unix纪元以来零秒的提交者日期,
  • 由N表示的提交具有自Unix纪元以来1112354055(测试套件的默认提交者日期)秒的提交者日期,
  • 由F表示的提交具有自Unix纪元以来(2 ^ 31-2)秒的提交者日期。
  • 最大观察到的偏移量为2 ^ 31,刚好足够溢出。

    这是向后兼容v1的原因:

    commit-graph:仅在整个链路中使用第二代生成

    签名作者:Derrick Stolee
    签名作者:Abhishek Kumar
    评审者:Taylor Blau
    评审者:Derrick Stolee

    由于已发布的 Git 版本可以理解提交图中 CDAT 代码块中的生成编号,但无法理解 GDAT 代码块,因此以下情况是可能的:
    1. "新" Git 写入带有 GDAT 代码块的提交图。 2. "旧" Git 在其上面写了一个没有 GDAT 代码块的拆分提交图。
    如果将拆分提交图的每个层面视为独立的(这在此提交之前就是这样),Git 只检查当前层的 chunk_generation_data 指针,则较低层(具有 GDAT 的层)中的提交将具有其生成编号作为其更正的提交日期,而较高层中的提交将具有其拓扑级别作为其生成编号。
    更正的提交日期通常比拓扑级别要大得多。
    这意味着,如果我们取两个提交,一个来自较高层,另一个从较低层可达,那么父代的生成编号小于子代的生成编号的预期将被违反。
    很难在测试中暴露此问题。
    由于我们从人为设置低的生成编号开始,任何按生成编号优先级遍历的提交步骤都会在遍历生成编号高的所有提交之前遍历生成编号低的提交。
    在我尝试过的所有情况中,提交图层本身都会“保护”任何不正确的行为,因为较低层中的任何提交都无法到达较高层中的提交。
    在这种情况下,这个问题将体现为性能问题,尤其是使用 "git log --graph"(man) 等工具时,由于低生成编号将导致入度队列在允许拓扑排序队列写入输出之前遍历所有较低层的提交(取决于上层的大小)。
    因此,在拆分提交图中写入新层时,仅当最顶层的 GDAT 代码块存在时,我们才写入 GDAT 代码块。
    这保证了如果一个层面有 GDAT 代码块,则所有较低层也必须有 GDAT 代码块。
    重写层面采用类似的方法:如果正在重写的一组层级下方存在最顶层,且它不包含 GDAT 代码块,则重写的结果也没有 GDAT 代码块。

    什么是“修正的提交日期”?

    commit-graph:实现修正的提交日期

    签名作者:Abhishek Kumar
    审核者:Taylor Blau
    审核者:Derrick Stolee

    大部分的准备工作已经完成,现在让我们来实现修正的提交日期。
    对于一个提交而言,修正的提交日期被定义为: - 没有父节点(根提交)的提交具有与其提交者日期相等的修正提交日期。 - 至少有一个父节点的提交具有与其提交日期以及父节点中最大的修正提交日期加一中的较大值相等的修正提交日期。
    特殊情况下,时间戳为零(01.01.1970 00:00:00Z)的根提交的修正提交日期是一,以便能够与GENERATION_NUMBER_ZERO(即未计算的修正提交日期)区分开。
    为了减少存储修正提交日期所需的空间,Git将修正提交日期的偏移量存储在commit-graph文件中。 提交的修正提交日期偏移量被定义为其修正提交日期与实际提交日期之间的差值。
    存储修正提交日期需要sizeof(timestamp_t)字节,大多数情况下是64位(uintmax_t)。 然而,修正提交日期的偏移量可以安全地使用32位存储。 这样就将GDAT块的大小减半,是commit-graph文件大小减小约6%。
    然而,如果一个提交存在问题但是有效,并且提交者日期为0 Unix时间,则使用偏移量可能会有问题,因为偏移量将与修正提交日期相同,因此需要64位来正确存储。
    虽然Git在这个阶段不会写出偏移量,但是Git将修正的提交日期存储在struct commit_graph_data的generation成员中。 在引入generation数据块后,它将开始写入提交日期的偏移量。

    这可以提高性能:

    commit-reach:在 paint_down_to_common() 中使用更正的提交日期

    签名作者:Abhishek Kumar
    审核者:Taylor Blau
    审核者:Derrick Stolee

    091f4cf ("commit: 不必要时不使用生成编号", 2018-08-30, Git v2.19.0-rc2 -- 合并) 更改了 paint_down_to_common(),使用提交日期而不是一级(拓扑级别)的生成编号v1,因为在某些拓扑结构上性能下降。
    实现了生成编号v2(更正的提交日期),我们不再需要依赖提交日期,可以使用生成编号。
    例如,在Linux存储库上,命令git merge-base(手册) v4.8 v4.9 遍历了167468个提交,对于提交者日期花费了0.135秒,对于更正的提交者日期花费了167496个提交,花费了0.157秒。
    虽然使用更正的提交日期,Git遍历的提交数量几乎与提交日期相同,但由于每次比较都必须访问提交块(对于更正的提交者日期)而不是访问结构成员(对于提交者日期),所以该过程较慢。
    由于这已经引起了问题(如859fdc0 (commit-graph: 定义GIT_TEST_COMMIT_GRAPH,2018-08-29,Git v2.20.0-rc0 -- 合并列在批次#1中),我们在t6404-recursive-merge中禁用提交图。
    然后,仍然使用Git 2.31(2021年第一季度),修复在更正提交日期数据时增量更新提交图文件的问题。

    请查看 commit bc50d6c, commit fde55b0, commit 9c2c0a8, commit 448a39e (2021年2月2日),以及 commit 90cb1c4, commit c4cc083 (2021年2月1日) 由 Derrick Stolee (derrickstolee) 提交的代码。
    (由 Junio C Hamano -- gitster -- 合并于 commit 5bd0b21, 2021年2月17日)

    commit-graph:分别计算代数

    署名:Derrick Stolee
    审核者:Taylor Blau

    compute_generation_numbers() 方法是由 3258c66 引入的(“commit-graph: compute generation numbers”,2018-05-01,Git v2.19.0-rc0 -- mergebatch #1 中列出),用于计算现在所知的“拓扑级别”。

    这些仍然存储在提交图文件中,以保持兼容性,而 c1a0911(“commit-graph: implement corrected commit date”,2021-01-16,Git v2.31.0 -- mergebatch #9 中列出)更新了该方法,以计算“修正提交日期”的新版本的生成编号。

    这两个方法为什么被分组是有道理的。
    它们执行必要提交的非常相似的遍历,并计算每个父节点上的类似最大值。
    然而,将这两个方法放在一起会以难以分离的微妙方式混淆它们。

    特别地,topo_level 块用于在所有情况下存储拓扑级别,但是 commit_graph_data_at(c)->generation 成员根据现有提交图文件的状态存储不同的值。* 如果现有的提交图文件具有“GDAT”块,则这些值表示修正的提交日期。* 如果现有的提交图文件没有“GDAT”块,则这些值实际上是拓扑级别。

    只有在将现有的提交图文件升级为具有“GDAT”块的文件时才会出现此问题。
    当前更改不解决此升级问题,但是在此处将实现分成两个部分有助于该过程,该过程将在下一个更改中进行。

    这有助于处理的重要事情是 num_generation_data_overflows 被错误地递增,从而触发了溢出块的写入。

    并且:

    commit-graph:对混合代际要格外小心

    署名:Derrick Stolee
    审核:Taylor Blau

    当升级到带有纠正提交日期的提交图(commit-graph)时,需要考虑一些事情。在计算新的提交图文件的生成数时,该文件预计添加具有纠正提交日期的“generation_data”块(chunk),我们需要确保对于这些提交,“commit_graph_data”结构体的“generation”成员设置为零。不幸的是,当没有可用的纠正提交日期时,回退到使用拓扑级别作为生成编号会对我们造成影响:解析提交通知(read_generation_data为false),并用拓扑级别填充“generation”。解决方案是通过迭代提交、解析提交来填充初始值,然后将生成值重置为零以触发重新计算。此循环仅在现有的提交图数据没有纠正提交日期时发生。

    还有:

    随着 Git 2.32(2021 年第二季度)的推出,通过网络协议可以学习到一种新的请求类型,以便给定对象名称列表来请求对象大小。

    请参见 提交记录 a2ba162(2021 年 4 月 20 日),作者为 Bruno Albuquerque(brunoga2
    (由 Junio C Hamano -- gitster --提交记录 eede711 中合并,2021 年 5 月 14 日)

    object-info:支持检索对象信息

    签名作者:Bruno Albuquerque

    有时候,获取对象的信息而不必完全下载它是很有用的。

    添加“object-info”功能,让客户端可以通过十六进制对象名称请求与对象相关的信息。

    目前仅返回大小信息。

    technical/protocol-v2现在在其手册页面中包含了此功能:

    object-info

    object-info 是检索一个或多个对象信息的命令。 它的主要目的是允许客户端基于此信息做出决策,而无需完全获取对象。目前只支持对象大小信息。

    object-info 请求需要以下参数:

    • size
      请求返回每个列出的对象 ID 的大小信息。

    • oid <oid>
      向服务器指示客户端想要获取信息的对象。

    object-info 的响应是所请求的对象 ID 和相关请求信息的列表,每个由单个空格分隔。

    output = info flush-pkt
    
    info = PKT-LINE(attrs) LF)
    *PKT-LINE(obj-info LF)
    
    attrs = attr | attrs SP attrs
    
    attr = "size"
    
    obj-info = obj-id SP obj-size
    

    "git receive-pack"(man) 是响应 git push(man) 请求的程序,但在中途被终止时未能清除过时的锁定文件。此错误已在 Git 2.41(2023 年第二季度)得到修正。

    请查看提交 c55c306(2023年3月10日),作者为Patrick Steinhardt(pks-t
    (由Junio C Hamano -- gitster --提交 ea09dff中合并,日期为2023年3月21日)

    receive-pack:修复死锁时的陈旧包文件锁定

    协助者:Jeff King
    签名作者:Patrick Steinhardt

    当在git-receive-pack中接受一个packfile时,我们将该packfile输入git-index-pack以生成packfile索引。
    由于packfile通常只包含不可达对象,直到引用已更新,同时运行垃圾回收可能会试图立即删除packfile,从而导致损坏。
    为了解决这个问题,我们要求git-index-pack在将packfile移动到指定位置之前创建一个.keep文件,在所有引用更新处理完毕后再将其删除。
    现在在生产系统中,我们观察到这些.keep文件有时未按预期被删除,结果是存储库随着时间的推移倾向于增长永远不会被删除的packfiles。
    这似乎是由于当我们将保留的packfile从隔离目录迁移到主对象数据库后,杀死git-receive-pack时发生了竞争所致。
    虽然这个竞争窗口通常很小,但可以通过安装proc-receive钩子来扩展,例如。
    通过将锁定文件注册为临时文件,从而在退出或接收信号时自动删除它,以解决这个竞争问题。

    这里有另一个用例/修复,说明了协议的工作方式:

    不支持协议v2的传输在某些条件下没有正确地回退到协议v0,这已经在Git 2.41(2023年第二季度)中得到了纠正。

    请参见提交 eaa0fd6(2023年3月17日),作者为Jeff King(peff
    (由Junio C Hamano -- gitster --合并于提交 f879501,2023年3月28日)

    git_connect():修复将v2降级为v0的边角情况

    签名作者:Jeff King

    git_connect()中有一个代码段用于检查我们是否正在使用protocol_v2进行推送,如果是,则将我们降级到protocol_v0(因为我们仅知道如何处理v2的fetch)。
    但它会忽略一些边角情况:
    1. 它检查"prog"变量,实际上这是远程端接收包路径。默认情况下,这仅是"git-receive-pack",但它可以是任意字符串(例如"/path/to/git receive-pack"等)。在这种情况下,我们会意外地停留在v2模式下。
    2. 除了"receive-pack"和"upload-pack"之外,还有一个值我们期望: "upload-archive" 用于处理"git archive --remote"(man)".
      receive-pack一样,它不理解v2,应该使用v0协议。
    在实践中,这两个问题都没有导致实际bug。
    我们向服务器发送一个“我们理解v2”的探测信号,但由于没有服务器对任何除upload-pack之外的内容实现v2,因此它被简单地忽略了。
    但如果我们将v2用于这些端点,这最终将成为一个问题,因为旧客户端会错误地声称它们理解该协议,导致无法解析服务器响应。
    我们可以通过传递程序路径和操作的“名称”来修复(1)。
    我在这里将名称视为字符串,因为这是transport_connect()中设置的模式之一(在此之前,我们只是丢弃了“名称”值)。
    我们可以通过仅允许已知的v2协议("upload-pack")来修复(2),而不是阻止未知的协议("receive-pack"和"upload-archive")。
    这意味着最终实现v2推送的人将不得不调整此列表,但这是合理的。
    我们将默认采用安全、保守的方法(坚持v0),任何正在开发v2的人都会很快意识到需要更新这个位置。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接