【Git・GitHub操作ガイド】第6章 コミット履歴を自在に操る:`git rebase` , `reflog` , `filter-repo`

はじめに:コミット履歴、見返せますか?

git commitを繰り返すうち、「wip」や「fix typo」といった細かなコミットだらけで、後から見返したときに何をしたかったのか分からなくなってしまった…。そんな経験はありませんか?

この記事では、そんなコミットを綺麗に整頓し、誰が見ても分かりやすい履歴に作り変えるための強力なGitコマンドを、具体的な手順とともに解説します。

これらの操作は非常に強力ですが、履歴を書き換えるという性質上、チーム開発においては細心の注意を払って利用する必要があります。「共有された履歴は書き換えない」という黄金律を常に心に留めておきましょう。
主に、まだリモートリポジトリにプッシュしていない、自分だけのローカルな作業ブランチを綺麗に整えるために使います。

目次

対象読者

この記事は、主に以下のような方を対象としています。

  • Gitの基本操作(add, commit, push)は覚えたが、コミット履歴を綺麗に整理する方法がわからない方
  • プルリクエストを送る前に、細かすぎるコミットを一つにまとめたい方
  • 間違えてコミットしてしまった内容を取り消したいが、安全な方法が知りたい方

この記事で紹介する主なコマンド

  • インタラクティブリベース (rebase -i): コミット履歴を編集するためのツールです。「あのコミットメッセージ、間違えちゃった」「この3つのコミット、一つにまとめたいな」「コミットの順番を入れ替えたい」といった、要望に応えます。
  • Reflog (reflog): Gitの「操作ログ」であり、あなたの命綱です。reset --hardでコミットを消してしまったり、リベースに失敗して途方に暮れたりした時でも、reflogを見れば過去の状態に復元できる可能性が残されています。
  • filter-repo: リポジトリの履歴全体にわたる大規模な修正を行うための専門ツールです。誤ってコミットしてしまったパスワードや巨大なファイルを、履歴の始まりから終わりまで、完全に消し去るような管理用途に使います。

コマンドごとの利用事例と実行手順

1. git rebase -i HEAD~<n>:インタラクティブ(対話的)リベース

概要

直近のn個のコミット、あるいは指定したコミットまでの履歴を、対話形式で自由に編集できる機能です。

rebasemergeの違いは?
mainブランチの最新の変更を取り込む際、git mergegit rebaseのどちらを使うか迷うかもしれません。

  • git merge: 変更の履歴を合流させ、マージしたという事実を「マージコミット」として残します。歴史が分岐・合流するため、複雑になることがあります。
  • git rebase: あなたのブランチの変更を一旦脇に置き、mainブランチの最新地点に移動してから、あなたの変更を一つずつ適用し直します。履歴が一直線になり、スッキリと見やすくなります。

この章で紹介するrebase -iは、後者の「履歴を繋ぎ直す」仕組みを応用して、自分のコミットを編集するテクニックです。

利用シーン

  • プルリクエストを作成する前に、"fix typo" "wip" "add log"のような細かすぎるコミットを、"feat: ユーザー認証機能を追加"のような一つの意味のあるコミットにまとめたい(スカッシュ)。
  • コミットメッセージを修正したい(リワード)。
  • コミットの順序を入れ替えたい。

実行手順

  1. feature/my-workブランチに、以下のような3つのコミットがあるとします。
- 0d1d7f4 (HEAD -> feature/my-work) Add documentation
- 2b8f879 Implement main logic
- a3c5c91 Start feature
  1. 直近3つのコミットを編集するために、以下のコマンドを実行します。
git rebase -i HEAD~3
  1. すると、テキストエディタが開き、以下のような編集画面が表示されます。
pick a3c5c91 Start feature
pick 2b8f879 Implement main logic
pick 0d1d7f4 Add documentation

# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# s, squash <commit> = use commit, but meld into previous commit
# ... (他にも多くのコマンドがあります) ...

このリストは上が古く、下が新しいコミット順になっています。

  1. 【例】3つのコミットを1つにまとめる(スカッシュ)
    2番目と3番目のコミットのpicksquash(またはs)に変更して、ファイルを保存・終了します。squashは「押しつぶす」という意味で、その名の通り、コミットを一つ前のコミットに押し込んでまとめます。
pick a3c5c91 Start feature
s 2b8f879 Implement main logic
s 0d1d7f4 Add documentation
  1. すると、再びテキストエディタが開き、3つのコミットメッセージを統合して、新しい一つのコミットメッセージを記述する画面が表示されます。ここで最終的なメッセージを綺麗に整えて保存・終了します。
  2. git log --onelineで履歴を確認すると、3つのコミットが1つにまとまっていることがわかります。

【TIPS】図で理解するrebase
このように、コミットの歴史が書き換わる様子は、git log --graphのようなコマンドでツリー構造を視覚的に確認すると、より直感的に理解できます。修正前と修正後で、ぜひ見比べてみてください。

