1. ホーム
  2. git

[解決済み] git rebase と git merge --ff-only の違いは何ですか?

2022-11-07 13:06:01

質問

読む限りでは、どちらもリニアな履歴を得るのに役立つようです。

私が実験したところでは、rebase は常に動作します。しかし、merge --ff-onlyは早送りできるシナリオでのみ動作します。

また、git merge はマージコミットを作成しますが、--ff-only を使用すると、本質的に git rebasing と等しい線形履歴を与えることに気づきました。つまり、--ff-only は git merge の目的を殺しているのですね?

では、実際に両者の違いは何なのでしょうか?

どのように解決するのか?

以下のことに注意してください。 git rebase は、異なる ジョブ よりも git merge (ありでもなしでも --ff-only ). 何 rebase が行うのは、既存のコミットを取り込んで をコピーすることです。 を行うことです。 たとえば、あなたが branch1 にいて、二つのコミットを行ったとします。 AB :

...-o--o--A--B   <-- HEAD=branch1
        \
         o--C    <-- branch2

で、この二つのコミットは、むしろ branch2 に置くことにしました。 できます。

  • で行った変更の一覧を取得する。 A (差分 A の親に対して)
  • で行った変更の一覧を取得します。 B (差分 B に対して A )
  • に切り替えます。 branch2
  • で行ったのと同じ変更を A で行ったのと同じ変更を行い、コミットし、コミットメッセージを A このコミットを A'
  • で行ったのと同じ変更を B で行ったのと同じ変更を行い、コミットし、コミットメッセージを B と呼びましょう。 B' .

このdiffとcopyとcommitを行うgitコマンドがあります。 git cherry-pick . というわけです。

git checkout branch2      # switch HEAD to branch2 (commit C)
git cherry-pick branch1^  # this copies A to A'
git cherry-pick branch1   # and this copies B to B'

これで、こうなりました。

...-o--o--A--B         <-- branch1
        \
         o--C--A'-B'   <-- HEAD=branch2

これで、再び branch1 に戻り、元の AB を使用すると git reset (私が使うのは --hard を使うことにします。その方が作業ツリーもすっきりするので便利です)。

git checkout branch1
git reset --hard HEAD~2

これは、元の AB , 1 ということで、これで

...-o--o               <-- HEAD=branch1
        \
         o--C--A'-B'   <-- branch2

これで、再チェックアウトする必要がある branch2 を再チェックアウトしてそこで作業を続ける必要があります。

これは git rebase これはコミットを移動させます (ただし、実際に移動させることはできません。git ではコミットを変更することはできないので、親 ID を変更するだけでも新しい別のコミットにコピーする必要があります)。

言い換えれば git cherry-pick は自動的な差分とやり直しで 1 のコミットです。 git rebase をやり直す自動化されたプロセスです。 複数の さらに、最後にラベルを移動してオリジナルを忘れたり隠したりします。

上記は、あるローカルブランチからコミットを移動する様子を示しています。 branch1 から別のローカルブランチ branch2 というように、gitでは 全く同じ処理 を実行したときに新しいコミットを取得するリモート追跡ブランチがある場合、コミットを移動させるためにまったく同じ処理を行います。 git fetch を実行したときに新しいコミットを取得するリモート追跡ブランチがある場合 (これには fetch の最初のステップである git pull ). まずブランチ feature の上流にある origin/feature の上流にある に移動し、自分自身でいくつかコミットしてください。

...-o        <-- origin/feature
     \
      A--B   <-- HEAD=feature

しかし、上流で何が起こったかを確認する必要があると判断し、次のように実行します。 git fetch , 2 そして、上流の誰かがコミットした C :

...-o--C     <-- origin/feature
     \
      A--B   <-- HEAD=feature

この時点では、単に feature 's AB にかけて C を与える。

...-o--C     <-- origin/feature
        \
         A'-B'  <-- HEAD=feature

これらは、あなたのオリジナルの AB であり、オリジナルはコピー完了後に捨てられる(ただし脚注1参照)。


リベースするものがない、つまり、あなた自身が行った作業がないこともあります。 つまり、グラフの前の fetch はこのようになります。

...-o      <-- origin/feature
           `-- HEAD=feature

もし、あなたが git fetch とコミットして C が入ってくるのですが、このとき残されるのは あなたの feature ブランチは古いコミットを指し示し、一方 origin/feature は先に進んでいます。

...-o--C   <-- origin/feature
     `---- <-- HEAD=feature

これは git merge --ff-only の出番です。現在のブランチをマージするように頼むと featureorigin/feature のように、矢印を前方にスライドさせることが可能であることを git は理解しています。 feature が直接コミットを指すようになります。 C . 実際のマージは必要ありません。

自分自身のコミットがあった場合 AB にマージしてほしいという要望がありました。 C を指定すると、git は本当の意味でのマージを行い、新しいマージコミット M :

...-o--C        <-- origin/feature
     \   `-_
      A--B--M   <-- feature

ここで --ff-only は停止してエラーを出します。 一方、Rebase は AB から A' であり B' を削除し、元の AB .

要するに(もう遅いか:-))、これらは単に異なることをするのです。 結果は同じこともあれば、そうでないこともあります。 もしコピーするのがOKなら AB を使用することができます。 git rebase を使うこともできますが、何か正当な理由がある場合は ではなく を使うことができます。 git merge で、おそらく --ff-only で、適宜merge-or-failします。


1 Git は実際にはオリジナルをしばらく(この場合は通常一ヶ月間)保持しますが、それを隠しておきます。 これを見つける最も簡単な方法は、git の "reflogs" で、各ブランチがどこを指していたのか、そしてどこを指していたのかを履歴として残しておくのです。 HEAD を更新する前に、それぞれのブランチがどこを指していたのか、そして がどこを指していたのかの履歴を保持しています。 HEAD .

最終的に reflog の履歴エントリは期限切れになり、その時点でこれらのコミットは ガベージコレクション .

2 あるいは、やはり git pull を実行することによって始まる便利なスクリプトです。 git fetch . 取得が完了すると、コンビニエンス・スクリプトは以下のいずれかを実行します。 git merge または git rebase というように、設定や実行の仕方によって異なります。