スパースチェックアウト(疎なチェックアウト)(sparse-checkout)機能により、ユーザーはその作業ディレクトリを HEAD のファイルのサブセットに専念させることができます。 core.sparseCheckoutCone によって有効化されるコーン モード パターンにより、HEAD のどのファイルがスパース チェックアウト コーンに属するかを検出する、非常に高速なパターン マッチングが可能になります。

Git 作業ディレクトリの 3 つの重要な規模的側面は以下のとおりです:

  • HEAD: HEAD にはいくつのファイルが存在しますか?

  • 存在している: スパース チェックアウト コーン(sparse-checkout cone)内にあるファイルの数。

  • 変更されている: ユーザーが作業ディレクトリで変更したファイルの数は?

ランダウの漸近記法(big-O notation) O(X) を使用して、特定の操作がこれらの側面に関してどれだけコストがかかるかを示します。

これらの側面は、その大きさの順に並べられています。ユーザーは (通常)、入力されたファイルよりも少ないファイルを変更します。また、私達は HEAD でのみファイルを入力できます。

これらの側面に極度の不均衡がある場合、問題が発生します。 たとえば、HEAD に数百万のパスが含まれているが、入力されたセットには数万しかない場合、 git statusgit add などのコマンドは、 O(入力) ではなく O(HEAD) 操作を必要とする操作によって支配される可能性があります。 主に、コストはインデックスの解析と再書き込みにあります。インデックスは主に、 SKIP_WORKTREE ビットでマークされた HEAD のファイルで満たされます。

スパースインデックス(sparse-index)は、インデックスを読み取り、O(HEAD) から O(Populated) に変更するこれらのコマンドを使用することを意図しています。 これを行うには、インデックス形式を大幅に変更する必要があります。つまり「sparse directory」エントリを追加します。

コーンモードパターンを使用すると、ディレクトリ全体の内容がスパースチェックアウト定義の範囲外になる時期を検出できます。 含まれるすべてのファイルを個別のエントリとしてリストする代わりに、スパースインデックス(sparse-index)にはディレクトリ名を持つエントリが含まれ、 HEAD でツリーのオブジェクト ID を参照し、 SKIP_WORKTREE ビットでマークされます。 そのディレクトリ内のパスの詳細を見つける必要がある場合は、ツリーを解析してそのリストを見つけることができます。

執筆時点では、スパースディレクトリエントリ(sparse-directory entries)は、インデックス形式とそのメモリ内データ構造に関する期待に反しています。 コードベースには、すべてのインデックスエントリを反復処理してファイルのみを表示することを期待する利用者がが多数います。 実際、これらのループは、ステージングされたすべてのファイルへの参照を想定しています。 これを処理する 1 つの方法は、ツリーを解析して、インデックスが読み込まれるときに、スパース ディレクトリ エントリをそのツリー内のすべてのファイルに置き換えることです。 ただし、ツリーの解析はインデックス形式の解析よりも遅いため、インデックスをそのままにしておいた場合よりも処理が遅くなります。 計画では、これらの統合をすべて「疎認識」(sparse aware)にすることで、ツリー解析によるこの拡張が不要になり、完全なインデックスを使用する場合よりも使用するリソースが少なくなります。

以下の実装計画は、スパースインデックス(sparse-index)とゆっくりと統合するための 4 つのフェーズに従います。 その意図は、Git コマンドを段階的に更新して、大幅な速度低下なしにスパースインデックスと安全にやり取りすることです。 これは常に可能であるとは限りませんが、ユーザーが日常業務で必要とする主要なコマンドが劇的に改善されることを願っています。

Phase I: Format and initial speedups

このフェイズで、Git はスパース インデックスを有効にして安全に解析する方法を学習します。 メモリ内データ構造を利用する全ての機構が、すべてのファイルが「HEAD」にあるという現在の想定で動作できるように、保護が配置されます。

最初に、すべてのインデックス解析でヘルパーメソッド ensure_full_index() が呼び出されます。このメソッドは、インデックスをスキャンして (ツリーを指している) スパースディレクトリ エントリを探し、ツリー オブジェクトを解析することによって (ブロブの内容を含む) パスの完全なリストに置き換えます。 これは、すべてのケースで遅くなります。 動作の唯一の顕著な変更点は、シリアル化されたインデックス ファイルにスパース ディレクトリ エントリが含まれていることです。

まず、私達は新しい必須インデックス拡張子 sdir を使用して、ファイル形式バージョン 2と3と4 のインデックスにスパース ディレクトリ エントリを挿入できるようにします。 これは、sparse-indexを理解しないGitのバージョンが操作することを防ぐ一方で、sparse-indexを理解しないツールは、インデックスと対話しない限り、リポジトリ上で操作することを可能にします。 新しいフォーマットであるインデックスv5が導入されると、デフォルトでスパースディレクトリのエントリーが含まれるようになる予定です。また、インデックスを改善するために検討されている他の機能も導入される可能性があります。

