いつGitマージの代わりにGitリベースを使用しますか?


1549

GitリベースとGitマージの使用はいつ推奨されますか?

リベースが成功した後もマージする必要がありますか?




6
リベースを使用したい人の1つの問題は、コードを定期的にプッシュするのを妨げることです。したがって、クリーンな履歴が必要なため、コードを共有できなくなります。
static_rtti

9
@static_rtti:それは真実ではありません。変更を定期的にプッシュできない場合は、リベースベースのフローを間違って使用しています。
juzzlin

5
Andrew Arnott氏の回答とPace氏の回答が以前に投稿されていなかったのは本当に残念です。
マークブース

回答:


1136

短縮版

  • Mergeは1つのブランチのすべての変更を取得し、1つのコミットでそれらを別のブランチにマージします。
  • リベースで、分岐したポイントを新しい開始ポイントに移動したい

では、どちらを使用するのですか?

マージ

  • 単一の機能を開発する目的でブランチを作成したとします。これらの変更をマスターに戻したい場合は、おそらくマージが必要です(すべての暫定コミットを維持する必要はありません)。

リベース

  • 2番目のシナリオは、開発を開始し、別の開発者が無関係な変更を加えた場合です。おそらく、プルしてからリベースして、リポジトリの現在のバージョンからの変更をベースにします。

105
@Robは、マージ時に暫定コミットを維持することについて言及しました。デフォルトでは、ブランチB(作業中の機能ブランチ)をブランチM(マスターブランチ)にマージすると、2つが分岐してからBで行われたコミットごとに1つのコミットがMに作成されると思います。ただし、-squashオプションを使用してマージすると、ブランチBで行われたすべてのコミットが「まとめて」まとめられ、ブランチMで単一のコミットとしてマージされ、マスターブランチのログがきれいに保たれます。独立して作業し、マスターにマージする開発者が多数いる場合は、スカッシングがおそらく必要です。
spaaarky21 2013年

19
マージに関する@ spaaarky21の仮定は正しくないと思います。ブランチBをマスターMにマージする場合、プレーンマージを使用するか--squashマージを使用するかに関係なく、Mでのコミットは1つだけです(Bに複数のコミットがある場合でも)。--squashが行うことは、親としてのBへの参照を削除することです。良い視覚化はここにあります:syntevo.com/smartgithg/howtos.html
page=

14
@jpeskinそれは私が見ているものではありません。確認のために簡単なテストを行いました。テキストファイル、init新しいリポジトリ、addファイル、およびを含むディレクトリを作成しますcommit。新しい機能ブランチをチェックアウト(checkout -b feature。)テキストファイルを変更し、コミットして繰り返し、機能ブランチに2つの新しいコミットがあるようにします。その後checkout mastermerge feature。ではlog、マスターでの最初のコミットに続いて、機能からマージされた2つが表示されます。の場合merge --squash feature、機能はマスターにマージされますがコミットされません。そのため、マスターでの唯一の新しいコミットは自分で作成したものになります。
spaaarky21 2013年

21
@ spaaarky21どちらも半分正しいようです。早送りマージが可能な場合(例のように)、gitはデフォルトですべてのコミットを機能ブランチBに含めます(または、ご提案のとおり、-squashを使用して1つのコミットに結合できます)。しかし、マージする2つの分岐ブランチMとBがある場合、Mにマージされた場合、gitはブランチBからの個々のコミットのすべてを含みません(--squashを使用するかどうかに関係なく)。
jpeskin 2013年

6
なぜこの回答に「(あなたはすべての暫定コミットを維持することを気にしない)」というのはまだ脇にあるのですか?それは'09年には意味がなく、今では意味がありません。また、他の開発者が必要な関連変更を行った場合にのみ、リベースする必要があります。関係のない変更を行った場合、機能ブランチは競合することなく簡単にマージされ、履歴が維持されます。
マークブース

372

それは簡単です。リベースでは、作業の新しいベースとして別のブランチを使用すると言います。

たとえば、ブランチmasterがある場合、新しい機能を実装するブランチを作成し、それに名前を付けます。cool-featureもちろん、マスターブランチは新しい機能のベースです。

次に、ある時点で、masterブランチに実装した新しい機能を追加します。ブランチに切り替えてmasterマージするだけですcool-feature

$ git checkout master
$ git merge cool-feature

ただし、この方法では、新しいダミーコミットが追加されます。スパゲッティの歴史を避けたい場合は、リベースできます:

$ git checkout cool-feature
$ git rebase master

そして、それをマージしmasterます:

$ git checkout master
$ git merge cool-feature

今回は、トピックブランチに同じマスターのコミットと新しい機能によるコミットがあるため、マージは早送りになります。


31
but this way a new dummy commit is added, if you want to avoid spaghetti-history-どのように悪いのですか?
アレックス

6
また、マージの--no-ffフラグは非常に便利です。
Aldo 'xoen' Giambelluca

