Communityコーディング&開発github.com

roselletabuena/git-safe-history-rewrite

Git 履歴を安全に書き換える — amend、reset、squash、reorder、drop、force-push など。他の人の作業を壊さず、履歴の書き換えが適切かどうかを判断し、リカバリポイントを取り、非インタラクティブシェルでもハングしない方法で実行します。

git-safe-history-rewrite とは?

このスキルは、Git の履歴書き換え操作(`commit --amend`、`reset`、`rebase`、`filter-repo` など)を安全に実行するためのものです。ユーザーが「amend」「squash」「fixup」「最後のコミットを取り消す」「コミットを削除する」「PR前にコミットを整理する」などと依頼したとき、または `git push --force` を使う場面で自動的に利用されます。スキルはまず、そのコミットがリモートにプッシュされているか、他の人が pull しているかを確認し、共有ブランチ上では `git revert` を推奨します。次に、`git branch backup/<branch>-pre-rewrite` でリカバリポイントを作成し、その後 `git commit --amend`、`git reset --soft`、`GIT_SEQUENCE_EDITOR` を使った rebase など、非インタラクティブな方法で履歴を書き換えます。最後に、`git push --force-with-lease=<branch>:<expected-remote-sha> --force-if-includes` を使って、安全に force push を行います。まれな `--force` や、`main`/`master` ブランチへの force push は禁止されています。このスキルは Claude Code、Cursor、Codex などの AI エージェント向けに設計されており、開発者の意図を正しく解釈し、チームのコラボレーションを壊さないようにするのに役立ちます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/roselletabuena/skills/tree/HEAD/plugins/git-workflows/skills/git-safe-history-rewrite

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

履歴を安全に書き換える

commit --amend、reset、rebase、filter-repo などのすべての書き換え操作は、新しいコミットを生成し、ブランチポインタを古いコミットから移動させます。これは他の誰も古いコミットを持っていない限り無害ですが、誰かが持っていると破壊的になります。その人のクローンには古い履歴が残っており、次に push または pull したときに重複や競合として戻ってきます。したがって、最初の質問は決して「どうやって」ではなく、「他に誰がこれらのコミットを持っているか?」です。

1. 判断:書き換えるか、元に戻すか

