git rebase
は、一見すると少し複雑に感じるかもしれませんが、具体的なシチュエーションで見ていくと非常に便利で強力なツールであることがわかります。
ここでは、開発現場でよくある3つのシナリオを通して git rebase
の使い方を解説します。
シナリオ1:作業ブランチを最新の状態に更新する
これが最も基本的な rebase の使い方です。あなたが新しい機能 feature-x を開発している間に、チームの他のメンバーが main ブランチを更新した、という状況を考えてみましょう。
状況
- あなたは
main
ブランチからfeature-x
ブランチを作成し、いくつかのコミットをしました。 - その間に、
main
ブランチには他の開発者の新しいコミットが追加されました。
目的
あなたの feature-x
ブランチを、最新の main
ブランチの先頭から枝分かれしたかのように見せたい。これにより、コミット履歴が一本道になり、非常に見通しが良くなります。
コマンド例
# 1. mainブランチを最新の状態にする
git checkout main
git pull origin main
# 2. feature-xブランチに切り替える
git checkout feature-x
# 3. feature-xブランチの基点を最新のmainに移動させる
git rebase main
何が起こるか?
- リベース前: feature-x は、古い main のコミットから分岐しています。
A---B---C (feature-x)
/
D---E---F---G (main)
- リベース後: feature-x でのあなたの変更(A, B, C)が、最新の main の先頭(G)の上に移動します。
A'--B'--C' (feature-x)
/
D---E---F---G (main)
コミットハッシュは変わりますが(AがA'になる)、変更内容は同じです。これで main
ブランチの最新の変更を取り込みつつ、履歴をきれいに保つことができます。
シナリオ2:散らかったコミットを一つにまとめる
開発中は「とりあえずコミット」や「タイポ修正」といった小さなコミットが増えがちです。プルリクエスト(マージリクエスト)を出す前に、これらを意味のある単位で一つのコミットにまとめると、レビューする人がとても楽になります。
状況
feature-x
ブランチに、以下のような細かなコミットがたくさんある。
commit 1: WIP
commit 2: add button
commit 3: fix typo
目的
これらのコミットを「feat: Add new feature X
」という一つのコミットにまとめる。
コマンド例
# 直近3つのコミットを対話的にリベースする
git rebase -i HEAD~3
HEAD~3
は「現在地から3つ前のコミット」という意味です。
何が起こるか?
- このコマンドを実行すると、テキストエディタが起動し、次のような画面が表示されます。
pick 1a2b3c4 WIP pick 5d6e7f8 add button pick 9g8h7i6 fix typo # Commands: # p, pick = use commit # s, squash = use commit, but meld into previous commit # ...
- 一番上のコミット(
1a2b3c4
)は残し、それ以降のコミットをsquash
(またはs
)に変更して保存します。
pick 1a2b3c4 WIP s 5d6e7f8 add button s 9g8h7i6 fix typo
- ファイルを保存して閉じると、次にコミットメッセージを編集する画面が開きます。ここで、3つのコミットメッセージを一つにまとめ、新しい分かりやすいメッセージ(例:
feat: Add new feature X
)を入力して保存します。
これで、3つのコミットが1つにまとまり、非常にクリーンな履歴が完成します。
シナリオ3:コンフリクト(競合)が発生した場合の対処
リベース中に、あなたの変更とリベース先のブランチの変更が同じファイルの同じ行で発生していると、コンフリクトが起こります。これは git merge
でも起こりうる状況ですが、対処法を覚えておけば怖くありません。
状況
シナリオ1の git rebase main
を実行したところ、コンフリクトが発生してリベースが一時停止した。
目的
コンフリクトを解消し、リベースを最後まで完了させる。
手順
- ターミナルにコンフリクトが発生したファイル名が表示されます。まずは git status で状況を確認しましょう。
- エディタでコンフリクトしたファイルを開きます。ファイル内には
<<<<<<<
,=======
,>>>>>>>
といったマーカーで競合箇所が示されています。
- これらのマーカーを削除し、どちらの変更を残すか、あるいは両方を組み合わせるかなど、コードを正しい状態に手動で修正します。
- 修正が完了したら、そのファイルを git add します。
git add <修正したファイル名>
- git rebase --continue を実行して、リベース処理を再開します。
git rebase --continue
まだ他にもコンフリクトがあれば、1〜5を繰り返します。
もし途中で「もうやめたい!」と思ったら、git rebase --abort
を実行すれば、リベースを始める前の状態に完全に戻すことができるので安心してください。
rebase
と merge
の使い分けと注意点 ⚠️
-
rebase
: 個人の作業ブランチで使い、コミット履歴をきれいに整えるのに最適です。 -
merge
: チームで共有しているブランチ(main
やdevelop
)に変更を取り込む際に使います。変更履歴がそのまま残ります。
シナリオ4:後からの変更を、過去の特定のコミットに含める
「あ、この一行の修正、さっきじゃなくて2つ前のコミットに含めるべきだった…」ということは、開発中によくあります。コミットの粒度をきれいに保ちたい場合に、このテクニックは非常に役立ちます。
状況
-
commit A: feat: ユーザープロフィール画面を追加 というコミットをした。
-
続けて、commit B: docs: READMEを更新 という別の作業をしてコミットした。
-
ここで、プロフィール画面に小さなタイポがあることに気づいた。このタイポ修正は commit B ではなく、関連する commit A に含めたい。
目的
新しく行う「タイポ修正」を、あたかも最初から commit A に含まれていたかのように、履歴をきれいに修正したい。
コマンド例 (--fixup
と autosquash
を使った方法)
-
まず、忘れていたタイポ修正を通常通り行います。
# (エディタでタイポを修正する)
-
修正したファイルを add します。
git add <タイポを修正したファイル>
-
次に、この変更をどのコミットに含めたいかをGitに伝えながら、一時的なコミットを作成します。
まず
git log
で、修正を含めたい過去のコミット(commit A
)のハッシュを調べます。git log # commit b4b5b6c ... (HEAD -> feature-y) # Author: ... # Date: ... # # docs: READMEを更新 (←これがcommit B) # # commit a1a2a3a ... (←修正を含めたいのはコレ!) # Author: ... # Date: ... # # feat: ユーザープロフィール画面を追加 (←これがcommit A)
--fixup
オプションを使ってコミットします。git commit --fixup a1a2a3a
これで
fixup! feat: ユーザープロフィール画面を追加
という特殊なメッセージのコミットが作成されます。 -
いよいよリベースを実行します。
--autosquash
オプションを付けて、修正したいコミット(commit A
)よりも古いコミットを基点にリベースを開始します。# commit A の親コミットを基点にリベースを開始 git rebase -i --autosquash a1a2a3a~1
(a1a2a3a~1 は a1a2a3a の1つ親のコミットという意味です)
何が起こるか?
-
--autosquash
オプションのおかげで、テキストエディタが開いた時点で、Gitが自動的にfixup
コミットを正しい位置に移動させ、アクションもfixup
に設定してくれます。pick a1a2a3a feat: ユーザープロフィール画面を追加 fixup c7c8c9c fixup! feat: ユーザープロフィール画面を追加 pick b4b5b6c docs: READMEを更新 # ...
fixup
はsquash
と似ていますが、fixup
コミットのメッセージは完全に破棄される、という違いがあります。タイポ修正のように、メッセージを残す必要がない場合に最適です。 -
あなたはこのファイルを何も編集せず、そのまま保存して閉じるだけでOKです。
-
Gitが自動的にリベース処理を行い、「タイポ修正」の変更が
commit A
に統合され、コミット履歴がきれいになります。
最終的な履歴
-
リベース前:
A (機能追加)
→B (ドキュメント更新)
→C (Aのタイポ修正)
-
リベース後:
A' (機能追加 + タイポ修正)
→B' (ドキュメント更新)
これで、後から気づいた修正を、まるでタイムマシンを使ったかのように過去の適切なコミットに含めることができました。コミット履歴を論理的で一貫性のあるものに保つための、非常に強力なテクニックです。
最も重要なルール:
他の人も使っている共有ブランチ(origin/main など)に対して rebase を実行してはいけません。 rebase は履歴を書き換えるため、他の人のリポジトリと深刻な食い違いを生む原因となります。rebase は、まだ自分しか使っていないローカルのブランチに対してのみ行う、と覚えておきましょう。