リベースとチェリーピックには一連のマージが含まれ、その結果は新しいひとり親コミット(single-parent commits)として記録されます。 これらのマージの1番目の親の側は「上流」(upstream)側を表し、多くの場合、2 番目の親の側よりもはるかに大きな変更セットが含まれます。 従来、一連のマージの最初の親側の名前変更は、マージごとに繰り返し再検出されていました。 このファイルは、すべてのマージが自動でクリーン(つまり、競合がなく、ユーザー入力または編集のために中断(interrupt)されない)であると仮定して、最適化として、履歴の上流側で名前変更を記憶することが、リベースおよびチェリーピック中に安全かつ効果的である理由を説明します。
Outline:
-
Assumptions
-
How rebasing and cherry-picking work
-
Why the renames on MERGE_SIDE1 in any given pick are always a superset of the renames on MERGE_SIDE1 for the next pick.
-
Why any rename on MERGE_SIDE1 in any given pick is almost always also a rename on MERGE_SIDE1 for the next pick
-
A detailed description of the counter-examples to #4.
-
Why the special cases in #5 are still fully reasonable to use to pair up files for three-way content merging in the merge machinery, and why they do not affect the correctness of the merge.
-
Interaction with skipping of "irrelevant" renames
-
Additional items that need to be cached
-
How directory rename detection interacts with the above and why this optimization is still safe even if merge.directoryRenames is set to "true".
1. Assumptions
1つ目と2つ目の仮定はこのドキュメント全体に渡る仮定です:
-
リベース/チェリーピック がマージ機構を呼び出すと、コミット達が移植される上流側が1番目の親の側として扱われます
-
すべてのマージは完全に自動化されています
and a third that will hold in sections 3-6 for simplicity, that I’ll later address in section 9:
-
ディレクトリの名前変更は発生しません
それぞれの仮定とそれを含める理由について、詳しく説明します:
最初の仮定は、この文書の目的をより明確にするためのものです。 最適化の実装は実際、これには依存しません。 ただし、rebase と cherry-pick の両方が実装された方法を反映しているため、この仮定はすべての場合に当てはまります。 そしてまた、 cherry-pick と rebase の実装は、下位互換性の理由により簡単には変更できません(たとえば、 git checkout のドキュメントの --ours フラグと --theirs フラグに関する議論、特に rebase での動作に関するコメントを参照してください)。 ただし、最適化により、1番目の親のチェック(checking first-parent-ness)が回避されます。 最適化を有効にする代わりの条件をチェックするため、 cherry-pick と rebase が使用する親の順序が変更された場合でも引き続き機能します。 しかし、この仮定を行うことで、このドキュメントがより明確になり、すべての例を 2 回繰り返す必要がなくなります。
2番目の仮定に違反した場合、最適化は単純にオフになるため、その後を考慮する必要はありません。 2番目の仮定は、「ユーザーが競合を解決したり、ファイルをさらに編集または微調整したりするために中断(interrupt)されることはない」とも言えます。 実際のリベースとチェリーピックはしばしば中断(interrupt)されますが(ユーザーが停止と編集を要求した対話的なリベースであるか、ユーザーが解決する必要がある競合があったため)、名前変更のキャッシュはディスクに保存されないので、ユーザーが操作を解決するためにリベースまたはチェリーピックが停止(stop)すると直ちに破棄されます。
The third assumption makes sections 3-6 simpler, and allows people to understand the basics of why this optimization is safe and effective, and then I can go back and address the specifics in section 9. It is probably also worth noting that if directory renames do occur, then the default of merge.directoryRenames being set to "conflict" means that the operation will stop for users to resolve the conflicts and the cache will be thrown away, and thus that there won’t be an optimization to apply. So, the only reason we need to address directory renames specifically, is that some users will have set merge.directoryRenames to "true" to allow the merges to continue to proceed automatically. The optimization is still safe with this config setting, but we have to discuss a few more cases to show why; this discussion is deferred until section 9.
2. How rebasing and cherry-picking work
以下の図について考えてみましょう(git-rebase マニュアルページより):
A---B---C topic
/
D---E---F---G main
topic を main にリベースまたはチェリーピッキングした後、以下のようになります:
A'--B'--C' topic
/
D---E---F---G main
コミット A' と B' と C' がどのように作成されるかというと、一連のマージによって行われます。この場合、リベースまたはチェリーピックは、特殊なマージ操作にて 3 つの A-B-C コミットのそれぞれを順番に使用します。 ここで、マージ操作の 3 つのコミットに、 MERGE_BASE と、MERGE_SIDE1 と MERGE_SIDE2 というラベルを付けることにしましょう。 この図では、その 3 つのマージのそれぞれに対する 3 つのコミットは以下のようになります:
To create A':
MERGE_BASE: E
MERGE_SIDE1: G
MERGE_SIDE2: A
To create B':
MERGE_BASE: A
MERGE_SIDE1: A'
MERGE_SIDE2: B
To create C':
MERGE_BASE: B
MERGE_SIDE1: B'
MERGE_SIDE2: C
ときどき、これらの 3 方向のマージが行われることに驚かれることがあります。 これらの 3 方向のマージを理解するには、これらを少し異なる観点から見ることが役に立ちます。 たとえば、 C' の作成は、以下のいずれかとして観る事ができます:
-
BとCの間の変更をB’に適用(apply)します
-
BとB’の間の変更をCに適用(apply)します
概念的には、上記の 2 つの文は、少なくともあなたがコミットの記録を決定する前の時点では、B と B' と C の 3 方向マージと同一です。
3. Why the renames on MERGE_SIDE1 in any given pick are always a superset of the renames on MERGE_SIDE1 for the next pick.
マージ機構は、MERGE_BASE と MERGE_SIDE1 と MERGE_SIDE2 から供給されるファイル名を使用します。 以下の 3 つの条件のいずれかでのみ、コンテンツを別のファイル名に移動(move)します:
-
競合の解決中にユーザーが両方の競合の断片を利用できるようにするため (例: ディレクトリ/ファイルの競合、シンボリックリンク対通常ファイル等のような 追加/追加タイプ の競合)
-
MERGE_SIDE1 がファイルの名前変更したとき。
-
MERGE_SIDE2 がファイルの名前変更したとき。
まず、cherry-pick または rebase シーケンスの 1 番目と 2 番目のピックに含まれるコミットを思い出してください:
To create A':
MERGE_BASE: E
MERGE_SIDE1: G
MERGE_SIDE2: A
To create B':
MERGE_BASE: A
MERGE_SIDE1: A'
MERGE_SIDE2: B
そして、特に、 E と G の間の名前変更は、 A と A' の間の名前変更のスーパーセットであることを示す必要があります。
A' は最初のマージで作成されます。 A' は、上記 3 つの理由のいずれかのみで名前変更されます。 最初のケースである競合では、キャッシュが削除され、この最適化が有効にならない状況が発生するため、このケースを考慮する必要はありません。 3 番目のケースである MERGE_SIDE2 の名前変更 (つまり、G から A へ) は、A' に表れますが、A にも表れます — したがって、 A と A' を比較すると、そのパスは名前変更として表示されません。 名前の変更が A' に表れる唯一の方法は、MERGE_SIDE1 による名前変更です。 それゆえ、A と A' の間のすべての名前変更は、E と G の間の名前変更のサブセットです。 同様に、E と G の間のすべての名前変更は、A と A' の間の名前変更のスーパーセットです。
4. Why any rename on MERGE_SIDE1 in any given pick is almost always also a rename on MERGE_SIDE1 for the next pick.
最初の 2 つのピックをもう一度見てみましょう:
To create A':
MERGE_BASE: E
MERGE_SIDE1: G
MERGE_SIDE2: A
To create B':
MERGE_BASE: A
MERGE_SIDE1: A'
MERGE_SIDE2: B
次に、最初のピックでの MERGE_SIDE1 からの任意の名前変更、つまり E から G への任意の名前変更を見てみましょう。デモンストレーションのために、ファイル名「oldfile」と「newfile」を使用してみることにします。 その最初のピックは次のように機能します。 つまり、名前の変更が検出されると、マージ機構は以下の 3 方向のコンテンツ マージを実行します:
E:oldfile
G:newfile
A:oldfile
そして新しい結果を生成します:
A':newfile
上記で、E→A が oldfile の名前変更しなかったと仮定したことに注意してください。 そのMERGE_SIDEが名前を変更した場合は、名前変更/名前変更(1to2) の競合が発生している可能性が高く、リベースまたはチェリーピック操作が停止(halt)し、名前変更のメモリ内キャッシュが削除(drop)されるため、これ以降を考慮する必要はありません。 E→A がファイルの名前を変更するだけでなく、 newfile に名前を変更するという特別なケースでは、名前変更による競合はなく、マージは成功します。 この特殊なケースでは、2 回目のマージで MERGE_BASE 内の A:newfile が検出されるため、名前変更はキャッシュに有効ではありません (t6429 の新しいテストケースの「rename same file identically」(同一ファイルを全く同じに名前変更する)という説明も参照してください)。 したがって、 rename/rename(1to1) は、キャッシュから名前変更を刈り込み、それらの名前変更に関連付けられた現在および主要なディレクトリの dir_rename_counts を減らすことによって、特別に処理する必要があります。 または、これらは非常にまれであるため、名前の変更/名前の変更(1to1)が発生したときに名前の記憶の最適化を無効にして、簡単な方法を取ることができます。
さて、前の段落では、 E→A で oldfile の名前を変更する特殊なケースについて説明しましたけれども、引き続き A で oldfile の名前が変更されていないと仮定して議論を続行すします。
B' を作成するための図によると、 MERGE_SIDE1 には A から A' への変更が含まれます。 そのため私達は A:oldfile と A':newfile が名前変更として表れるかどうかに興味があるのです。 以下ご留意下さい:
-
A':oldfile はなくなります(なぜなら、マージ機構で破棄検出(break detection)を行わず、 G:newfile が名前変更として検出されたため、G:oldfile が存在する可能性はありませんでした。 よって結果に「oldfile」はありません)。
-
A:newfile はなくります (もしあった場合、名前変更/追加 の競合が発生していたはずです)。
-
明らかに、 A:oldfile と A':newfile は「関連」(related)があります(A':newfile は、 A:oldfile を含むクリーンな 3 方向のコンテンツ マージから派生したものです)。
上記の 3 番目のポイントについても説明できます。3 方向のコンテンツのマージは、ベースと一方の側の違いを他方の側に適用することと見なすこともできます。 したがって、 E:oldfile と G:newfile の間の変更(関連していると検出されたもの、つまり 50% 未満の変更)を A:oldfile に適用することによって、 A':newfile が作成されたと見なすことができます。
したがって、 A:oldfile と A':newfile は、 E:oldfile と G:newfile が関連しているのと同じように関連しています — これらはまったく同一の違いがあります。 後者は名前変更として検出されたので、 A:oldfile と A':newfile もほとんど常に名前変更として検出できるはずです。
5. A detailed description of the counter-examples to #4.
We already noted in section 4 that rename/rename(1to1) (i.e. both sides renaming a file the same way) was one counter-example. The more interesting bit, though, is why did we need to use the "almost" qualifier when stating that A:oldfile and A':newfile are "almost" always detectable as renames?
Let’s repeat an earlier point that section 4 made:
A':newfile は、E:oldfile と G:newfile の間の変更を
A:oldfile に適用することによって作成されました。
E:oldfile と G:newfile の間の変更は、 E:oldfile のサイズの 50% 未満でした。
E:oldfile のサイズの 50% 未満であった変更が、 A:oldfile のサイズの 50% 未満であるならば、 A:oldfile と A':newfile では名前変更として検出されます。 ただし、 E:oldfile と A:oldfile の間で劇的なサイズの縮小がある場合 (ただし、E:oldfile、G:newfile、および A:oldfile の間の変更は依然として何らかの形できれいにマージされるとして)、従来の名前変更検出では A:oldfile と A':newfile の間の名前変更が検出されません。
これが発生する可能性のある例を以下に示します:
-
E:oldfileには20行あります
-
G:newfileは、ファイルの先頭に10行の新しい行を追加しました
-
A:oldfileは、ファイルの最初の3行を保持し、残りをすべて削除しました。
then
=> A':newfile would have 13 lines, 3 of which matches those in A:oldfile.
E:oldfile -> G:newfile would be detected as a rename, but A:oldfile and
A':newfile would not be.
6. Why the special cases in #5 are still fully reasonable to use to pair up files for three-way content merging in the merge machinery, and why they do not affect the correctness of the merge.
In the rename/rename(1to1) case, A:newfile and A':newfile are not renames since they use the same filename. However, files with the same filename are obviously fine to pair up for three-way content merging (the merge machinery has never employed break detection). The interesting counter-example case is thus not the rename/rename(1to1) case, but the case where A did not rename oldfile. That was the case that we spent most of the time discussing in sections 4 and 5. The remainder of this section will be devoted to that case as well.
では、 A:oldfile と A':newfile が名前変更として検出されない場合でも、マージ機構で3方向のコンテンツをマージするためにそれらを組(pair)にすることが合理的なのはなぜでしょうか? これには複数の理由があります:
-
As noted in sections 4 and 5, the diff between A:oldfile and A':newfile is exactly the same as the diff between E:oldfile and G:newfile. The latter pair were detected as renames, so it seems unlikely to surprise users for us to treat A:oldfile and A':newfile as renames.
-
実際、
oldfileとnewfileは、E..G チェーンでの構成方法が原因で、ある時点で名前変更として検出されました。 そして、私達はこの リベース/チェリーピック ですでにその情報を使用しました。 ユーザーは、私たちがファイルを名前変更として扱い続けていることに驚くことはまずなく、その理由をすぐに理解できると思います。 -
ファイルを名前変更としてマークまたは宣言することは、マージの最終目標ではありません。 マージでは、名前変更を使用して、3方向のコンテンツマージでペアにするのが適切なファイルを決定します。
-
A:oldfile と A':newfile は、「既に」 3方向のコンテンツマージでペアになっています。つまりそれは A':newfile がどのように作成されたかという事です。 実際、その3方向のコンテンツマージはクリーンでした。 したがって、後の3方向のコンテンツマージでそれらを再度使用することは非常に合理的です。
ただし、上記では一般的なシナリオに焦点を当てています。 考えうる異常なシナリオをすべて見て、最適化なしと最適化ありを比較してみましょう。 以下の理論的なケースを検討してみてください。 そして、私達は以下のそれぞれについて掘り下げて、可能なものと、可能な場合の意味を判断します:
-
最適化を行わないと、2回目のマージで競合が発生します。 最適化を行っても、2回目のマージで競合が発生します。 質問: これらの競合は紛らわしいほどの差異でしょうか? ある場合においてはより良いでしょうか?
-
最適化を行わないと、2回目のマージで競合は発生しません。 最適化を行っても、2回目のマージで競合は発生しません。 質問:これらのマージは同一ですか?
-
最適化を行わないと、2回目のマージで競合が発生します。 最適化を行うと、2回目のマージで競合は発生しません。 質問: こあれはありえますか? バグまたは、バグフィックスまた、それ以外の何かか?
-
最適化を行わないと、2回目のマージで競合は発生しません。 最適化を行うと、2回目のマージで競合が発生します。 質問: こあれはありえますか? バグまたは、バグフィックスまた、それ以外の何かか?
私は 4 つのケースすべてを検討しますが、順不同です。
4番目のケースは不可能です。 名前変更の記憶の最適化を行わないコードで競合が発生しないようにするには、 B:oldfile が A:oldfile と正確に一致する必要があります — 一致しない場合は、変更/削除 の競合が発生します。 A:oldfile が B:oldfile と正確に一致する場合、A:oldfile と、A':newfile と B:oldfile の間の3方向のコンテンツマージは競合せず、結果として A' からの newfile のバージョンを提供します。
そして4番目のケースと同一のロジックにより、2 番目のケースは実際には同一のマージになります。 A:oldfile が B:oldfile と正確に一致する場合、検出されない名前変更はこのように言います「ええっと、一方が ‘oldfile` を変更せず、もう一方がそれを削除したようです。よってそれを削除します。 そして、 A に newfile という名前の新しいファイルがあるので、そのままにしておきます。」 これは、 A:oldfile と A’:newfile と B:oldfile の3方向のコンテンツマージと同一の結果をもたらします — A' からの newfile のバージョンを含む oldfile の削除が結果に表れます。
3番目のケースは興味深いです。 これは、A:oldfile と A':newfile が十分に類似しているだけでなく、それらの間の変更が A:oldfile と B:oldfile の間の変更と競合しなかったことを意味します。 これは、ファイルが 3 方向のコンテンツ マージで使用できるほど類似しているという私たちの推測を検証したものでして、つまり、このように使用したことは完全に正しいと思われます。 (補足: ここでの 1 つの特定の例は、啓発的かもしれません。 B が A の直接の復帰(revert)であるとしましょう。A は B の直接の親であるため、B は明らかに A の完全な復帰(revert)でした。 コミットを選択できる場合は、その即時の復帰(revert)も選択できるはずです。 ただし、これは面白いレアケース(corner cases)の 1 つです。 この最適化がなければ、コミットをきれいに選択することに成功しましたが、E:oldfile と A:oldfile のサイズが異なるため、直ちに戻す(revert)ためにそれをチェリーピック(cherry-pick)することはできません。)
考慮すべきなのは最初のケースのみです — それは最適化の有無にかかわらず競合が発生した場合です。 最適化を行わないと、 変更/削除 の競合が発生し、 A':newfile と B:oldfile の両方がツリーに残され、ユーザーが処理できるようになり、2 つの潜在的な類似性についてのヒントがなくなります。 最適化により、 A:oldfile と A':newfile と B:oldfile が 3 方向のコンテンツマージされ、ファイルが関連していると思われる競合マーカーが表示されますが、ユーザーには解決する機会が与えられます。 前述したように、「oldfile」と「newfile」は E と G の間だったので、ユーザーは「oldfile」と「newfile」を関連性があるものとして扱っていることに驚かないと思います。 いずれにせよ、どちらの場合も競合に遭遇し、ユーザーに知っていることを伝え、解決するように依頼したため、このケースは心配する必要がはありません。
つまり、要約すると、ケース 4 は不可能であり、ケース 2 は同一の振る舞いをもたらし、ケース 1 と 3 は、最適化を使用しない場合と同じか、またはより良い振る舞いをもたらすように見えます。
7. Interaction with skipping of "irrelevant" renames
以前の最適化では、「無関係」(irrelevant)と見なされるパスの名前変更検出をスキップしていました。 たとえば、以下のコミットを参照してください:
-
32a56dfb99 ("merge-ort: precompute subset of sources for which we need rename detection", 2021-03-11)
-
2fd9eda462 ("merge-ort: precompute whether directory rename detection is needed", 2021-03-11)
-
9bd342137e ("diffcore-rename: determine which relevant_sources are no longer relevant", 2021-03-13)
関連性は常に、履歴の「相手側」(other side)が何をしたかによって決定されます。たとえば、 our side が名前を変更したファイルを変更したり、 our side が名前を変更したディレクトリにファイルを追加したりします。 これは、リベースまたはチェリーピックでシリーズの最初のコミットを選択するときに「無関係」(irrelevant)であるパスが、次のコミットを選択するときに突然「関連あり」(relevant)になる可能性があることを意味します。
この結果、関連するパスの名前変更検出結果のみをキャッシュすることができ、そしてそれゆえに、後続のコミットで関連性を再確認する必要があります。 これらの後続のコミットに、名前変更の検出に関連する追加のパスがある場合は、名前変更の検出をやり直す必要があります — ただし、名前変更がまだ検出されていないパスに限定することはできます。
8. Additional items that need to be cached
私達は、名前変更だけでなく、それ以上のものをキャッシュしなければならないことがわかりました。 私達は以下もキャッシュします:
A) non-renames (i.e. unpaired deletes)
B) counts of renames within directories
C) sources that were marked as RELEVANT_LOCATION, but which were
downgraded to RELEVANT_NO_MORE
D) the toplevel trees involved in the merge
これらはすべて以下のとおり struct rename_info に格納されます
-
cached_pairs (値が NULL の場合のみ、実際の名前変更とともに)
-
dir_rename_counts
-
cached_irrelevant
-
merge_trees
The reason for (A) comes from the irrelevant renames skipping optimization discussed in section 7. The fact that irrelevant renames are skipped means we only get a subset of the potential renames detected and subsequent commits may need to run rename detection on the upstream side on a subset of the remaining renames (to get the renames that are relevant for that later commit). Since unpaired deletes are involved in rename detection too, we don’t want to repeatedly check that those paths remain unpaired on the upstream side with every commit we are transplanting.
The reason for (B) is that diffcore_rename_extended() is what generates the counts of renames by directory which is needed in directory rename detection, and if we don’t run diffcore_rename_extended() again then we need to have the output from it, including dir_rename_counts, from the previous run.
The reason for (C) is that merge-ort’s tree traversal will again think those paths are relevant (marking them as RELEVANT_LOCATION), but the fact that they were downgraded to RELEVANT_NO_MORE means that dir_rename_counts already has the information we need for directory rename detection. (A path which becomes RELEVANT_CONTENT in a subsequent commit will be removed from cached_irrelevant.)
The reason for (D) is that is how we determine whether the remember renames optimization can be used. In particular, remembering that our sequence of merges looks like:
Merge 1:
MERGE_BASE: E
MERGE_SIDE1: G
MERGE_SIDE2: A
=> Creates A'
Merge 2:
MERGE_BASE: A
MERGE_SIDE1: A'
MERGE_SIDE2: B
=> Creates B'
この最適化を可能にするのは、ツリー A と A' が Merge 1 と Merge 2 の両方に表示され、A が A' の親であるという事実です。 そのため、次にマージするように求められたものと比較するために、ツリーを保存します。
9. How directory rename detection interacts with the above and why this optimization is still safe even if merge.directoryRenames is set to "true".
仮定セクションで述べたように:
"""
…ディレクトリの名前変更が発生したときに、merge.directoryRenames のデフォルトが
`conflict` に設定されていることは、ユーザーが競合を解決するために操作が停止(stop)し、
キャッシュが破棄されることを意味することにも注意してください。
つまり、最適化は適用されません。
したがって、ディレクトリの名前変更に具体的に対処する必要がある唯一の理由は、
一部のユーザーが merge.directoryRenames を `true` に設定して、
マージが自動的に続行できるようにするためです。
"""
特定のピックが次のピックにどのように影響するかを調べる必要があることを思い出してください。 それでは、セクション 1 の図の最初の 2 つのピックをもう一度見てみましょう:
First pick does this three-way merge:
MERGE_BASE: E
MERGE_SIDE1: G
MERGE_SIDE2: A
=> creates A'
Second pick does this three-way merge:
MERGE_BASE: A
MERGE_SIDE1: A'
MERGE_SIDE2: B
=> creates B'
現在、ディレクトリの名前変更検出が存在するため、履歴の一方がディレクトリを名前変更し、もう一方が古いディレクトリに新しいファイルを追加した場合、マージ(merge.directoryRenames=true を使用)によってファイルを新しいディレクトリに移動できます。 古いディレクトリに新しいファイルを追加するには、質的に異なる 2 つの方法があります。 新しいファイルを作成するか、ファイル名を名前変更してそのディレクトリにします。 また、ディレクトリの名前変更は履歴のどちらの側でも実行できるため、考慮すべき 4 つのケースがあります:
-
MERGE_SIDE1 renames old dir, MERGE_SIDE2 adds new file to old dir
-
MERGE_SIDE1 renames old dir, MERGE_SIDE2 renames file into old dir
-
MERGE_SIDE1 adds new file to old dir, MERGE_SIDE2 renames old dir
-
MERGE_SIDE1 renames file into old dir, MERGE_SIDE2 renames old dir
これら 4 つのケースを検討する前に、最後に 1 つ注意してください。ディレクトリの名前変更の検出に関して、この最適化をどのように実装するかについて、これらすべてのケースを検討する際に留意する必要があるいくつかの重要な性質があります:
-
ディレクトリの名前変更を適用(apply)した「後」に、名前変更のキャッシュ(rename caching)が発生します
-
ディレクトリの名前変更検出によって作成された名前変更は、ディレクトリの名前変更を行った履歴の側(the side of history)に記録されます。
-
dir_rename_counts, the nested map of {oldname ⇒ {newname ⇒ count}}, is cached between runs as well. This basically means that directory rename detection is also cached, though only on the side of history that we cache renames for (MERGE_SIDE1 as far as this document is concerned; see the assumptions section). Two interesting sub-notes about these counts:
-
指定された側(side)で名前変更検出を再度実行する必要がある場合(例: 一部のパスは、以前にはなかった名前変更検出に関連しています)は、dir_rename_counts をクリアして再計算し、cached_pairs を使用します。 これを行うことが重要な理由は、RELEVANT_LOCATION 周辺の最適化が存在するためです。これにより、ディレクトリの名前変更検出のために不要な名前変更を計算したり、無関係なディレクトリの dir_rename_counts を計算したりできなくなります。 ただし、その後のマージでは、同じ名前またはディレクトリが必要になる場合があります。 このような場合に dir_rename_counts を「修正」する最も簡単な方法は、単に再計算することです。
-
rename/rename(1to1) エントリをキャッシュから刈り込み(prune)する場合は、dir_rename_counts を更新して、関連するディレクトリと関連する親ディレクトリの数を減らす必要もあります(名前変更が最初に見つかったときに diffcore-rename.c の update_dir_rename_counts() がインクリメントしたものを元に戻すため)。 代わりに、非常にまれな 名前変更/名前変更(1to1) のケースが発生したときに名前変更の記憶の最適化を無効にすると、上記のように、次に名前変更の検出が発生したときに dir_rename_counts が再計算されます。
-
-
選択する複数のコミットがある側は、名前変更をキャッシュしない履歴の側(side)です。 したがって、ディレクトリ名変更検出 (常に過半数を埋める) によって行われるものを除いて、ディレクトリ内の名前変更の数を変更するための追加のコミットはありません。
-
以下に示すように、キャッシュする「名前変更」は、ディレクトリの名前変更によってわずかに変更されます。
さて、これらの注意事項を整理して、4 つのケースを順番に見ていきます:
ケース 1: MERGE_SIDE1 は old dir の名前変更し、 MERGE_SIDE2 は old dir に新しいファイルを追加します
This case looks like this:
MERGE_BASE: E, Has olddir/
MERGE_SIDE1: G, Renames olddir/ -> newdir/
MERGE_SIDE2: A, Adds olddir/newfile
=> creates A', With newdir/newfile
MERGE_BASE: A, Has olddir/newfile
MERGE_SIDE1: A', Has newdir/newfile
MERGE_SIDE2: B, Modifies olddir/newfile
=> expected B', with threeway-merged newdir/newfile from above
In this case, with the optimization, note that after the first commit:
* MERGE_SIDE1 remembers olddir/ -> newdir/
* MERGE_SIDE1 has cached olddir/newfile -> newdir/newfile
Given the cached rename noted above, the second merge can proceed as
expected without needing to perform rename detection from A -> A'.
Case 2: MERGE_SIDE1 は old dir を名前変更し、 MERGE_SIDE2 はファイルの名前を old dir に名前変更します
This case looks like this:
MERGE_BASE: E oldfile, olddir/
MERGE_SIDE1: G oldfile, olddir/ -> newdir/
MERGE_SIDE2: A oldfile -> olddir/newfile
=> creates A', With newdir/newfile representing original oldfile
MERGE_BASE: A olddir/newfile
MERGE_SIDE1: A' newdir/newfile
MERGE_SIDE2: B modify olddir/newfile
=> expected B', with threeway-merged newdir/newfile from above
In this case, with the optimization, note that after the first commit:
* MERGE_SIDE1 remembers olddir/ -> newdir/
* MERGE_SIDE1 has cached olddir/newfile -> newdir/newfile
(NOT oldfile -> newdir/newfile; compare to case with
(p->status == 'R' && new_path) in possibly_cache_new_pair())
Given the cached rename noted above, the second merge can proceed as
expected without needing to perform rename detection from A -> A'.
Case 3: MERGE_SIDE1 adds new file to old dir, MERGE_SIDE2 renames old dir
This case looks like this:
MERGE_BASE: E, Has olddir/
MERGE_SIDE1: G, Adds olddir/newfile
MERGE_SIDE2: A, Renames olddir/ -> newdir/
=> creates A', With newdir/newfile
MERGE_BASE: A, Has newdir/, but no notion of newdir/newfile
MERGE_SIDE1: A', Has newdir/newfile
MERGE_SIDE2: B, Has newdir/, but no notion of newdir/newfile
=> expected B', with newdir/newfile from A'
In this case, with the optimization, note that after the first commit there
were no renames on MERGE_SIDE1, and any renames on MERGE_SIDE2 are tossed.
But the second merge didn't need any renames so this is fine.
ケース 4: MERGE_SIDE1 はファイルの名前を old dir に名前変更し、MERGE_SIDE2 は old dir を名前変更します
This case looks like this:
MERGE_BASE: E, Has olddir/
MERGE_SIDE1: G, Renames oldfile -> olddir/newfile
MERGE_SIDE2: A, Renames olddir/ -> newdir/
=> creates A', With newdir/newfile representing original oldfile
MERGE_BASE: A, Has oldfile
MERGE_SIDE1: A', Has newdir/newfile
MERGE_SIDE2: B, Modifies oldfile
=> expected B', with threeway-merged newdir/newfile from above
In this case, with the optimization, note that after the first commit:
* MERGE_SIDE1 remembers oldfile -> newdir/newfile
(NOT oldfile -> olddir/newfile; compare to case of second
block under p->status == 'R' in possibly_cache_new_pair())
* MERGE_SIDE2 renames are tossed because only MERGE_SIDE1 is remembered
Given the cached rename noted above, the second merge can proceed as
expected without needing to perform rename detection from A -> A'.
最後に、 skip-irrelevant-renames 最適化との相互作用により、名前変更されたディレクトリ内のすべてのファイルの名前変更検出しない場合があることをここで指摘しておきます。 このような場合、ディレクトリが名前変更されたかどうかはわかりません。 ある種の「このディレクトリは名前変更されていません」ステートメントをキャッシュしないように注意する必要があります。 もしそうなら、リベースされている後続のコミットはファイルを古いディレクトリに追加する可能性があり、ユーザーはそれが正しいディレクトリにあることを期待するでしょう — つまり「このディレクトリは名前変更されていません」という誤ったキャッシュが除外されます。