git rebaseの使い方と実践的な運用ガイド

git rebaseの基本的な使い方からinteractiveモード、mergeとの使い分けまで、実務で役立つ知識を体系的に解説します。コミット履歴を整理し、チーム開発の品質を高めましょう。
代表 / エンジニア
Gitを使ったチーム開発で、コミット履歴が煩雑になり「もう少し整理してからプルリクエストを出したい」と感じたことはないでしょうか。私自身、レビューしにくい履歴を何度も作ってしまい、git rebaseの重要性に気づかされました。本記事では、実務で役立つgit rebaseの使い方を体系的にお伝えします。
git rebaseとは何か
git rebaseは、あるブランチのコミットを別のブランチの先端に「付け替える」操作です。mergeがブランチの合流地点にマージコミットを作成するのに対し、rebaseはコミット履歴を直線的に再構成します。
最初は「履歴を書き換える」という説明だけでは、どのような場面で使うべきか分からなかったのですが、実際にチーム開発で運用してみると、その価値が実感できました。
基本的な構文は以下のとおりです。
# featureブランチをmainの最新に付け替える
git checkout feature
git rebase mainこの操作により、featureブランチのコミットがmainブランチの最新コミットの後ろに再配置されます。結果として、あたかもmainの最新状態から開発を始めたかのような、きれいな履歴が得られます。
rebase前後の履歴を比較する
言葉だけでは分かりにくいので、具体的な履歴の変化を見てみましょう。
rebase前の状態:
C---D---E (feature)
/
A---B---F---G (main)featureブランチはコミットBの時点でmainから分岐しています。mainにはその後F、Gというコミットが追加されています。
rebase後の状態:
C'---D'---E' (feature)
/
A---B---F---G (main)featureブランチのコミットC、D、EがmainのGの後ろに付け替えられ、C'、D'、E'として再生成されます。ここでダッシュ(')が付いているのは、コミットの内容は同じでも、コミットハッシュが変わっていることを意味します。この「ハッシュが変わる」という点が、後述する注意事項に直結してきます。
git pullでのrebase活用
日常的に使える場面として、git pull時のrebaseがあります。通常のgit pullはfetchとmergeを行いますが、--rebaseオプションを付けるとmergeの代わりにrebaseが実行されます。
# 通常のpull(merge)
git pull origin main
# rebaseを使ったpull
git pull --rebase origin main通常のpullでは、ローカルとリモートの両方に新しいコミットがあると、不要なマージコミットが生成されます。--rebaseを使えば、リモートの変更を取り込みつつ履歴を直線的に保てるのです。
毎回--rebaseを指定するのが面倒な場合は、Gitの設定でデフォルト化できます。
# pull時にデフォルトでrebaseを使用する設定
git config --global pull.rebase trueこの設定を入れてからは、余計なマージコミットが作られなくなり、個人的にはかなりストレスが減りました。
git rebase -i(interactiveモード)の活用法
実務でとくに重宝するのが、git rebase -i(interactiveモード)です。コミットの並べ替え、統合、メッセージの修正など、柔軟な履歴編集が可能になります。
# 直近3コミットを対話的に編集
git rebase -i HEAD~3実行するとエディタが開き、各コミットに対する操作を指定できます。主要なコマンドを以下にまとめました。
| コマンド | 省略形 | 動作 |
|---|---|---|
| pick | p | コミットをそのまま採用 |
| reword | r | コミットメッセージを変更 |
| edit | e | コミット内容を修正 |
| squash | s | 前のコミットに統合(メッセージ編集あり) |
| fixup | f | 前のコミットに統合(メッセージ破棄) |
| drop | d | コミットを削除 |
私がよく使うのはsquashとfixupです。開発中は細かくコミットしておき、プルリクエスト前にrebase -iで意味のある単位にまとめるという運用は、多くのチームで採用されています。同じように細かいコミットの扱いに悩んでいる方も多いのではないでしょうか。
実践例:コミットをまとめる
# エディタで以下のように編集
pick a1b2c3d ユーザー認証機能の実装
fixup d4e5f6g typo修正
fixup h7i8j9k レビュー指摘の修正この操作で3つのコミットが1つに統合され、最初のコミットメッセージだけが残ります。
実践例:コミットメッセージを修正する
急いでコミットした結果、メッセージが雑になってしまうことはよくあります。rewordを使えば、コミット内容はそのままにメッセージだけを変更できます。
# エディタで以下のように編集
reword a1b2c3d 修正
pick d4e5f6g バリデーション追加保存してエディタを閉じると、rewordを指定したコミットのメッセージを編集する画面が再度開きます。ここで「ユーザー入力のバリデーションエラー表示を修正」のように、意味が伝わるメッセージに書き換えましょう。
実践例:コミットの順序を入れ替える
エディタ上で行の順序を変えるだけで、コミットの適用順序を入れ替えられます。
# 元の順序
pick a1b2c3d APIエンドポイントの追加
pick d4e5f6g テストコードの追加
pick h7i8j9k APIのバリデーション追加
# 入れ替え後(バリデーションをAPI追加の直後に移動)
pick a1b2c3d APIエンドポイントの追加
pick h7i8j9k APIのバリデーション追加
pick d4e5f6g テストコードの追加関連する変更を隣接させることで、レビュー時に変更の流れが追いやすくなります。ただし、コミット間に依存関係がある場合はコンフリクトが発生する可能性があるため、注意が必要です。
実践例:editでコミットの内容を修正する
editコマンドを使うと、過去のコミットの内容自体を修正できます。「あのコミットにこのファイルの変更を含めるべきだった」というときに便利です。
# エディタで以下のように編集
edit a1b2c3d ユーザー認証機能の実装
pick d4e5f6g テストコードの追加保存すると、指定したコミットの時点でrebaseが停止します。
# ファイルを修正
vim src/auth.ts
# 変更をステージングして、コミットに追加
git add src/auth.ts
git commit --amend --no-edit
# rebaseを続行
git rebase --continueこの操作は強力ですが、後続のコミットとコンフリクトする可能性があることは意識しておきましょう。
--autosquashで効率的にコミットを整理する
開発中に「後でまとめる前提の修正コミット」を作ることがよくあります。--fixupオプションを使ってコミットしておけば、rebase時に自動で並べ替えとfixupが行われます。
# 通常のコミット
git commit -m "ログイン機能の実装"
# 上記コミットに対するfixupコミットを作成
git commit --fixup=<ログイン機能のコミットハッシュ>
# autosquashで自動的にfixupを適用
git rebase -i --autosquash mainエディタが開いたとき、fixupコミットは自動的に対象コミットの直後に配置され、コマンドもfixupに設定済みの状態になります。手動で並べ替える手間が省けるため、日常的にfixupコミットを活用している方にはおすすめの機能です。
--autosquashをデフォルトで有効にする設定もあります。
git config --global rebase.autosquash truerebaseとmergeの使い分け
rebaseとmergeのどちらを使うべきか、一概には言えない部分もあります。私自身、プロジェクトごとに試行錯誤を重ねた結果、以下のような判断基準に落ち着きました。
| 観点 | rebase | merge |
|---|---|---|
| コミット履歴 | 直線的で読みやすい | 分岐・合流が記録される |
| マージコミット | 作成されない | 作成される |
| コンフリクト解消 | コミットごとに解消が必要 | 一度にまとめて解消 |
| 共有ブランチでの使用 | 原則として避ける | 安全に使用可能 |
| 履歴の正確性 | 実際の開発過程と異なる場合がある | 開発過程がそのまま残る |
推奨される使い分けの指針:
- rebaseが適切な場面:ローカルのfeatureブランチをmainに追従させるとき、プルリクエスト前にコミットを整理するとき
- mergeが適切な場面:共有ブランチへの統合、リリースブランチの運用、マージ履歴を明示的に残したいとき
振り返ると、最初はすべてmergeで運用していた時期もありましたが、rebaseを適切に組み合わせることで、レビュー効率が大きく向上したと感じています。
具体的な運用フロー
私が実際のプロジェクトで採用している運用フローを紹介します。featureブランチでの開発からプルリクエスト作成までの一連の流れです。
# 1. mainから最新の状態でfeatureブランチを作成
git checkout main
git pull --rebase origin main
git checkout -b feature/user-profile
# 2. 開発中は細かくコミット(後で整理する前提)
git commit -m "プロフィール画面のUI作成"
git commit -m "WIP: APIとの接続"
git commit -m "API接続完了"
git commit -m "typo修正"
git commit -m "レビュー対応: エラーハンドリング追加"
# 3. mainの最新を取り込む
git fetch origin
git rebase origin/main
# 4. コミットを整理する
git rebase -i origin/mainステップ4のinteractive rebaseでは、5つのコミットを意味のある2つにまとめます。
pick a1b2c3d プロフィール画面のUI作成
squash d4e5f6g WIP: APIとの接続
squash h7i8j9k API接続完了
squash l0m1n2o typo修正
squash p3q4r5s レビュー対応: エラーハンドリング追加squashを選ぶとメッセージの編集画面が表示されるので、以下のように整理したメッセージを書きます。
ユーザープロフィール機能の実装
- プロフィール画面のUIを作成
- APIとの接続とエラーハンドリングを実装チームでの運用ルールを決める
rebaseとmergeの使い分けは、チームで認識を揃えておくことが大切です。以下のような方針をあらかじめドキュメント化しておくと、メンバー間の混乱を防げます。
- featureブランチ → main:rebaseで最新に追従した後、プルリクエスト経由でmerge(
--no-ffでマージコミットを残す) - mainへの直接push:禁止
- push済みブランチのrebase:原則禁止。やむを得ない場合は事前にチームに共有
- コミット整理:プルリクエスト作成前にinteractive rebaseで整理することを推奨
このような方針は、プロジェクトの初期段階で決めておくのが理想です。後からルールを導入しようとすると、メンバーそれぞれの運用が定着してしまっていて、統一が難しくなることがあります。
rebaseで注意すべきポイント
git rebaseは強力なツールですが、誤った使い方をするとチームに混乱をもたらす可能性があります。以下のチェックリストを参考にしてください。
rebase前の安全確認チェックリスト:
- 操作対象がローカル専用のブランチであること
- リモートにpush済みのコミットをrebaseしようとしていないこと
- 作業中の変更がstashまたはコミット済みであること
- コンフリクトが発生した場合の対処法を理解していること
とくに重要なのは「push済みのコミットをrebaseしない」という原則です。rebaseはコミットハッシュを変更するため、他のメンバーがすでに取得したコミットをrebaseすると、履歴の不整合が発生します。
push済みのブランチをrebaseしてしまった場合のリカバリ
原則として避けるべきですが、もしpush済みのブランチをrebaseしてしまった場合の対処法も知っておくと安心です。
# force pushが必要になる(通常のpushは拒否される)
git push --force-with-lease origin feature/user-profileここで--forceではなく--force-with-leaseを使うのがポイントです。--force-with-leaseは、リモートブランチが自分の認識と異なる状態になっている場合(他のメンバーが新しいコミットをpushしていた場合など)にpushを拒否してくれます。--forceは無条件に上書きしてしまうため、他のメンバーの変更を消してしまうリスクがあります。
# 危険:他のメンバーの変更を上書きする可能性あり
git push --force origin feature/user-profile
# 安全:リモートに予期しない変更があればpushを拒否
git push --force-with-lease origin feature/user-profileもし自分がrebaseする前の状態に戻したい場合は、git reflogを使います。
# reflogでrebase前の状態を探す
git reflog
# 出力例
# a1b2c3d HEAD@{0}: rebase (finish): returning to refs/heads/feature
# ...
# f8e9d0c HEAD@{5}: commit: レビュー対応
# rebase前の状態に戻す
git reset --hard HEAD@{5}git reflogはGitが記録している操作履歴を表示するコマンドです。rebaseやresetで「消えた」と思ったコミットも、実はreflogから復元できることが多いです。私も何度かこれに助けられました。
コンフリクト解消の実践手順
コンフリクトが発生した場合は、以下の手順で対処します。
# コンフリクトを解消した後
git add <解消したファイル>
git rebase --continue
# rebaseを中断したい場合
git rebase --abort--abortで元の状態に安全に戻せることを知っておくだけでも、rebaseへの心理的なハードルは下がるかもしれません。
rebaseでのコンフリクト解消は、mergeの場合と少し異なります。rebaseではコミットを一つずつ再適用していくため、複数のコミットでコンフリクトが発生すると、その都度解消が必要になります。
# rebase中にコンフリクトが発生
Auto-merging src/components/Header.tsx
CONFLICT (content): Merge conflict in src/components/Header.tsx
# 1. コンフリクトファイルを確認
git status
# 2. ファイルを開いてコンフリクトを解消
vim src/components/Header.tsx
# 3. コンフリクトマーカーを確認して適切な内容に修正
# <<<<<<< HEAD
# mainブランチの内容
# =======
# featureブランチの内容
# >>>>>>> コミットメッセージ
# 4. 解消したファイルをステージング
git add src/components/Header.tsx
# 5. rebaseを続行
git rebase --continue
# 6. 次のコミットでもコンフリクトが発生する場合は、再度1〜5を繰り返すコミット数が多いブランチでは、コンフリクト解消を何度も繰り返す必要が出てくることがあります。そうした場合はgit rerere(reuse recorded resolution)を有効にしておくと、一度解消したコンフリクトのパターンを記憶してくれるため、同じコンフリクトの再解消を自動化できます。
# rerereを有効にする
git config --global rerere.enabled truerebaseに関連する便利なオプション
ここまで基本的な使い方を紹介してきましたが、実務で知っておくと役立つオプションをいくつか補足します。
--onto:付け替え先を柔軟に指定する
通常のrebaseは現在のブランチを指定したブランチの先端に付け替えますが、--ontoを使うとより柔軟な制御が可能です。
# featureブランチのうち、developから分岐した部分だけをmainに付け替える
git rebase --onto main develop featureこの操作は、featureブランチがdevelopから分岐していて、その変更だけをmainに移植したいといった場面で役立ちます。
# before
E---F---G (feature)
/
C---D (develop)
/
A---B (main)
# after: git rebase --onto main develop feature
E'---F'---G' (feature)
/
A---B (main)--keep-base:分岐元を変えずにコミットを整理する
--keep-baseは、ブランチの分岐元を変えずにinteractive rebaseを行いたいときに便利です。
# mainとの分岐点を変えずにコミットを整理
git rebase -i --keep-base maingit rebase -i HEAD~nでも似たことはできますが、「mainから分岐した後のコミットすべて」を対象にしたい場合、コミット数を数える必要がありません。
--update-refs:関連ブランチも一緒に更新する
Git 2.38以降で使える比較的新しいオプションです。スタックされたブランチ(ブランチが連鎖している状態)をrebaseする際に、関連するブランチの参照も自動的に更新してくれます。
# 連鎖したブランチをまとめてrebase
git rebase --update-refs mainこのオプションがない場合、親ブランチをrebaseした後に子ブランチも手動で付け替える必要がありましたが、--update-refsによってその手間が省けます。
トラブルシューティング
rebaseを使っていると、予期しない状況に遭遇することがあります。よくあるケースと対処法をまとめました。
rebase中に途中でやめたくなった
# rebaseを完全に中止し、rebase前の状態に戻る
git rebase --abortコンフリクト解消が複雑すぎる場合や、そもそもrebaseの方針を見直したい場合は、迷わず--abortしましょう。
rebase後にテストが通らなくなった
rebaseは各コミットを順番に再適用するため、途中のコミットでビルドが壊れることがあります。--execオプションを使うと、各コミットの適用後に指定したコマンドを実行できます。
# 各コミットの適用後にテストを実行
git rebase -i main --exec "npm test"テストが失敗したコミットでrebaseが停止するため、どのコミットで問題が発生したかを特定しやすくなります。
間違えてrebaseを完了してしまった
rebase完了後でも、git reflogを使って元の状態に戻ることができます。
# 操作履歴を確認
git reflog
# rebase前の状態に戻す(HEAD@{n}は適切な番号に置き換え)
git reset --hard HEAD@{n}git reflogはGitのセーフティネットとも言える機能です。rebaseに限らず、「やり直したい」ときの最後の砦として覚えておくと安心です。
大量のコンフリクトが発生してしまう
長期間mainに追従していなかったブランチをrebaseすると、大量のコンフリクトに見舞われることがあります。こうした事態を防ぐには、日常的にmainの変更を取り込む習慣をつけることが大切です。
# 毎日の作業開始時にmainを取り込む習慣
git fetch origin
git rebase origin/mainどうしてもコンフリクトが大量になる場合は、無理にrebaseせず、mergeで取り込むことも選択肢の一つです。道具に振り回されるのではなく、状況に応じて柔軟に判断することが大事だと、私も経験を通して学びました。
まとめ
git rebaseは、コミット履歴の可読性を高め、チーム開発の質を向上させるための重要なツールです。interactiveモードを活用すればコミットの整理も柔軟に行えますし、mergeとの適切な使い分けによって、プロジェクトの運用はより洗練されたものになります。
本記事で紹介した内容を振り返ります。
- 基本のrebase:featureブランチをmainの最新に付け替え、直線的な履歴を維持する
- interactive rebase:squash、fixup、reword、editなどを使い、コミットを意味のある単位に整理する
- --autosquash:fixupコミットと組み合わせることで、コミット整理を効率化する
- mergeとの使い分け:ローカルでの整理にはrebase、共有ブランチへの統合にはmergeを使う
- 安全な運用:push済みのコミットはrebaseしない、
--force-with-leaseを使う、reflogで復元する - トラブル対処:
--abortで安全に中断、--execでテスト実行、reflogでやり直し
最初から完璧に使いこなす必要はありません。まずはローカルブランチでのコミット整理から試してみて、少しずつ運用に取り入れていくのがよいのではないでしょうか。
aduceでは、Git運用を含む開発プロセスの改善やチーム開発体制の構築を支援しています。開発フローの見直しや技術的な課題でお困りの際は、お問い合わせからお気軽にご相談ください。