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