3
ユーザーSean Schofieldが@ アレックスをコメントに含めると、「最終的にurの内容をマスターにマージすると(すでに説明したように簡単です)、urの履歴の「トップ」にあるため、リベースも素晴らしいです。機能が書き込まれる可能性があるが数週間後にマージされるプロジェクトでは、履歴に戻ってマスターに「詰め込まれ」ているため、それらをマスターにマージしたくないだけです。個人的にはgit logを実行して、最近の機能は「トップ」にあります。コミット日は保持されます-リベースはその情報を変更しません。」
Adrien Be

4
すべてのこれらの用語は(ことを覚えておいてください-私はそれがここで繰り返しクマだと思うmergerebasefast-forward、など)有向非巡回グラフの特定の操作を参照しています。それらはそのメンタルモデルを念頭に置いて考えるのがより簡単になります。
ロイ・ティンカー

10
@Aldoリベースの履歴について、「クリーン」または「整頓」はありません。あなたは本当に何が起こっているのかわからないので、それは一般に不潔であり、私見はひどいです。「最もクリーンな」Gitの履歴は、実際に発生したものです。:)
Marnen Laibow-Koser

269

TSamperが述べ自分の答えを補足するために

  • マージの前に行うのがリベースであることがよくあります。これは、マージするブランチYの作業をブランチに統合するためBです。
    しかし、再び、マージする前にブランチの競合を解決します(つまり、「ブランチからの最近のポイントから開始して、ブランチでの作業を再生します」のように「リベース」B)。
    正しく実行されると、ブランチからブランチBは早送りすることができます。

  • マージは宛先ブランチに直接影響します B。つまり、マージは簡単です。さもなければ、ブランチBが安定した状態に戻るまでに時間がかかる可能性があります(すべての競合を解決するための時間)


リベース後のマージのポイント?

私が説明する場合、私はB自分のブランチにリベースし、自分のブランチにBとどまっている間に、からのより最近のポイントから自分の作業を再生する機会を得るためにだけです。
この場合でも、「リプレイ」した作業をに取り込むにはマージが必要ですB

他のシナリオ(たとえば、Git Ready説明されています)はB、リベースを通じて直接作業を取り込むことです(これにより、すてきなコミットがすべて保存されるか、インタラクティブなリベースを通じてそれらを並べ替える機会が与えられます)。
その場合(Bブランチにいるときにリベースする場合)は正しいです。それ以上のマージは必要ありません。

マージもリベースもしていないデフォルトのGitツリー

rebase1

私たちはリベースして取得します:

rebase3

その2番目のシナリオはすべてです。新しい機能をマスターに戻すにはどうすればよいですか。

最初のリベースシナリオについて説明する私のポイントは、リベースはその前段階としても使用できることをすべての人に思い出させることです(「新機能をマスターに戻す」ことです)。
リベースを使用して、最初にマスターを新しい機能のブランチに「組み込む」ことができます。リベースは、新しい機能のコミットをから再生しますがHEAD master、まだ新しい機能のブランチにあり、ブランチの開始点を古いマスターのコミットからに効果的に移動しますHEAD-master
これによりブランチ内のすべての競合を解決できます(つまり、競合の解決ステージに時間がかかりすぎる場合にマスターが並行して進化し続けることを許可します)。
次に、マスタとマージに切り替えることができますnew-feature(またはリベースnew-feature上にmaster、あなたの中で行わコミットを保持したい場合new-feature ブランチ)。

そう:

  • 「リベースvsマージ」は、たとえばの作業をインポートする2つの方法と見なすことができますmaster
  • しかし、「リベースしてマージ」は、最初に競合を個別に解決してから作業を取り戻すための有効なワークフローになる場合があります。

17
リベース後のマージは、競合を解決する必要のない簡単な早送りです。
obecalp 2009

4
@obelcap:確かに、これは一種のアイデアです環境内のすべての問題の競合(新機能ブランチ内のリベースマスター)を取り、次に共同マスターし、新機能をマージします:1ピコ秒(高速フォワード)マスターに進化がない場合
VonC 2009

27
最終的に自分のコンテンツをマスターにマージすると(既に説明したように取るに足らないことですが)、リベースはコミット履歴の「トップ」に配置されるため、素晴らしいです。機能が記述されていても数週間後にマージされる大規模なプロジェクトでは、履歴に戻ってマスターに「詰め込まれ」ているため、それらをマスターにマージしたくないだけです。個人的には、git logを実行して、その最近の機能を「トップ」に表示できることが好きです。コミット日は保持されることに注意してください-リベースはその情報を変更しません。
Sean Schofield

3
@Joe:精神的には、「私の変更(プライベートブランチで単独で行われたもの)を他のブランチの上で再生しますが、リベースが完了したらプライベートブランチに残してください」と言っています。これは、ローカルチェック履歴をクリーンアップし、「チェックポイントコミット」、分割された二分法、不適切な非難結果を回避する良い機会です。「Gitワークフロー」を参照してください:sandofsky.com/blog/git-workflow.html
VonC '15

