简体中文 ▾ 主题 ▾ 最新版本 ▾ partial-clone 上次更新于 2.49.0

“部分克隆”功能是 Git 的一种性能优化,它允许 Git 在没有完整仓库副本的情况下运行。这项工作的目标是让 Git 更好地处理超大型仓库。

在克隆和获取操作期间,Git 会下载仓库的完整内容和历史记录。这包括仓库生命周期中的所有提交、树和 blob。对于超大型仓库,克隆可能需要数小时(或数天)并占用 100+GiB 的磁盘空间。

在这些仓库中,用户常常不需要很多 blob 和树,例如:

  1. 树中位于用户工作区域之外的文件。例如,在一个每个提交中包含 50 万个目录和 350 万个文件的仓库中,如果用户只需要源代码树的一个狭窄“锥形”区域,我们可以避免下载许多对象。

  2. 大型二进制资产。例如,在一个仓库中,如果大型构建工件被签入到树中,我们可以避免下载这些不可合并二进制资产的所有先前版本,而只下载实际引用的版本。

部分克隆允许我们在克隆和获取操作期间 **提前** 避免下载这些不需要的对象,从而减少下载时间和磁盘使用量。缺失的对象可以在稍后按需“按需获取”。

能够稍后提供缺失对象的远程仓库称为 promisor 远程仓库,因为它承诺在请求时发送对象。最初 Git 只支持一个 promisor 远程仓库,即用户从中克隆的 origin 远程仓库,并且该远程仓库配置在“extensions.partialClone”配置选项中。后来实现了对多个 promisor 远程仓库的支持。

使用部分克隆要求用户在线,并且 origin 远程仓库或其他 promisor 远程仓库可用于按需获取缺失的对象。这可能对用户有问题,也可能没有。例如,如果用户可以停留在预先选择的源代码树子集中,他们可能不会遇到任何缺失的对象。或者,如果用户知道他们将离线,他们可以尝试预先获取各种对象。

非目标

部分克隆是一种限制 **在给定提交范围** 内下载的 blob 和树数量的机制——因此,它独立于也不旨在与现有的 DAG 级机制冲突,例如限制请求的提交集(即浅克隆、单分支或获取 引用规范)。

设计概述

部分克隆逻辑上由以下部分组成:

  • 一种客户端向服务器描述不需要或不希望的对象的方式。

  • 一种服务器从发送给客户端的 packfile 中省略这些不希望的对象的方式。

  • 一种客户端优雅地处理缺失对象(以前由服务器省略)的方式。

  • 一种客户端按需回填缺失对象的方式。

设计细节

  • 一个名为“filter”的新 pack-protocol 功能被添加到 fetch-pack 和 upload-pack 的协商中。

    这使用了现有的功能发现机制。请参阅 gitprotocol-pack[5] 中的“filter”。

  • 客户端向 clone 和 fetch 传递一个“filter-spec”,该 spec 会传递给服务器,以在 packfile 构建期间请求过滤。

    有各种可用的过滤器来适应不同的情况。请参阅 Documentation/rev-list-options.adoc 中的“--filter=<filter-spec>”。

  • 在服务器端,pack-objects 在创建“filtered”packfile 时应用所请求的 filter-spec。

    这些 filtered packfile 在传统意义上是 **不完整的**,因为它们可能包含引用 packfile 中未包含的对象,而客户端尚没有这些对象的对象。例如,filtered packfile 可能包含引用缺失 blob 的树或标签,或者引用缺失树的提交。

  • 在客户端,这些不完整的 packfile 被标记为“promisor packfiles”,并被不同命令以不同方式处理。

  • 在客户端,将一个仓库扩展添加到本地配置中,以防止旧版本的 git 由于无法处理的缺失对象而 mid-operation 失败。请参阅 extensions.partialClonegit-config[1] 中。

