3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PRを出す前にコミット履歴を整えろと先輩に叩き込まれた話 〜git rebase -i 入門〜

3
Last updated at Posted at 2026-05-08

はじめに

前回の記事では「git pull するな、git fetch して git rebase しろ」というテーマで、リモートの変更を取り込むときの話をしました。

今回は少し違う話です。
前回が「リモートから取り込むときの rebase」だとすると、今回は「自分のブランチを push する前の rebase」の話です。
同じ rebase という言葉を使いますが、目的がまったく違います。

具体的には git rebase -i、いわゆる「インタラクティブ rebase」の話です。

この記事は「Git実務シリーズ」の第2弾です。
シリーズを通して「Gitを履歴管理ツールとして使う」をテーマに、実務で学んだことを整理しています。

「コミット履歴整えてください」

入社して数ヶ月が経ち、ようやく自分でタスクを持てるようになった頃の話です。

機能実装が終わって、意気揚々と PR を出しました。レビュアーの先輩からコメントが来ました。

「コードは問題ないですが、コミット履歴を整えてから再レビューお願いします」

コミット履歴を整える?

そのとき自分のブランチのコミット履歴はこんな感じでした。

* 9f3a1b2 fix
* 7c2d4e5 fix2
* 3a8b9c0 やっぱ違った
* 1e2f3a4 WIP
* 5d6e7f8 WIP2
* 2b3c4d5 ログイン機能追加

「コードは動いてるし、何が問題なんだろう」と正直思いました。

でも今なら先輩が言いたかったことがよくわかります。

なぜ汚い履歴がダメなのか

レビュアーが diff を追えない

PR のレビューでは、コミット単位で変更を追うことがよくあります。

「このコミットで何をしたのか」がわかれば、レビュアーは変更の意図を理解しながらコードを読めます。

でも WIP fix fix2 が並んでいると、どのコミットで何をしたのかがわかりません。結局ファイル全体の diff を読むしかなくなり、レビュアーの負担が増えます。

git blame したときに意味のわからないコミットに当たる

git blame はファイルの各行が「どのコミットで変更されたか」を表示するコマンドです。

バグの原因を調査しているとき、問題の行を git blame したら fix2 というコミットに当たった、という状況を想像してください。

「fix2 って何を fix したんだ?」となります。コミットメッセージが手がかりにならないので、コミットの中身を全部読むしかありません。

revert したいときに巻き戻し単位がおかしい

「この機能、やっぱり一旦戻してください」という状況は実務でたまに起きます。

そのとき git revert で特定のコミットを打ち消したいのに、WIP fix fix2 が混在していると、どこまで戻せばいいのかわかりません。

1コミットで1つの意図がまとまっていれば、「このコミットを revert すれば元に戻る」という操作ができます。

git rebase -i の基本

git rebase -i(インタラクティブ rebase)は、コミット履歴を後から編集できる機能です。

直近 N 件のコミットを編集したい場合はこう書きます。

git rebase -i HEAD~N

たとえば直近 5 件を編集したいなら:

git rebase -i HEAD~5

「このコミットより後を全部編集したい」という場合は、コミットハッシュで起点を指定することもできます。

git rebase -i abc1234

abc12341つ後のコミットから HEAD までが編集対象になります。abc1234 自体は含まれないので注意してください。

実行するとエディタが開き、こんな画面が表示されます。

pick 2b3c4d5 ログイン機能追加
pick 5d6e7f8 WIP2
pick 1e2f3a4 WIP
pick 3a8b9c0 やっぱ違った
pick 7c2d4e5 fix2
pick 9f3a1b2 fix

# Rebase a1b2c3d..9f3a1b2 onto a1b2c3d (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor

コミットの順番が git log逆順(古い順)で表示されます。最初に混乱するポイントです。

pick / reword / edit / squash / fixup の意味

各コマンドの意味を整理します。