4
@scoarescoareキーはローカルな変更に互換性があるかを確認することである上に、最新の上流分岐の。コミットの1つで競合が発生した場合、すぐにそれが表示されます。マージは1つだけ(マージされた)コミットを導入します。これにより、ローカルコミットの中でどれがその競合を追加したのかを簡単に確認する方法なしに多くの競合が引き起こされる可能性があります。だから、クリーナー履歴に加えて、あなたは変化のより正確なビューを取得あなたが紹介する、とは対照的に、(リベースで再生)コミットによってコミットすべての上流分岐によって導入された変更を(1つのマージに投棄)。
VonC 2013年

229

TL; DR

疑問がある場合は、マージを使用してください。

簡潔な答え

リベースとマージの唯一の違いは次のとおりです。

  • 結果の履歴のツリー構造(通常、コミットグラフを見るとのみ目立ちます)は異なります(1つはブランチを持ち、もう1つはブランチを持ちません)。
  • Mergeは通常、追加のコミットを作成します(例:ツリー内のノード)。
  • マージとリベースは、競合を異なる方法で処理します。リベースは一度に1つのコミットで競合を提示し、マージはそれらを一度に提示します。

したがって、簡単な答えは、履歴をどのように見せたいかに基づいてリベースまたはマージを選択することです

長い答え

使用する操作を選択するときに考慮すべきいくつかの要因があります。

変更を取得するブランチは、チーム外の他の開発者(オープンソース、パブリックなど)と共有していますか?

その場合、リベースしないでください。リベースはブランチを破壊し、それらを使用しない限り、それらの開発者は壊れた/一貫性のないリポジトリを持つことになりますgit pull --rebase。これは、他の開発者をすぐに混乱させる良い方法です。

あなたの開発チームはどれほど熟練していますか?

リベースは破壊的な操作です。つまり、正しく適用しないと、コミットされた作業が失われたり、他の開発者のリポジトリの一貫性が損なわれたりする可能性があります。

私はチームに取り組んできました。開発者はすべて、企業が分岐とマージに対処するための専任スタッフを雇う余裕があった時代から来ました。これらの開発者は、Gitについてあまり知らないので、知りたくありません。これらのチームでは、何らかの理由でリベースを勧めるリスクを冒しません。

ブランチ自体は有用な情報を表していますか

一部のチームは、各ブランチが機能(またはバグ修正、サブ機能など)を表す機能ごとのブランチモデルを使用します。このモデルでは、ブランチは関連するコミットのセットを識別するのに役立ちます。たとえば、そのブランチのマージを元に戻すことで、機能をすばやく元に戻すことができます(公平にするために、これはまれな操作です)。または、2つのブランチを比較して機能を比較します(より一般的)。リベースはブランチを破壊し、これは簡単ではありません。

また、開発者ごとのブランチモデルを使用するチームにも取り組みました(全員が参加しました)。この場合、ブランチ自体は追加情報を伝えません(コミットにはすでに作者がいます)。リベースしても害はありません。

何らかの理由でマージを元に戻したいですか?

マージを元に戻す場合と比較して、リベースを元に戻す(元に戻す場合と同様)ことは、(リベースに競合がある場合)かなり困難または不可能です。元に戻す可能性があると思われる場合は、マージを使用してください。

あなたはチームで働いていますか?もしそうなら、あなたはこのブランチでオール・オア・ナッシングのアプローチを取ることをいとわないのですか?

リベース操作は、対応するでプルする必要がありますgit pull --rebase。自分で作業している場合は、適切なときにどちらを使用するかを思い出すことができます。チームで作業している場合、これを調整するのは非常に困難です。これが、ほとんどのリベースワークフローがすべてのマージ(およびgit pull --rebaseすべてのプル)にリベースを使用することを推奨する理由です。

一般的な神話

Mergeは履歴を破棄します(コミットを押しつぶします)

次のマージがあると仮定します。

    B -- C
   /      \
  A--------D

マスターブランチ(A-D)のみのログを見ると、BおよびCに含まれる重要なコミットメッセージを見逃すため、マージによってコミット履歴が「破棄される」と言う人もいます。

これが本当なら、このような質問はありません。基本的に、(-first-parentを使用して)表示しないように明示的に要求しない限り、BとCが表示されます。これは自分で試すのはとても簡単です。

リベースにより、より安全でシンプルなマージが可能

2つのアプローチは異なる方法でマージされますが、一方が常に他方よりも優れていることは明らかではなく、開発者のワークフローに依存する場合があります。たとえば、開発者が定期的にコミットする傾向がある場合(たとえば、仕事から家に移動するときに1日に2回コミットする場合)、特定のブランチに対して多くのコミットが存在する可能性があります。これらのコミットの多くは、最終的な製品のようには見えない可能性があります(機能ごとにアプローチを1回または2回リファクタリングする傾向があります)。他の誰かがコードの関連領域で作業していて、彼らが私の変更をリベースしようとした場合、それはかなり退屈な操作になる可能性があります。

