Git rebase

このドキュメントでは、git rebase コマンドについて徹底的に議論します。リポジトリのセットアップ履歴の書き換えページでも、rebase コマンドについて説明しています。このページでは、git rebase の構成と実行についてさらに詳しく説明します。一般的な rebase の使用事例と落とし穴についてもここで紹介します。

rebase は、1 つのブランチから別のブランチへの変更の統合を専門とした、2 つの Git ユーティリティの 1 つです。もう 1 つの変更統合ユーティリティは git merge です。マージは常に、変更レコードを先へ動かします。代わりに、rebase は強力な履歴書き換え機能を備えています。マージとリベースの詳細については、「マージとリベース」ガイドを参照してください。リベース自体には 2 つのメインモード (「手動」と「インタラクティブ」) があります。異なるリベースモードについて以下でより詳細に説明します。

git rebase とは

リベースは、コミットのシーケンスを新しいベースコミットに移動または組み合わせるプロセスです。リベースは、ブランチワークフローにおいて最も役立ち、簡単に視覚化されています。一般的な動作を次の図に示します。

Git チュートリアル: git rebase

コンテンツという観点において、リベースは、あるコミットから他のコミットにブランチを移動させ、別のコミットからブランチを作成したように見せています。しかし Git の内部では、新たなコミットを生成してそれを移動先のベースコミットに適用することによってこれを行っています。ここでは、ブランチそのものは同じものに見えていても、それを構成するコミットは全く異なることを理解することが重要です。

使用法

リベースの主要な目的はプロジェクト履歴の直線性を維持することにあります。たとえば、あるフィーチャーブランチでの作業開始後に master ブランチに進行があった状況を考えます。フィーチャーブランチの master ブランチへ最新の更新を取得したいけれども、最新の master ブランチから作業を開始したかのように、ブランチの履歴をクリーンなまま残したいとします。これにより、後にフィーチャーブランチを master ブランチにクリーンマージする際にメリットがあります。「クリーンな履歴」を維持する理由は何でしょうか。Git オペレーションを実行して回帰の導入を調査すると、履歴を整理することのメリットが明白になります。実際には、次のようなシナリオが考えられます。

  1. バグが master ブランチで特定されます。正常に作動していた機能が現在は壊れています。
  2. 開発者は、git log を使用して master ブランチの履歴を調べます。「クリーンな履歴」があるため、開発者はプロジェクトの履歴について素早く理解できます。
  3. 開発者は git log を使用していつバグが導入されたかを特定できないため、git bisect を実行します。
  4. git 履歴がクリーンなため、git bisect には、回帰を探した際に比較するコミットのセットがあります。開発者は、バグにつながるコミットを素早く見つけ、それに従って対応できます。

git log および git bisect の詳細については、個別の使用例ページを参照してください。

フィーチャーを master ブランチに統合するには、直接マージとリベース後のマージの 2 つの方法があります。前者のオプションを選択すると三方向マージとマージコミットが必要となるのに対し、後者は早送りマージが可能であり履歴の直線性は完全に維持されます。次の図は、master ブランチへのリベースによって早送りマージが可能となる理由を説明しています。

Git rebase: master 上のブランチ

リベースは、上流側の変更をローカルリポジトリに統合する一般的な方法です。Git merge を使用して上流側の変更をプルする場合、プロジェクトの進行状況を確認するたびに余計なマージコミットが生成されます。これに対してリベースとは、「私の変更作業は皆が変更を完了したものをベースとして行いたい」と言うに等しいのです。

公開リポジトリのリベースは厳禁

履歴の書き換えで説明したように、公開リポジトリにプッシュされたコミットをリベースしてはなりません。リベースは過去のコミットを新たなコミットに置き換えるものであり、プロジェクト履歴の一部が欠落したように見えます。

Git リベース標準モードと Git リベースのインタラクティブ モード

Git rebase interactive は、git rebase が -- i 引数を受け入れます。これは、「インタラクティブ」を表します。引数が指定されていない場合、コマンドは標準モードで実行します。いずれの場合でも、別のフィーチャーブランチを作成したと想定しましょう。

# Create a feature branch based off of master
git checkout -b feature_branch master
# Edit files
git commit -a -m "Adds new feature"

Git rebase 標準モードは、現在の作業ブランチのコミットを自動的に取得し、渡されたブランチの HEAD に適用します。

git rebase <base>

 

現在のブランチを自動的に <base> にリベースするコマンドで、リベース先としてはすべての種類のコミット参照 (ID、ブランチ名、タグ、HEAD への相対参照) を使用できます。

