マージとリベース
git rebase
コマンドは、初心者は避けるべき魔術的な Git ブードゥーであるという評判を得ていますが、実際には、慎重に使用すれば開発チームの作業を非常に容易にしてくれます。この記事では、git rebase
を関連する git merge
コマンドと比較し、典型的な Git ワークフローにリベースを組み込める可能性のあるすべての機会を特定します。
考え方の概要
git rebase
で最初に理解すべきことは、git merge
と同じ問題を解決するということです。これらのコマンドは両方とも、変更を1つのブランチから別のブランチに統合することを目的に設計されています。ただし、実現する方法は非常に異なっています。
専用ブランチの新しいフィーチャーで作業を開始し、その後で別のチームメンバーが master
ブランチを新しいコミットで更新すると何が生じるか考えてみましょう。結果として、フォーク済み履歴が作成されます。これは、コラボレーションツールとして Git を使用したことがある人なら誰てもよく知っています。
ここで、master
の新しいコミットが作業中のフィーチャーに関連しているとします。新しいコミットを自分の フィーチャー
ブランチに取り込むには、マージまたはリベースの2つのオプションがあります。
マージオプション
最も容易なオプションは、次のようなコマンドを使用して master
ブランチをフィーチャーブランチにマージすることです。
git checkout feature
git merge master
または、これを1本の直線に凝縮することができます。
git merge feature master
これは、両方のブランチの履歴を連結する新しい「マージコミット」を feature
ブランチに作成します。ブランチの構造は次のようになります。
マージは非破壊的な操作であるため安心です。既存のブランチは決して変更されません。これにより、リベースの潜在的な落とし穴がすべて回避されます (後述)。
逆に言えば、これは上流の変更を取り込む必要が生じるたびに feature
ブランチに無関係なマージコミットが作成されることも意味します。master
が非常に活発な場合、フィーチャーブランチの履歴がかなり乱雑になる可能性があります。高度な git log
オプションでこの問題を軽減することはできますが、他の開発者にはプロジェクトの履歴が理解しづらくなる可能性があります。
リベースオプション
マージに変わる方法として、次のコマンドを使用し、feature
ブランチを master
ブランチにリベースできます。
git checkout feature
git rebase master
これにより、フィーチャー
ブランチ全体がマスター
ブランチの先端から開始され、新しいコミットのすべてを効率的にマスター
に組み込むことができます。しかし、リベースは、マージコミットを使用する代わりに、元のブランチでコミットごとにまったく新しくコミットを作成することによってプロジェクト履歴を再書き込みします。
リベースの主な利点は、プロジェクト履歴が非常にすっきりすることです。第一に、git merge
が必要とする不要なマージコミットが除去されます。第二に、上記の図からわかるように、リベースによって、履歴は完全に直線的になります。フィーチャー
の先端からプロジェクトの開始までずっとフォークなしにたどっていくことができます。このため、git log
、git bisect
、および gitk
のようなコマンドでプロジェクトをナビゲートすることが容易になります。
しかし、この初期のコミット履歴には、安全性とトレーサビリティという2つのトレードオフがあります。リベースの黄金律 に従わない場合、プロジェクト履歴を書き換えるとコラボレーションワークフローに大打撃を与える恐れがあります。さらに、重要度は低くなりますが、リベースはマージコミットによって提供されるコンテキストを失います。このため、ユーザーは上流の変更がいつフィーチャーに組み込まれたのかわからなくなります。
対話式リベース
対話式リベースでは、コミットが新しいブランチに移動するときにコミットを変更する機会があります。これは、ブランチのコミット履歴を完全に制御できるという点で、自動化されたリベースよりもはるかに強力です。一般的に、これはフィーチャーブランチを master
にマージする前に乱雑な履歴をクリーンアップするために使用されます。
対話式リベースのセッションを開始するには、i
オプションを git rebase
コマンドに渡します。
git checkout feature
git rebase -i master
これにより、移動しようとしているすべてのコミットがリストされたテキストエディタが開きます。
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
このリストは、リベース実行後のブランチの表示を正確に定義しています。pick
コマンドを変更し、エントリーを並び替える (またはこのいずれかを行う) ことで、ブランチの履歴の表示を必要に応じて変えられます。たとえば、2 番目のコミットが最初のコミットの小さな問題を修正している場合、fixup
コマンドを使用して単一のコミットに圧縮できます。
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
ファイルを保存して閉じると、Git はユーザーの指示に従ってリベースを実行します。その結果、プロジェクト履歴は次のようになります。
このような重要でないコミットを削除すると、フィーチャーの履歴がはるかに理解しやすくなります。これは git merge
ではまったくできないことです。
リベースの黄金律
リベースの特徴を理解できたら、次に最も重要なことは、実行してはいけないときを知ることです。git rebase
の黄金律は、リベースを public ブランチでは決して使用しないことです。
たとえば、master
を自分の フィーチャー
ブランチにリベースした場合に生じることを考えてみましょう。
リベースは master
のすべてのコミットを feature
の先端に移動します。問題は、これがあなたのリポジトリのみで生じたことです。その他のすべての開発者は依然として元の master
で作業しています。リベースはまったく新しくコミットを作成することになるので、Git はあなたの master
ブランチの履歴は他のすべての人の履歴から分岐したと考えます。
2つの master
ブランチを同期する唯一の方法は、マージして1つに戻すことですが、その結果、マージコミットが追加され、さらに同じ変更を含むコミットが 2 セット生じます (元のブランチの変更と、リベースしたブランチの変更)。言うまでもなく、これは非常に紛らわしい状況です。
したがって、git rebase
を実行する前に、常に「このブランチは他の人にも表示されているだろうか」と、自問してください。答えが「はい」の場合、キーボードから手を離し、非破壊的な変更方法 (git revert
コマンドなど) を検討してください。「いいえ」の場合は、自由に履歴を再書き込みしても安全です。
プッシュの強制
リベースした master
ブランチをリモートリポジトリにプッシュバックしようとしても、Git はその実行を許可しません。これはリベース済みのブランチがリモート側の master
ブランチと競合するためです。しかし、次のように --force
フラグを渡すことによって、プッシュの実行を強制することが可能です。
# このコマンドの使用には細心の注意が必要です! git push --force
これは、ご使用のリポジトリからリベースしたブランチと一致するようにリモート側のマスター
ブランチを上書きし、チームの他のメンバーに多大な混乱を招くことになります。したがって、自分が何をしようとしているか正確にわかっている場合にのみ、細心の注意を払ってこのコマンドを使用してください。
強制的にプッシュする必要があるのは、(バックアップ目的などで) プライベートフィーチャーブランチをリモートリポジトリにプッシュした後、ローカルクリーンアップを実行した場合です。これは、「しまった、フィーチャーブランチの元のバージョンではなく、こっちの現在のブランチをプッシュしたかったのに」と言っているようなものです。ここで重要なのは、フィーチャーブランチの元のバージョンのコミットから作業している人は誰もいないことです。
ワークフローの手引き
リベースは、チームが快適に感じる程度に応じて、既存の Git ワークフローに組み込むことができます。このセクションでは、機能の開発の様々な段階でリベースがもたらすメリットを見ていきます。
git rebase
を利用するあらゆるワークフローでの最初のステップは、各フィーチャーの専用ブランチを作成することです。これにより、リベースを安全に使用するために必要なブランチ構造が得られます。
ローカルクリーンアップ
ワークフローにリベースを組み込む最良の方法の1つは、ローカルの進行中のフィーチャーをクリーンアップすることです。対話式リベースを定期的に実行することで、フィーチャーブランチ内の各コミットに焦点を当て、意義があることを確認できます。これにより、コードを分割して個別にコミットすることを心配せずにコードを記述できます。コードは後で修正できます。
git rebase
を呼び出す場合、新規ベース用に2つのオプションがあります。フィーチャの親ブランチ (master
など)、またはフィーチャー内の以前のコミットです。最初のオプションの例は、対話式リベースセクションで見ました。後者のオプションは、最新のいくつかのコミットの修正のみ行う場合に便利です。たとえば、次のコマンドは最新の3つのコミットに対してのみ対話式リベースを開始します。
git checkout feature git rebase -i HEAD~3
新規ベースとして HEAD~3
を指定することによって、実際にはブランチを移動しておらず、単にそのブランチの後に続く3つのコミットを対話式に再書き込みしているだけです。これは、上流の変更をフィーチャーブランチに取り込まない
ので注意してください。
この方法でフィーチャー全体を書き換える場合、git merge-base
コマンドを使用して feature
ブランチの元のベースを見つけると便利です。次のコマンドは、元のベースのコミット ID を返します。これを git rebase
に渡すことができます。
git merge-base feature master
このような対話式リベースの利用は、ローカルブランチにのみ影響を与えるため、git rebase
をワークフローに導入するには優れた方法です。他の開発者に表示されるのは、完了した製品のみであり、必然的にフィーチャーブランチ履歴はすっきりとして、追跡しやすくなります。
しかし、これもプライベートフィーチャーブランチに関してのみ有効です。同じフィーチャーブランチを介して他の開発者とコラボレーションしている場合、そのブランチはパブリックであり、その履歴を書き換えることはできません。
git merge
には、対話式リベースによるローカルコミットのクリーンアップに代わる方法はありません。
上流の変更をフィーチャーに取り込む
「考え方の概要」セクションでは、フィーチャーブランチが git merge
または git rebase
のいずれかを使用して、master
から上流の変更を取り込めることを確認しました。マージは自分のリポジトリの全履歴を保持する安全なオプションですが、リベースは、フィーチャーブランチを master
の先端に移動することで直線的な履歴を作成します。
このような git rebase
の利用は、ローカルクリーンアップに似ています (さらに、同時に実行可能です) が、処理中に master
から上流のコミットを取り込みます。
マスター
の代わりにリモートブランチにリベースすることは、まったく理にかなっていることを覚えておいてください。同じフィーチャーで別の開発者とコラボレーションしていて、その変更を自分のリポジトリに取り込む必要がある場合にこのようなことが生じる可能性があります。
たとえば、あなたと John という名前の別の開発者がコミットを feature
ブランチに追加した場合、リモート側の feature
ブランチを John のリポジトリから取り出した後のあなたのリポジトリは、次のようになります。
このフォークは、master
の上流の変更を統合する場合とまったく同じ方法で解決できます。つまり、ローカルのフィーチャー
と John のフィーチャー
をマージするか、ローカルのフィーチャー
を John のフィーチャー
の先端にリベースします。
このリベースでは、あなたのローカルの feature コミットのみが移動し、その前にあるものはそのままであるため、リベースの黄金律
に違反しません。これは、「私の変更内容を John が既に行ったことに追加する」と言っているのと同じです。ほとんどの状況で、これはマージコミットを介してリモートブランチと同期するよりも直感的です。
既定では、git pull
コマンドはマージを実行しますが、--rebase
オプションを渡すことで、リモートブランチをリベースで統合するように強制できます。
プルリクエストを使用してフィーチャーをレビューする
コードレビュープロセスの一環としてプルリクエストを使用する場合は、プルリクエストを作成した後に git rebase
を使用しないでください。プルリクエストを行うとすぐに、他の開発者があなたのコミットを見ることになります。つまり、それは パブリック ブランチです。履歴を書き換えると、Git とチームメイトはフィーチャーに追加された後続のコミットを追跡できなくなります。
他の開発者の変更は git merge
で取り込む必要があり、 git rebase
ではありません。
このため、プルリクエストを送信する前に、対話式リベースを使用してコードをクリーンアップすることが一般的に勧められます。
承認済みフィーチャーの統合
フィーチャーがチームに承認された後、そのフィーチャーを master
ブランチの先端にリベースしてから git merge
を使用してフィーチャーをメインのコードベースに統合するというオプションがあります。
これは上流の変更をフィーチャーブランチに組み込むのと似た状況ですが、master
ブランチではコミットを書き換えることができないため、最終的に git merge
を使用してフィーチャーを統合する必要があります。しかし、マージの前にリベースを実行するとマージが早送りされ、完全に線形の履歴を確実に得ることができます。また、これにより、プルリクエスト中に追加された後続のコミットをまとめて1つのコミットにする機会も得られます。
git rebase
に慣れていない人は、一時的なブランチでいつでもリベースを実行できます。そうすれば、誤ってフィーチャーの履歴を壊してしまった場合、元のブランチをチェックアウトしてもう一度やり直すことができます。例:
git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Clean up the history]
git checkout master
git merge temporary-branch
概要
ブランチのリベースを開始するために実際に知っておく必要があるのはこれだけです。不要なマージコミットのないすっきりした、直線的な履歴にしたいのであれば、別のブランチから変更を統合する際に、git merge
ではなく、git rebase
を使用するようにします。
反対に、プロジェクトの完全な履歴を保持し、パブリックコミットを再書き込みするリスクを回避したい場合は、git merge
の一点張りで通すこともできます。いずれのオプションも完全に有効ですが、少なくともこれで git rebase
の利点を活用するオプションを使用できるようになりました。