Git rebase
このドキュメントでは、git rebase
コマンドについて徹底的に議論します。リポジトリのセットアップと履歴の書き換えページでも、rebase コマンドについて説明しています。このページでは、git rebase
の構成と実行についてさらに詳しく説明します。一般的な rebase の使用事例と落とし穴についてもここで紹介します。
rebase は、1 つのブランチから別のブランチへの変更を統合することを専門とした、2 つの Git ユーティリティの 1 つです。もう 1 つの変更統合ユーティリティは git merge
です。マージは常に、変更レコードを先へ動かします。代わりに、rebase には強力な履歴書き換え機能を備えています。マージとリベースの詳細については、「マージとリベース」ガイドを参照してください。リベース自体には 2 つのメイン モード (「手動」と「インタラクティブ」) があります。異なるリベース モードについて以下でより詳細に説明します。
git rebase とは
リベースは、コミットのシーケンスを新しいベース コミットに移動または組み合わせるプロセスです。リベースは、フィーチャー ブランチ ワークフローにおいて最も役立ち、簡単に視覚化されています。一般的な動作を次の図に示します:
コンテンツという観点において、リベースは、あるコミットから他のコミットにブランチを移動させ、別のコミットからブランチを作成したように見せています。しかし Git の内部では、新たなコミットを生成してそれを移動先のベースコミットに適用することによってこれを行なっています。ここでは、ブランチそのものは同じものに見えていても、それを構成するコミットは全く異なることを理解することが重要です。
使用法
リベースの主要な目的はプロジェクト履歴の直線性を維持することにあります。たとえば、あるフィーチャー ブランチにおける作業開始後に main ブランチに進行があった状況を考えます。フィーチャー ブランチの main ブランチへ最新の更新を取得したい一方で、最新の main ブランチから作業を開始したかのようにブランチの履歴をクリーンなまま残したいとします。これによって、後でフィーチャー ブランチを main ブランチへクリーン マージする際にメリットがあります。「クリーンな履歴」を維持する理由は何でしょうか。Git オペレーションを実行して回帰の導入を調査すると、履歴を整理することのメリットが明白になります。その他の実際的なシナリオは次のようになります。
- バグは main ブランチで特定されています。現在、正常に作動している機能に不具合があります。
- 開発者は
git log
を使用して main ブランチの履歴を調べられます。「クリーンな履歴」によって、開発者はプロジェクトの履歴について素早く理解できます。 - 開発者は
git log
を使用して、いつバグが導入されたかを特定できます。開発者はこれにより、git bisect
を実行できます。 - git 履歴がクリーンなため、
git bisect
には、回帰を探した際に比較するコミットのセットがあります。開発者は、バグにつながるコミットを素早く見つけ、それに従って対応できます。
git log および git bisect の詳細については、個別の使用例ページを参照してください。
フィーチャーを main ブランチに統合するには、直接マージとリベース後のマージの 2 つの方法があります。前者のオプションを選択すると三方向マージとマージ コミットになって、後者のオプションを選択すると早送りマージになって履歴の直線性は完全に維持されます。次の図は、main ブランチへのリベースによって早送りマージが可能となる理由を説明しています。
リベースは、上流側の変更をローカルリポジトリに統合する一般的な方法です。Git merge を使用して上流側の変更をプルする場合、プロジェクトの進行状況を確認するたびに余計なマージコミットが生成されます。これに対してリベースとは、「私の変更作業は皆が変更を完了したものをベースとして行いたい」と言うに等しいのです。
公開リポジトリのリベースは厳禁
履歴の書き換えで説明したように、公開リポジトリにプッシュされたコミットをリベースしてはなりません。リベースは過去のコミットを新たなコミットに置き換えるものであり、プロジェクト履歴の一部が欠落したように見えます。
Git リベース標準モードと Git リベースのインタラクティブ モード
Git rebase interactive は、git rebase が -- i
引数を受け入れます。これは、「インタラクティブ」を表します。引数が指定されていない場合、コマンドは標準モードで実行します。いずれの場合でも、別のフィーチャー ブランチを作成したと想定しましょう。
# Create a feature branch based off of main
git checkout -b feature_branch main
# Edit files
git commit -a -m "Adds new feature"
Git rebase 標準モードは、現在の作業ブランチのコミットを自動的に取得し、渡されたブランチの HEAD に適用します。
git rebase <base>
現在のブランチを自動的に
にリベースするコマンドで、リベース先としてはすべての種類のコミット参照 (ID、ブランチ名、タグ、HEAD
への相対参照) を使用することができます。
git rebase
を -i
フラグで実行するとインタラクティブなリベース セッションが開始されます。インタラクティブなリベースでは、すべてのコミットをそのまま新しい ベースに移動するのではなく、対象となる個々のコミットの改変が可能です。これを使用して、既存の一連のコミットの削除、分割、改変を行って履歴を整理することができます。これはちょうど、Git commit --amend
の強化版と言えます。
git rebase --interactive <base>
インタラクティブなリベースセッションを使用して現在のブランチを
にリベースするコマンドです。このコマンドを実行すると、エディターが開き、リベースする個々のコミットに対するコマンド (下で説明します) の入力が可能となります。ここでのコマンドは、個々のコミットを新しいベースに移動する方法を指定します。また、エディターにおけるコミットの並びを直接編集することによりコミットの順番を並び替えることもできます。リベースの各コミットでコマンドを指定したら、Git は rebase コマンドを適用してコミットの再生を開始します。リベースの edit コマンドは次のようになります。
pick 2231360 some old commit
pick ee2adc2 Adds new feature
# Rebase 2cf755d..ee2adc2 onto 2cf755d (9 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
その他の rebase コマンド
履歴の書き換えのページで説明したように、リベースを使用すると、古いコミットと複数のコミット、コミット済みのファイル、および複数のメッセージを変更することができます。これらは最も一般的な用途ですが、git rebase
には、より複雑な用途で便利な追加コマンド オプションもあります。
git rebase -- d
: 再生時医、コミットはコミットは最終的な組み合わせられたコミット ブロックから破棄されます。git rebase -- p
: コミットをそのままの状態にします。コミットのメッセージやコンテンツは変更されず、引き続き、ブランチ履歴の個別履歴となります。git rebase -- x
: 再生の間、マークが付いた各コミット上でコマンド ライン シェル スクリプトを実行します。便利な例として、特定のコミットでのコードベースのテスト スイートの実行があります。これにより、リベース時に回帰を特定するのに役立ちます。
要約
インタラクティブなリベースを使用することにより、履歴の見かけについて完全な作り変えが可能になります。この機能によって、コード開発中に乱雑なコミットを繰り返した履歴が残っていたとしても事後にそれを見直して整理することができるため、開発者には大幅に自由になります。
ほとんどの開発者は、 master ブランチにマージする前にフィーチャーブランチの見栄えをよくするためにインタラクティブなリベースを使用する傾向があります。インタラクティブなリベースを使用すると、重要性の低いコミットを一纏めにし、不要なコミットを削除し、その他すべてを整理してから「公式」なリポジトリにコミットすることができます。事情を知らない者にとっては、このフィーチャー開発が全体的によく計画されたコミットの 1 本の系列として順調に進行したかのように見えます。
インタラクティブなリベースの真価は、書き換えられた main ブランチの履歴で確認できます。事情を知らない者にとっては、開発者が有能で最小限のコミットを一度ずつ実行しただけで開発を完了できたかのように見えます。このように、リベースはプロジェクトの履歴をクリーンにして分かりやすくする機能です。
構成オプション
一部の rebase プロパティは、git config
を使用して設定することができます。これらのオプションを使用することで、git rebase
出力の外観が変更されます。
rebase.stat
: 既定で false に設定されているブール値。このオプションは、前回の rebase の後に変更された内容を示す、視覚的な diffstat コンテンツの表示を切り替えます。
rebase.autoSquash:
--autosquash
の動作を切り替えるブール値。
rebase.missingCommitsCheck:
欠落しているコミットに関する rebase の動作を変更する、複数の値を設定できます。
warn | インタラクティブ モードで警告を出力し、削除されたコミットについて警告します |
| rebase を停止し、コミットの警告メッセージを出力します |
| 既定で設定され、失われているコミット警告を無視する |
rebase.instructionFormat:
インタラクティブな rebase 表示に使用されるgit log
形式の文字列
rebase の高度な用途
コマンドライン引数 --onto
は、git rebase
へ渡すことができます。git rebase --onto
モードでは、コマンドは以下に展開されます。
git rebase --onto <newbase> <oldbase>
--onto
特定の ref を rebase の最後に渡せる、より強力なフォームまたは rebase を実現します。
次のような、ブランチを使用したリポジトリの例があるとします。
o---o---o---o---o main
\
o---o---o---o---o featureA
\
o---o---o featureB
featureB は featureA に基づいていますが featureA の変更に依存しておらず、main からの分岐に過ぎないことがわかります。
git rebase --onto main featureA featureB
featureA は
です。main
は
となって、featureB は
の HEAD
が何を示しているかの参照となります。結果は次のようになります。
o---o---o featureB
/
o---o---o---o---o main
\
o---o---o---o---o featureA
リベースの危険性を理解する
Git リベースを操作する際に考慮する必要がある注意点の 1 つとして、rebase ワークフロー中にマージの競合の頻度が上がる可能性があるということがあります。これは、main から離れた古いブランチがある場合に発生します。最終的には、main に対して rebase を実行します。その際に、ブランチの変更と競合する可能性がある多くの新しいコミットが含まれる場合があります。main に対してブランチのリベースを頻繁に行ってより頻繁にコミットを行うことによって、この問題は簡単に修正できます。--continue
および --abort
の各コマンド ライン引数を、事前に git rebase
に渡せます。そうでない場合は、競合を処理する際に rebase がリセットされます。
rebase のより深刻な注意点は、インタラクティブな履歴書き換えからコミットが失われることです。インタラクティブ モードで rebase を実行し、squash または drop などのサブコマンドを実行すると、ブランチの直前のログからコミットが削除されます。一見すると、これによって、コミットが完全に削除されたように見えます。git reflog
を使用すると、これらのコミットが復元され、rebase 全体を元に戻すことができます。git reflog
を使用して失われたコミットを見つける方法の詳細は、Git reflog ドキュメント ページにアクセスしてください。
Git rebase 自体はそれほど危険ではありません。本当の危険は、履歴を書き換えるインタラクティブな rebase を実行し、他のユーザーと共有しているリモート ブランチへ結果を強制プッシュした際に発生します。この操作には、プルした際に他のリモート ユーザーの作業が上書きされる機能を持っているため、回避すべきパターンです。
上流の rebase からの復旧
別のユーザーがリベースを実行し、コミットしたブランチへ強制的にプッシュすると、git pull
は、強制的にプッシュされた tip を持つその前のブランチをオフにしたコミットを上書きして、その以前のブランチをオフにし、コミットを上書きします。幸いにも、git reflog
を使用すると、リモート ブランチの reflog を取得できます。リモート ブランチの reflog で、リベース前の ref を見つけることができます。その後、上記の高度なリベースの用途セクションに記載されているように、--onto
オプションを使用してリモート ref に対してブランチをリベースできます。
概要
この記事では、git rebase
の使用について説明します。基本的な使用事例と高度な事例、およびより高度な例を紹介します。主要な論点をいくつか紹介します。
- git rebase の標準モードとインタラクティブ モード
- git rebase 構成オプション
- git rebase --onto
- git rebase によってコミットが失われる
ここまでで、git rebase
の使用方法と他のツール( git reflog
、git fetch
、および git push
など) について見てきました。詳細については、対応するページにアクセスしてください。