Git でコミットした直後に「メッセージを間違えた……」というケースは誰にでもあります。直近のコミットから過去のコミット、さらにマージコミットまで、メッセージを変更するための正しい手順と、チーム開発で安全に運用するためのベストプラクティスをまとめます。
重要:コミットメッセージの変更は 履歴の書き換えです。そのため、とくにチームで開発している場合において、共有ブランチ(例:
main)で履歴を変えると、他メンバーのローカル履歴を壊す恐れがあります。リモートのブランチ保護設定次第では、強制プッシュが禁止されている場合もあります。作業前にチーム内で合意を形成するか、ルールについて確認してください。
直近のコミットは git commit --amend で置き換えます。-m を付けるとエディタを開かずにメッセージを指定できます。
# 直近のコミットメッセージだけを差し替える
git commit --amend -m "修正後のメッセージ"
````
`--amend` は 直前のコミットを新しいコミットで置き換えるため、コミット ID が変わります。
よく使うバリエーション:
```bash
# コミットし忘れたファイルを追加(メッセージは変更なし)
git add <<忘れたファイル>>
git commit --amend --no-edit
# 既にコミットしたファイルを直前コミットから除外(メッセージは変更なし)
git rm --cached <<いらないファイル>>
git commit --amend --no-edit
# 署名付きコミットを維持して再生成(署名必須なリポジトリ向け)
git commit --amend -m "修正後のメッセージ"
# 著者情報を修正
git commit --amend --author="Correct Name <correct@example.com>" --no-editこれらのオプションは公式ドキュメントで定義されています。
--amend でコミット ID が変わると、通常の git push は 非 fast-forward として拒否されます。そこで 安全な強制プッシュである --force-with-lease を使うのが推奨です。
git push --force-with-lease origin <ブランチ名>--force-with-lease は「自分が最後に取得したリモートの先端が変わっていない場合だけ」上書きし、他人の未取得コミットを誤って消す事故を防ぎます。必要に応じて --force-if-includes を併用すると「ローカルブランチのreflogによって、リモートの更新を取り込んでいることを確認する」ことが可能なので、さらに安全な運用が可能です。
直近ではないコミットを修正するには インタラクティブ rebase を使います。対象コミットを含めた範囲を指定して「reword」または「edit」を指示します。
git log --oneline --graph --decorate などで履歴を把握し、対象のコミット(例:abc1234)を特定します。git log はコミット履歴の巡回表示、git reflog は 「HEAD がどのコミットを指してきたか」というローカルの移動履歴であり用途が異なります。対象の選定には git log の利用が適しています。
例として、直近 4 コミットを対象にします(古いものが上、最初に再適用される順で表示されます)。
git rebase -i HEAD~4この一覧は 最古のコミットが上に並びます。ここでは「直近からnコミットまでを表示する」という処理なので、実際にコミットメッセージを修正できるのはもう少し後の手順です。
pick を reword に変更reword abc1234 <現在のメッセージ>
pick def5678 ...
pick ghi9012 ...
pick jkl3456 ...表示されたコミットの履歴から、実際に修正したいコミットを探します。そして、その行について、pickと表示されている部分を書き換えます。書き換えは2パターンあり、reword はコミット内容は維持してメッセージだけ変更、edit はそのコミットで停止してcommit --amend や reset などを使って内容やメッセージを変更します。
reword abc1234 <現在のメッセージ>
pick def5678 ...
reword ghi9012 ... ←今回はこのコミットメッセージを書き換えるとする
pick jkl3456 ...修正したいコミットについていずれかを指定し保存、終了します。
なお、ここでは「どのコミットメッセージを修正するか?」の指示だけであり、実際にコミットメッセージを修正するのは次の手順です。
今度は、先の手順で指定したコミットについて書き換えます。メッセージを修正可能な画面が表示され、先ほどの手順で修正を指定したコミットが表示されているはずです。ここでやっとコミットメッセージの内容を書き換えできます。
保存して閉じると rebase が進みます。
git push --force-with-lease origin <ブランチ名>履歴を書き換えたため、安全な強制プッシュで更新します。
注意:インタラクティブ rebase の対象に マージコミットは通常含まれません。マージを保持して書き換える場合は次節の
--rebase-mergesを使います。
$ git rebase -i HEAD~2
error: cannot rebase: You have unstaged changes.
error: Please commit or stash them.git rebaseを実行すると、上記のようなエラーメッセージが表示されることがあります。これは、作業ツリーやインデックス(ステージ)に未コミットの変更が残っているため、git rebaseを開始できないことを示しています。git rebaseは基本的にクリーンな状態(作業ツリーとインデックスが HEAD と一致)でのみ実行されます。git rebaseは対象コミット群を再適用していく過程で、作業ツリーやインデックスを更新します。未コミットの変更が残っていると、適用順序や競合解決に支障が出るため、gitは事前にクリーンを要求するわけです。
対処法はいろいろあるものの、簡単なのはgit stashを使っていったん退避してしまう方法です。
git stash push -u # 追跡外(-u/--include-untracked)も含めて退避
git rebase -i HEAD~2
git stash pop # rebase後に戻す(競合時は手動解決)あるいは、明示的なスナップショットとして一度コミットしてしまう方法です。これから進めると、git stashとは異なり通常のコミットなので、失敗時の巻き戻しや復旧、比較などの手段が容易です。
git add -A # すべてステージ
git commit -m "wip: 作業途中のスナップショット"
git rebase -i HEAD~2 # 目的の rebase を実施--rebase-merges)マージコミットは通常の -i では落ちます。マージ構造を保ったまま履歴を書き換えるには --rebase-merges を付けます。
# 例:直近 10 コミット分でマージ構造を保持
git rebase -i --rebase-merges HEAD~10このモードの「merge -c <commit>」を使うと、元のマージコミットのメッセージを再編集できます(-C は元メッセージをそのまま使う)。マージを再作成するため、競合の再解決や手作業の再適用が必要になることがあります。
git filter-repo)多くのコミットに含まれる表記を一斉に修正したい場合、従来の git filter-branch よりも高速で安全な git filter-repo の利用が推奨されています(Git プロジェクトも推奨)。例えばメッセージ内の語句置換は次のように実行できます。
# expressions.txt に 'foo==>bar' のような置換規則を記述しておく
git filter-repo --replace-message expressions.txtgit filter-repo はコミットメッセージ、ファイル内容やパスの改変などを効率的に履歴全体へ適用できます。破壊的操作なので事前にバックアップを取り、作業後は安全な強制プッシュで共有します。
--force-with-lease(必要に応じて --force-if-includes)を使用する。共同作業中の他者のコミットを誤消去しにくくなります。git fetch しておき、PRやIssue、Slackなどのチャットで状況を共有する。git log と git reflog の正しい使い分けgit log:ブランチの コミット履歴を辿って表示(作者、日時、メッセージなど)。コミットを探す・範囲指定に向いています。git reflog:ローカルで HEAD や各 ref が指した位置の履歴(移動履歴)。HEAD@{n} は「n 回前に HEAD が指していたコミット」を意味します。復旧や「amend 前の位置に戻す」といった用途向けで、一般的なコミット選定には不向きです。注意点:
HEAD@{3}の番号は reflog の移動回数に基づく指標で、git rebase -i HEAD~nの 「n 個前のコミット」とは意味が異なります。対象コミット数を指定するHEAD~nとは混同しないでください。
git commit --amend、過去のコミットは git rebase -i の reword/edit、マージコミットは --rebase-merges を使う。--force-with-lease(必要に応じて --force-if-includes)。共有ブランチでの履歴書き換えはチーム合意のもとで実施する。git log を使い、git reflog は復旧や履歴移動の確認に使い分ける。git filter-repo を検討する。What’s the difference between git reflog and log? - Stack Overflow