在 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)
签名作者: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日)
签名作者: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的原因:
签名作者: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 代码块。
什么是“修正的提交日期”?
签名作者: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日)
署名:Derrick Stolee
审核者:Taylor Blau
compute_generation_numbers()
方法是由 3258c66 引入的(“commit-graph
: compute generation numbers”,2018-05-01,Git v2.19.0-rc0 -- merge 在 batch #1 中列出),用于计算现在所知的“拓扑级别”。
这些仍然存储在提交图文件中,以保持兼容性,而 c1a0911(“commit-graph
: implement corrected commit date”,2021-01-16,Git v2.31.0 -- merge 在 batch #9 中列出)更新了该方法,以计算“修正提交日期”的新版本的生成编号。
这两个方法为什么被分组是有道理的。
它们执行必要提交的非常相似的遍历,并计算每个父节点上的类似最大值。
然而,将这两个方法放在一起会以难以分离的微妙方式混淆它们。
特别地,topo_level
块用于在所有情况下存储拓扑级别,但是 commit_graph_data_at(c)->generation
成员根据现有提交图文件的状态存储不同的值。* 如果现有的提交图文件具有“GDAT”块,则这些值表示修正的提交日期。* 如果现有的提交图文件没有“GDAT”块,则这些值实际上是拓扑级别。
只有在将现有的提交图文件升级为具有“GDAT”块的文件时才会出现此问题。
当前更改不解决此升级问题,但是在此处将实现分成两个部分有助于该过程,该过程将在下一个更改中进行。
这有助于处理的重要事情是 num_generation_data_overflows
被错误地递增,从而触发了溢出块的写入。
并且:
署名: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 日)
签名作者:Bruno Albuquerque
有时候,获取对象的信息而不必完全下载它是很有用的。
添加“object-info”功能,让客户端可以通过十六进制对象名称请求与对象相关的信息。
目前仅返回大小信息。
technical/protocol-v2
现在在其手册页面中包含了此功能:
object-info
object-info
是检索一个或多个对象信息的命令。
它的主要目的是允许客户端基于此信息做出决策,而无需完全获取对象。目前只支持对象大小信息。
object-info
请求需要以下参数:
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日)
协助者: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日)
签名作者:Jeff King
在
git_connect()
中有一个代码段用于检查我们是否正在使用
protocol_v2
进行推送,如果是,则将我们降级到
protocol_v0
(因为我们仅知道如何处理v2的fetch)。
但它会忽略一些边角情况:
- 它检查"
prog
"变量,实际上这是远程端接收包路径。默认情况下,这仅是"git-receive-pack
",但它可以是任意字符串(例如"/path/to/git receive-pack
"等)。在这种情况下,我们会意外地停留在v2模式下。
- 除了"
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的人都会很快意识到需要更新这个位置。