Before: Rebase前のコミット履歴

Rebase前のコミット履歴

After: squashで1つにまとめた後のコミット履歴

squashで1つにまとめた後のコミット履歴
  • 【注意点】
    mainブランチなど、すでに他の人と共有しているブランチでリベースを行うと履歴の食い違いが発生し、チームに大きな混乱を招きます。
    他の人が古い履歴を元に作業している場合、あなたがリベース後の履歴を強制的にプッシュすると、チーム全体の履歴に食い違いが生じ、深刻なコンフリクトが多発したり、最悪の場合、他の人の作業内容が消えてしまう危険性があります!
    リベースは、まだプッシュしていない、あるいは自分専用のブランチでのみ行いましょう!!

コマンドの実行例:git rebase -i(履歴を必要なものだけに整理する)

  • README.mdとindex.html(ホーム画面テンプレート)の履歴のみに整理する
# 1) git logで現在のログを確認
hogehoge@mac hello-world % git log --oneline  
27e8d44 (**HEAD** -> **feature/a-branch**) ホーム画面のテンプレート作成
26ddfca Revert "upd:add fifth line"
7767ada upd:add fifth line
237f791 upd:add 4th line
1e0cd76 upd:add 3rd line
0a344a3 docs: update README.md
19f8f2f Revert "feat: Add initial index file and Update README file"
462a977 feat: Add initial index file and Update README file
97aea68 Initial commit
hogehoge@mac hello-world %

# 2) ログ整理開始
hogehoge@mac hello-world % git rebase -i HEAD~6
hint: Waiting for your editor to close the file... 
pick 0a344a3 # docs: update README.md
pick 1e0cd76 # upd:add 3rd line
pick 237f791 # upd:add 4th line
pick 7767ada # upd:add fifth line
pick 26ddfca # Revert "upd:add fifth line"
pick 27e8d44 # ホーム画面のテンプレート作成

# Rebase 1e0cd76..25202a8 onto 1e0cd76 (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
# ... 中略 ...
#
# However, if you remove everything, the rebase will be aborted.

# 3) スカッシュする履歴を指定
pick 0a344a3 # docs: update README.md
s 1e0cd76 # upd:add 3rd line
s 237f791 # upd:add 4th line
s 7767ada # upd:add fifth line
s 26ddfca # Revert "upd:add fifth line"
pick 27e8d44 # ホーム画面のテンプレート作成

# :wqで保存

---
# 4) 整理する内容が表示され、新しいコミットメッセージを入力する。
feat: Add initial content to README  <- 入力したメッセージ
# 
# This is a combination of 5 commits.
# This is the 1st commit message:

docs: update README.md

# This is the commit message #2:

upd:add 3rd line

# This is the commit message #3:

upd:add 4th line

# This is the commit message #4:

upd:add fifth line

# This is the commit message #5:

Revert "upd:add fifth line"

This reverts commit 193b0cde8e5e9eec4f3974a790145b3ced3a2e6f.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Mon Sep 8 00:55:50 2025 +0900
#
# interactive rebase in progress; onto 19f8f2f
# Last commands done (5 commands done):
#    squash 7767ada # upd:add fifth line
#    squash 26ddfca # Revert "upd:add fifth line"
# Next command to do (1 remaining command):
#    pick 27e8d44 # ホーム画面のテンプレート作成
# You are currently rebasing branch 'feature/a-branch' on '19f8f2f'.
#
# Changes to be committed:
#       modified:   README.md
#

---
# :wqで保存

hogehoge@mac hello-world % git rebase -i HEAD~6
[detached HEAD 7c323f1] docs: update README.md
 Date: Mon Sep 8 00:55:50 2025 +0900
 1 file changed, 3 insertions(+)
Successfully rebased and updated refs/heads/feature/a-branch.
hogehoge@mac hello-world %

# 整理後の
hogehoge@mac hello-world % git log --oneline   
2a4a213 (**HEAD** -> **feature/a-branch**) ホーム画面のテンプレート作成
7c323f1 docs: update README.md
19f8f2f Revert "feat: Add initial index file and Update README file"
462a977 feat: Add initial index file and Update README file
97aea68 Initial commit

2. git reflog:ローカルgitログの確認と復元

概要

HEADが移動したすべての記録、つまりcommit, reset, rebase, mergeなど、あなたがリポジトリで行ったほぼすべての操作履歴を保持している「保険」のようなコマンドです。

利用シーン

  • git reset --hardで必要なコミットを誤って消してしまった!
  • リベース操作に失敗して、ブランチの状態がおかしくなってしまった!

実行手順

  1. 困っている状況ですが、気を取り直して、まずgit reflogを実行します。
git reflog

すると、以下のような操作ログが表示されます。

