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