事前準備(共通)
mkdir git-missions && cd git-missions
git init
echo "v1" > app.txt
git add . && git commit -m "feat: v1"
git checkout -b feature/work
echo "v2" >> app.txt
git commit -am "feat: v2"
echo "secret" > secret.txt
git add secret.txt && git commit -m "chore: accidentally add secret"
echo "v3" >> app.txt
git commit -am "feat: v3"
これで、事故の香り漂う履歴が完成した。
では出陣。
ミッション1: 直前コミットのメッセージを修正
目的: 直前の「feat: v3」を「fix: correct output」に書き換える
条件: まだ push していない想定
git commit --amend -m "fix: correct output"
クリア判定:
git log --oneline -1 に新しいメッセージが表示されること。
失敗時救済:
git reflog で amend 前のハッシュを確認してメモし、
git reset --hard <hash> で戻る。
ミッション2: 過去2つ前のメッセージだけ直す
目的: 「chore: accidentally add secret」を「chore: add placeholder」に変更する。
条件: push 前提。rebase で1個だけ編集。
git rebase -i HEAD~3
# エディタで該当コミット行を 'reword' に変更
# 保存後にメッセージ入力
クリア判定:
git log --oneline に新しいメッセージが表示されること。
注意:
既に共有ブランチに push 済みなら、履歴の書き換えは要相談。
…どうせ君ら相談しないけど。
ミッション3: 機密ファイルを履歴から抹消
目的: secret.txt を全履歴から削除する。
推奨: git filter-repo(なければ filter-branch は時代遅れ)
# pipx install git-filter-repo 等で入れておく
git filter-repo --path secret.txt --invert-paths
クリア判定:
git log --name-only --pretty=oneline | grep secret.txt が空になる。
注意:
共有済みなら強制 push地獄。やる前に別ブランチで試そう。
ミッション4: 事故ったマージコミットを取り消す
目的: ダミーの merge を作ってから git revert -m 1。
git checkout -b temp-base main 2>/dev/null || git checkout -b main
# main がない場合の保険。とりあえず main を作る。
git checkout -B main
git merge feature/work --no-ff -m "merge: feature/work"
git revert -m 1 HEAD
クリア判定:
取り消しコミットが追加され、内容がマージ前に戻る。
ミッション5: 未来に push 済みの誤コミットを「打ち消し」で修正
目的: 履歴は書き換えず、取り消しコミットで修正する。
git revert <誤コミットのハッシュ>
クリア判定:
新しい「revert: ...」コミットが積まれる。
使い所:
共有ブランチの無血停戦に。
ミッション6: 途中の1コミットだけ抜き取って別ブランチへ
目的: feat: v2 だけを hotfix/only-v2 に持っていく。
git checkout -b hotfix/only-v2 main
git cherry-pick <"feat: v2"のハッシュ>
クリア判定:
git log --oneline に v2 のコミットだけ登場する。
ミッション7: 連続する小粒コミットを1つに潰す(squash)
目的: feature/work の最後の3コミットを1つにまとめる。
git checkout feature/work
git rebase -i HEAD~3
# 2,3個目を 'squash' にしてメッセージ整える
クリア判定:
git log --oneline -3 が1つにまとまっている。
ミッション8: 取り消したい範囲をまとめて revert
目的: v2 から v3 までを一括打ち消す。
git revert <古いハッシュ>^..<新しいハッシュ>
クリア判定:
範囲打ち消しコミットが複数追加される。
ミッション9: 退路確保からの実験(reflogで帰還)
目的: 破壊的操作の前に退避ブランチを切り、好き放題して戻る。
git branch safety/backup
# ここで reset --hard や rebase をわざと失敗させる
git reflog
git reset --hard <安全そうな地点>
クリア判定:
どれだけ暴れても元に戻れることを理解する。
ミッション10: 直前コミットの内容だけ取り消してメッセージは残す
目的: メッセージはそのまま、変更だけ取り消す。
git revert --no-edit HEAD
クリア判定:
「revert: …」が追加され、実質ノーチェンジに近い状態へ。
ミッション11: ファイル単位で「このファイルだけ昔に戻す」
目的: app.txt だけ2つ前の状態に戻す。
git checkout HEAD~2 -- app.txt
git commit -m "revert(app.txt): restore to HEAD~2"
クリア判定:
他のファイルは現状維持で、app.txt だけ巻き戻る。
ミッション12: 公開履歴の著者名を修正して地獄を見る前に学ぶ
目的: 過去3コミットの author を統一(ローカル練習のみにすること)。
git rebase -i HEAD~3
# 全部 'edit' にして各停止点で:
git commit --amend --author="Your Name <you@example.com>" --no-edit
git rebase --continue
クリア判定:
git log --format='%h %an <%ae>' -3 が期待どおりになっている。
注意:
公開リポでやるなら事前合意。チームが燃える。
おまけ: 命を守る心得
push 前に 「これ、共有ブランチ?」 と一呼吸。
そして常に git branch safety/backup で退路を確保。