设置和配置
获取和创建项目
基本快照
分支与合并
共享和更新项目
检查和比较
打补丁
调试
电子邮件
外部系统
服务器管理
指南
管理
底层命令
- 2.50.1 无更改
-
2.50.0
2025-06-16
- 2.40.1 → 2.49.1 无更改
-
2.40.0
2023-03-12
- 2.38.3 → 2.39.5 无更改
-
2.38.2
2022-12-11
- 2.38.1 无更改
-
2.38.0
2022-10-02
Git bundle 文件存储一个 pack-file 以及一些额外元数据,包括一组引用(refs)和一套(可能为空的)必要提交(commits)。有关更多信息,请参阅 git-bundle[1] 和 gitformat-bundle[5]。
Bundle URI 是 Git 可以下载一个或多个 bundle 以便在从远程抓取剩余对象之前引导对象数据库的位置。
一个目标是加快网络连接到源服务器较差的用户的克隆和抓取速度。另一个好处是允许像 CI 构建农场这样的重度用户利用本地资源获取大部分 Git 数据,从而减轻源服务器的负载。
为了启用 bundle URI 功能,用户可以使用命令行选项指定 bundle URI,或者源服务器可以通过协议 v2 能力来发布一个或多个 URI。
设计目标
bundle URI 标准旨在足够灵活以满足多种工作负载。bundle 提供者和 Git 客户端在创建和使用 bundle URI 方面有多种选择。
-
bundle 可以拥有服务器所需的任何名称。此名称可以通过使用 bundle 内容的哈希值来引用不可变数据。然而,这意味着每次内容更新后都需要一个新的 URI。如果服务器正在发布该 URI(并且服务器知道正在生成新的 bundle),这可能是可以接受的,但对于使用命令行选项的用户来说,这将不符合人体工程学。
-
bundle 可以专门为引导完整克隆而组织,但也可以为了引导增量抓取而组织。bundle 提供者必须决定采用几种组织方案中的一种,以最大限度地减少增量抓取期间的客户端下载量,但 Git 客户端也可以选择是否将 bundle 用于这些操作中的任何一种。
-
bundle 提供者可以选择支持完整克隆、部分克隆或两者。客户端可以检测哪些 bundle 适用于仓库的部分克隆过滤器(如果有)。
-
bundle 提供者可以使用单个 bundle(仅用于克隆),或一个 bundle 列表。当使用 bundle 列表时,提供者可以指定客户端是否需要所有 bundle URI 才能完成完整克隆,或者任何一个 bundle URI 是否足够。这允许 bundle 提供者为不同地理区域使用不同的 URI。
-
bundle 提供者可以使用启发式方法(例如创建令牌)来组织 bundle,以帮助客户端避免下载不需要的 bundle。当 bundle 提供者不提供这些启发式方法时,客户端可以使用优化来最大限度地减少下载的数据量。
-
bundle 提供者不需要与 Git 服务器关联。客户端可以选择使用 bundle 提供者,而无需 Git 服务器的发布。
-
客户端可以选择发现 Git 服务器发布的 bundle 提供者。这可能发生在
git
clone
期间、git
fetch
期间、两者兼有或两者皆无。用户可以选择最适合他们的组合。 -
客户端可以随时手动配置 bundle 提供者。客户端还可以选择手动指定 bundle 提供者作为
git
clone
的命令行选项。
每个仓库都不同,每个 Git 服务器也有不同的需求。希望 bundle URI 功能足够灵活,能够满足所有需求。如果不能,则可以通过其版本控制机制进行功能扩展。
服务器要求
为了提供 bundle 服务器的服务器端实现,不需要 Git 协议的其他部分。这允许服务器维护者使用静态内容解决方案,例如 CDN,来提供 bundle 文件。
在 bundle URI 功能的当前范围内,所有 URI 都应是 HTTP(S) URL,内容通过对该 URL 的 GET
请求下载到本地文件。服务器可以在这些请求中包含身份验证要求,目的是触发已配置的凭据助手以进行安全访问。(未来的扩展可以使用 "file://" URI 或 SSH URI。)
假设服务器返回 200
OK
响应,则检查 URL 处的内容。首先,Git 尝试将文件解析为版本 2 或更高版本的 bundle 文件。如果文件不是 bundle,则使用 Git 的配置解析器将文件解析为纯文本文件。该配置文件中的键值对应描述一个 bundle URI 列表。如果这些解析尝试均未成功,则 Git 将向用户报告错误,指明提供的 bundle URI 包含错误数据。
服务器提供的任何其他数据均被视为错误数据。
Bundle 列表
Git 服务器可以使用一组 key=value
对来发布 bundle URI。一个 bundle URI 也可以以 Git 配置格式提供一个纯文本文件,其中包含相同的 key=value
对。在这两种情况下,我们都将其视为一个 bundle 列表。这些对指定了关于 bundle 的信息,客户端可以使用这些信息来决定下载哪些 bundle 以及忽略哪些。
一些键关注列表本身的属性。
- bundle.version
-
(必需)此值提供 bundle 列表的版本号。如果未来的 Git 更改启用了一项功能,需要 Git 客户端对 bundle 列表文件中的新键做出反应,则此版本将递增。当前唯一的版本号是 1,如果指定任何其他值,Git 将无法使用此文件。
- bundle.mode
-
(必需)此值有两个可能的值:
all
和any
。当指定all
时,客户端应预期需要所有与仓库要求匹配的列出的 bundle URI。当指定any
时,客户端应预期任何一个与仓库要求匹配的 bundle URI 就足够了。通常,any
选项用于列出位于不同地理区域的多个不同 bundle 服务器。 - bundle.heuristic
-
如果存在此字符串值键,则 bundle 列表旨在很好地与增量
git
fetch
命令配合使用。该启发式方法表明每个 bundle 都有额外的可用键,有助于确定客户端应下载哪些 bundle 子集。目前唯一计划的启发式方法是creationToken
。
剩余的键包含一个 <id> 段,它是服务器为每个可用 bundle 指定的名称。<id> 必须只包含字母数字和 -
字符。
- bundle.<id>.uri
-
(必需)此字符串值是下载 bundle <id> 的 URI。如果 URI 以协议(
http://
或https://
)开头,则 URI 是绝对的。否则,URI 被解释为相对于 bundle 列表所使用的 URI。如果 URI 以/
开头,则该相对路径是相对于 bundle 列表所使用的域名。(这种相对路径的使用旨在使得将一组 bundle 分发到具有不同域名的多个服务器或 CDN 更容易。) - bundle.<id>.filter
-
此字符串值表示一个对象过滤器,该过滤器也应出现在此 bundle 的头部。服务器使用此值来区分不同类型的 bundle,客户端可以从中选择那些与其对象过滤器匹配的 bundle。
- bundle.<id>.creationToken
-
此值为一个非负 64 位整数,用于对 bundle 列表进行排序。当
bundle.heuristic=creationToken
时,它用于在抓取期间下载一部分 bundle。 - bundle.<id>.location
-
此字符串值发布 bundle URI 的实际服务位置。这可以用来向用户提供一个选项,选择使用哪个 bundle URI,或者仅仅作为 Git 选择了哪个 bundle URI 的信息指示器。这仅在
bundle.mode
为any
时有价值。
这是一个使用 Git 配置格式的 bundle 列表示例
[bundle] version = 1 mode = all heuristic = creationToken
[bundle "2022-02-09-1644442601-daily"] uri = https://bundles.example.com/git/git/2022-02-09-1644442601-daily.bundle creationToken = 1644442601
[bundle "2022-02-02-1643842562"] uri = https://bundles.example.com/git/git/2022-02-02-1643842562.bundle creationToken = 1643842562
[bundle "2022-02-09-1644442631-daily-blobless"] uri = 2022-02-09-1644442631-daily-blobless.bundle creationToken = 1644442631 filter = blob:none
[bundle "2022-02-02-1643842568-blobless"] uri = /git/git/2022-02-02-1643842568-blobless.bundle creationToken = 1643842568 filter = blob:none
此示例使用 bundle.mode=all
以及 bundle.
<id>.creationToken
启发式方法。它还使用 bundle.
<id>.filter
选项来呈现两组并行的 bundle:一组用于完整克隆,另一组用于无 blob 的部分克隆。
假设此 bundle 列表位于 URI https://bundles.example.com/git/git/
,因此这两个无 blob 的 bundle 具有以下完全展开的 URI
-
https://bundles.example.com/git/git/2022-02-09-1644442631-daily-blobless.bundle
-
https://bundles.example.com/git/git/2022-02-02-1643842568-blobless.bundle
发布 Bundle URI
如果用户知道他们正在克隆的仓库的 bundle URI,则可以通过命令行选项手动指定该 URI。但是,Git 主机可能希望在克隆操作期间发布 bundle URI,以帮助不了解该功能的用户。
此功能唯一需要的是服务器能够发布一个或多个 bundle URI。此发布采用新协议 v2 能力的形式,专门用于发现 bundle URI。
客户端可以选择任意 bundle URI 作为选项,或者通过一些探索性检查选择性能最佳的 URI。由 bundle 提供者决定是拥有多个 URI 更可取,还是通过服务器端基础设施进行地理分布式部署的单个 URI 更可取。
使用 Bundle URI 进行克隆
bundle URI 的主要需求是加快克隆速度。Git 客户端将按照以下流程与 bundle URI 交互:
-
用户通过
--bundle-uri
命令行选项指定 bundle URI,或者客户端发现 Git 服务器发布的 bundle 列表。 -
如果从 bundle URI 下载的数据是 bundle,则客户端检查 bundle 头部,以确认所需的提交 OID 是否存在于客户端仓库中。如果缺少某些 OID,客户端将延迟解包,直到其他 bundle 被解包,使这些 OID 存在。当所有必需的 OID 都存在时,客户端使用 refspec 解包数据。使用的 refspec 是
+refs/*:refs/bundles/*
。这些引用被存储起来,以便后续的git
fetch
协商可以将每个 bundled ref 作为have
进行通信,从而减少通过 Git 协议抓取的数据量。为了允许从这个引用命名空间修剪引用,Git 可能会引入一个编号命名空间(例如refs/bundles/
<i>/*
),以便可以删除过时的 bundle 引用。 -
如果文件是 bundle 列表,则客户端检查
bundle.mode
以查看列表是all
形式还是any
形式。-
如果
bundle.mode=all
,则客户端考虑所有 bundle URI。列表会根据与客户端仓库的部分克隆过滤器匹配的bundle.
<id>.filter
选项进行缩减。然后,请求所有 bundle URI。如果提供了bundle.
<id>.creationToken
启发式方法,则 bundle 将按创建令牌递减的顺序下载,当一个 bundle 拥有所有必需的 OID 时停止。然后可以按创建令牌递增的顺序解包 bundle。如果 bundle 列表没有发布具有更大创建令牌的 bundle,客户端会将最新的创建令牌存储起来,作为避免未来下载的启发式方法。 -
如果
bundle.mode=any
,则客户端可以选择任何一个 bundle URI 进行检查。客户端可以使用多种方式在这些 URI 中进行选择。如果初始选择未能返回结果,客户端还可以回退到另一个 URI。
-
请注意,在克隆期间,我们预期将需要所有 bundle,并且可以使用 bundle.
<uri>.creationToken
等启发式方法按时间顺序或并行下载 bundle。
如果给定的 bundle URI 是一个具有 bundle.heuristic
值的 bundle 列表,则客户端可以选择将该 URI 存储为它选择的 bundle URI。然后,客户端可以在后续的 git
fetch
调用期间直接导航到该 URI。
下载 bundle URI 时,客户端可以选择在决定下载全部内容之前检查初始内容。这可能提供足够的信息来确定 URI 是 bundle 列表还是 bundle。在 bundle 的情况下,客户端可以检查 bundle 头部,以确定所有公布的提示(tips)是否已在客户端仓库中,并取消剩余的下载。
使用 Bundle URI 进行抓取
当客户端抓取新数据时,它可以决定在从源远程抓取之前先从 bundle 服务器抓取。这可以通过命令行选项完成,但更有可能的是使用在克隆期间指定的配置值。
抓取操作遵循相同的过程从 bundle 列表下载 bundle(尽管我们不希望在这里使用并行下载)。我们期望当精简 bundle(thin bundle)中所有必需的提交 OID 都已存在于对象数据库中时,该过程将结束。
当使用 creationToken
启发式方法时,如果 bundle 的创建令牌不大于已存储的创建令牌,客户端可以避免下载任何 bundle。抓取新的 bundle 后,Git 会更新此本地创建令牌。
如果 bundle 提供者没有提供启发式方法,那么客户端应在下载完整的 bundle 数据之前尝试检查 bundle 头部,以防 bundle 提示(tips)已存在于客户端仓库中。
错误条件
如果 Git 客户端在根据 bundle URI 或在该位置找到的 bundle 列表下载信息时发现意外情况,则 Git 可以忽略该数据并继续,如同没有提供 bundle URI 一样。远程 Git 服务器是最终的真相来源,而不是 bundle URI。
以下是一些错误条件示例
-
客户端无法连接到给定 URI 的服务器,或者连接在无法恢复的情况下丢失。
-
客户端收到 4xx 级别的响应(例如
404
Not
Found
或401
Not
Authorized
)。客户端应使用凭据助手查找并为 URI 提供凭据,但要符合 Git 其他 HTTP 协议在处理特定 4xx 级别错误方面的语义。 -
服务器报告任何其他失败响应。
-
客户端收到的数据无法解析为 bundle 或 bundle 列表。
-
bundle 包含一个不符合预期的过滤器。
-
客户端无法解包 bundle,因为所需的提交 OID 不在对象数据库中,并且没有更多 bundle 可以下载。
还有一些情况可能被视为浪费,但并非错误条件
-
下载的 bundle 包含的信息多于克隆或抓取请求所需的信息。一个主要示例是,如果用户使用
--single-branch
请求克隆,但下载了存储所有refs/heads/*
引用中所有可到达提交的 bundle。这可能最初是浪费的,但也许这些对象会通过客户端关心的后续引用更新变得可到达。 -
在
git
fetch
期间下载的 bundle 包含已存在于对象数据库中的对象。如果我们将 bundle 用于抓取,这可能是不可避免的,因为客户端在向远程服务器执行“追赶”抓取后,几乎总是会稍微领先于 bundle 服务器。当客户端抓取频率远高于服务器计算 bundle 的频率时,这种额外的工作最为浪费,例如客户端使用每小时预抓取并进行后台维护,而服务器每周才计算一次 bundle。因此,除非服务器通过bundle.heuristic
值明确推荐,否则客户端不应将 bundle URI 用于抓取。
Bundle 提供者组织示例
bundle URI 功能特意设计为灵活适应 bundle 提供者组织对象数据的不同方式。然而,在这里描述一个完整的组织模型可能会有所帮助,以便提供者可以从该基础开始。
此组织示例是 GVFS 缓存服务器(参见本文档末尾部分)所用模型的简化版本,该模型在加速超大型仓库的克隆和抓取方面非常有益,尽管使用了 Git 之外的额外软件。
bundle 提供者在多个地理区域部署服务器。每个服务器管理自己的 bundle 集。服务器可以跟踪多个 Git 仓库,并根据模式为每个仓库提供 bundle 列表。例如,当镜像一个位于 https://
<domain>/
<org>/
<repo> 的仓库时,bundle 服务器的 bundle 列表可能在 https://
<server-url>/
<domain>/
<org>/
<repo> 可用。源 Git 服务器可以在“any”模式下列出所有这些服务器。
[bundle] version = 1 mode = any
[bundle "eastus"] uri = https://eastus.example.com/<domain>/<org>/<repo>
[bundle "europe"] uri = https://europe.example.com/<domain>/<org>/<repo>
[bundle "apac"] uri = https://apac.example.com/<domain>/<org>/<repo>
这个“列表的列表”是静态的,只有在添加或删除 bundle 服务器时才会发生变化。
每个 bundle 服务器管理自己的 bundle 集。初始 bundle 列表只包含一个 bundle,其中包含从源服务器克隆仓库时接收到的所有对象。该列表使用 creationToken
启发式方法,并根据服务器的时间戳为该 bundle 生成一个 creationToken
。
bundle 服务器定期运行 bundle 列表的更新,例如每天一次。在此任务期间,服务器从源服务器抓取最新内容,并生成一个 bundle,其中包含可从最新源引用(origin refs)访问的对象,但这些对象未包含在先前计算的 bundle 中。此 bundle 被添加到列表中,并确保 creationToken
严格大于先前的最大 creationToken
。
当 bundle 列表变得过大,例如超过 30 个 bundle 时,最旧的“N 减去 30”个 bundle 将合并为一个 bundle。此 bundle 的 creationToken
等于合并后的 bundle 中最大的 creationToken
。
这里提供一个 bundle 列表示例,尽管它只有两个每日 bundle,而不是完整的 30 个列表
[bundle] version = 1 mode = all heuristic = creationToken
[bundle "2022-02-13-1644770820-daily"] uri = https://eastus.example.com/<domain>/<org>/<repo>/2022-02-09-1644770820-daily.bundle creationToken = 1644770820
[bundle "2022-02-09-1644442601-daily"] uri = https://eastus.example.com/<domain>/<org>/<repo>/2022-02-09-1644442601-daily.bundle creationToken = 1644442601
[bundle "2022-02-02-1643842562"] uri = https://eastus.example.com/<domain>/<org>/<repo>/2022-02-02-1643842562.bundle creationToken = 1643842562
为了避免对象数据即使在源服务器中变得不可访问后仍然永久存储和提供,这种 bundle 合并可以更谨慎。不是对旧 bundle 进行绝对合并,而是通过查看较新的 bundle 并确保它们所需的提交都在这个合并的 bundle 中(或在另一个较新的 bundle 中)来创建 bundle。这允许“过期”在此时间窗口内未被新提交使用的对象数据。这些数据可以通过后续的推送重新引入。
这种数据组织有两个主要目标。首先,通过从更近的来源下载预计算的对象数据,仓库的初始克隆变得更快。其次,git
fetch
命令可以更快,特别是如果客户端几天没有抓取。但是,如果客户端 30 天没有抓取,则 bundle 列表组织将导致重新下载大量对象数据。
使这种组织对频繁抓取的用户更有用的一种方法是更频繁地创建 bundle。例如,可以每小时创建 bundle,然后每天将这些“每小时”bundle 合并为一个“每日”bundle。每日 bundle 在 30 天后合并到最旧的 bundle 中。
如果此仓库的客户端期望使用无 blob 的部分克隆,建议使用 blob:none
过滤器重复此 bundle 策略。此无 blob bundle 列表与完整 bundle 保持在同一列表中,但使用 bundle.
<id>.filter
键来分隔两组。对于非常大的仓库,bundle 提供者可能只提供无 blob 的 bundle。
实施计划
本设计文档作为一份展望性文档独立提交,目标是在多个补丁系列的过程中实现所有提及的客户端功能。以下是提交这些功能的潜在大纲:
-
将 bundle URI 集成到
git
clone
中,并带有一个--bundle-uri
选项。这将包括一个新的git
fetch
--bundle-uri
模式,用作git
clone
底层的实现。这里的初始版本将预期在给定 URI 处有一个单独的 bundle。 -
实现从 bundle URI 解析 bundle 列表的能力,并更新
git
fetch
--bundle-uri
逻辑,以正确区分bundle.mode
选项。专门设计该功能,使得配置格式解析将键值对列表馈送到 bundle 列表逻辑中。 -
创建
bundle-uri
协议 v2 命令,以便 Git 服务器可以使用键值对发布 bundle URI。连接到现有的键值输入到 bundle 列表逻辑中。允许git
clone
发现这些 bundle URI 并从 bundle 数据引导客户端仓库。(此选择通过配置选项和命令行选项进行选择加入。) -
允许客户端理解
bundle.heuristic
配置键和bundle.
<id>.creationToken
启发式方法。当git
clone
发现带有bundle.heuristic
的 bundle URI 时,它会配置客户端仓库在后续的git
fetch
<remote> 命令期间检查该 bundle URI。 -
允许客户端在
git
fetch
期间发现 bundle URI,并在设置了bundle.heuristic
的情况下为后续抓取配置 bundle URI。 -
实现“检查头部”启发式方法,以在
bundle.
<id>.creationToken
启发式方法不可用时减少数据下载。
随着这些功能的审查,本计划可能会更新。我们还期望随着此功能的成熟并在实际场景中得到应用,将发现并实现新的设计。
相关工作:Packfile URI
Git 协议已经具备一项功能,即 Git 服务器在响应客户端请求时,可以列出一组 URL 以及 packfile 响应。然后,客户端需要下载这些位置的 packfile,以便完整理解响应。
此机制由 Gerrit 服务器(使用 JGit 实现)使用,在降低 CPU 负载和提高用户克隆性能方面非常有效。
这种机制的一个主要缺点是源服务器需要准确地知道这些 packfile 中包含什么,并且 packfile 需要在服务器响应后的一段时间内对用户可用。源服务器与 packfile 数据之间的这种耦合很难管理。
此外,此实现极难与抓取(fetches)配合使用。
相关工作:GVFS 缓存服务器
GVFS 协议 [2] 是一组 HTTP 端点,在 Git 的部分克隆功能创建之前独立于 Git 项目设计。此协议的一个特点是“缓存服务器”的概念,它可以与构建机器或开发人员办公室共置,以传输 Git 数据而不会使中心服务器过载。
VFS for Git 著名的端点是 GET
/gvfs/objects/{oid}
端点,它允许按需下载对象。这是该产品文件系统虚拟化的关键部分。
然而,一个更微妙的需求是 GET /gvfs/prefetch?lastPackTimestamp=<t> 端点。给定一个可选的时间戳,缓存服务器会响应一个预计算 packfile 列表,其中包含在这些时间间隔内引入的提交和树。
缓存服务器使用以下策略计算这些“预抓取”(prefetch)packfile
-
每小时,会生成一个带有给定时间戳的“每小时”包。
-
每晚,前 24 个每小时包会汇总成一个“每日”包。
-
每晚,所有超过 30 天的预抓取包都会汇总成一个包。
当用户针对具有缓存服务器的仓库运行 gvfs
clone
或 scalar
clone
时,客户端会请求所有预抓取 packfile,这最多是 24
+
30
+
1
个 packfile,仅下载提交和树。然后客户端会向源服务器请求引用,并尝试检出该提示引用。(还有一个额外的端点,用于获取给定提交的所有可到达的树,以防该提交尚未存在于预抓取 packfile 中。)
在 git
fetch
期间,一个钩子会使用先前下载的预抓取 packfile 中最新的时间戳请求预抓取端点。只下载时间戳较晚的 packfile 列表。大多数用户每小时抓取一次,因此他们最多只会获取一个每小时预抓取包。机器已关机或超过 30 天未抓取的用户可能会重新下载所有预抓取 packfile。这种情况很少见。
需要注意的是,客户端总是联系源服务器以获取引用公告,因此引用通常“领先”于预抓取的包数据。当 git
checkout
或 git
log
等命令需要时,缺失的对象会使用 GET
gvfs/objects/{oid}
请求按需下载。一些 Git 优化会禁用可能导致这些按需下载过于激进的检查。
另请参阅
[1] https://lore.kernel.org/git/RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com/ 早期关于 bundle URI 功能的 RFC。