リベースはよりクールで/よりセクシーで/より専門的です

エイリアスrmしたい場合rm -rf「時間を節約する」ためしは、おそらくリベースが適しています。

私の2セント

私はいつかいつか、Gitリベースが問題を解決する素晴らしいツールであるというシナリオに出くわすといつも思います。Git reflogが私の問題を解決する素晴らしいツールであるというシナリオに出くわすと思います。私はGitで5年以上働いています。それは起こっていません。

散らかった歴史は私にとって本当に問題ではありませんでした。エキサイティングな小説のように自分のコミット履歴を読むだけではありません。歴史が必要なほとんどの場合、とにかくGit BlameまたはGit bisectを使用します。その場合、マージコミットが実際に役立ちます。マージによって問題が発生した場合、それは私にとって意味のある情報であるためです。

アップデート(2017年4月)

私の個人的なアドバイスはまだ残っていますが、私は個人的にリベースの使用をやわらげたことに言及する義務があります。私は最近、Angular 2 Materialプロジェクトとたくさんやり取りしています。彼らはリベースを使用して、非常にクリーンなコミット履歴を維持しています。これにより、特定の不具合を修正したコミットと、そのコミットがリリースに含まれていたかどうかを非常に簡単に確認できました。これは、リベースを正しく使用するための良い例です。


5
検証された答えである必要があります。
Mik378

これは確かに最良の答えです。特に最新の更新での明確な発言で。git履歴をクリーンでクリアに保つのに役立ちますが、安全に使用できます。
zquintana

5
私はこの答えが大好きです。しかし:Rebaseは「クリーン」な履歴を作成しません。これはより線形の履歴を作成しますが、各コミットが非表示になっていることを誰が知っているので、それはまったく同じではありませんか?最もクリーンでクリアなGit履歴は、ブランチとコミットの整合性を維持するものです。
Marnen Laibow-Koser 2018

3
「一般的な神話、コミットBとCが表示されます」:必ずしもそうではありません。マージが早送りマージである場合、実際にはBとCのみが表示され、競合が発生しない場合にのみ可能です。競合がある場合は、単一のコミットを取得します!ただし、最初にマスターを機能にマージし、そこで競合を解決できます。その後、機能をマスターにマージすると、履歴BとCがコミットされ、(最初の)マスターから機能にマージされた1つのコミットXが履歴に表示されます。
Jeremy Benks、

とても良い説明!もっと賛成しなければなりません!
1

185

ここでの答えの多くは、マージによってすべてのコミットが1つに変わると言うため、コミットを保持するためにリベースを使用することをお勧めします。これは誤りです。そして、もしあなたがあなたのコミットをすでにプッシュしているなら、悪い考えです

Mergeはコミットを抹消しませ。Mergeは履歴を保存します!(gitkを見てください)Rebaseは、履歴をリライトします。これは、プッシュした後の悪いことです。

マージを使用します-すでにプッシュしたときは常にリベースしません

これは、Linus(Gitの作者)が引き受けたものですWayback Machineによって回収された私のブログでホストされています)。それは本当に良い読み物です。

または、以下の同じアイデアの独自のバージョンを読むことができます。

マスターでブランチをリベースする:

  • コミットがどのように作成されたかについての誤った考えを提供します
  • 十分にテストされていない可能性がある中間コミットの束でマスターを汚染する
  • 元のトピックブランチが作成されてからリベースされるまでの間にマスターに加えられた変更が原因で、これらの中間コミットにビルドブレークが実際に導入される可能性があります。
  • マスターで適切な場所を見つけてチェックアウトすることが難しくなります。
  • コミット時のタイムスタンプをツリー内の時系列順に揃えないようにします。したがって、マスターではコミットAがコミットBに先行することがわかりますが、コミットBが最初に作成されました。(何?!)
  • トピックブランチの個々のコミットは、それぞれ個別に解決する必要があるマージの競合を伴う可能性があるため、さらに多くの競合が発生します(さらに、各コミットで何が起こったかについて履歴にあります)。
  • 歴史の書き直しです。リベースされているブランチがどこかにプッシュされた(自分以外の人と共有された)場合、履歴を書き直してから、そのブランチを持っている他の全員を台無しにしています。

対照的に、トピックブランチをマスターにマージする:

  • トピックブランチが作成された場所の履歴を保持します。マスターからトピックブランチへのマージを含め、トピックブランチを最新に保つのに役立ちます。開発者がビルド時にどのコードを使用していたかについて、正確なアイデアが得られます。
  • マスターは、主にマージで構成されるブランチであり、これらの各マージコミットは、トピックブランチを統合する準備ができた場所であるため、通常、チェックアウトしても安全な「良い点」です。
  • トピックブランチにあったという事実を含め、トピックブランチの個々のコミットはすべて保持されるため、それらの変更を分離するのは自然であり、必要な場所にドリルインできます。
  • マージの競合は(マージの時点で)1回だけ解決する必要があるため、トピックブランチで行われた中間コミットの変更を個別に解決する必要はありません。
  • スムーズに複数回行うことができます。トピックブランチを定期的にマスターに統合すると、人々はトピックブランチを構築し続け、独立してマージし続けることができます。