处理缺失对象

  • 由于部分克隆或获取,或者由于仓库损坏,对象可能缺失。为了区分这些情况,本地仓库将从 promisor 远程仓库获得的此类 filtered packfile 特别标记为“promisor packfiles”。

    这些 promisor packfile 由一个具有任意内容的“<name>.promisor”文件(类似于“<name>.keep”文件)组成,除了它们的“<name>.pack”和“<name>.idx”文件。

  • 本地仓库将“promisor 对象”视为一个对象,它(尽其所能)知道 promisor 远程仓库承诺拥有它,要么是因为本地仓库在其 promisor packfile 中拥有该对象,要么是因为另一个 promisor 对象引用了它。

    当 Git 遇到一个缺失的对象时,Git 可以判断它是否是一个 promisor 对象并适当地处理它。如果不是,Git 可以报告损坏。

    这意味着客户端无需显式维护一个昂贵的、需要修改的缺失对象列表。[a]

  • 由于几乎所有 Git 代码当前都期望任何引用的对象都存在于本地,并且我们不想迫使每个命令首先执行一次 dry-run,因此添加了一个回退机制,允许 Git 尝试从 promisor 远程仓库动态获取缺失的对象。

    当正常的对象查找无法找到对象时,Git 会调用 promisor_remote_get_direct() 来尝试从 promisor 远程仓库获取对象,然后重试对象查找。这允许对象在没有复杂预测算法的情况下被“容错加载”。

    出于效率原因,不检查缺失的对象是否确实是 promisor 对象。

    动态对象获取通常很慢,因为对象是逐个获取的。

  • checkout(以及任何使用 unpack-trees 的其他命令)已被教导一次性预取所有必需的缺失 blob。

  • rev-list 已被教导打印缺失的对象。

    这可以被其他命令用于批量预取对象。例如,“git log -p A..B”可能希望首先执行类似“git rev-list --objects --quiet --missing=print A..B”的操作,然后批量预取这些对象。

  • fsck 已更新为完全了解 promisor 对象。

  • GC 中的 repack 已更新为完全不触碰 promisor packfile,并且只 repack 其他对象。

  • 全局变量“fetch_if_missing”用于控制对象查找是否会尝试动态获取缺失的对象或报告错误。

    我们对这个全局变量不满意,并希望删除它,但这需要对对象代码进行大量重构,以传递一个额外的标志。

获取缺失对象

  • 对象的获取是通过调用“git fetch”子进程来完成的。

  • 本地仓库发送一个包含所有请求对象哈希的请求,并且不执行任何 packfile 协商。然后它会收到一个 packfile。

  • 由于我们正在重用现有的获取机制,获取当前会获取请求对象所引用的所有对象,即使它们不是必需的。

  • 使用 --refetch 进行获取将从远程仓库请求一个完整的新的 filtered packfile,该 packfile 可用于更改过滤器而无需动态获取缺失的对象。

使用多个 promisor 远程仓库

可以配置和使用多个 promisor 远程仓库。

这允许用户例如拥有多个地理位置靠近的缓存服务器来获取缺失的 blob,同时继续从中央服务器进行过滤的 git-fetch 命令。

在获取对象时,promisor 远程仓库会一个接一个地尝试,直到所有对象都被获取。

被视为“promisor”远程仓库的远程仓库是由以下配置变量指定的:

  • extensions.partialClone = <name>

  • remote.<name>.promisor = true

  • remote.<name>.partialCloneFilter = ...

只能使用 extensions.partialClone 配置变量配置一个 promisor 远程仓库。此 promisor 远程仓库将在获取对象时作为最后一个被尝试的。

我们决定将其作为我们尝试的最后一个,因为使用多个 promisor 远程仓库的用户很可能是因为其他 promisor 远程仓库在某些方面(例如,它们更近或对某些对象更快)比 origin 更好,而 origin 可能是 extensions.partialClone 指定的远程仓库。

这个理由不是很充分,但必须做出一个选择,而且无论如何,长期计划应该是使顺序某种程度上完全可配置。

但目前,其他 promisor 远程仓库将按照它们在配置文件中出现的顺序进行尝试。

当前限制

  • 除了它们在配置文件中出现的顺序之外,无法指定尝试 promisor 远程仓库的顺序。

    也无法指定在从一个远程仓库获取时使用的顺序,以及在从另一个远程仓库获取时使用的不同顺序。

  • 无法仅将特定对象推送到 promisor 远程仓库。

    无法按照特定顺序同时推送到多个 promisor 远程仓库。

  • 动态对象获取只会向 promisor 远程仓库请求缺失的对象。我们假设 promisor 远程仓库对仓库有完整的视图,并且可以满足所有此类请求。

  • Repack 本质上将 promisor 和非 promisor packfile 视为 2 个不同的分区,并且不混合它们。

  • 动态对象获取会 **为每个项目** 调用一次 fetch-pack,因为大多数算法会遇到一个缺失的对象,并在继续工作之前需要解决它。如果需要许多对象,这可能会产生显著的开销 — 以及多次身份验证请求。

  • 动态对象获取目前使用现有的 pack protocol V0,这意味着每个对象都通过 fetch-pack 请求。服务器将在建立连接时发送完整的 info/refs 集。如果存在大量引用,这可能会产生显著的开销。

