という文字は、既存のコミットを変更することを意味するため、実際には使用できません。この場合、既存のコミットF
は、B
のハッシュIDをその親として格納します。既存のコミットは変更できません。コミットは完全に読み取り専用です(実際は、Gitの内部オブジェクトはすべて読み取り専用です)。
私は、既存のコミットのいずれかに関連付けられたスナップショットを変更せず、代わりにその親としてそのF
店E
のハッシュIDをふりするとします。あなたはになります。になります。になります。と同様のことがいくつかあります。しかし、それらを見る前に、git rebase
を見てみましょう。
ここgit rebase
を使用しての問題はそれが物事が行くことを始めるところである(git cherry-pick
を使用してかのように行われた各コピーで、(今のところすべてが順調と良いです)コピーコミットによって機能をリベースであります違う)。
がコミットの親のスナップショット、例えば、I
対J
、またはB
対F
対のコミットのスナップショットの比較:にGitはします、これは、単一のコミットチェリー選びます。 git diff <hash1> <hash2>
を実行するか、またはより簡単にgit show <hash2>
を実行して、自分で行うことができます。
ここに示す差分を親(<hash1>
の上記)から現行またはHEAD
の差分にマージし、マージ結果を使用して新しいコミットを行います。
元のコミットのメッセージを新しいコミットにコピーします。
いつものように、分岐1が長くコミットすること、現在のブランチに行き、新しいコミットとHEAD
を変更するには、新しいコミットに名前を付けるためにコミット。
マージステップと、Gitがスナップショット(コミット)をチェンジセット(差分)に変換しているという事実は、チェリーピックされた最終結果が元のものとは非常に異なるスナップショットになる可能性があることを意味します。これはすべて、プロセスを開始するときに<hash1>
が何であるかによって異なります(HEAD
)。
コマンドは基本的に、一連のコミットを一度にチェリーピッキングするプロセスを自動化します。すべてのコピーが終了すると、git rebase
はブランチラベルを最後にコピーされたコミットを指すように強制します。これは、そのように上のその親F'
ある新しいG'
にG
をコピーし、そして、あなたがその親E
ある新しいF'
にF
をコピーすることが可能になります。
A--B--F--G--H--I--J <-- (original)
\
C--D--E <-- (target of rebase)
\
F'-G'-H'-I'-J' <-- branch
時々git rebase
は文字通りgit cherry-pick
を実行します時には、通常は同じ結果を生成するはずのメソッドを使用しますが、いくつかの奇妙なコーナーケースではそうしません。
は今、それが
F
を使用しようとしていたときに代わりにこのすべての、私たちは、Gitはに「ルックアサイド」できるのGitオブジェクトを作成することとします。以下のための内部GitのハッシュIDを含め、同様
A--B--F--G--H--I--J <-- branch
\
C--D--E <-- some_name
\
Frepl <-- refs/replace/<hash>
我々はFrepl
のためにそれにF
のコミットメッセージをコピーし、他の分野のほとんどをコピーします。この置換-FOR-F
は、このようなグラフになりますコミットのためのツリーオブジェクト。しかし、B
をコミットする代わりに、をコミットしてE
をコミットしてください。
何か理由でコミットしようとする度に、F
からFrepl
に「目を向ける」ためにGitが必要です。そして、これを行う方法があります:refs/replace/big-ugly-hash-id
のrefs/replace/big-ugly-hash-id
のFrepl
を与えます。ここでbig-ugly-hash-id
はコミットF
の実際のハッシュIDです。
置換を行うGitコマンドはgit replace
です。探しているものは自動です:私たちがgit --no-replace-objects
を実行しない限り、Gitは常にそれを行います。これはすべて完了しましたの変更なし Gitオブジェクトなので、リポジトリに追加するだけです。
置換オブジェクトへの最大の欠点は、デフォルトでは、交換用のオブジェクトをコピーしませんgit clone
(それはそれ、また自分の名前をフェッチしない)ということです。これは、クローンが見た目で置換を持たず、それを脇に見ないことを意味します。 fetch refspecに置換を明示的に追加して取得することはできますが、それは少し苦労します。 git push
操作では、デフォルトで転送されません。
大きな利点は、新しく改良されたコミットのために元のコミットの使用を誰もが止める必要がないことです。 誰もが切り替えられる場合は、git rebase
を使用して多くのコミットをコピーし、ブランチラベルを移動できます。または、最初にgit replace
を使用して置換オブジェクトを作成し、git filter-branch
をフィルタなしで実行しますが、置き換えが発生したブランチをフィルタリングするように指示することができます。何git filter-branch
は(のブランチ上でそれをフィルタリングするように言われています)すべてのコミットコピーすることです。それは少なくとも論理的にはたくさんの最適化があります。各コミットを最も古いコミットから最新のものへと一時ディレクトリに移動し、ある順序ですべてのフィルタを適用し、次にフィルタを使って新しいコミットを行います。新しいコミットが元のビットと同じビットである場合、2つのコミットは実際には1つのコミットになります。そうでない場合、コピーは新しいコミットです。それぞれの新しいコピーのデフォルトの親は、親の以前のコピーで作成されたコミットです(しかし、それを変更できるように--parent-filter
があります)。これはすべて置換のルックアサイドギミックが起こって、ですので、GitがコピーF
に行くと、実際にはFrepl
がコピーされ、その後に戻り、E
に戻ります。私たちは、このフィルタに名前branch
を持っている場合、結果はGitのコピーそして、その後、その後、その後、その後、その後、その後、その後、その後A
、B
、C
、D
、E
、Frepl
、G
、H
、I
、およびJ
は、与えていることである:
A--B--F--G--H--I--J <-- refs/original/refs/heads/branch
\
C--D--E <-- some_name
\
Frepl <-- refs/replace/<hash>
\
G'-H'-I'-J' <-- branch
I'
であるが、ツリー(スナップショット)がJ
と同じであるJ'
がコミットすることを指していることに注意してください。 に戻ると、G'
に戻り、Frepl
に戻ります(git replace
を実行したときに元に戻したコピー:フィルタリング中は変更されません)。これはE
(それ自体の変更されていないコピー)を指し、D
に戻るなど、A
に戻ります。
refs/original/refs/heads/branch
のようなすべてのrefs/original/
の名前を投げ捨てると、最終的な効果は、置換コミットを「セメントに埋め込む」ことです。 という名前のrefs/replace/
を削除すると、リポジトリにコミットしたかのように見えます。A-B-C-D-E-Frepl-G'-H'-I'-J'
多くのコミットのハッシュIDが変更されているため、このリポジトリは元のリポジトリと互換性がなくなりました。 branch
という名前から始めれば、すべての適切な場所に新しくコピーされたコミットのみが表示されます。もちろん
あなたがgit --no-replace-objects filter-branch
を実行した場合、それは交換用の索引を無効にします。これを行う理由は決してありません。