a1b2c3d HEAD@{0}: reset: moving to a1b2c3d
f4a1b2c HEAD@{1}: commit: Add important feature
...
  1. f4a1b2cが、resetで消してしまう直前の、戻りたいコミットだとします。
  2. git reset --hard <コミットID>を実行して、その状態に完全に復元します。
git reset --hard f4a1b2c

これで、失われたコミットが復活します。reflogはあなたの最後の希望です。

【注意点】

reflogで追跡されるのは、あくまであなたのローカルリポジトリ上の操作履歴のみです。リモートリポジトリにpushした履歴は、他の開発者と共有されているためreflogで安易に戻すことはできません。reflogは、ローカルでのみ作業しているときの「お守り」だと考えましょう。


3. 【管理用】git filter-repo:リポジトリの履歴を大規模に書き換える

概要

リポジトリの全履歴の中から、特定のファイルや情報を完全に削除するための、強力な外部ツールです。(pip install git-filter-repoなどで別途インストールが必要です)
※以前はgit filter-branchというコマンドが使われていましたが、操作が複雑で危険性も高かったため、現在は非推奨となり、このgit-filter-repoの使用が公式に推奨されています。

利用シーン

  • 機密情報(パスワード、APIキー)を誤って過去のコミットに含めてしまったので、全履歴から完全に抹消したい。
  • 巨大な動画ファイルを誤ってコミットしてしまい、リポジトリが肥大化したので、そのファイルを履歴から消し去りたい。

実行手順(例:特定のファイルを履歴から削除)

git filter-repoはリポジトリ全体を書き換える非常に強力な「最終兵器」です。実行する際は、個人ではなくリポジトリ管理者が、以下の手順に沿って慎重に作業を進める必要があります。

  1. 事前準備:ミラークローンを作成する
    安全のため、元のリポジトリを直接操作するのではなく、「ミラークローン」という特殊なクローンを作成して作業します。

ミラークローンとは?
git clone --mirrorで作成されるクローンは、通常のgit cloneとは少し異なります。ワーキングツリー(実際にファイルが見えるディレクトリ)を持たず、リモートリポジトリの内部データベース(.gitディレクトリの中身)を完全にそのままコピーしたものです。「裸の(bare)リポジトリ」とも呼ばれます。

リモートリポジトリと全く同じ構造を持つため、履歴の書き換えのようなリポジトリ全体に影響を及ぼす操作を行うのに最適です。

# 元のリポジトリのミラークローンを作成
git clone --mirror https://github.com/your-org/your-repo.git

# 作成されたディレクトリに移動
cd your-repo.git

これにより、ブランチやタグを含むリポジトリの全データが、作業用のyour-repo.gitディレクトリに複製されます。

  1. 履歴の書き換えを実行する
    git filter-repoコマンドを実行して、不要なファイルを履歴から完全に削除します。
# 'path/to/secret.txt' というファイルを全歴史から削除
git filter-repo --path path/to/secret.txt --invert-paths

コマンドが完了すると、指定したファイルを含まない新しいコミット履歴が作成されます。

  1. リモートリポジトリへ変更を反映する
    【最重要警告】この操作は、リモートリポジトリの履歴を強制的に書き換えます。実行する前に、必ずチームメンバー全員に連絡を取り、作業を一時停止してもらってください。

準備ができたら、書き換えた履歴をリモートリポジトリにプッシュします。

# 書き換えた全てのブランチとタグを強制的にプッシュ
git push origin --force --all
git push origin --force --tags
  1. 後処理:チームメンバーのローカル環境を更新する
    歴史を書き換えた後は、チームメンバー全員が手元にある古いローカルリポジトリを一度削除し、新しくクローンし直す必要があります。
# (各メンバーのPCで実行)
# 古いリポジトリのディレクトリを削除
rm -rf your-repo

# 新しい履歴を持つリポジトリをクローンし直す
git clone https://github.com/your-org/your-repo.git

この後処理を全員が徹底しないと、誰かが古い履歴をプッシュしてしまい、書き換え作業が台無しになる危険性があります。


参考資料


まとめ

この記事では、Gitのコミット履歴を自在に操るための、以下の強力なコマンドを解説しました。

  • git rebase -i: コミットの統合や順序変更、メッセージ修正を行い、履歴を綺麗に整えるための基本ツール。
  • git reflog: reset --hardなどの危険な操作からでも復旧できる、ローカル作業の「保険」。
  • git filter-repo: リポジトリ全体の履歴から機密情報などを完全に削除する、管理者向けの「最終兵器」。

特にrebase -iは、プルリクエストを出す前の習慣にすると、共同開発者から感謝されること間違いなしです。まずは個人用のブランチで安全に試し、分かりやすいコミット履歴を目指しましょう。

インタラクティブリベースは、特にプルリクエスト前の履歴整理に絶大な効果を発揮します。まずは安全な個人ブランチで、rewordsquashから試してみてください。

さあ、あなたのMacを快適な開発環境にしましょう!!

SNSでもご購読できます。

コメントを残す

*