「パーシャルクローン」(部分クローン)機能は、リポジトリの完全なコピーがなくても Git が機能できるようにする、Git のパフォーマンス最適化です。 この作業の目標は、Git が非常に大きなリポジトリをより適切に処理できるようにすることです。

クローンおよびフェッチ操作中に、Git はリポジトリの完全なコンテンツと履歴をダウンロードします。 これには、リポジトリの存続期間中のすべての、コミットとツリーとブロブが含まれます。 非常に大きなリポジトリの場合、クローンには数時間(または数日)かかり、100ギガバイト超のディスク容量を消費する可能性があります。

多くの場合、これらのリポジトリには、以下のようにユーザーが必要としない多くのブロブとツリーがあります:

  1. ツリー内のユーザーの作業領域の外にあるファイル。 たとえば、コミットごとに 500K のディレクトリと 3.5M のファイルがあるリポジトリでは、ユーザーがソース ツリーの狭い「コーン」のみを必要とする場合、多くのオブジェクトをダウンロードすることを避けることができます。

  2. 大規模なバイナリ資産。 たとえば、大規模なビルド生成物がツリーにチェックインされるリポジトリでは、これらのマージ不可能なバイナリ資産の以前のバージョンをすべてダウンロードすることを回避し、実際に参照されているバージョンのみをダウンロードできます。

パーシャルクローン(部分クローン)を使用すると、クローンおよびフェッチ操作で不要なオブジェクトを事前にダウンロードすることを回避し、ダウンロード時間とディスク使用量を減らすことができます。 不足しているオブジェクトは、後で必要に応じて「デマンドフェッチ」(demand fetched)することができます。

不足しているオブジェクトを後で提供できるリモートは、要求されたときにオブジェクトを送信することを約束するため、promisor リモートと呼ばれます。 当初、Git は 1 つの promisor リモートのみをサポートしていました。これは、ユーザーがクローンを作成し、「extensions.partialClone」構成オプションで構成された元のリモートです。 その後、複数の promisor リモートのサポートが実装されました。

パーシャルクローンを使用するには、ユーザーがオンラインであり、元のリモートまたは他の promisor リモートが、不足しているオブジェクトのオンデマンド フェッチに使用できる必要があります。 これは、ユーザーにとって問題になる場合とそうでない場合があります。 たとえば、ユーザーがソース ツリーの事前に選択されたサブセット内にとどまることができる場合、不足しているオブジェクトに遭遇することはありません。 または、オフラインになることがわかっている場合、ユーザーはさまざまなオブジェクトのプリフェッチを試みることができます。

Non-Goals

パーシャルクローンは、コミットの特定の範囲内でダウンロードされるブロブとツリーの数を制限するメカニズムです。したがって、要求されたコミットのセットを制限する既存の DAG レベルのメカニズム(つまり、 shallow clone または single branch または fetch <refspec>)とは無関係であり、それらと競合することを意図していません。

Design Overview

パーシャルクローンは、論理的に以下の部分で構成されます:

  • クライアントが、必要としない または 欲しくない オブジェクトをサーバーに説明するためのメカニズム。

  • サーバーが、クライアントに送信されるパックファイルからそのような不要なオブジェクトを除外するメカニズム。

  • 不足しているオブジェクト(以前はサーバー側で省略されていた)をクライアントが適切に処理するためのメカニズム。

  • クライアントが不足しているオブジェクトを必要に応じて埋め戻すメカニズム。

Design Details

  • fetch-pack および upload-pack ネゴシエーションに、新しい pack-protocol 機能「filter」が追加されました。

    これは、既存の機能検出メカニズムを使用します。 gitprotocol-pack(5) の「filter」を参照してください。

  • クライアントは「filter-spec」を clone および fetch に渡します。これはサーバーに渡され、パックファイルの構築中にフィルタリングを要求します。

    さまざまな状況に対応するために、さまざまなフィルターが用意されています。 Documentation/rev-list-options.txt (または git rev-list)の 「--filter=<filter-spec>」を参照してください。

  • サーバー上の pack-objects は、要求された filter-spec を適用して、クライアント用に「フィルター処理された」パックファイルを作成します。

    これらのフィルタリングされたパックファイルは、パックファイルに含まれていないオブジェクトを参照し、クライアントがまだ持っていないオブジェクトを含む可能性があるため、従来の意味では「不完全」です。 たとえば、フィルタリングされたパックファイルには、不足しているブロブを参照するツリーまたはタグ、または不足しているツリーを参照するコミットが含まれる場合があります。

  • クライアントでは、これらの不完全なパックファイルは「promisor packfiles」としてマークされ、さまざまなコマンドによって異なる方法で処理されます。

  • クライアントでは、古いバージョンの git で処理できないオブジェクトが見つからないために操作中に失敗するのを防ぐために、リポジトリ拡張機能がローカル構成に追加されます。 Documentation/technical/repository-version.txt の「extensions.partialClone」を参照してください。

