数日前、master
完全に直線的な履歴を持つブランチがありました。その後、 という機能ブランチを作成しました。このブランチを と呼びますfeat/feature-a
。そのブランチで作業した後、 にマージされるようコードレビューに提出しましたmaster
。
がレビューされている間にfeat/feature-a
、 によって導入されたコードに依存する別の機能に取り組みたいと思いましたfeat/feature-a
。そこで、ブランチfeat/feature-b
から ブランチを作成しましたfeat/feature-a
。
に取り組んでいる間にfeat/feature-b
、feat/feature-a
がマスターにマージされました。そのため、マスターには によって導入されたコードが含まれるようになりましたfeat/feature-a
。マスターにマージしたいのですfeat/feature-b
が、次のようなマージ競合が多数発生します。
<<<<<<< HEAD
=======
// Some code that was introduced by feat/feature-b
>>>>>>> c948094... My commit message from feat/feature-b
feat/feature-a
私の推測では、変更を自分のブランチに取り入れたためfeat/feature-b
、それらの変更を「複製」しようとして、マージ競合が発生していると考えられます。
これらは手動で解決できますが、数十のファイルに複数回存在するため、より良い解決策があれば知りたいです。
ベストアンサー1
要約: 使用git rebase --onto <target> <limit>
としてコメントで役に立たない提案実際のマージであれば、このようなことは起きないはずです。ここで「実際のマージ」とはどういう意味か、また、問題のコミットのグラフを描くと、どのように分岐するかの図を示します。まず、次のようなものから始めます。
...--E---H <-- master
\
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
ここでは2つのコミット(正確な数は重要ではないが)があり、のみfeat/feature-b
、I
と呼ばれる、J
ここ; 2つのコミットが にあります両方機能ブランチはF
および と呼ばれG
、コミットは1つあります。のみmaster
、 と呼ばれます。H
(コミットE
およびそれ以前のコミットは3つすべて枝。
を に実際のマージしてと をmaster
取り込むとします。グラフ形式では、次のようになります。F
G
...--E---H--K <-- master
\ /
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
実際のマージはK
、親コミット履歴ポインタとして、コミットH
(master
)とG
( )の両方を持っていることに注意してください。そのため、Gitは後で、マージが「 から開始する」ことを意味することをfeat/feature-a
認識します。(より正確には、コミットはJ
G
G
マージベース後でマージするためです。
このマージはうまくいきました。しかし、以前はそうではありませんでした。代わりに、マージを行った人はいわゆる「squash merge」機能を使用していました。squash-mergeは同じものをもたらしますが、変更実際のマージとは異なり、マージはまったく生成されません。代わりに、マージされたコミットの数に関係なく、それらの作業を複製する単一のコミットが生成されます。この場合、 と の作業を複製するF
ためG
、次のようになります。
...--E---H--K <-- master
\
F--G <-- feat/feature-a
\
I--J <-- feat/feature-b
K
からへのバックポインタがないことに注意してくださいG
。
したがって、マージ(実際のまたはsquash-not-really-a-"merge") の場合feat/feature-b
、Git は で始まるべきだと考えますE
。(技術的には、 は、以前の実際のマージの場合のようにE
ではなく、マージ ベースですG
。) ご覧のとおり、これによりマージ競合が発生します。(多くの場合、それでも「正常に機能」しますが、このケースのように、機能しない場合もあります。)
それは将来的には問題ないかもしれないが、今の問題はそれをどう修正するかだ。
ここでやりたいことはコピー独占的feat/feature-b
にコミットする新しいコミットは、 の後に続きますK
。つまり、図は次のようになります。
I'-J' <-- feat/feature-b
/
...--E---H--K <-- master
\
F--G <-- feat/feature-a
\
I--J [no longer needed]
これを行う最も簡単な方法はリベースこれらのコミットは、リベース以来手段コピー。問題は、単純なgit checkout feat/feature-b; git rebase master
コピーでは多すぎるコミットします。
解決策は伝えることですgit rebase
コピーすることを約束するこれを行うには、引数を からmaster
(feat/feature-a
またはコミットの生のハッシュID G
、つまり最初の1つのコミットを識別するもの)に変更します。ないコピーする)しかし、それはgit rebase
すでに存在する場所にコピーすることを意味するので、それは良くありません。新しい問題は、 を追加することです--onto
。これにより、「コピー先」の部分と「何をコピーするか」の部分を分離できます。
git checkout feat/feature-b
git rebase --onto master feat/feature-a
(これは、名前がfeat/feature-a
commit を指していることをG
前提としています。そうでない場合は、 commit に名前を付ける別の方法を見つける必要がありますG
。コミット ハッシュを見つけるには、独自のグラフを描画したり、出力を詳しく調べたりする必要があるかもしれませんgit log
)。
1 Git スタイルの逆方向の「最初」です。最新のコミットから始めて、古いコミットへの接続を逆方向にたどります。Git はすべてを逆方向に行うため、ここで逆方向に考えると役立ちます。:-)