3
また、git mergeには「--no-ff」(早送りなし)オプションがあり、特定のマージによって導入されたすべての変更を本当に簡単に元に戻すことができます。
ティアゴ2014

3
もう少し明確にしておくと、「既にプッシュしたときはいつでも」という状況を指します。これは大胆なはずです。Linusへのリンクの投稿は素晴らしいです、ところで、それを明確にします。
honzajde 2015

2
しかし、PRを介してトピックブランチをマスターにマージする前に(マスターではなくブランチの競合を解決するため)、マスターからトピックブランチに「更新」するのがベストプラクティスではありませんか。私たちはそのようにやっているので、ほとんどのトピックブランチは最後のコミットとして「ブランチマスターをトピックにマージ...」を持っていますが、これはリベースの「機能」としてリストされており、誰もマージについて言及していません...?
ProblemsOfSumit

2
@AndrewArnott「ほとんどのトピックブランチは、競合することなくターゲットブランチにマージできるはずです」20の開発者が30のブランチで作業している場合、どうすればそれが可能でしょうか。あなたが作業している間はマージがあります-もちろん、PRを作成する前にターゲットからトピックブランチを更新する必要があります...いいえ?
ProblemsOfSumit

3
通常、@ Sumitではありません。どちらか一方または両方のブランチに変更が加えられていても、Gitはどちらの方向でも問題なくマージできます。同じコード行(または非常に近い行)が2つのブランチ間で変更された場合にのみ、競合が発生します。それがいずれかのチームで頻繁に発生する場合、競合の解決は税金であり、速度が低下するため、チームは作業の配分方法を再考する必要があります。
Andrew Arnott、2016年

76

TLDR:それは最も重要なものに依存します-きちんとした履歴または開発のシーケンスの真の表現

きちんとした履歴が最も重要な場合は、最初にリベースしてから変更をマージして、新しいコードが正確に何であるかを明確にします。すでにブランチをプッシュしている場合は、結果に対処できない限り、リベースしないでください。

シーケンスの真の表現が最も重要な場合は、リベースせずにマージします。

マージとは、変更を宛先にマージする単一の新しいコミットを作成することです。注意:この新しいコミットには2つの親があります。コミットの文字列からの最新のコミットと、マージする他のブランチの最新のコミットです。

リベースとは、現在の一連のコミットをヒントとして使用して、まったく新しい一連のコミットを作成することです。言い換えれば、私がリベースしているところから変更を始めた場合、私の変更がどのように見えるかを計算します。したがって、リベースの後、変更を再テストする必要があり、リベース中に、いくつかの競合が発生する可能性があります。

これを踏まえて、なぜリベースするのですか?開発履歴を明確にするためです。機能Xで作業していて、完了したら変更をマージするとします。宛先には、「追加された機能X」の行に沿って何かを言う単一のコミットがあります。これで、マージする代わりに、リベースしてからマージした場合、宛先の開発履歴には、単一の論理的な進行ですべての個別のコミットが含まれます。これにより、後で変更を確認することがはるかに簡単になります。50人の開発者が常にさまざまな機能をマージしている場合、開発履歴を確認するのがどれほど難しいか想像してみてください。

とはいえ、作業中のブランチをすでにアップストリームにプッシュしている場合は、リベースせずにマージする必要があります。アップストリームにプッシュされていないブランチの場合、リベース、テスト、およびマージします。

もう1つ、リベースする必要があるのは、アップストリームにプッシュする前にブランチからコミットを取り除きたいときです。例:早い段階でいくつかのデバッグコードを導入するコミットと、そのコードをさらにクリーンアップする他のコミット。これを行う唯一の方法は、インタラクティブなリベースを実行することです。git rebase -i <branch/commit/tag>

更新:また、Gitを使用して、非線形履歴をサポートしないバージョン管理システム(Subversionなど)にインターフェースする場合にも、リベースを使用する必要があります。git-svnブリッジを使用する場合、Subversionにマージして戻す変更は、トランクでの最新の変更に加えて、変更の順次リストであることが非常に重要です。これを行う方法は2つしかありません:(1)手動で変更を再作成する、および(2)rebaseコマンドを使用する。

