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 ブランチに統合するには、直接マージとリベース後のマージの二つの方法があります。前者のオプションを選択すると三方向マージとマージコミットが必要となるのに対し、後者は早送りマージが可能であり履歴の直線性は完全に維持されます。次の図は、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: 再生の間、マークが付いた各コミット上でコマンド ライン シェル スクリプトを実行します。便利な例として、特定のコミットでのコードベースのテスト スイートの実行があります。これにより、リベース時に回帰を特定するのに役立ちます。

要約

Interactive rebasing gives you complete control over what your project history looks like. This affords a lot of freedom to developers, as it lets them commit a "messy" history while they're focused on writing code, then go back and clean it up after the fact.

ほとんどの開発者は、 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 リベースを操作する際に考慮すべき注意点の 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 は、強制的にプッシュされた tip を持つその前のブランチをオフにしたコミットを上書きして、その以前のブランチをオフにし、コミットを上書きします。幸いにも、git reflog を使用すると、リモート ブランチの reflog を取得できます。リモート ブランチの reflog で、リベース前の ref を見つけることができます。その後、上記の高度なリベースの用途セクションに記載されているように、--onto オプションを使用してリモート ref に対してブランチをリベースできます。

まとめ

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

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

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

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

この対話式チュートリアルを利用しましょう。

今すぐ始める