Handling Missing Objects

  • パーシャルクローンまたはフェッチが原因でオブジェクトが不足しているか、あるいはリポジトリの破損が原因で不足している可能性があります。 これらのケースを区別するために、ローカル リポジトリは、promisor リモートから取得されたそのようなフィルタリングされたパックファイルを「promisor packfiles」として特別に示します。

    これらの promisor パックファイルは、 <name>.pack ファイルや <name>.idx ファイルに加えて、(<name>.keep ファイルなど)任意のコンテンツを含む <name>.promisor ファイルで構成されます。

  • ローカルリポジトリは「プロミサー オブジェクト」を、(可能な限り)プロミスリモートが持っていると約束しているオブジェクトと見なします。ローカルリポジトリがプロミスパックファイルの1つにそのオブジェクトを持っているか、他のプロミスオブジェクトがそれを参照しているからです。

    Git が不足しているオブジェクトに遭遇すると、Git はそれがプロミサーオブジェクトであるかどうかを確認し、プロミサーオブジェクトならばそれを適切に処理できます。 そうでない場合、Git は破損を報告できます。

    このため、クライアントが高価な不足オブジェクトリストを明示的に保持する必要はありません。 [a](※Footnotes参照)

  • 現在、ほとんどすべての Git コードは、参照されたオブジェクトがローカルに存在することを想定しており、すべてのコマンドで最初にドライランを実行することを強制したくはないため、フォールバックメカニズムが追加され、Git が プロミサーリモートから不足しているオブジェクトを動的にフェッチできるようになります。

    通常のオブジェクトルックアップでオブジェクトが見つからない場合、Git は promisor_remote_get_direct() を呼び出してプロミサーリモートからオブジェクトの取得を試み、それからオブジェクトルックアップを再試行します。 これにより、複雑な予測アルゴリズムを使用せずに、オブジェクトを「断層化」(faulted in)させることができます。

    効率上の理由から、不足しているオブジェクトが実際にプロミサーオブジェクトであるかどうかのチェックは実行されません。

    動的オブジェクトのフェッチは、オブジェクトが一度に 1 つずつフェッチされるため、遅くなる傾向があります。

  • checkout (および unpack-trees を使用するその他のコマンド) は、必要なすべての不足しているブロブを 1 つのバッチで一括プリフェッチするようになりました。

  • rev-list は不足オブジェクトを出力するようになりました。

    これは、他のコマンドでオブジェクトを一括してプリフェッチするために使用できます。 たとえば、 git log -p A..B は、最初に git rev-list --objects --quiet --missing=print A..B のような処理を行い、それらのオブジェクトをまとめてプリフェッチする必要がある場合があります。

  • fsck は更新され、プロミサーオブジェクトを完全に認識するようになりました。

  • GC の repack は更新され、プロミサーパックファイルにはまったく触らず、他のオブジェクトのみを再パックするようになりました。

  • グローバル変数「fetch_if_missing」は、オブジェクトルックアップが不足しているオブジェクトを動的にフェッチしようとするか、エラーを報告しようとするかを制御するために使用されます。

    私達はこのグローバル変数には満足できず、削除したいと考えていますが、追加のフラグを渡すには、オブジェクトコードを大幅にリファクタリングする必要があります。

