目次:
-
用語定義
-
sparse-checkouts(まばらなチェックアウト)の目的
-
主たる関心事に関するユースケース
-
すごく単純化された概念モデル(この文書の虎の巻です!)
-
期待される振る舞い
-
振る舞いクラス
-
サブコマンドごとのデフォルト
-
スパース仕様 V.S. スパース・パターン
-
実装時の疑問
-
実装の 目標/計画
-
既知のバグ
-
Reference Emails
用語定義
-
conemode -
sparse-checkout で、目的の、ファイル群のサブセットを指定するための 2 つのモードのうちの 1 つ。 円錐モード(cone mode)では、 ユーザーはディレクトリを指定します(そのディレクトリ下にある全てのものと、先頭のディレクトリ内にある全てのものの両方を取得します)。 一方、 非コーン・モードでは、 ユーザーは gitignore スタイルのパターンを指定します。 sparse-checkout init|set への --[no-]cone オプションによって制御されます。
-
SKIP_WORKTREE -
追跡中のファイルがスパース仕様にマッチせず、 作業ツリーから削除されると、 インデックス内の当該ファイルには SKIP_WORKTREE ビットが設定されます。 追跡中のファイルに SKIP_WORKTREE ビットが設定されているにもかかわらず、 その後に当該ファイルがユーザーによって作業ツリーに書き込まれた場合、 SKIP_WORKTREE ビットは後続の Git 操作の開始時にクリアされることに注意してください。
ほとんどのスパース・チェックアウトの利用者はこの実装の詳細を知らないため、 一般にユーザー向けの説明やコマンド・フラグではこの用語の使用を避ける必要があります。 残念ながら、 この低レベルな詳細は
sparse-checkoutサブコマンドが使用される前に公開されており、 この記事の執筆時点でもまださまざまな場所で公開されたままです。 -
sparse-checkout -
作業ツリーに存在するファイルを、 全ての追跡中ファイルのサブセットに減らすために使用される git のサブコマンド。 また、 ユーザーの希望するサブセットに対応するスパース・パターンを追跡するために使用される $GIT_DIR/info ディレクトリ内のファイル名。
-
sparsecone -
円錐モード(cone mode)参照
-
sparsedirectory -
ディレクトリに対応するインデックス内のエントリ。 通常はそのディレクトリ下にあるすべてのファイルの代わりにインデックスに現れます。 「スパースインデックス」(sparse-index)も参照してください。 混乱を引き起こす可能性があるのは、「スパース・ディレクトリ」がスパース仕様と一致しないことです。 つまり、 ディレクトリが作業ツリーに存在しないことです。 将来的に名前が変更される可能性があります(例えば「skipped directory」)。
-
sparseindex -
ディレクトリ下にあるすべてのファイルの代わりにディレクトリ・エントリを記録することでインデックスをスパース(疎らに)する(つまり、 そのディレクトリは、遺憾ながら「スパース・ディレクトリ」とも呼ばれる、「スキップされたディレクトリ」になります)、 sparse-checkout の特別なモードで、これを複数のディレクトリに対して行う可能性があります。 init|set|reapply への --[no-]sparse-index オプションによって制御されます。
-
sparsitypatterns -
patterns from $GIT_DIR/info/sparse-checkout used to define the set of files of interest. A warning: It is easy to over-use this term (or the shortened "patterns" term), for two reasons: (1) users in cone mode specify directories rather than patterns (their directories are transformed into patterns, but users may think you are talking about non-cone mode if you use the word "patterns"), and (2) the sparse specification might transiently differ in the working tree or index from the sparsity patterns (see "Sparse specification vs. sparsity patterns").
-
sparsespecification -
利用者の注目領域内のパスの組。 これは通常、 スパース・パターンにマッチする追跡中のファイルだけですが、 スパース仕様が一時的に異なり、 追加のファイルが含まれる場合があります。 (「スパース仕様 V.S. スパース・パターン」も参照下さい)
-
履歴を扱う場合、スパース仕様は正確にスパース・パターンにマッチするファイルの組です。
-
作業ツリーと対話する場合、 スパース仕様は、 クリアされた SKIP_WORKTREE ビットを持つ追跡中ファイルの組、 または作業コピーに存在する追跡中ファイルです。
-
インデックスの結果を変更または表示する場合、 スパース仕様は、クリアされた SKIP_WORKTREE ビットを持つファイルの組、 または HEAD とはインデックスが異なるファイルの組です。
-
インデックスと作業コピーを使用する場合、スパース仕様は上記のパスを合わせたものです。
-
-
vivifying -
コマンドが追跡中のファイルを作業ツリーに復元する(そして、 たぶん、そのファイルのインデックス内の SKIP_WORKTREE ビットもクリアする)ことを、ファイルの「vivifying」(復活)と呼びます。
sparse-checkouts(まばらなチェックアウト)の目的
sparse-checkout は、 ユーザーのファイル群のサブセットを操作できるようにするために存在します。
sparse-checkouts は、「追跡中のファイル」を 2 つのカテゴリ — スパース・サブセットとその他すべて — に再分割するものと考えることができます。 その実装としては、 インデックス内の「残りすべて」を SKIP_WORKTREE ビットでマークし、 作業ツリーから除外します。 SKIP_WORKTREE ビットをマークしたファイルは引き続き追跡中ですが、 作業ツリーには存在しないだけです。
以前は、スパース・チェックアウトは「SKIP_WORKTREE とは、 そのファイルが作業ツリーから欠落しているが、 ファイルの内容が HEAD と一致するかのように装うことを意味する」と定義していました。 それはインチキ(実際には、 作業ツリーで欠落しているファイルが HEAD ではなくインデックスと一致することを意味していました)であるだけでなく、いくつかのコマンドにしかまともな動作を提供しない低レベルのものだった。 その指導原理がユーザーの期待に反するような結果をもたらすことは驚くほど多く、 よくない概念モデルであった。 しかし、それは長年にわたって存在したので、 今でもコードベースの片隅に残っているかもしれません。
とにかく、 「ファイル群のサブセットを操作する」という考え方は非常に単純ですが、 一部の Git サブコマンドの動作に影響を与える、 複数の異なる高レベルのユースケースが存在します。 さらに、これらのユースケースの 1 つだけを考慮したとしても、 スパース・チェックアウトはさまざまなサブコマンドを 6 種類以上の異なる方法で変更できます。 まずは、以下のような高レベルのユースケースを検討してみましょう。
|
A)
|
ユーザーはリポジトリのスパース(まばら)な部分「のみ」に関心があります |
|
A*)
|
ユーザーは、これまでにダウンロードしたリポジトリのスパース(まばら)な部分「のみ」に関心があります |
|
B)
|
ユーザーはスパース(まばら)な作業ツリーを望んでいますが、 より大きな全体で作業しています |
|
C)
|
sparse-checkout は、 Git のための特別に作成された内部仮想ファイル・システムと連携できるようにする舞台裏の実装詳細です。 ユーザーは実際には、遅延的に取り込まれた「完全な」作業ツリーを操作しているため、 スパース・チェックアウトは遅延取り込み部分で役立ちます。 |
これらのそれぞれをもう少し詳しく説明する価値があるかもしれません:
(Behavior A) Users are only interested in the sparse portion of the repo
ユーザーは、 リポジトリに他のものが存在することを知っているかもしれませんが、 気にしません。 ユーザーはリポジトリの他の部分には興味がなく、 関心のある領域内の変更についてのみ知りたいと考えています。 履歴の他のファイル達(たとえば diff/log/grep/ など)を表示することは、有用性を損なう事であり、 履歴の他の部分の変更達が、 ユーザーが興味を持っている変更を矮小化する可能性があるため、 大きな迷惑になる可能性がある。
これらのユーザーの中には、部分(partial)クローンを、(スパース仕様内の BLOB をダウンロードした方法で、)スパース・チェックアウトとともに使用し、 非接続開発を行うことを希望してこのユースケースにたどり着いた人もいます。 これらのユーザーは通常、 リポジトリの他の部分を気にしないだけでなく、 Git コマンドがそれらの部分を操作しようとするのを妨げるものであると考えています。 コマンドがスパース仕様の範囲外の履歴内のパスにアクセスしようとすると、 部分クローンはオンデマンドで追加の BLOB をダウンロードしようとしますが失敗し、 その後ユーザーのコマンドも失敗します。 (これは場合によっては避けられないかもしれません。たとえば、 git merge でスパース仕様の外側で調整するための重要な変更がある場合など、 ユーザーがネットワークに接続することを強制される頻度を制限する必要があります。)
また、 常にネットワークに接続していても構わない部分クローンを使用しているユーザーであっても、 他のさまざまなコマンド(マージまたはプル後に出力される diffstat など)の副作用として BLOB をダウンロードする必要があるため、 ローカル・リポジトリのサイズが不必要に増大するのではないかという懸念につながる可能性があります[10]。
(Behavior A*) Users are only interested in the sparse portion of the repo that they have downloaded so far (a variant on the first usecase)
This variant is driven by folks who using partial clones together with sparse checkouts and do disconnected development (so far sounding like a subset of behavior A users) and doing so on very large repositories. The reason for yet another variant is that downloading even just the blobs through history within their sparse specification may be too much, so they only download some. They would still like operations to succeed without network connectivity, though, so things like git log -S${SEARCH_TERM} -p or git grep ${SEARCH_TERM} OLDREV would need to be prepared to provide partial results that depend on what happens to have been downloaded.
このバリエーションは、 履歴クエリ操作のスパース仕様が、「スパース・パターン」から「既にダウンロードした BLOB に限定されたスパース・パターン」に変更された「振る舞いA」とみなすことができます。
(Behavior B) Users want a sparse working tree, but are working in a larger whole
Stolee はこのユースケースを以下のように説明しています[11]:
「また、 私はそれが大きな全体の一部であることを認識しているユーザーにも焦点を当てています。 彼らは大規模なリポジトリを操作していることを知っていますが、 自分たちが貢献するために必要な部分は何かに焦点を当てています。 複数の「担当」(roles)が、 まったく異なりほとんど結合していないコードベース部分を使用することが予想されます。 他の「設計」ユーザーの中には、 ツリー全体にわたって操作したり、 必要に応じてコードベースの異なるセクション間を行き来したりします。 この状況では、 スパースチェックアウト定義、 特に git log にあまりにも多くの機能の範囲を設定することには慎重であるべきで、それはコードベースの見方があなたの「観点」に依存すると非常に混乱する可能性があるためです。
また、 プロジェクト間の複雑な依存関係により、 最終的に「振る舞いB」が必要になる場合もあります。 スパース・チェックアウト(まばらなチェックアウト)を使用する最初の試みには、 通常、 直接関心のあるディレクトリと、 それらのディレクトリがリポジトリ内で依存しているものが含まれます。 しかし、 ここにそれを台無しにするものがあります(there’s a monkey wrench here)。 統合テストを行う場合、 序列が反転します。 統合テストを実行するには、 関心のあるものとそのツリー内の依存関係だけが必要なのではなく、 あなたが興味のあるものに依存するもの、 またはその依存関係のいずれかに依存するもの全ても必要で…そして、その展開されたグループのツリー内の依存関係がすべて必要です。 これにより、 あなたのスパース・チェックアウト(まばらなチェックアウト)をほぼ密なチェックアウトに簡単に変えることができます。
当然ながら、 これはチェックアウトがスパース(まばら)である利点を殺す傾向があります。 この難問に対する解決策がいくつかあります。 それは、 (たぶん、 あなたがどこかの CI キャッシュでプルしたリポジトリ内の依存関係のビルド・バージョンがあるでしょうから、 )リポジトリ内の依存関係を取得しないようにするか、 あるいは、 ユーザーが統合テストを直に実行せずにコード・レビューを送信するときに CI サーバー上で実行するよう指示する事です。 あるいはその両方を行います。 リポジトリ内の依存関係をもみ消すか、 依存しているものをもみ消すかに関係なく、 リポジトリの他のもみ消された部分の問い合わせを行って認識する必要があるのは確かで、 特に依存関係が複雑である場合や、 比較的頻繁に変更される場合には注意が必要です。 したがって、 そのような用途では、 スパース・チェックアウトを使用して、 直接に構築や変更するものを制限できますが、 これらのユーザーは、 スパース・チェックアウト・パスによって履歴内のバージョンの問い合わせを制限することを必ずしも望んでいる訳ではありません。
単純にパフォーマンス上の回避策として、 「振る舞いA」ではなく「振る舞いB」に興味を持つ人もいるかもしれません。 非円錐モードを使用している場合は、 非円錐モード固有の等比級数的パフォーマンス増大の問題に対処する必要があります。 このモードでは、 パスがスパース仕様にマッチするかどうかをチェックするすべての操作にコストがかかる可能性があります。 そのため、 これらのユーザーは、 作業コピーを操作するときにのみ高価なチェックにお金を払うつもりであり、 遅いコマンドを使用するよりも履歴クエリから「無関係な」結果を取得することを好む可能性があります。
(Behavior C) sparse-checkout is an implementational detail supporting a special VFS.
このユースケースは、 実際に完全なチェックアウトまたは密なチェックアウトをユーザーに提示しようとするという点で、 スパース・チェックアウトの従来の定義に少々反しています。 ただし、 このユースケースでは、 同一の基礎技術基盤を新しいやりかたで利用することで、 ユーザーにパフォーマンス上の利点をもたらします。 基本的なアイデアとしては、 ある企業で社内用に Git 対応仮想ファイル・システムを持っていて、 そのすべてのファイル・システム・アクセスの傍受により、 部分クローンを介してオンデマンドでアクセスされたファイルを取り出したりしたり書き込んだりすることで、 すべてのファイルが作業ツリーに存在するかのように見せることができるというものです。 VFS はスパース・チェックアウトを使用して、 Git が多くのファイルに書き込んだり注意を払うのを防ぎ、 ユーザー・アクセスと作業ツリー内のファイルの変更に基づいてスパース・チェックアウト・パターン自体を手動で更新します。 このような VFS の詳細な説明については、コミット ecc7c8841d("repo_read_index: add config to expect files outside sparse patterns", 2022-02-25) と [17] のリンクを参照してください。
ここでの最大の違いは、 ユーザーはスパース・チェックアウト機構が使用中であることにまったく気づいていないことです。 スパース・パターンはユーザーによって指定されるのではなく、 VFS の完全な制御下にあります(そして、 パターンは VFS によって頻繁かつ動的に更新されます)。 ユーザーはチェックアウトが高密度(まばらでない)であると認識するため、 コマンドはすべてのファイルが存在するかのように動作する必要があります。
主たる関心事に関するユースケース
このドキュメントの残りの大部分は、 振る舞いA と 振る舞いB に焦点を当てます。 他の 2 つのケースと、それらに焦点を当てない理由についてもいくつか書いておきます:
(振る舞いA*)
このユースケースをサポートすることは難しく、 多大な労力がかかると予想されます。 現時点では実装の予定はありませんが、 将来的には代替手段となる可能性があります。 追加の代替手段の存在を知ることは、 コマンド・ライン・フラグの選択に影響を与える可能性があるため(たとえば、単なる2値フラグではなくトライステート・フラグやクアッドステート・フラグが必要かどうか等)、 少なくとも注意は払っておくことが重要です。
さらに、 振る舞いA に関する以下の説明は、 おそらくこのユースケースでも依然として有効であると思いますが、 唯一の例外は、 スパース仕様を再定義して、 既にダウンロードされた BLOB に制限することです。 難しいのは、その変更された定義を尊重できるコマンドを作成することです。
(振る舞いC)
この使用例は、 初期のスパース・チェックアウトに関する文書化された前提条件の一部に違反しています(SKIP_WORKTREE としてマークされたファイルは、 作業ツリーに存在するものとしてユーザーに表示されるため)。 この違反は、 スパース・チェックアウトに関連するさまざまな動作がこのユースケースにあまり適していないことを意味する可能性があり、 これを処理するにはドキュメントとコードの両方に調整が必要になる可能性があります。 ただし、 このユースケースは、 いくつかの例外を除いてすべてが高密度(まばらでない)チェックアウトのように動作するという点で、 おそらくサポートする最も単純なモデルでもあります(たとえば、VFS が必要に応じて残りを遅延的に書き込むことがわかっているため、 ブランチ・チェックアウトと switch は書き込む内容が少なくなります)。
公開されているVFS関連のコードがないため、 このようなユースケースをテストできる人の数は限られています。
振る舞いC のユースケースに注目する主な理由は、 振る舞いA と 振る舞いB をより適切にサポートするために修正を行う際に、 このユースケースの人々が元の非スパース処理を受けられるように調整する必要がある追加の場所が存在する可能性があるためです。 例については、 ecc7c8841d ("repo_read_index: add config to expect files outside sparse patterns", 2022-02-25)を参照してください。 振る舞いC に注目する 2 番目の理由は、 振る舞いC を利用する人々が自分が 振る舞いB 陣営の一員であると思い込み、 実際の 振る舞いB の人々のために問題を解決するパッチを提案しないようにするためです。
思いっきり単純化した概念モデル
上記の振る舞いの違いを思いっきり単純化すると以下のようになります:
- (振る舞いA)
-
ワークツリーと履歴の操作をスパース仕様に制限する
- (振る舞いB)
-
ワークツリーの操作をスパース仕様に制限します。 履歴操作がすべてのファイルにわたって機能するようにします
- (振る舞いC)
-
ワークツリーまたは履歴の操作をスパース仕様に制限しないでください。ただし、ブランチのチェックアウトやスイッチは例外で、インデックスにマッチするファイルの書き込みを回避し、代わり後で遅延して行えるようにします。
期待される振る舞い
前述したように、 ファイルのサブセットを操作するという単純な考え方にもかかわらず、 このような機能を適切に操作するには、 さまざまなサブコマンドにさまざまな動作変更を加える必要があります。 さまざまな例については、[1,2,3,4,5,6,7,8,9,10] を参照してください。 特に [2] では、スパース・チェックアウト・コンテキストで個別に正しく動作する他のコマンドを単に組み合わせただけでは、 上位レベルのコマンドが正しく動作することを意味しないことが分かります。 場合によってはさらに調整が必要になる場合があります。 したがって、これらの違いを理解することは有益です。
-
高レベルのユースケースに関係なく、コマンドは同一の振る舞いをします。
-
スパース仕様内のファイルのみを参照するコマンド
-
diff (
--cachedなし、または REVISION 引数なし) -
grep (
--cachedなし、または REVISION 引数なし) -
diff-files
-
-
commands that restore files to the working tree that match sparsity patterns, and remove unmodified files that don’t match those patterns:
-
switch
-
checkout (switch風の部分)
-
read-tree
-
reset --hard
-
-
commands that write conflicted files to the working tree, but otherwise will omit writing files to the working tree that do not match the sparsity patterns:
-
merge
-
rebase
-
cherry-pick
-
revert
-
amandapply--cachedshould probably be in this section but are buggy (see the "Known bugs" section below)The behavior for these commands somewhat depends upon the merge strategy being used: -
ortは上記のとおり振る舞います -
octopusandresolvewill always vivify any file changed in the merge relative to the first parent, which is rather suboptimal.It is also important to note that these commands WILL update the index outside the sparse specification relative to when the operation began, BUT these commands often make a commit just before or after such that by the end of the operation there is no change to the index outside the sparse specification. Of course, if the operation hits conflicts or does not make a commit, then these operations clearly can modify the index outside the sparse specification.最後に、 これらのコマンドの少なくとも最初の 4 つは、 (前のセクションのコマンドと同様に、)スパース仕様とスパース・パターンの間の差異を削除しようとしていることに注意することが重要です。
-
-
コミットは完全ツリー(full-tree)である必要があるため、 常にスパース性を無視するコマンド群:
-
archive
-
bundle
-
commit
-
format-patch
-
fast-export
-
fast-import
-
commit-tree
-
-
commands that write any modified file to the working tree (conflicted or not, and whether those paths match sparsity patterns or not):
-
stash
-
apply (
--index無し、 または--cached無し)
-
-
-
振る舞いA と 振る舞いB で若干異なる可能性があるコマンド:
Commands in this category behave mostly the same between the two behaviors, but may differ in verbosity and types of warning and error messages.-
追跡中のファイルを変更するコマンド:
-
add
-
rm
-
mv
-
update-index
The fact that files can move between the 'tracked' and 'untracked' categories means some commands will have to treat untracked files differently. But if we have to treat untracked files differently, then additional commands may also need changes: -
status
-
clean
In particular, `status` may need to report any untracked files outside the sparsity specification as an erroneous condition (especially to avoid the user trying to `git add` them, forcing `git add` to display an error).`clean` がどのように変更するか(あるいは変更するかどうか)は正確にはわかりませんが、 非追跡ファイルにも影響を与えるもう 1 つのコマンドです。`update-index` は少し特殊かもしれません。 `--[no-]skip-worktree` フラグは、 その性質上、スパース仕様を無視する必要がある場合があります。 また、現在の `--[no-]ignore-skip-worktree-entries` のデフォルトは完全にインチキです。
-
-
インデックスと作業ツリーの両方のパスを手動で調整するためのコマンド
-
restore -
checkoutの restore風な部分These commands should be similar to add/rm/mv in that they should only operate on the sparse specification by default, and require a special flag to operate on all files.また、 これらのコマンドには現在多くの問題が出ていることに注意してください(下記「Known bugs」セクション参照)
-
-
-
振る舞いA と 振る舞いB で大きく異なるコマンド:
-
履歴を問い合わせ(query)するコマンド
-
diff (
--cachedまたは REVISION 引数を伴う) -
grep (
--cachedまたは REVISION 引数を伴う) -
show (コミット引数が与えられた場合)
-
blame (1 つ以上の
-Cフラグが渡された場合にのみこの問題が発生します)-
と、 annotate
-
-
log
-
whatchanged (may not exist anymore)
-
ls-files
-
diff-index
-
diff-tree
-
ls-tree
Note: for log and whatchanged, revision walking logic is unaffected but displaying of patches is affected by scoping the command to the sparse-checkout. (The fact that revision walking is unaffected is why rev-list, shortlog, show-branch, and bisect are not in this list.)ls-files は、 たとえば `git ls-files -t` が、 何がスパースで何がスパースでないのかを確認するためによく使用されるという点で少し特殊かもしれません。 あるいは、 `-t` は常に完全なツリーに対して機能するようにすべきかもしれませんね?
-
-
-
私にはどこに分類すべきか分からないコマンド
-
range-diff
Is this like `log` or `format-patch`? -
cherry
See range-diff
-
-
sparse-checkouts の影響を受けないコマンド
-
shortlog
-
show-branch
-
rev-list
-
bisect
-
branch
-
describe
-
fetch
-
gc
-
init
-
maintenance
-
notes
-
pull (merge と rebase には必要な変更があります)
-
push
-
submodule
-
tag
-
config
-
filter-branch (sparse-checkout 設定なしで、 個別のチェックアウトで動作します)
-
pack-refs
-
prune
-
remote
-
repack
-
replace
-
bugreport
-
count-objects
-
fsck
-
gitweb
-
help
-
instaweb
-
merge-tree (ワークツリーやインデックスには影響せず、マージは常に完全なツリー(full-tree)を計算します)
-
rerere
-
verify-commit
-
verify-tag
-
commit-graph
-
hash-object
-
index-pack
-
mktag
-
mktree
-
multi-pack-index
-
pack-objects
-
prune-packed
-
symbolic-ref
-
unpack-objects
-
update-ref
-
write-tree (インデックスを操作します。 スパース・ディレクトリ・エントリを使用するように最適化される可能性があります)
-
for-each-ref
-
get-tar-commit-id
-
ls-remote
-
merge-base (マージは完全ツリー(full tree)を計算するため、 マージ・ベースも同様である必要があります)
-
name-rev
-
pack-redundant
-
rev-parse
-
show-index
-
show-ref
-
unpack-file
-
var
-
verify-pack
-
<
githelp--allの「Interacting with Others」セクションにあるすべて> -
<
githelp--allの「Low-level Commands / Syncing Repositories」セクションにあるすべて> -
<
githelp--allの「Low-level Commands / Internal Helpers」セクションにあるすべて> -
<
githelp--allの「External commands」セクションにあるすべて>
-
-
影響を受ける可能性のあるコマンド群だけど、 こんなの気にする人いる?
-
merge-file
-
merge-index
-
gitk?
-
振る舞いクラス
上記により、 振る舞いクラスがいくつあります:
-
"restrict"(制限)
Commands in this class only read or write files in the working tree within the sparse specification.(`switch` や、 `reset --hard` など、)新しいコミットに移動するとき、 これらのコマンドは操作の開始時点でスパース仕様の範囲外にあるインデックス・ファイルを更新する可能性がありますが、 操作の終了までにそれらのインデックス・ファイルは再び HEAD と一致するため、 これらのファイルは再びスパース仕様の範囲外になります。パスが明示的に指定されている場合、 これらのパスはスパース仕様と交錯し、 そのようなパス上でのみ動作します。 (例: `git restore [--staged] -- '*.png'` 、 `git reset -p -- '*.md'` )これらのコマンドの一部では、 その操作の最後で、 スパース仕様とスパース・パターンの間の一時的な差異を除去しようとする場合があります(詳細については「"Sparse specification vs. sparsity patterns」を参照してください。 ただし、 これは基本的に、 スパース・パターンにマッチしない未変更のファイルを削除し、それらのファイルを SKIP_WORKTREE としてマークすることを意味するか、 または、 スパース・パターンにマッチするファイルを復活(vivifying)させ、 それらのファイルを !SKIP_WORKTREE としてマークします)。 -
"restrict modulo conflicts"(競合外の制限)
Commands in this class generally behave like the "restrict" class, except that:(1) これらはスパース仕様を無視し、競合するファイル達を作業ツリーに書き込みます(したがって、 そのようなファイル達を含めるようにスパース仕様を一時的に拡張します。) (2) これらは、 新しいコミットへ移動させるコマンドとグループ化されています。 これは、 新しいコミットへ移動させるのは多くの例外があることがわかっていても、 コマンドはコミットを作成してからそのコミットに移動させることが多いためです。 (たとえば、ユーザーは空になったコミットを rebase したり、 競合する cherry-pick を使用したり、 `merge --no-commit` を実行したり、 `apply --index` も `am --no-commit` のようなものです。) そのため、 これらのコマンドはスパース仕様外のインデックス・ファイルに変更を加えることはできますが、 そのようなファイルには SKIP_WORKTREE のマークが付けられます。 -
"restrict also specially applied to untracked files"(非追跡ファイルにも特別に適用される制限)
Commands in this class generally behave like the "restrict" class, except that they have to handle untracked files differently too, often because these commands are dealing with files changing state between 'tracked' and 'untracked'. Often, this may mean printing an error message if the command had nothing to do, but the arguments may have referred to files whose tracked-ness state could have changed were it not for the sparsity patterns excluding them. -
"no restrict"(制限しない)
Commands in this class ignore the sparse specification entirely. -
"restrict or no restrict dependent upon behavior A vs. behavior B"(振る舞いA と 振る舞いB に応じて、 制限する または 制限しない)
Commands in this class behave like "no restrict" for folks in the behavior B camp, and like "restrict" for folks in the behavior A camp. However, when behaving like "restrict" a warning of some sort might be provided that history queries have been limited by the sparse-checkout specification.
サブコマンドごとのデフォルト
注意: 目的の振る舞いを実現するためのコマンドに応じて、異なるデフォルトがあることに注意してください:
-
Commands defaulting to "restrict":
-
diff-files
-
diff (
--cachedなし、または REVISION 引数なし) -
grep (
--cachedなし、または REVISION 引数なし) -
switch
-
checkout (switch風の部分)
-
reset (<commit>)
-
restore
-
checkout (restore風の部分)
-
checkout-index
-
reset (pathspec を伴う)
This behavior makes sense; these interact with the working tree.
-
-
デフォルトで "restrict modulo conflicts"(競合外の制限) に設定されているコマンド:
-
merge
-
rebase
-
cherry-pick
-
revert
-
am
-
apply --index (これは
am--no-commitのようなものです) -
read-tree (
-mまたは-uを使用する場合;--no-commitマージのようなもの) -
reset (<tree-ish> 、read-tree との類似性による)
These also interact with the working tree, but require slightly different behavior either so that (a) conflicts can be resolved or (b) because they are kind of like a merge-without-commit operation.( `am` および `apply` に関する下記「Known bugs」セクションも参照してください)
-
-
デフォルトで "no restrict" (制限なし)に設定されているコマンド:
-
archive
-
bundle
-
commit
-
format-patch
-
fast-export
-
fast-import
-
commit-tree
-
stash
-
apply (
--index無し)These have completely different defaults and perhaps deserve the most detailed explanation:最初のグループのコマンド(format-patch、fast-export、bundle、archive など)の場合、 これらは履歴を交信するためのコマンドであり、 リポジトリのサブセットに制限すると壊れます。 そのため、 これらはフルパスで動作するため、 オーバーライドするための `--restrict` オプションはありません。 これらのコマンドの一部は、 エクスポートされる内容を手動で制限するためのパスを受け付ける場合がありますが、 それは非常に明示的である必要があります。stash の場合、 ユーザーの変更が失われないように、 ファイルを復活(vivify)させる必要があります。`--index` なしで適用する場合、 そのコマンドはインデックスなしで作業ツリー(または、 `--cached` を渡した場合は作業ツリーなしのインデックス)を更新する必要があります。 これらの更新をスパース仕様で制限すると、 ユーザーからの変更が失われます。
-
-
デフォルトで「非追跡ファイルにも特別に適用される制限」が設定されているコマンド:
-
add
-
rm
-
mv
-
update-index
-
status
-
clean (?)
-
Our original implementation for the first three of these commands was
"no restrict", but it had some severe usability issues:
* `git add <somefile>` if honored and outside the sparse
specification, can result in the file randomly disappearing later
when some subsequent command is run (since various commands
automatically clean up unmodified files outside the sparse
specification).
* `git rm '*.jpg'` could very negatively surprise users if it deletes
files outside the range of the user's interest.
* `git mv` has similar surprises when moving into or out of the cone,
so best to restrict by default
So, we switched `add` and `rm` to default to "restrict", which made
usability problems much less severe and less frequent, but we still got
complaints because commands like:
git add <file-outside-sparse-specification>
git rm <file-outside-sparse-specification>
would silently do nothing. We should instead print an error in those
cases to get usability right.
update-index needs to be updated to match, and status and maybe clean
also need to be updated to specially handle untracked paths.
There may be a difference in here between behavior A and behavior B in
terms of verboseness of errors or additional warnings.
-
「振る舞いA や 振る舞いB に応じて制限するか、あるいは制限しない」に該当するコマンド:
-
diff (
--cachedまたは REVISION 引数を伴う) -
grep (
--cachedまたは REVISION 引数を伴う) -
show (コミット引数が与えられた場合)
-
blame (1 つ以上の
-Cフラグが渡された場合にのみ、この問題が発生します)-
と、 annotate
-
-
log
-
と、その派生: shortlog, gitk, show-branch, whatchanged, rev-list
-
-
ls-files
-
diff-index
-
diff-tree
-
ls-tree
For now, we default to behavior B for these, which want a default of "no restrict".注意: これらのコマンドのうち、 diff と grep もまた、 デフォルトの「制限」(restrict)で別のリストに表示されますが、これは作業ツリーの検索に限定されている場合に限られることに注意してください。 作業ツリーと履歴の区別は 振る舞いB の操作の基本であるため、 これは期待どうりのものです。 ただし、 `--cached` を指定した diff と grep の場合、「制限」(restrict)振る舞いを行う場合、 スパース仕様とスパース・パターンの違いを処理することが重要であることに注意してください。これらの長期的なデフォルトとしては、「制限」(restrict)の方が合理的かもしれません[12]。 また、これらのコマンドの「制限」(restrict)をサポートするには、 実装にかなりの作業が必要となる可能性があり、 複数のリリースにまたがって実装作業が行われる可能性があります。 その振る舞いが、 それをサポートするコマンドのデフォルトである場合、 振る舞いB のユーザーは、 必要な動作を得るために、 git のバージョンに応じて、 コマンドにフラグを徐々に追加する方法を学ぶ必要があります。 この段階的な対応は苦痛を伴うため、 私達は、 少なくとも完全に実装されるまでは回避するべきです。
-
スパース仕様 V.S. スパース・パターン
正常に動作する状況では、 スパース仕様は $GIT_DIR/info/sparse-checkout ファイルによって直接指定されます。 ただし、 以下のいくつかの理由で過渡的にそこから逸脱する可能性があります:
-
競合を解決する必要がある時(マージすると競合したファイルが復活(vivify)します)
-
ファイルを暗黙的に復活(vivify)させる Git コマンドの実行中(例:
gitstashapply) -
ファイルを明示的に復活(vivify)させる Git コマンドの実行中(例:
gitcheckout--ignore-skip-worktree-bitsFILENAME) -
これらのファイルに書き込む他のコマンド(おそらくユーザーが他の場所からコピーしたもの)
上記の最後の項目については、 作業ツリーに存在するファイルの SKIP_WORKTREE ビットが自動的にクリアされることに注意してください。 これはコミット 82386b4496("Merge branch en/present-despite-skipped", 2022-03-09)以降で当てはまります。
ただし、 このような状況は以下の理由により、過渡的なものです:
-
このような過渡的な差異は、 unpack_trees() を呼び出すコマンド(checkout、 merge、 reset 等)の副作用として自動的に取り除かれる可能性があります。
-
ユーザーは
gitsparse-checkoutreapplyを実行して、 このような過渡的な差異の修正を要求することもできます。 さまざまな場所でこのコマンドの実行が推奨されています。 -
これらの違いを暗黙的に修正する追加のコマンドも大歓迎です。 私達は、 将来、さらにこのようなコマンドを追加する可能性があります。
ステージされてない変更または競合のあるファイルの削除は避けますが、 そうでない場合は、 これらの過渡的な差異を積極的に修正しようとします。 ユーザーがこれらの差異を維持したい場合は、 git sparse-checkout の set または add サブコマンドを実行して、 意図するスパース仕様を反映するべきです。
ただし、 「関連するファイルのサブセット」に限定された履歴に対してクエリを実行する必要がある場合、 そのような過渡的に拡張されたスパース仕様は無視されます。 これには以下のように一組の理由があります:
-
The behavior wanted when doing something like git grep expression REVISION is roughly what the users would expect from git checkout REVISION && git grep expression (modulo a "REVISION:" prefix), which has a couple ramifications:
-
REVISION には現在のインデックスにないパスが含まれている可能性があるため、 それらのパスの SKIP_WORKTREE 設定を調べる事ができるパスはありません。
-
checkoutはスパース仕様の過渡的な差異を削除しようとするコマンドの 1 つであるため、 SKIP_WORKTREE を調べようとするのではなく、 修正されたスパース仕様 (つまり $GIT_DIR/info/sparse-checkout) を使用するのが合理的です。
したがって、 過渡的に拡張された(または制限された)スパース仕様は作業ツリーには適用されますが、 スパース・パターンを使用する履歴クエリには常に適用されません(これに関する初期の議論については、[16] を参照してください。)
作業ツリーに存在する追加ファイルに基づいて過渡的に拡張された作業ツリーのスパース仕様と同様に、 インデックス内で変更される追加ファイルも考慮する必要があります。 特に、 ユーザーが、(HEAD に関連した、)スパース・パターンにマッチしないファイルへの変更をステージングしており、 そのファイルが作業ツリーには存在しない場合でも、 スパース仕様のファイル部分を考慮する必要があります。 具体的には、 インデックスに関連する問い合わせを実行します(例: git diff --cached [REVISION]、 git diff-index [REVISION]、 git restore --staged --source=REVISION -- PATHS など)。 過渡的に拡張されたスパースは、 振る舞いB ではインデックス操作を履歴と一緒にまとめ、 フルのツリー(full-tree)で操作する傾向があるため、 通常、 インデックスの指定は 振る舞いA でのみ問題になります。
実装時の疑問
-
オプション
--scope={sparse,all} は、 私達以外の人々にとっても良い書き方に思えますか? もっと良い選択肢があるでしょうか?-
使用中の名前、またはパッチに登場する名前、または以前に提案された名前:
-
--sparse/--dense
-
--ignore-skip-worktree-bits
-
--ignore-skip-worktree-entries
-
--ignore-sparsity
-
--[no-]restrict-to-sparse-paths
-
--full-tree/--sparse-tree
-
--[no-]restrict
-
--scope={sparse,all}
-
--focus/--unfocus
-
--limit/--unlimited
-
-
`--scope={sparse,all}` という表記に少し傾倒している根拠:
-
We want a name that works for many commands, so we need a name that does not conflict
-
We know that we have more than two possible usecases, so it is best to avoid a flag that appears to be binary.
-
--scope={sparse,all} isn’t overly long and seems relatively explanatory
-
--sparse, as used in add/rm/mv, is totally backwards for grep/log/etc. Changing the meaning of--sparsefor these commands would fix the backwardness, but possibly break existing scripts. Using a new name pairing would allow us to treat--sparsein these commands as a deprecated alias. -
There is a different
--sparse/--densepair for commands using revision machinery, so using that naming might cause confusion -
There is also a
--sparsein both pack-objects and show-branch, which don’t conflict but do suggest that--sparseis overloaded -
The name --ignore-skip-worktree-bits is a double negative, is quite a mouthful, refers to an implementation detail that many users may not be familiar with, and we’d need a negation for it which would probably be even more ridiculously long. (But we can make --ignore-skip-worktree-bits a deprecated alias for --no-restrict.)
-
-
If a config option is added (sparse.scope?) what should the values and description be? "sparse" (behavior A), "worktree-sparse-history-dense" (behavior B), "dense" (behavior C)? There’s a risk of confusion, because even for Behaviors A and B we want some commands to be full-tree and others to operate sparsely, so the wording may need to be more tied to the usecases and somehow explain that. Also, right now, the primary difference we are focusing is just the history-querying commands (log/diff/grep). Previous config suggestion here: [13]
-
Is
--no-expanda good alias for ls-files’s--sparseoption? (--sparsedoes not map to either--scope=sparseor--scope=all, because in non-cone mode it does nothing and in cone-mode it shows the sparse directory entries which are technically outside the sparse specification) -
振る舞いAにおいて:
-
Does ls-files'
--no-expandoverride the default--scope=all, or does it need an extra flag? -
Does ls-files'
-toption imply--scope=all? -
Does update-index’s
--[no-]skip-worktreeoption imply--scope=all?
-
-
sparse-checkout: once behavior A is fully implemented, should we take an interim measure to ease people into switching the default? Namely, if folks are not already in a sparse checkout, then require
sparse-checkoutinit/setto take a--set-scope=(sparse|worktree-sparse-history-dense|dense) flag (which would set sparse.scope according to the setting given), and throw an error if the flag is not provided? That error would be a great place to warn folks that the default may change in the future, and get them used to specifying what they want so that the eventual default switch is seamless for them.
-
実装の 目標/計画
-
この文書全般について賛同を得ること。
-
「実装時の疑問」セクション(上記)に対する答えを見つけ出すこと
-
「既知のバグ」セクション(下記)のバグを修正すること
-
部分クローン(partial clone)のスパース仕様内の BLOB を埋め戻し(backfilling)するための何らかの方法を提供すること
[Below here is kind of spitballing since the first two haven't been resolved] -
update-index: デフォルトを
--no-ignore-skip-worktree-entriesに切り替えて、 「ああん、バグがある? それじゃ、 ユーザーにこのバグをトリガーしないようにリクエストするフラグを追加しよう」という、 この愚かなフラグを削除 -
フラグと構成(config)について
-
add/rm/mv の
--sparseを--scope=allの非推奨のエイリアスにします -
Make
--ignore-skip-worktree-bitsin checkout-index/checkout/restore a deprecated aliases for--scope=all -
Create config option (sparse.scope?), tie it to the "Cliff notes" overview
-
Add --scope=sparse (and --scope=all) flag to each of the history querying commands. IMPORTANT: make sure diff machinery changes don’t mess with format-patch, fast-export, etc.
-
既知のバグ
このリストは以前はもっと長かったのですが([1,2,3,4,5,6,7,8,9] を参照)、 私達の鋭意ある取り組みによりだいぶ短くなりました。
-
振る舞いA は Git では十分にサポートされていません。 (以前はどちらの振る舞いも十分にサポートされていなかったのですが、 2 つのうち振る舞いBの実装がより容易でした。)
-
am と apply について:
apply, without `--index` or `--cached`, relies on files being present in the working copy, and also writes to them unconditionally. As such, it should first check for the files' presence, and if found to be SKIP_WORKTREE, then clear the bit and vivify the paths, then do its work. Currently, it just throws an error.`--cached` または `--index` を指定して apply を実行すると、 SKIP_WORKTREE ビットは保持されません。 ファイルに競合がある場合はこれで問題ありませんが、 そうでない場合は、 SKIP_WORKTREE ビットを `--cached` と、 おそらく `--index` に対しても保存するべきです。am は、 競合がない場合、 ファイルを復活(vivify)させ、 SKIP_WORKTREE ビットの保存に失敗します。 競合があり、 `-3` が指定されていない場合、 ファイルは復活(vivify)後、 パッチが適用されないというメッセージが表示されます。 競合があり、 `-3` が指定されている場合、 ファイルを復活(vivify)させ、 それらの復活(vivify)したファイルがマージによって上書きされると警告します。 -
reset--hardについて:reset --hard provides confusing error message (works correctly, but misleads the user into believing it didn't):$ touch addme $ git add addme $ git ls-files -t H addme H tracked S tracked-but-maybe-skipped $ git reset --hard # usually works great error: Path 'addme' not uptodate; will not remove from working tree. HEAD is now at bdbbb6f third $ git ls-files -t H tracked S tracked-but-maybe-skipped $ ls -1 tracked`git replace --hard` は、 エラー・メッセージとは裏腹に、 インデックスと作業ツリーから addme を削除しましたが、 それは `reset --hard` の振る舞いに沿って行われたものです。 -
read-tree
`read-tree` doesn't apply the 'SKIP_WORKTREE' bit to *any* of the entries it reads into the index, resulting in all your files suddenly appearing to be "deleted". -
Checkout、 restore について:
These command do not handle path & revision arguments appropriately:$ ls tracked $ git ls-files -t H tracked S tracked-but-maybe-skipped $ git status --porcelain $ git checkout -- '*skipped' error: pathspec '*skipped' did not match any file(s) known to git $ git ls-files -- '*skipped' tracked-but-maybe-skipped $ git checkout HEAD -- '*skipped' error: pathspec '*skipped' did not match any file(s) known to git $ git ls-tree HEAD | grep skipped 100644 blob 276f5a64354b791b13840f02047738c77ad0584f tracked-but-maybe-skipped $ git status --porcelain $ git checkout HEAD~1 -- '*skipped' $ git ls-files -t H tracked H tracked-but-maybe-skipped $ git status --porcelain M tracked-but-maybe-skipped $ git checkout HEAD -- '*skipped' $ git status --porcelain $注意: リビジョンなしの checkout (または `restore --staged`)では、 ls-files でそのようなファイルが確かに存在することが示されている場合でも、 インデックスから復元(restore)するファイルが見つからないことに注意してください。同様の問題が HEAD (restore の場合は `--source=HEAD`)でも発生しますが、 HEAD~1 が指定されると突然機能するようになります。 その後、 以前は機能しなかったとしても、 HEAD を指定しても機能するようになります。ディレクトリにも問題があります:$ git sparse-checkout set nomatches $ git status On branch main You are in a sparse checkout with 0% of tracked files present.nothing to commit, working tree clean $ git checkout . error: pathspec '.' did not match any file(s) known to git $ git checkout HEAD~1 . Updated 1 path from 58916d9 $ git ls-files -t S tracked H tracked-but-maybe-skipped -
checkout と
restore--stagedの続き:These commands do not correctly scope operations to the sparse specification, and make it worse by not setting important SKIP_WORKTREE bits:$ git restore --source OLDREV --staged outside-sparse-cone/ $ git status --porcelain MD outside-sparse-cone/file1 MD outside-sparse-cone/file2 MD outside-sparse-cone/file3`--scope=all` モードを `git list` に追加して、 スパース仕様の範囲外で動作できるようにすることもできますが、 その場合は SKIP_WORKTREE ビットを適切に設定することが重要になります。 -
パフォーマンスの問題ついて。 以下参照:
https://lore.kernel.org/git/CABPp-BEkJQoKZsQGCYioyga_uoDQ6iBeW+FKr8JhyuuTMK1RDw@mail.gmail.com/
Reference Emails
sparse-checkout で発生したさまざまなバグを詳しく説明したメール達:
[1] (Original descriptions of behavior A & behavior B):
[2] (Fix stash applications in sparse checkouts; bugs from behavioral differences):
[3] (Present-despite-skipped entries):
[4] (Clone --no-checkout interaction):
https://lore.kernel.org/git/pull.801.v2.git.git.1591324899170.gitgitgadget@gmail.com/ (clone --no-checkout)
[5] (The need for update_sparsity() and avoiding read-tree -mu HEAD):
[6] (SKIP_WORKTREE is advisory, not mandatory):
[7] (worktree add should copy sparsity settings from current worktree):
[8] (Avoid negative surprises in add, rm, and mv):
[9] (Move from out-of-cone to in-cone):
[10] (Unnecessarily downloading objects outside sparse specification):
[11] (Stolee’s comments on high-level usecases):
[12] Others commenting on eventually switching default to behavior A:
[13] Previous config name suggestion and description:
https://lore.kernel.org/git/CABPp-BE6zW0nJSStcVU=_DoDBnPgLqOR8pkTXK3dW11=T01OhA@mail.gmail.com/
[14] Tangential issue: switch to cone mode as default sparse specification mechanism:
[15] Lengthy email on grep behavior, covering what should be searched:
[16] Email explaining sparsity patterns vs. SKIP_WORKTREE and history operations, search for the parenthetical comment starting "We do not check".
GCg@mail.gmail.com/">https://lore.kernel.org/git/CABPp-BFsCPPNOZ92JQRJeGyNd0e-TCW-LcLyr0i_+VSQJP+GCg@mail.gmail.com/