Git入門:バージョン管理のきほん
直前のコミットを直す(commit --amend)
この回でやること
直前のコミットのメッセージや内容を作り直す git commit --amend を学びます。amend は修正ではなく別コミットへの置き換えであること、push 済みに使うと事故になる理由まで確認しましょう。
- 読む 約 8 分
コミットした 3 秒後に、add 忘れに気づく
ターミナル
$ git commit -m "ログイン機能を追加"
[feature/login 8f3c1a2] ログイン機能を追加
1 file changed, 24 insertions(+)
$ git status
Untracked files:
src/validator.js一緒に記録するはずだった src/validator.js が漏れています。メッセージの打ち間違いや、デバッグ用の出力の消し忘れも同じ場面です。
ここで「漏れを追加」というコミットをもう 1 つ積むと、半年後に履歴を読む人にとってはただのノイズになります。直前の 1 件を言い直すのが git commit --amend です。
ターミナル
$ git add src/validator.js
$ git commit --amend --no-edit
[feature/login 6c2a8f5] ログイン機能を追加
2 files changed, 31 insertions(+)--no-edit は「メッセージはこのままでいい」という指定です。2 files changed に変わり、本来入るはずだった 2 ファイルが 1 つの記録にまとまりました。逆に入れすぎたファイルがあるときは、git restore --staged で降ろしてから同じように打ちます。
直すのではなく、作り直して差し替える
--amend は直前のコミットを編集しているように見えますが、実際は違います。コミットの名前は中身から計算された指紋なので、中身が 1 文字でも変われば別の名前になります。
ターミナル
$ git log --oneline -1
8f3c1a2 ログイン機能を追加
$ git commit --amend -m "ログイン画面のバリデーションを追加"
$ git log --oneline -1
4d9b7e1 ログイン画面のバリデーションを追加起きているのは 3 手です。ステージにあるものを合わせて新しいコミットを作り、ブランチの指す先をそちらへ移し、元のコミットは誰からも指されない状態で残ります。
ここから 2 つのことが言えます。1 つは、何度でもやり直せることです。git add を忘れたまま --amend を打つのはよくある失敗ですが、中身は変わらず名前だけが新しくなるので、add してもう一度打てば済みます。
もう 1 つは、対象にできるのが直前の 1 個だけだということです。2 つ前を差し替えると、その後ろに連なる全部の親が変わるので、鎖を作り直す別の作業になります。
push 済みに使うと、相手の履歴と食い違う
自分の手元だけにあるコミットなら、--amend は安全です。問題は、すでに送ったあとです。名前が変わるので、手元は新しいコミット、リモートは古いコミットを指したまま、枝分かれした履歴になります。この状態で送ろうとすると、Git は non-fast-forward と言って拒否します。
ここで強制的に上書きするのが、いちばんやってはいけない対応です。その間に同僚が送っていた作業ごと消えますし、古いほうをすでに受け取っていた人の手元には、同じ変更が 2 回入った奇妙な履歴ができ上がります。厄介なのは、直した本人の手元では何も壊れて見えないことです。 異常が出るのは他の人の手元なので、原因の切り分けに時間がかかります。
このあと出てくる rebase も、同じようにコミットを作り替えます。公開済みの履歴に使えば、同じ事故が起きます。
判断は git status の先頭でつきます。
ターミナル
$ git status
On branch feature/login
Your branch is ahead of 'origin/feature/login' by 1 commit.ahead of ... by 1 commit は、まだ送っていないコミットが 1 個ある、という意味です。この範囲なら誰にも影響しません。共有済みだと分かったら --amend は使わず、git revert で打ち消してください。
git commit --amendは直前の 1 個だけを言い直す。2 つ前は対象にできない- 中身を書き換えるのではなく、新しいコミットを作って差し替えている
- 名前が変わるので、送ったあとに使うと相手の履歴と食い違う。そのときは
revert