Fetching Missing Objects

  • オブジェクトのフェッチは、「git fetch」サブプロセスを呼び出すことによって行われます。

  • ローカルリポジトリは、要求されたすべてのオブジェクトのハッシュを含む要求を送信し、パックファイルのネゴシエーションは実行しません。 そして次に、パックファイルを受け取ります。

  • 既存のフェッチ機構を再利用しているため、フェッチでは現在、要求されたオブジェクトが参照するすべてのオブジェクトを、必要でないにもかかわらずフェッチしています。

  • --refetch を使用してフェッチすると、リモートから、完全に新しいフィルタリングされたパックファイルが要求されます。 これを利用して、 欠落しているオブジェクトを動的にフェッチすることなくフィルタを変更できます。

Using many promisor remotes

複数のプロミサーリモートを構成して使用できます。

これにより、たとえばユーザーは、中央サーバーからフィルタリングされた「git-fetch」コマンドを実行しながら、不足しているブロブを取得するための地理的に近い複数のキャッシュサーバーを持つことができます。

オブジェクトをフェッチするとき、すべてのオブジェクトがフェッチされるまで、プロミサーリモートが順々に試行されます。

「プロミサー」リモートと見なされるリモートは、以下の構成変数で指定されたものです:

  • extensions.partialClone = <name>

  • remote.<name>.promisor = true

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

「extensions.partialClone」構成変数を使用して構成できるプロミサーリモートは 1 つだけです。 このプロミサーリモートは、オブジェクトをフェッチするときに最後に試行されるものになります。

私達はこれを最後に試行することにしました。これは、多くのプロミサーリモートを使用している可能性が高いためです。他のプロミサーリモートは、何らかの理由で(おそらく、ある種のオブジェクトに対してより近いか、より高速です)、originよりも優れていて、そして、originは extensions.partialClone で指定されたリモートである可能性があります。

この正当化はあまり強力ではありませんが、私達は何らかの選択を行う必要がありました。とにかく、長期的な計画では、順番を何らかの形で完全に構成可能にする必要があるでしょう。

今のところ、他のプロミサーリモートは、構成ファイルに表れる順序で試行されます。

Current Limitations

  • プロミサーリモートが構成ファイルに表れる順序以外の方法で、試行される順序を指定することはできません。

    また、あるリモートからフェッチするときに使用する順序と、別のリモートからフェッチするときに、それぞれ別の順序を指定することもできません。

  • 特定のオブジェクトのみをプロミサーリモートにプッシュすることはできません。

    複数のプロミサーリモートに特定の順序で同時にプッシュすることはできません。

  • 動的なオブジェクトのフェッチは、不足ているオブジェクトについてプロミサーリモートにのみ要求します。 プロミサーリモートにはリポジトリの完全なビューがあり、そのようなすべての要求を満たすことができると想定しています。

  • repack は基本的にプロミサーパックファイルと非プロミサーパックファイルを 2 つの異なるパーティションとして扱い、それらを混在させません。

  • ほとんどのアルゴリズムは不足しているオブジェクトに出くわし、作業を続行する前にそれを解決する必要があるため、動的なオブジェクトの取得では「項目ごとに」 fetch-pack が呼び出されます。 これにより、多くのオブジェクトが必要な場合、かなりのオーバーヘッド — および複数の認証リクエスト — が発生する可能性があります。

  • 動的オブジェクト フェッチは現在、既存のパック プロトコル V0 を使用しています。これは、各オブジェクトが fetch-pack を介して要求されることを意味します。 接続が確立されると、サーバーは info/refs の完全なセットを送信します。 多数のrefsがある場合、これによりかなりのオーバーヘッドが発生する可能性があります。