更新2:リベースについて考えるもう1つの方法は、開発スタイルから、コミットするリポジトリで受け入れられるスタイルへの一種のマッピングを可能にすることです。小さな小さな塊でコミットしたいとしましょう。タイプミスを修正する1つのコミット、未使用のコードを削除する1つのコミットなどがあります。必要な作業が完了するまでに、長い一連のコミットがあります。ここで、コミットしているリポジトリが大きなコミットを奨励しているとしましょう。そのため、実行している作業では、1つまたは2つのコミットが予想されます。どのようにしてコミットの文字列を取得し、それらを期待されるものに圧縮しますか?インタラクティブなリベースを使用して、小さなコミットを少数の大きなチャンクに押しつぶします。逆が必要な場合も同様です。スタイルがいくつかの大きなコミットであった場合、しかし、リポジトリは小さなコミットの長い文字列を要求しました。リベースを使用しても同じことができます。代わりにマージした場合は、コミットスタイルをメインリポジトリに移植しました。多くの開発者がいる場合、しばらくしていくつかの異なるコミットスタイルの履歴をたどるのがどれほど難しいか想像できます。

更新3:Does one still need to merge after a successful rebase?はい、そうです。その理由は、リベースには本質的にコミットの「シフト」が含まれるためです。上記で述べたように、これらのコミットは計算されますが、分岐の時点から14のコミットがあった場合、リベースに問題がないと仮定すると、(リベースしているポイントの)14コミット先になります。リベースが行われます。リベースの前にブランチがありました。あなたは後に同じ長さの枝を持つでしょう。変更を公開する前に、まだマージする必要があります。つまり、必要なだけリベースします(これも、変更をアップストリームにプッシュしていない場合に限ります)。リベースした後でのみマージします。


1
マスターとのマージは、早送りになる可能性があります。機能ブランチには、マイナーなバグがある、またはコンパイルさえしない、いくつかのコミットがあるかもしれません。機能ブランチで単体テストのみを行う場合、統合の際にいくつかのエラーが発生します。マスターとマージする前に、統合テストが必要であり、いくつかのバグを示す可能性があります。これらが修正された場合、機能が統合される可能性があります。バギーなコードをマスターにコミットしたくないので、all-commits-fast-forwardを防ぐためにリベースが必要なようです。
mbx

1
@mbx git mergeは、--no-ffマージコミットを強制するオプションをサポートしています。
Gavin S. Yancey 2017年

63

マージは間違いなく変更を統合する最も簡単で最も一般的な方法ですが、それだけではありません。リベースは統合の代替手段です。

マージを少し良く理解する

Gitがマージを実行すると、3つのコミットが検索されます。

  • (1)共通祖先コミット。プロジェクト内の2つのブランチの履歴をたどる場合、それらには常に少なくとも1つの共通のコミットがあります。この時点では、両方のブランチは同じ内容であり、その後異なる形で進化しました。
  • (2)+(3)各ブランチのエンドポイント。統合の目的は、2つのブランチの現在の状態を結合することです。したがって、それぞれの最新のリビジョンは特に重要です。これら3つのコミットを組み合わせると、目的の統合が実現します。

早送りまたはマージコミット

非常に単純なケースでは、分岐が発生したため、2つのブランチのうちの1つに新しいコミットがありません。最新のコミットが依然として共通の祖先です。

ここに画像の説明を入力してください

この場合、統合の実行は非常に簡単です。Gitは、共通の祖先コミットの上に他のブランチのすべてのコミットを追加することができます。Gitでは、この最も単純な統合形式を「早送り」マージと呼びます。その後、両方のブランチがまったく同じ履歴を共有します。

ここに画像の説明を入力してください

ただし、多くの場合、両方のブランチが個別に前進しました。

ここに画像の説明を入力してください

統合を行うには、Gitはそれらの違いを含む新しいコミット、つまりマージコミットを作成する必要があります。

ここに画像の説明を入力してください

ヒューマンコミットとマージコミット

通常、コミットは人間によって慎重に作成されます。これは、関連する変更のみをラップし、コメントで注釈を付ける意味のあるユニットです。

マージコミットは少し異なります。開発者によって作成されるのではなく、Gitによって自動的に作成されます。そして、関連する一連の変更をラップする代わりに、その目的は、結び目のように2つのブランチを接続することです。後でマージ操作を理解したい場合は、両方のブランチと対応するコミットグラフの履歴を確認する必要があります。

Rebaseとの統合

一部の人々は、そのような自動マージコミットなしで行くことを好みます。代わりに、彼らはプロジェクトの履歴が単一の直線で進化したかのように見えることを望んでいます。ある時点で複数のブランチに分割された兆候は残っていません。

ここに画像の説明を入力してください

リベース操作をステップごとに見ていきましょう。シナリオは前の例と同じです。ブランチBからブランチAへの変更を統合する必要がありますが、今はリベースを使用しています。

ここに画像の説明を入力してください

これは3つのステップで行います

  1. git rebase branch-A // Synchronises the history with branch-A
  2. git checkout branch-A // Change the current branch to branch-A
  3. git merge branch-B // Merge/take the changes from branch-B to branch-A

まず、GitはブランチAでのすべてのコミットを「取り消し」ます。これは、行が分岐し始めた後(共通の祖先コミットの後)に発生しました。ただし、もちろん、それらが破棄されることはありません。代わりに、それらのコミットは「一時的に保存される」と考えることができます。