次に、 ensure_full_index() または expand_index_to_path() への呼び出しを挿入することにより、インデックスの利用側がスパースインデックスでの操作から保護されます。 特定のパスが要求された場合、それらは index_file_exists()index_name_pos() API 呼び出し内から保護され、必要に応じて ensure_full_index() を呼び出します。 ここでの意図は、sparse-checkout と対話するときに既存の動作を維持することです。 私達はテストなしで偶然に変更が発生することは臨んでいません。 これらの場所の多くは、保護ガードを削除する前に変更する必要はありませんが、期待される動作が発生することを確認するためのテストなしで変更するべきではありません。

まばらなインデックスが存在する場合、またはより一般的には、スパースチェックアウトのシナリオで、一部のコマンドの動作を「変更」することが望ましい場合があります。 そのような場合、これらは慎重に伝達され、テストされるべきです。 このフェーズにおいては、このような動作の変更は意図されていません。

コードベースのスキャン中、キャッシュ エントリのすべての反復で「ensure_full_index()」チェックが必要なわけではありません。 基本的な理由は以下のとおりです:

  1. ループはゼロ以外のステージのエントリをスキャンしています。 これらのエントリは、スパース ディレクトリ エントリに折りたたまれません。

  2. ループはサブモジュールをスキャンしています。 これらのエントリは、スパース ディレクトリ エントリに折りたたまれません。

  3. ループはインデックス API の一部であり、特にフォーマットの読み取りまたは書き込みに関するものです。

  4. ループは、キャッシュ エントリの正しい順序をチェックしており、スパース ディレクトリ エントリが正しい場所にある場合にのみ正しいです。

  5. ループは、「SKIP_WORKTREE」ビットが設定されたエントリを無視するか、またはスパースディレクトリ エントリをすでに認識しています。

  6. 分割インデックス機能を使用する場合、この時点でスパースインデックスは無効になるため、分割インデックス API を保護するための努力は行われません。

これらのガードを挿入した後でも、command_requires_full_index リポジトリ設定を使用して、ほとんどの Git コマンドのスパース インデックスを拡張し続けます。この設定はデフォルトでオンになり、すべてのインデックス操作が適切にガードされているという十分な確信が得られるまで、1つずつビルトインを無効にしていきます。

このフェーズを完了するために、コマンド git statusgit add は sparse-index と統合され、O(Populated) パフォーマンスで動作するようになります。 これらは、sparse-checkout 定義の内外での操作について慎重にテストされます。

Phase II: Careful integrations

このフェーズでは、すべてのインデックス拡張機能と API がスパースインデックスで適切に機能することを確認することに重点を置きます。 これには、特に sparse-checkout 定義の外で作業ディレクトリと対話する操作について、テスト範囲を大幅に拡大する必要があります。 これらの動作の一部は、t1092-sparse-checkout-compatibility.sh で既に失敗としてマークされている一部のテストなど、望ましいものではない場合があります。

特別な統合が必要なインデックス拡張は以下のとおりです:

  • FS Monitor

  • Untracked cache

これらの機能を統合しながら、インデックスと対話するためのより優れた API につながる可能性のあるパターンを探す必要があります。 一般的な使用パターンを API 呼び出しにまとめると、スパースディレクトリを慎重に処理する必要がある場所の数を減らすことができます。

Phase III: Important command speedups

この時点で、スパース ディレクトリ ロジックをテストおよび実装するためのパターンは比較的安定しているはずです。 このフェーズでは、インデックスを使用して O(Populated) として動作する最も一般的なビルトインのいくつかを更新することに焦点を当てます。 以下は、この時点で統合する価値のあるコマンドの潜在的なリストです:

  • git commit

  • git checkout

  • git merge

  • git rebase

うまくいけば、「git merge」や「git rebase」などのコマンドは、代わりに、マージ ORT 戦略などのインデックスをデータ構造として使用しないマージ アルゴリズムの恩恵を受けることができます。 これらのトピックが成熟するにつれて、sparse-index 機能を使用して、デフォルトでリポジトリの ORT 戦略を有効にする可能性があります。

git status および git add とともに、これらのコマンドは、作業ディレクトリとのユーザーの対話の大部分をカバーします。 さらに、以下のコマンドと統合できます:

  • git grep

  • git rm

これらは、sparse-checkout 定義を持つリポジトリで動作が変わる可能性があるものとして提案されています。 スパース インデックスを使用する場合は、この動作を自動的に含めるとよいでしょう。 動作の切り替えをユーザーに明確にするには、ある程度明確にする必要があります。

このフェーズは、トピック間の競合があまりなく並行作業が可能な最初のフェーズです。

Phase IV: The long tail

この最後のフェーズは、「フェーズ」ではなく、これまでのすべての作業の後の「新常態」(the new normal)です。

まず、command_requires_full_index オプションを削除して、API ガードにヒットした場合にのみ展開することができます。

O(Populated) として動作するために特別な注意を払うことができる多くの Git コマンドがありますが、いくつかは非常にまれなため、sparse-index が存在する場合に追加のオーバーヘッドを残しても問題ありません。

更新に役立つコマンドを以下に示します:

  • git sparse-checkout set

  • git am

  • git clean

  • git stash