git fetch --prune
git status --short --branch                  # ahead/behind、およびダーティ状態
git branch -r --contains <commit>            # どのリモートブランチがすでに保持しているか
git log --oneline @{u}..HEAD                 # ローカルのみに存在するコミット
コミットが存在する場所実行すること
ローカルのみ、プッシュされていない自由に書き換える。
このユーザーのみが作業するブランチ(PR ブランチ)にプッシュされた書き換え、その後 lease 付きでプッシュ(ステップ5)。
共有ブランチまたは保護ブランチ:main、master、develop、release/*、または他の人のコミットが上に積まれているもの書き換えない。 git revert を使用する。
すでにリリースまたはデプロイ済みgit revert。書き換えではデプロイを取り消せない。

ユーザーが「main からあのコミットを削除して」と言った場合、彼らが望んでいるのは通常、その影響をなくすことであり、SHA を消すことではありません。git revert <sha> はそれを実現し、履歴を正直に保ち、通常の push だけで済みます。マージコミットの場合は、git revert -m 1 <merge-sha> を使用します。なぜ revert を選んだかを説明し、ユーザーが共有ブランチから消去することに固執する場合は、それが誰に影響するかを伝え、明示的な了承を得てください。

後続のコミットが同じ行を編集した場合、revert は競合します。これはまさに、悪いコミットの上にコミットが積み上げられたケースです。通常の競合解決(git-conflict-resolution を参照)と同じように解決します。悪い変更を削除し、後続の作業を保持してから git revert --continue を実行します。

revert の落とし穴が1つあります。マージを revert した後、同じブランチを再度マージしても何も戻ってきません。Git はそれらのコミットはすでに取り込まれたと認識するからです。作業を再適用するには、まず revert を revert します。

2. 何かを触る前にリカバリポイントを作成する

git rev-parse HEAD                           # 記録
git branch backup/<branch>-pre-rewrite       # 実在の参照。reflog だけではない

reflog もブランチがどこにあったかを記録しますが、このクローンにローカルであり、期限があります(到達可能なエントリは90日、デフォルトでは到達不能なものは30日)。reflog expire 後の git gc で終了します。名前付きのバックアップブランチはコストがかかりません。ユーザーが結果を確認するまでそのままにしておいてください。

3. 最も破壊的でないコマンドを選ぶ

目標コマンド注意
最後のコミットのメッセージを修正する、または追加し忘れたファイルを追加するgit commit --amend-m "..." または --no-edit でエディタの起動を回避。
コミットを取り消すが変更をステージングしたままにするgit reset --soft HEAD~1
コミットを取り消し、ステージングも解除するがファイルは保持するgit reset --mixed HEAD~1(デフォルト)
ブランチを戻し、ローカルの編集を失わないようにするgit reset --keep <commit>コミットされていない変更を上書きする場合は中断。--hard よりもこちらを推奨。
コミットされていない作業も捨てるgit reset --hardコミットされていない作業は reflog に含まれず、復元できません。 不要なものがないことを確認した後にのみ使用。不安な場合は最初に git stash -u。
1つのファイルの変更を破棄するgit restore <file>
修正を前のコミットに squash または fold するgit commit --fixup=<sha> の後に autosquash以下を参照。
コミットを削除または並べ替える提供された todo で rebase -i以下を参照。

pre-commit フックが失敗した場合、コミットは作成されていません。 git commit --amend を実行して「再試行」すると、前のコミットが変更され、新しい変更が間違ったコミットに折り込まれます。フックが失敗した後は、問題を修正し、再ステージングして、通常の新しいコミットを作成します。

インタラクティブエディタを使わないインタラクティブリベース

git rebase -i はエディタを開きますが、非インタラクティブシェルではハングします。これに依存しないでください。機能する2つの方法:

# fix-up コミットをターゲットに折り込む - git が生成した todo を受け入れる:
git commit --fixup=<target-sha>
GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash <base>

# 削除または並べ替え:自分で todo を書く。
GIT_SEQUENCE_EDITOR="sed -i '/^pick <short-sha> /d'" git rebase -i <base>

<base> は、変更したい最初のコミットの前のコミットです。リベースが競合で停止した場合は、解決し、git add してから、GIT_EDITOR=true git rebase --continue を実行します。問題が発生した場合は、git rebase --abort で開始前に戻ります。

4. 書き換えが意図した通りに変更されたかを確認する

メッセージの編集、並べ替え、squash では、最終的なツリーが同一である必要があります:

git diff backup/<branch>-pre-rewrite HEAD --stat      # 空 = 同じ内容
git range-diff backup/<branch>-pre-rewrite...HEAD     # コミットごとの比較

コンテンツを削除する意図があった場合、diff はそのコンテンツのみを表示し、それ以外は何も表示しないはずです。予期しないものがあれば、バックアップから復元してやり直します。

5. 公開 — ユーザーの許可を得た場合のみ

書き換え結果をプッシュすることは、外部に見え、元に戻すのが難しいステップです。ユーザーがすでにプッシュを要求していない限り、最初に確認します。

git push --force-with-lease=<branch>:<expected-remote-sha> --force-if-includes origin <branch>
  • 決してベアの --force を使用しないでください。 リモートにあるものをすべて上書きします。これには、あなたが見たことのないチームメイトのプッシュも含まれます。
  • 期待される SHA を指定します。 単なる --force-with-lease はリモート追跡参照と比較しますが、IDE のバックグラウンドでの git fetch がその参照を静かに更新するため、lease は何も保護しなくなります。ステップ1で確認した SHA を使用します(git rev-parse origin/<branch>)。
  • --force-if-includes(git 2.30+)は、リモートがローカルブランチが統合したことのないものに移動した場合、さらに拒否します。
  • main/master/develop/release/* に force push しないでください。 ホスティングのルールがそれをブロックしている場合、そのルールは正しいです。回避しないでください。

プッシュ後、古いブランチをチェックアウトしている人は pull ではなく新しいブランチにリセットする必要があること、およびオープンな PR のレビューコメントがアンカーを失う可能性があることをユーザーに伝えます。

ガードレール

  • 署名付きコミットは書き換えられると署名を失います。 -S で再署名するか、ユーザーに伝えます。
  • 他の人が作成したコミットは、たとえフィーチャーブランチ上でも、尋ねずに書き換えないでください。
  • 書き換えられたコミットの上にあるスタックブランチは古くなります。 git-stacked-pr-rebase を使用して手動でリベースするのではなく、再スタックします。
  • コミットされた秘密情報は、書き換えでは修正されません。 最初に認証情報をローテーションします。漏洩したと想定します。
  • 明確に報告します: 古い先端、新しい先端、バックアップ参照の場所、プッシュされていないものがあるかどうか。revert が正しい選択だった場合は、それを実行したことを伝えます。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

関連スキル