ここに画像の説明を入力してください

次に、統合するブランチBからのコミットを適用します。この時点で、両方のブランチはまったく同じに見えます。

ここに画像の説明を入力してください

最後のステップで、ブランチAの新しいコミットが再適用されます。ただし、ブランチBからの統合されたコミット(それらは再ベース化されています)の上に新しい位置で適用されます。

結果は、開発が一直線に行われたように見えます。結合されたすべての変更を含むマージコミットの代わりに、元のコミット構造が保持されました。

ここに画像の説明を入力してください

最後に、クリーンなブランチブランチAを取得します不要な自動生成されたコミットのないを。

注:素晴らしいから撮影ポストgit-tower。の欠点rebase、同じ投稿でもよく読んでいます。


非常にクールな図の+1。私はいつも、Gitフローの例を同様の方法で説明できないようにしたいと思っていました。
Mikayil Abdullayev

60

マージ/リベースの前:

A <- B <- C    [master]
^
 \
  D <- E       [branch]

git merge master

A <- B <- C
^         ^
 \         \
  D <- E <- F

git rebase master

A <- B <- C <- D' <- E'

(A、B、C、D、E、Fはコミットです)

この例と、Gitについてよりよく説明された情報は、Git The Basics Tutorialにあります。



25

この回答は、Git Flowを中心に広く方向付けられています。テーブルは、ASCIIテーブルジェネレーターで生成され、履歴ツリーはこの素晴らしいコマンド(別名git lg)で生成れています。

git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

履歴ツリーとの一貫性を保つために、テーブルは新しい順になっています。git mergegit merge --no-ff最初の違いも参照してください(git merge --no-ff履歴を現実に近づけるため、通常は使用します)。

git merge

コマンド:

Time          Branch "develop"             Branch "features/foo"
------- ------------------------------ -------------------------------
15:04   git merge features/foo
15:03                                  git commit -m "Third commit"
15:02                                  git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

結果:

* 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo)
|           Third commit - Christophe
* 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago)
|           Second commit - Christophe
* 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge --no-ff

コマンド:

Time           Branch "develop"              Branch "features/foo"
------- -------------------------------- -------------------------------
15:04   git merge --no-ff features/foo
15:03                                    git commit -m "Third commit"
15:02                                    git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

結果:

*   1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/foo' - Christophe
| * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago)
|/            Second commit - Christophe
* c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git mergegit rebase

最初のポイント:機能を常に開発にマージし、開発を機能からリベースしないでください。これは、リベースゴールデンルールの結果です。

黄金のルールgit rebaseは、それを公共のブランチで使用しないことです。

つまり

どこかにプッシュしたものをリベースしないでください。

私は個人的に付け加えます:それが機能ブランチであり、かつあなたとあなたのチームが結果を認識していない限り

したがって、git mergevs の問題git rebaseはほとんど機能ブランチにのみ適用されます(次の例で--no-ffは、マージ時に常に使用されています)。もう1つのより良い解決策があるかどうかはわからないので(議論が存在するため)、両方のコマンドの動作のみを提供します。私の場合、git rebaseより良い履歴ツリーを生成するので、私は使用することを好みます:)

機能ブランチ間

git merge

コマンド:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- --------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

結果:

*   c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago)
| |\            Merge branch 'features/foo' into features/bar - Christophe
| * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | |           Fifth commit - Christophe
| * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | |           Fourth commit - Christophe
* | |   98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \ \            Merge branch 'features/foo' - Christophe
| |/ /
|/| /
| |/
| * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

コマンド:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git rebase features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

結果:

*   7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
| * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

からdevelop機能ブランチへ

git merge

コマンド:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git merge --no-ff develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

結果:

*   9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago)
| |\            Merge branch 'develop' into features/bar - Christophe
| |/
|/|
* |   5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | |           Third commit - Christophe
| * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ /            Second commit - Christophe
| * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
* 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

コマンド:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git rebase develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

結果:

*   b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
*   856433e - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\            Merge branch 'features/foo' - Christophe
| * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

サイドノート

git cherry-pick

特定のコミットが1つだけ必要な場合git cherry-pickは、優れた解決策です(この-xオプションは、「(コミットから選択されたチェリー...)」という行を元のコミットメッセージ本文に追加するので、通常は使用することをお勧めします- git log <commit_sha1>確認するにはそれ):

コマンド:

Time           Branch "develop"              Branch "features/foo"                Branch "features/bar"
------- -------------------------------- ------------------------------- -----------------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git cherry-pick -x <second_commit_sha1>
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

結果:

*   50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago)
| |           Second commit - Christophe
| * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
|/|
| * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git pull --rebase

私はそれをDerek Gourlayよりもうまく説明できるかわかりません...基本的にはgit pull --rebasegit pull:)の代わりに使用しますが、記事に欠けているのは、デフォルトで有効にできることです

