git commit コマンド: message 修正、削除、キャンセル、プッシュ、オプション
この記事では、git commit
コマンドに関連するほぼすべてのことを取り上げます。コミットメッセージの編集、削除、中断、プッシュ後の中断、そしてその他の便利なオプションについて説明します。
1. git commit コマンドの詳細
git init コマンド で新しいリポジトリを作成し、git add コマンド で必要な変更をステージしたら、いよいよコミットです。
git commit
コマンドは、現在ステージされているすべての変更を新しい履歴として現在のリポジトリに登録する役割を果たします。
これは Git システムの核となる部分なので、もう少し具体的に説明する価値があります。
それでは、順を追って詳しく見ていきましょう。
git commit
コマンドを入力すると、Git はリポジトリにステージされた変更があるかどうかをチェックします。ステージ済みの変更がない場合は、次のようなメッセージが表示されます。
$ git commit
On branch main
nothing to commit, working tree clean
-
変更の存在を確認した後、Git は新しいコミットオブジェクトを作成し、そのコミットに SHA-1 ハッシュ値を割り当てます。git add postで見たように、このオブジェクトはひとつのコミットに関連するすべてのデータを保存する役割を担います。
-
author、commiter、commit message などのメタデータがコミットオブジェクトに追加されます。
-
コミット オブジェクトには、コミット時のファイル システムを表すツリー オブジェクトへの参照も格納されます。ツリー オブジェクトは、実際のファイル コンテンツを保持する Blob オブジェクトを指します。 このようにして、コミットオブジェクトは現在の状態の既知のスナップショットを持ちます。
-
現在の作業ブランチの最新のコミットが、新しいコミットオブジェクトの親コミット値として格納されます。これにより、新しく作成されたコミットが現在のブランチに登録されるようになります。
-
最後に、以前は最新のコミットを指していた
HEAD
が、新しく追加されたコミットを指すように変更され、現在のブランチの最後のコミットとなります。
こうすることで、ステージ状態のすべての変更が、そのリポジトリの履歴の一部として記録されます。シンプルなコマンドですが、その裏ではさまざまなことが行われています。
次は、すでにコミット済みのコミットメッセージを変更する方法を説明します。
2. コミットメッセージの編集方法
Git は、一度コミットした内容を非常に重視するシステムです。 特に、プッシュが完了したコミットについてはそうです。 ひとつのコミットが多くのチームメンバーとの同期を壊してしまう可能性があるからです。
とはいえ、私たちは皆人間であり、ミスを犯すこともある。 コミットメッセージにタイプミスがあった場合は、すぐに修正してチームに知らせることができます。その方法を見てみましょう。
まず、現在のブランチの最後のコミットを編集するコマンドです。
$ git commit --amend -m "new commit message"
実際の実行結果を見てみましょう。
コミットメッセージ 2.md created
が 2.md created!!!
に変わっています。
しかし、ここで知っておかなければならないことがあります。
同じ場所にある2つのコミットはハッシュ値が異なります。
変更前は 7d16880
で、変更後は ff55e3d
です。
一見修正されたように見えますが、Git は内部的に古いコミットを削除し、同じ変更と新しいメッセージを含む新しいコミットを登録していました。
これは、古いコミットで新しい作業を始めたチームメンバーと衝突する可能性があります。 これを避けるには、コミットメッセージを変更したら、新しいコミットと同じようにチームに通知しなければなりません。
2.1. 既存のコミットをすでにコミットしてしまった場合は?
既存のコミットをローカルで編集する際に --amend
オプションを指定し、編集前にそのコミットがすでにプッシュされていた場合、ローカルリポジトリとリモートリポジトリの間に差異が生じます。
この場合、その時点でローカルリポジトリをプッシュすることはできません。リモートリポジトリがあなたのコミットを事前に台無しにするのを防ぐためです。
この場合、以下のようなエラーメッセージが表示されます。
$ git push origin main
! [rejected] your-branch -> your-branch (non-fast-forward)
error: failed to push some refs to 'your-remote-repository-url'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
この状況を解決するには2つの方法がある。
2.2. 強制プッシュによる解決
最初の方法は、リモートレポの拒否をオーバーライドして、現在のローカルレポを強制的にプッシュすることです。I 単独でプロジェクトを運営している場合、あるいはチーム全員が現在の問題を認識している場合、これは簡単です。
コマンドは1つだけです。
$ git push -f
または
$ git push --force
しかし、リモートリポジトリに新しいコミットが登録されたときや、チームメンバーが既存のコミットで作業しているときなど、それ以外の状況ではコミット履歴が乱れる可能性があります。
そのため、この方法は本当に安全な状況でのみ使用してください。
2.3. プルとマージの回避策
2番目の方法はいくらか安全です。リモートリポジトリから既存のコミット履歴をプルし、ローカルのコミット履歴とマージまたはリベースします。その後、通常のマージと同じようにプッシュバックすることができます。
この方法は安全ですが、余分な作業が必要になります。共同作業を続けるのであれば、2番目の方法がよいでしょう。
pull、merge、rebase コマンドの詳細については、対応する記事を参照ください。
3. コミットの取り消し方法
コミットを取り消すと既存のコミットは消えてしまいますが、既存のコミットに登録されていた変更が戻ってきて修正され、ステージされなくなります。
コマンドは次のとおりです。
$ git reset HEAD~1
実際に動かしてみましょう。
まず 2.md
の内容を新しいテキストに置き換え、次に add
を実行し、そして commit
を実行しました。
コミットを完了した後、git status
コマンドで確認したところ、もう変更はありませんでした(nothing to commit
)。
それから git reset HEAD~1
を実行してコミットを取り消すと、2.md
ファイルの変更がステージされていない状態に戻りました。(unstaged changes after reset
)
コミットを取り消した後に確認したステータスでも、2.md
ファイルがアンステージ状態になっていることがわかります。
最後に git log{:shell}:
を実行すると、上でログに記録した 1ac689b
のコミットが消えていることがわかります。
これが、すべての変更を失わずにコミットを取り消す方法です。
コミットを削除する方法
コミットを削除する方法です。既存のコミットを削除し、それに対して登録された変更も削除します。変更が不要な場合は、セクション3のキャンセル方法の代わりに使うことができます。
コマンドは次のとおりです。
$ git reset --hard HEAD~1
実際にやってみましょう。
git status
で確認すると、現在ステージされている変更はありません。git add
コマンドでステージし、コミットを完了させてください。ログをチェックすると、新しいハッシュ c2072b8
のコミットが登録されていることがわかります。
ここで、git reset ---hard HEAD~1
を実行して最後のコミットを削除します。履歴を見ると、c2072b8
のコミットは削除に成功しています。
5. プッシュ完了コミットの取り消し
セクション 2.1で説明しましたが、リモートリポジトリにプッシュしたコミットを git reset
コマンドで取り消したり削除したりするのは簡単ではありません。
ローカルリポジトリとリモートリポジトリのコミット履歴が異なるため、リモートリポジトリがプッシュを拒否してしまうからです。
これを回避するひとつの方法として、セクション 2.2にあるforce-pushという方法がありますが、これは推奨されないと述べました。 この方法の代わりに、Git には古いコミットを明示的に取り消し、新しいコミットを登録するコマンドが用意されています。
コマンドは次のとおりです。
$ git revert HEAD --no-edit
このコマンドは、前のコミットからの変更をすべて取り消す新しいコミットを作成します。これは開発者が手作業で行うこともできますが、開発者も人間なので一行でも見逃してしまう可能性があります。 そのため、上記のコマンドを使用することをお勧めします。
実際の実行を見てみましょう。
これが現在の新しいコミットの状態です。
先ほど登録したコミットを明示的に取り消す新しいコミットを登録してみましょう。
新しいコミットが作成され、既存のコミットを差し戻したというメッセージが自動的に書き込まれました。もし --no-edit
オプションを指定しなかった場合は、次のようになります、
vi'のようなプレーンテキストエディタを開き、そこに好きなコミットメッセージを書くことができます。
6. 開発を容易にするその他のオプション
git commit
コマンドの -m
オプションと --amend
オプションについては、ここまでのサンプルコードで説明しました。ここでは、その他のオプションをいくつか紹介します。
6.1. git add -u コマンドを使わない場合の -a オプション
git commit -a
オプションは、git add
コマンドを使わずにすべての変更を自動的にステージしてコミットします。
このオプションは追跡されていない状態のファイルを除外することに注意してください。次のように使用します。
$ git commit -a -m "commit without add command"
6.2. 空のコミット用の --allow-empty オプション
新しい変更のないコミットを残しておきたいことがあります。このオプションを使うと、何も変更されていないコミットを残すことができます。
次のように使います。
$ git commit --allow-empty -m "No changes"
6.3. --no-verifyオプションでpre-commitやcommit-msgフックを上書きする
Git フックは、あるイベントが発生したときに定義したスクリプトを実行するイベントトリガーです。pre-commitフック
とcommit-msgフック
は、新しいコミットが登録されたときに起動します。
no-verify
オプションはこれらのフックを無視して新しいコミットを作成する方法です。以下のように使用します。
$ git commit --no-verify -m "This Commit ignore hooks"
7. おわりに
この記事では、git commit
コマンドについて知っておくべきことをほぼすべて説明しました。コミットの方法を知ることは、コミットの変更や削除の方法を知ることと同じくらい重要だと私は信じています。
この記事で取り上げた git reset
コマンドと git revert
コマンドについては、もう少し突っ込んだ記事を書くつもりです。