コマンド 略記 意味
pick p そのまま使う
reword r コミットメッセージだけ書き直す
edit e そのコミットで一時停止し、内容を修正する
squash s 直前のコミットに統合する(メッセージも統合)
fixup f 直前のコミットに統合する(メッセージは捨てる)

実務でよく使うのは squash(または fixup)、rewordedit です。

squash と fixup の違い

どちらも「直前のコミットに統合する」操作ですが、メッセージの扱いが違います。

  • squash:統合後にメッセージを編集できる(両方のメッセージが候補として表示される)
  • fixup:統合先のメッセージをそのまま使う(統合元のメッセージは捨てる)

WIPfix typo のような「残す必要のないメッセージ」は fixup が便利です。

edit:コミットの中身ごと直したいとき

edit は「そのコミットで rebase を一時停止する」コマンドです。

reword がメッセージだけ直すのに対して、edit はコミットの内容(ファイルの変更)ごと修正できます。
「あのコミット、実はファイルを1つ追加し忘れてた」みたいなときに使います。

# rebase -i で対象コミットを edit に変更
edit 2b3c4d5 ログイン機能追加

保存して閉じると、該当コミットの時点で rebase が止まります。
そこで修正を加えて --amend します。

# ここで内容を修正する
git add .
git commit --amend   # コミット内容+メッセージを上書き

# 次のコミットへ進む
git rebase --continue

--amend で上書きしたら --continue で rebase を再開します。
複数のコミットを edit にしていれば、それぞれの時点で止まって同じ操作を繰り返します。

実務フロー:WIP コミットを整理してから PR を出す

実際の作業フローを見てみます。

Before:作業中のコミット履歴

作業中はこんな感じでコミットを積み重ねていきます。

* 9f3a1b2 fix
* 7c2d4e5 fix2
* 3a8b9c0 やっぱ違った
* 1e2f3a4 WIP
* 5d6e7f8 WIP2
* 2b3c4d5 ログイン機能追加

「とりあえず動いた」「あ、バグあった」「直した」みたいなコミットが積み重なっています。
これ自体は悪いことではないので、作業中はどんどんコミットしていったほうがいいです。

問題は、この状態のまま PR を出すことです。

rebase -i で整理する

git rebase -i HEAD~6

エディタが開いたら、こう書き換えます。

pick 2b3c4d5 ログイン機能追加
fixup 5d6e7f8 WIP2
fixup 1e2f3a4 WIP
fixup 3a8b9c0 やっぱ違った
fixup 7c2d4e5 fix2
fixup 9f3a1b2 fix

保存して閉じると、6つのコミットが1つにまとまります。

After:整理後のコミット履歴

* a1b2c3d ログイン機能追加

すっきりしました。

もし「ログイン機能追加」というメッセージも直したければ、pickreword に変えます。

reword 2b3c4d5 ログイン機能追加
fixup 5d6e7f8 WIP2
...

コミットメッセージの書き方については別の記事で詳しく扱う予定ですが、「何をしたか」だけでなく「なぜしたか」を書くと後から読みやすくなります。

最初に混乱したポイント

pick のまま保存して何も起きない

初めて git rebase -i を使ったとき、全部 pick のまま保存して閉じました。

何も変わりませんでした。

「あれ、失敗した?」と焦りましたが、これは正常です。pick は「そのまま使う」なので、何も変えなければ何も起きません。

コミットの順番が逆

git log では新しいコミットが上に表示されますが、git rebase -i のエディタでは古いコミットが上に表示されます。

「squash は直前のコミットに統合する」という説明の「直前」が、エディタ上では「1行上」を指します。最初はここで混乱しました。

squash したらエディタがまた開いた

squash を使うと、統合後のコミットメッセージを編集するためにエディタが再度開きます。

fixup を使えばこのステップがスキップされます。「メッセージは統合先のものをそのまま使う」という場合は fixup の方が楽です。

コンフリクトが起きたら

rebase -i の途中でコンフリクトが発生することがあります。最初は確実にパニックになりますが、落ち着いて対処できます。