git rebase-i フラグで実行するとインタラクティブなリベースセッションが開始されます。インタラクティブなリベースでは、すべてのコミットをそのまま新しいベースに移動するのではなく、対象となる個々のコミットの改変が可能です。これを使用して、既存の一連のコミットの削除、分割、改変を行って履歴を整理できます。これはちょうど、Git commit --amend の強化版といえます。

git rebase --interactive <base>

インタラクティブなリベースセッションを使用して現在のブランチを <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 本の系列として順調に進行したかのように見えます。

インタラクティブなリベースの威力は、書き換えられた master ブランチの履歴に現れます。事情を知らない者にとっては、開発者が有能で、最小限のコミットを一度ずつ実行しただけで開発を完了できたかのように見えます。このように、リベースはプロジェクト履歴を整理してわかりやすくする機能です。

構成オプション

一部の rebase プロパティは、git config を使用して設定できます。これらのオプションを使用することで、git rebase 出力の外観が変更されます。

  • rebase.stat: 既定で false に設定されているブール値。このオプションは、前回の rebase の後に変更された内容を示す、視覚的な diffstat コンテンツの表示を切り替えます。
  • rebase.autoSquash: --autosquash の動作を切り替えるブール値。
  • rebase.missingCommitsCheck: 欠落しているコミットに関する rebase の動作を変更する、複数の値を設定できます。
warn インタラクティブモードで警告を出力し、削除されたコミットについて警告します

error

rebase を停止し、削除されたコミットの警告メッセージを出力します

ignore

既定で設定され、失われているコミット警告を無視します
  • 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 master
        \
         o---o---o---o---o featureA
              \
               o---o---o featureB

 

featureB は featureA に基づいていますが、featureA の変更に依存しておらず、master からの分岐にすぎないことがわかります。

 git rebase --onto master featureA featureB

featureA は <oldbase> です。master<newbase> となり、featureB は <newbase>HEAD が何を示しているかの参照となります。結果は次のようになります。


                      o---o---o featureB
                     /
    o---o---o---o---o master
     \
      o---o---o---o---o featureA

リベースの危険性を理解する

git rebase を操作する際に考慮すべき注意点の 1 つとして、rebase ワークフロー中にマージの競合の頻度が上がる可能性があるということがあります。これは、master から離れた、古いブランチがある場合に発生します。最終的には、master に対して rebase を実行します。その際、ブランチの変更と競合する可能性がある、多くの新しいコミットが含まれる可能性があります。この問題は、master に対してブランチのリベースを頻繁に行い、より頻繁にコミットを行うことによって簡単に修正できます。--continue および --abort コマンドライン引数を事前に git rebase へ渡すことができます。そうでない場合、競合を処理する際に rebase がリセットされます。

rebase のより深刻な注意点は、インタラクティブな履歴書き換えからコミットが失われることです。インタラクティブモードで rebase を実行し、squash または drop などのサブコマンドを実行すると、ブランチの直前のログからコミットが削除されます。一見すると、これによって、コミットが完全に削除されたように見えます。git reflog を使用すると、これらのコミットが復元され、rebase 全体を元に戻すことができます。git reflog を使用して失われたコミットを見つける方法の詳細は、Git reflog ドキュメントページにアクセスしてください。

git rebase 自体はそれほど危険ではありません。本当の危険は、履歴を書き換えるインタラクティブな rebase を実行し、他のユーザーと共有しているリモートブランチへ結果を強制プッシュした際に発生します。この操作には、プルした際に他のリモートユーザーの作業が上書きされる機能を持っているため、回避すべきパターンです。

上流の rebase からの復旧

別のユーザーがリベースを実行し、コミットしたブランチへ強制的にプッシュすると、git pull は、強制的にプッシュされた部分を持つその前のブランチを基にしたコミットを上書きします。幸いにも、git reflog を使用すると、リモートブランチの reflog を取得できます。リモートブランチの reflog で、リベース前の ref を見つけることができます。その後、上記の高度なリベースの用途セクションに記載されているように、--onto オプションを使用してリモート ref に対してブランチをリベースできます。

概要

この記事では、git rebase の使用について説明し、基本的な使用事例と高度な事例、より高度な例を紹介しました。主要な論点は、次のとおりです。

  • git rebase の標準モードとインタラクティブモード
  • git rebase 構成オプション
  • git rebase --onto
  • git rebase によってコミットが失われる

ここまでで、git rebase の使用方法と他のツール (git refloggit fetchgit push など) について見てきました。詳細については、対応するページにアクセスしてください。

Git を学ぶ準備はできていますか?

この対話式のチュートリアルをお試しください。

今すぐ始める