Future Work

  • プロミサーリモートが試行される順序を指定する方法を改善します。

    たとえば、次のように明示的に指定できるようにします:「このリモートから取得するときは、これらのプロミサーリモートをこれこれの順序で使用したいが、そのリモートにプッシュまたはフェッチするときは、それらのプロミサーリモートをあれあれの順序で使用したい。」

  • プロミサーリモートへのプッシュを許可します。

    ユーザーは、それぞれがリポジトリの不完全なビューを持つ複数のプロミサーリモートを使用して、三角形のワークフロー(a triangular work flow)で作業することを希望する場合があります。

  • 非パス名ベースのフィルタがパックファイルビットマップを使用できるようにします(存在する場合)。 これは、最初の実装時は単に省略されていました。

  • [5,6] で提案されているように、一連のオブジェクトを動的に取得するための長時間実行プロセスの使用を調査して、プロセスの起動とオーバーヘッドのコストを削減します。

    パック プロトコル V2 が、長時間実行されるプロセスが単一の長時間実行接続を介して一連の要求を行うことを許可できると素敵だと思います。

  • パック プロトコル V2 を調査して、サーバーとの接続ごとに info/refs ブロードキャストを回避し、不足しているオブジェクトを動的に取得します。

  • ルーズ プロミサー オブジェクトを処理する必要性を調査します。

    プロミサーパックファイル内のオブジェクトは、サーバーから動的に取得できる不足しているオブジェクトを参照できます。ルースオブジェクトはローカルでのみ作成されるため、不足しているオブジェクトを参照するべきではないと想定されていました。 たとえば、不足しているツリーを動的にフェッチし、それを単一のオブジェクト パックファイルではなくルーズオブジェクトとして格納する場合は、その仮定を再検討する必要があるかもしれません。

    これは、ルーズオブジェクトをプロミサーとしてマークする必要があることを必ずしも意味するものではありません。 オブジェクト ルックアップまたは is-promisor 関数を緩和するだけで十分な場合があります。

Non-Tasks

  • 「ブロブのロードを要求する」という話題が持ち上がるたびに、要求されたオブジェクトに関連する可能性のある追加のオブジェクトをサーバーが「推測」して送信できるようにすることを誰かが提案しているようです。

    実際にそれを行う作業はありません。 それが一般的な提案であることを文書化しているだけです。 それがどのように機能するかはわかりませんし、それに取り組む計画もありません。

    サーバーが要求されたよりも多くのオブジェクトを送信することは有効ですが (動的なオブジェクト フェッチの場合でも)、これに基づいて構築していません。

Footnotes

[a] 変更コストの高い不足しているオブジェクトのリスト: この文書の前半で 不足オブジェクトの単一リストの必要性について説明しました。 これは基本的に、クローンまたは後続のフェッチ中にサーバーによって 省略された OID のソートされた線形リストになります。

このファイルは、すべてのオブジェクト ルックアップでメモリにロードする必要があります。 明示的な「git fetch」コマンドのたびに、*および*動的オブジェクト フェッチのたびに、(.git/index のように) 読み取り、更新、および再書き込みする必要があります。 このファイルは、すべてのオブジェクト ルックアップでメモリにロードする必要があります。 明示的な「git fetch」コマンド かつ 動的オブジェクトのフェッチごとに、(.git/index のように) 読み取り、更新、および再書き込みする必要があります。

不足しているオブジェクトが多数ある場合、このファイルの読み取り、更新、および書き込みのコストにより、すべてのコマンドに大きなオーバーヘッドが追加される可能性があります。 たとえば、不足しているブロブが 1 億個ある場合、このファイルはディスク上で少なくとも 2GiB になります。

「プロミサー」の概念により、参照しているパックファイルのタイプに基づいて、不足しているオブジェクトを 「推測」 します。

[0] https://crbug.com/git/2 Bug#2: Partial Clone

[1] https://lore.kernel.org/git/20170113155253.1644-1-benpeart@microsoft.com/
Subject: [RFC] Add support for downloading blobs on demand
Date: Fri, 13 Jan 2017 10:52:53 -0500

[2] https://lore.kernel.org/git/cover.1506714999.git.jonathantanmy@google.com/
Subject: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)
Date: Fri, 29 Sep 2017 13:11:36 -0700

[3] https://lore.kernel.org/git/20170426221346.25337-1-jonathantanmy@google.com/
Subject: Proposal for missing blob support in Git repos
Date: Wed, 26 Apr 2017 15:13:46 -0700

[4] https://lore.kernel.org/git/1488999039-37631-1-git-send-email-git@jeffhostetler.com/
Subject: [PATCH 00/10] RFC Partial Clone and Fetch
Date: Wed, 8 Mar 2017 18:50:29 +0000

[5] https://lore.kernel.org/git/20170505152802.6724-1-benpeart@microsoft.com/
Subject: [PATCH v7 00/10] refactor the filter process code into a reusable module
Date: Fri, 5 May 2017 11:27:52 -0400

[6] https://lore.kernel.org/git/20170714132651.170708-1-benpeart@microsoft.com/
Subject: [RFC/PATCH v2 0/1] Add support for downloading blobs on demand
Date: Fri, 14 Jul 2017 09:26:50 -0400