# コンフリクトしているファイルを確認
git status

# ファイルを編集してコンフリクトを解消したら
git add .
git rebase --continue

「やっぱり元に戻したい」というときは --abort で rebase 前の状態に戻せます。

git rebase --abort

--abort の存在を知っているだけで、rebase -i への心理的なハードルがかなり下がります。「最悪 abort すればいい」と思えると、怖くなくなります。

force push との付き合い方

rebase -i でコミット履歴を書き換えた後、リモートに push しようとするとエラーになります。

$ git push origin feature/login
 ! [rejected]        feature/login -> feature/login (non-fast-forward)
error: failed to push some refs to 'origin/...'
hint: Updates were rejected because the tip of your current branch is behind

ローカルの履歴を書き換えたので、リモートと食い違っているためです。

このとき必要なのが force push です。

# ❌ これは危険
git push --force

# ✅ こちらを使う
git push --force-with-lease

--force-with-lease を使う理由

--force は「リモートの状態を無視して強制的に上書きする」コマンドです。

もし自分が rebase している間に、誰かが同じブランチに push していた場合、--force を使うとその変更を消してしまいます。

--force-with-lease は「自分が最後に fetch した時点からリモートが変わっていなければ push する」という安全装置付きの force push です。

自分しか触っていない feature branch であっても、--force-with-lease を習慣にしておくと安心です。

鉄則:自分しか触っていないブランチでだけやれ

rebase -i は「履歴を書き換える」操作です。

共有ブランチ(main、develop など)に対して rebase -i を使うと、他のメンバーの履歴と衝突して大変なことになります。

たとえば、チームメンバーが feature/loginabc1234 というコミットを起点に自分のブランチを切って作業していたとします。
そこに自分が rebase -i で abc1234 を書き換えて force push すると、そのメンバーのブランチは「存在しないコミットを親に持つ」状態になります。
Git 的には孤立したコミット(orphan)になり、そのメンバーは自分の変更を取り込むために複雑な操作を強いられます。

rebase -i は自分しか触っていない feature branch でだけ使う。
これは第1弾でも触れた rebase の鉄則と同じです。

どこまで整えるか問題

「どこまで整えればいいのか」は正直、チームや状況によります。

個人的な感覚としては:

  • 1コミット1意図を基本にする
  • WIP fix fix typo はまとめる
  • 機能追加とリファクタリングは分けておくと revert しやすい
  • 完璧主義になりすぎない

「完璧に整えなければ」と思いすぎると、rebase -i 自体が怖くなります。

「レビュアーが読んで意図がわかる粒度」を目安にするのが現実的です。

チームによっては「PR はすべて squash merge するからコミット履歴は気にしなくていい」という運用もあります。まずはチームのルールを確認しましょう。

先輩はなぜ整えろと言ったのか

当時は「コードが動いてるんだから履歴なんてどうでもいい」と思っていました。

でも実際に自分がレビュアーになってみると、コミット履歴が整っているかどうかで、レビューのしやすさが全然違うことがわかりました。

整った履歴は「この機能をこういう順番で実装しました」というドキュメントになります。
コードの変更意図が伝わるので、レビュアーは「なぜこう書いたのか」を推測する必要がなくなります。

先輩が言いたかったのは「ルールを守れ」ではなく、「レビュアーへの配慮として履歴を整えろ」ということだったんだと思います。

まとめ

git rebase -i は最初は怖く感じますが、使い方を覚えると PR 前の作業が格段に楽になります。

  • 作業中は気にせずどんどんコミットしていい
  • PR を出す前に git rebase -i HEAD~N で整理する
  • fixup で不要なコミットをまとめる
  • reword でメッセージを直す
  • edit で過去のコミットの中身を修正する
  • push は --force-with-lease
  • 自分しか触っていないブランチでだけやる

履歴は未来の自分やチームメンバーが読むものです。

読みやすい履歴を作ることが、良いエンジニアリングの一部だと思っています。


Git実務シリーズ

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?