2017-12-14 13 views
0

別のブランチの歴史に1つのブランチから挿入し、既存のコミット、:Gitリポジトリ:ように私は別の歴史の中に1つのブランチからいくつかのコミットを挿入したい

A - B - F - G - H - I - J (branch working) 
    \ 
     C - D - E   (old branch) 

になる...

A - B - C - D - E - F - G - H - I - J (continue with branch working) 

私がこれまでに試したことは、複数の競合が継続しないようにしましたが、それ以降のコミットの状態が同じであれば、私は気にしません。コミットFがコミットEの子になる方法を「知らない」という問題はありますか?

答えて

2

という文字は、既存のコミットを変更することを意味するため、実際には使用できません。この場合、既存のコミットFは、BのハッシュIDをその親として格納します。既存のコミットは変更できません。コミットは完全に読み取り専用です(実際は、Gitの内部オブジェクトはすべて読み取り専用です)。

私は、既存のコミットのいずれかに関連付けられたスナップショットを変更せず、代わりにその親としてそのFEのハッシュIDをふりするとします。あなたはになります。になります。になります。と同様のことがいくつかあります。しかし、それらを見る前に、git rebaseを見てみましょう。

ここgit rebaseを使用しての問題はそれが物事が行くことを始めるところである(git cherry-pickを使用してかのように行われた各コピーで、(今のところすべてが順調と良いです)コピーコミットによって機能をリベースであります違う)。

  • がコミットの親のスナップショット、例えば、IJ、またはBF対のコミットのスナップショットの比較:に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-idrefs/replace/big-ugly-hash-idFreplを与えます。ここで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のコピーそして、その後、その後、その後、その後、その後、その後、その後、その後ABCDEFreplGHI、および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を実行した場合、それは交換用の索引を無効にします。これを行う理由は決してありません。

関連する問題