
シリーズ:Git 実践シリーズ
01
ソース管理ワークフロー
02
タグの使い方(ローカル / Gitea Web)
03
コミットメッセージ規約
04
rebase の使い方
05
GitHub / Gitea Actions
git rebase は「履歴を整える」ための強力なコマンドですが、同時に もっとも誤解されやすいコマンド でもあります。「怖いから触らない」という人も多い一方、使いどころを押さえると履歴が驚くほど読みやすくなります。
この記事では、まずmerge との違いを図で理解し、そのうえで「最新の main に追従する」「自分の作業コミットを整理する」という2大用途を実演します。最後に、絶対に守るべき 黄金ルール とコンフリクト対応まで扱うので、安心して使えるようになります。
💡 前提条件
ブランチを切って作業し、merge で統合する流れ(第1回の内容)を理解していると読みやすいです。操作はすべてローカルで行い、リモートへの影響は黄金ルールの章で扱います。
merge と rebase は何が違うのか
どちらも「あるブランチの変更を別の状態と合流させる」操作ですが、 履歴の残り方 が決定的に違います。次の状況を例にします。main が先に進んでいて、自分のfeature ブランチを最新のmain に合わせたい場面です。
merge は分岐と合流の事実をそのまま記録します。rebase はfeature のコミットを いったん外して main の最新コミットの上に並べ直す イメージです。このとき F1・F2 は内容こそ同じでも 別のコミットとして作り直され、ハッシュが変わります (図でF1' と表記しているのはこのためです)。これが後述の黄金ルールに直結する、最重要ポイントです。
| 観点 | merge | rebase |
|---|---|---|
| 履歴の形 | 分岐を残す(非線形) | 一直線(線形) |
| マージコミット | 作られる | 作られない |
| 元のコミット | そのまま保持 | 作り直す(ハッシュが変わる) |
| 分岐した事実 | 残る | 消える(平坦になる) |
| 共有済みの履歴 | 安全 | 危険(書き換え) |
| 向いている場面 | main への統合・公開ブランチ | 自分の作業ブランチの整理 |
✨ ひとことで言うと
merge は「事実を残す」、rebase は「物語を整える」 。チームで共有する統合は merge、手元の作業ブランチを公開前に整えるのは rebase、と覚えておくと迷いません。
用途その1:最新の main に追従する
長く作業しているとmain が先に進み、自分のブランチが古くなります。これを最新化するのが一番基本的な rebase です。feature ブランチにいる状態で実行します。 Step 1
# まず main の最新を取得しておく
$ git switch main
$ git pull origin main
# 作業ブランチに戻り、main の上に rebase する
$ git switch feature
$ git rebase main
#Successfully rebased and updated refs/heads/feature. これでfeature は「最新の main から枝分かれしたかのような」きれいな状態になります。merge で追従するとマージコミットが積み重なって履歴が複雑になりがちですが、rebase なら一直線を保てます。
✨ pull --rebase という近道
同じブランチをリモートと同期するとき、git pull (=fetch+merge)の代わりにgit pull --rebase を使うと、余計なマージコミットを作らずに最新を取り込めます。常にこの挙動にしたいならgit config --global pull.rebase true を設定しておきます。
用途その2:コミットを整理する(対話的 rebase)
作業中はfix: typo 、wip 、やっぱり戻す のような細かいコミットが増えがちです。これらを公開前に 意味のある単位へまとめ直す のが対話的 rebase(git rebase -i )です。
$ git rebase -i HEAD~3 エディタが開き、対象コミットが「やることリスト(todo)」として並びます。各行の先頭のpick を、別のアクションに書き換えて操作します。
pick a1b2c3d feat: ログイン機能を追加
pick d4e5f6g fix: typo修正
pick h7i8j9k fix: もう一つのtypo 下の2つを最初のコミットにまとめたい場合、pick をsquash (またはfixup )に変えます。
pick a1b2c3d feat: ログイン機能を追加
squash d4e5f6g fix: typo修正
fixup h7i8j9k fix: もう一つのtypo
# squash = 直前のコミットに統合し、メッセージは編集する
# fixup = 直前のコミットに統合し、メッセージは捨てる 保存してエディタを閉じると、3つのコミットが1つのfeat: ログイン機能を追加 にまとまります。todo で使える主なアクションは次の通りです。
| アクション | 意味 |
|---|---|
pick | そのまま採用(既定) |
reword | コミットは残し、メッセージだけ書き直す |
edit | その地点で止め、内容を修正できる |
squash | 直前のコミットに統合(メッセージは結合して編集) |
fixup | 直前のコミットに統合(このコミットのメッセージは破棄) |
drop | そのコミットを丸ごと削除する |
✨ 行を並べ替えれば順序も変わる
todo の行そのものを上下に入れ替えると、コミットの順序も変えられます。「先にこの修正を入れておきたかった」というときに便利です。ただし内容が依存し合っているコミットを入れ替えるとコンフリクトの原因になるので、独立した変更にとどめるのが無難です。
黄金ルール:共有した履歴を rebase しない
rebase はコミットを作り直す= 履歴を書き換える 操作です。すでにpush して他人が取得しているコミットを rebase すると、自分の手元と共有先で「同じはずのコミット」が食い違い、チーム全体が混乱します。
⚠️ ️ 守るべき唯一のルール
自分だけが触っている、まだ push していないコミットだけを rebase する。 共有ブランチ(特にmain )や、他人が見ている可能性のあるブランチの履歴は書き換えないこと。これさえ守れば rebase で事故は起きません。
では「自分の作業ブランチを整理した後、すでに push 済みだった」場合はどうするか。履歴が変わっているため通常の push は拒否されます。このときだけ--force-with-lease を使います。
# 通常の push は拒否される
$ git push origin feature
#! [rejected] feature -> feature (non-fast-forward)
# --force-with-lease なら「他人の新しい変更がない」ことを確認した上で上書きする
$ git push origin feature --force-with-lease✨ --force より --force-with-lease
単なる--force は問答無用で上書きするため、自分が知らない他人の変更まで消すおそれがあります。--force-with-lease は「自分が最後に見た状態から進んでいなければ」という条件付きで上書きするので、ひとりで使う作業ブランチでも安全側に倒せます。共有ブランチへの強制 push は依然として禁物です。
rebase 中のコンフリクト対応
rebase はコミットを1つずつ順番に適用し直すため、途中でコンフリクト(競合)が起きると、その地点で停止します。慌てず次の3コマンドで対応します。
# 競合発生。どのファイルが衝突したか確認
$ git status
# ① ファイルを編集して競合を解消したら、解決済みとして add
$ git add 解決したファイル
# ② 次のコミットへ処理を進める
$ git rebase --continue
# やり直したくなったら、rebase前の状態に完全に戻す
$ git rebase --abort| コマンド | 使う場面 |
|---|---|
git rebase --continue | 競合を解決しadd した後、次へ進める |
git rebase --abort | 中断して rebase 開始前の状態に戻す(最初の安全網) |
git rebase --skip | そのコミットの適用を飛ばす(不要だと確信できる場合のみ) |
🎯 困ったら abort
「何が起きているか分からなくなった」ときはgit rebase --abort でいつでも開始前に戻れます。さらに、誤って消したように見えるコミットもgit reflog から救出できます。rebase は思っているより 取り返しがつく 操作です。
コマンドリファレンス
| コマンド | コマンド内容 |
|---|---|
git rebase main | 現在のブランチを main の先端の上に乗せ直す(追従)。 |
git rebase -i HEAD~3 | 直近3コミットを対話的に整理(squash/reword等)。 |
git pull --rebase | マージコミットを作らずにリモートの変更を取り込む。 |
git rebase --continue | コンフリクト解決後に処理を再開する。 |
git rebase --abort | rebaseを中止して開始前の状態に戻す。 |
git push --force-with-lease | 整理後の自分のブランチを安全に上書き push する。 |
git reflog | 操作の履歴をたどり、消えたように見えるコミットを救出する。 |
トラブルシュート
❓ rebase したら push が拒否された
履歴を書き換えたため通常の push は通りません。 自分専用の作業ブランチ であればgit push --force-with-lease で上書きします。共有ブランチなら rebase 自体を避け、merge に切り替えてください。
❓ 同じコンフリクトが何度も出てきて終わらない
rebase は各コミットを順に適用するため、似た競合が連続することがあります。落ち着いて1つずつadd →--continue を繰り返してください。収拾がつかなければgit rebase --abort で戻り、merge での統合に切り替える判断も有効です。
❓ rebase でコミットを消してしまった気がする
git reflog を実行すると、これまでのHEADの移動履歴が一覧で出ます。目的のコミットのハッシュを見つけ、git reset --hard そのハッシュ またはgit checkout -b 救出ブランチ そのハッシュ で復元できます。
❓ そもそも merge と rebase どちらを使えばいい?
共有・統合は merge、手元の整理は rebase が基本方針です。チームに「mainへの統合はmerge、PR前のブランチ整理はrebase」のように運用ルールを決めておくと、メンバー間で履歴の作法が揃います。
まとめ
今回学んだこと
- merge は分岐を残す(非線形)、rebase はコミットを並べ直して一直線にする(線形)
- rebase はコミットを作り直すため、ハッシュが変わる
- 追従:
git rebase mainで feature を最新の上に乗せ直す - 整理:
git rebase -iで squash / reword / drop してコミットをまとめる - 黄金ルール:push 済み・共有ブランチの履歴は rebase しない
- 整理後の上書きは
--force-with-leaseを使う(--forceより安全) - コンフリクトは add →
--continue、迷ったら--abort、最後の砦はgit reflog
🎯 この記事のゴール
rebase の正体は「履歴を整えるための付け替え」であり、黄金ルールさえ守れば怖くない操作です。まずは自分専用ブランチでgit rebase -i HEAD~2 を試し、細かいコミットを1つにまとめる感覚をつかんでみてください。次回は、こうして整えた履歴を Gitea Actions で自動チェック・自動デプロイにつなげます。