未来工作

  • 改进指定 promisor 远程仓库尝试顺序的方法。

    例如,这可以允许明确指定类似:“当从这个远程仓库获取时,我希望按此顺序使用这些 promisor 远程仓库,但是,当推送到或从那个远程仓库获取时,我希望按那个顺序使用那些 promisor 远程仓库。”

  • 允许推送到 promisor 远程仓库。

    用户可能希望使用多个 promisor 远程仓库进行三角形工作流,每个仓库对仓库都有不完整的视图。

  • 允许基于非路径名的过滤器使用 packfile 位图(如果存在)。这只是在初始实现期间的疏忽。

  • 研究使用一个长期运行的进程来动态获取一系列对象,如 [5,6] 中所述,以减少进程启动和开销成本。

    如果 pack protocol V2 允许该长期运行的进程通过一个长期运行的连接进行一系列请求,那将是很好的。

  • 研究 pack protocol V2 以避免每次与服务器连接时广播 info/refs 来动态获取缺失的对象。

  • 研究处理 loose promisor 对象的需求。

    promisor packfile 中的对象允许引用可以从服务器动态获取的缺失对象。我们曾假设 loose 对象仅在本地创建,因此不应引用缺失的对象。如果,例如,我们动态获取一个缺失的树并将其存储为 loose 对象而不是单个对象 packfile,我们可能需要重新审视这个假设。

    这并不一定意味着我们需要将 loose 对象标记为 promisor;放宽对象查找或 is-promisor 函数可能就足够了。

非任务

  • 每次出现“按需加载 blob”的主题时,似乎总有人建议允许服务器“猜测”并发送可能与请求对象相关的附加对象。

    实际上还没有进行任何这方面的工作;我们只是记录这是一个常见的建议。我们不确定它将如何工作,也没有计划进行这项工作。

    服务器发送比请求更多的对象是有效的(即使是对于动态对象获取),但我们不基于此进行构建。

脚注

[a] 昂贵的、需要修改的缺失对象列表:在部分克隆设计的早期,我们讨论了对单个缺失对象列表的需求。这本质上是一个 OID 的排序线性列表,这些 OID 在克隆或后续获取期间被服务器省略。

此文件需要在每次对象查找时加载到内存中。在每次显式的“git fetch”命令 **以及** 任何动态对象获取时,它都需要被读取、更新和重写(类似于 .git/index)。

如果存在许多缺失的对象,读取、更新和写入此文件的成本可能会为每个命令增加显著的开销。例如,如果存在 1 亿个缺失的 blob,此文件在磁盘上至少会是 2GiB。

使用“promisor”概念,我们根据引用它的 packfile 的类型 **推断** 一个缺失的对象。

[0] https://crbug.com/git/2 Bug#2:部分克隆

[1] https://lore.kernel.org/git/20170113155253.1644-1-benpeart@microsoft.com/
主题:[RFC] 添加对按需下载 blob 的支持
日期:2017 年 1 月 13 日星期五 10:52:53 -0500

[2] https://lore.kernel.org/git/cover.1506714999.git.jonathantanmy@google.com/
主题:[PATCH 00/18] 部分克隆(从克隆到懒加载获取,共 18 个补丁)
日期:2017 年 9 月 29 日星期五 13:11:36 -0700

[3] https://lore.kernel.org/git/20170426221346.25337-1-jonathantanmy@google.com/
主题:Git 仓库缺失 blob 支持提案
日期:2017 年 4 月 26 日星期三 15:13:46 -0700

[4] https://lore.kernel.org/git/1488999039-37631-1-git-send-email-git@jeffhostetler.com/
主题:[PATCH 00/10] RFC 部分克隆和获取
日期:2017 年 3 月 8 日星期三 18:50:29 +0000

[5] https://lore.kernel.org/git/20170505152802.6724-1-benpeart@microsoft.com/
主题:[PATCH v7 00/10] 将 filter 过程代码重构为可重用模块
日期:2017 年 5 月 5 日星期五 11:27:52 -0400

[6] https://lore.kernel.org/git/20170714132651.170708-1-benpeart@microsoft.com/
主题:[RFC/PATCH v2 0/1] 添加对按需下载 blob 的支持
日期:2017 年 7 月 14 日星期五 09:26:50 -0400