git config --global pull.rebase true

git rerere

繰り返しますが、ここでうまく説明されてます。簡単に言えば、有効にすると、同じ競合を何度も解決する必要がなくなります。


15

プロGitの本は上の本当に良い説明があるリベースページを

基本的に、マージは2つのコミットを取り、それらを結合します。

リベースは、2つの共通の祖先に移動し、変更を互いの上に徐々に適用します。これにより、「よりクリーン」で直線的な履歴が作成されます。

ただし、リベースすると、以前のコミットを破棄して新しいコミットを作成します。したがって、公開されているリポジトリをリベースしないでください。リポジトリで作業している他の人はあなたを嫌います。

その理由だけで、私はほぼ完全にマージします。私のブランチは99%の確率でそれほど大きな違いはないので、競合がある場合は1つか2つしかありません。


1
マージはコミットを結合しません-これは履歴を書き換えます。リベースはそれを行います。
kellyfj 2016年

4

Gitリベースを使用して、履歴の分岐パスとリポジトリ構造を線形にします。

また、作成したブランチを非公開にしておくためにも使用されます。変更をリベースしてサーバーにプッシュした後、ブランチを削除すると、作業したブランチの証拠がなくなるためです。したがって、ブランチがローカルの関心事になります。

リベースを実行した後、通常のマージを実行するかどうかを確認するために使用していた追加のコミットも削除します。

そして、はい、rebaseコマンドはリベース中に言及したブランチ(masterなど)の上に作業を置き、マスターブランチの直接の子孫としてブランチの最初のコミットを行うため、リベースが成功した後でもマージを行う必要があります。つまり、このブランチからマスターブランチに変更をもたらすために、早送りマージを実行できます。


4

開発者が1人だけの場合は、マージではなくリベースを使用して履歴を明確にすることができます

ここに画像の説明を入力してください


3

Gerritがレビューと配信の統合に使用される大規模な開発に多少関連するいくつかの実用的な例:

機能ブランチを新しいリモートマスターに上げるときにマージします。これにより、最小限の作業が可能になり、たとえばgitkの機能開発の履歴を簡単にたどることができます。

git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature

配信コミットを準備するときにマージします。

git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master

何らかの理由で配信コミットが統合に失敗したときにリベースし、新しいリモートマスターに向けて更新する必要があります。

git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master

3

リベースとマージとは何かについて何度も説明されましたが、いつ何を使用する必要がありますか?

いつリベースを使用すべきですか?

  • あなたがブランチをプッシュしていない/他の誰もブランチで作業していないとき
  • あなたは完全な歴史が欲しい
  • 自動生成された「マージされた..」コミットメッセージはすべて避けたい

Gitがリベースすると履歴が変更されます。したがって、他の誰かが同じブランチで作業している場合や、それをプッシュした場合は使用しないでください。しかし、ローカルブランチがある場合は、ブランチをマスターにマージする前にマージリベースマスターを実行して、より明確な履歴を維持できます。これを行うと、マスターブランチにマージした後、マスターブランチでブランチを使用したことが表示されなくなります。自動生成された「マージされた..」がないため、履歴は「よりクリーン」ですが、自動生成された「マージされた..」コミットなしで、マスターブランチの完全な履歴。

ただし、git merge feature-branch --ff-only機能をメインにマージするときに、単一のコミットを作成するときに競合が発生しないことを確認するために使用します。これは、機能ブランチの履歴を取得するときに作業するすべてのタスクに機能ブランチを使用しているが、「マージされたコミット」ではない場合に興味深いです。

2番目のシナリオは、ブランチからブランチし、メインブランチの変更点を知りたい場合です。リベースには、すべてのコミットが含まれているため、情報が提供されます。

マージはいつ使用すべきですか?

  • あなたがブランチをプッシュしたとき/他の人もそれに取り組んでいます
  • 完全な履歴は必要ありません
  • 単にマージするだけで十分です

機能ブランチのすべての履歴をマスターブランチに保存する必要がない、またはしたくない場合、または他の人が同じブランチで作業している場合/プッシュした場合。それでも履歴が必要な場合は、マスターを機能ブランチにマージしてから、機能ブランチをマスターにマージします。これにより、マスターに機能ブランチの履歴がある高速転送マージになります(マスターをマージしたために機能ブランチにあったマージコミットを含む)。


-4

いつ使用しgit rebaseますか?履歴を書き換えるため、ほとんどありません。git mergeプロジェクトで実際に起こったことを尊重するため、ほとんどの場合、これが望ましい選択です。


1
@benjaminhullありがとうございます。ただし、私の回答が事実に基づいていることを願っています。私見の意見はこの種のものではほとんど意味がありません。あなたの実際の歴史を失うことは後に人生を困難にするという事実です。
Marnen Laibow-Koser

1
同意します。Mergeが履歴の破損などにつながることは決してありません(プッシュされたコミットをリベースする場合)
サーフライダー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.