はじめに
船井総研デジタルのoswです。要件がペンディングになったため、業務で使っているリモートリポジトリのあるブランチを元に戻す、という作業を行いました。その際、git revet
、git reset
がごちゃごちゃになっていたため整理します。
結論
両者はコミットをある状態まで戻すという点においては共通していますが、下記のようになっているようです。(用途は私の主観です)
項目 | git reset | git revert |
---|---|---|
戻した時点までのコミット履歴が残る | X | O |
「戻したというコミット」が積まれる | X | O |
リモートリポジトリへそのままpushできる | X | O |
用途 | 個人開発 | チーム開発 |
reset
の場合コミット履歴がその時点まで戻るため、戻した時点から先のコミット履歴は見えなくなります。revert
はリポジトリの状態をその時点まで戻すものの、コミット履歴はそのままで、新たに「戻した」というコミットが積まれます。
上記の関係で、reset
で過去に戻したリポジトリをpushしようとすると、リモートリポジトリより古い状態になってしまうため-f
を付けて強制的に実行しないとpushできません。一方revert
はコミット履歴はそのまま残り、新たに「戻した」というコミットを積むためリモート+αの状態になりそのままpushが可能です。
pushの問題はさておき、reset
でも戻した後に空コミットを追加し、revertした旨をコミットメッセージに追加は可能かと思いますが、戻したコミット履歴は消えてしまうためどんな作業のコミットを戻したのか全て記述するのはかなり大変かと思います。
reset
はローカルリポジトリ上でやらかしてしまった時の対策として使うのが良さそうです。成果物を作成する過程で「不必要だった作業を戻した」というコミットをrevert
で履歴に残す意味もないですし、pushしたらリモートリポジトリのコミット履歴がぐちゃぐちゃになりそうです。
学習環境
- Windows 11 (WSL2 Ubuntu)
- VSCode 1.76.2
- Git 2.34.1
検証環境
話を簡単にするため、ローカルにリポジトリに下記READMEを作成し、それをいじって検証していきます。
ファイルに差分が入っていきますが、基本的には見出しを追加、削除するだけになります。
- 作成するREADME
## A
A
A
## B
B
B
## C
C
C
前提知識
コミット履歴を指定する際、HEADや@、それと合わせて"~"、"^"などが使われますが、これらの意味はこちらの記事で詳細に解説してくださっています。
ただ、マージされた場合に関しては"~"、"^"で挙動が異なるようです。
そもそもgit reset
とは
resetはオプションによって挙動が変わってくると思いますが、こちらの記事が非常にわかりやすいです。
そもそもgit revert
とは
revert
は元の習慣、状態に戻るという意味のようです。
〔もとの習慣・状態などに〕帰る,逆戻りする 〔to〕
https://ejje.weblio.jp/content/revert
gitにおけるrevertはリポジトリを特定のコミットの状態に戻し、revertしたことを示すコミットを積み上げるというもののようです。
この時、resetとは異なりコミット履歴はそのまま残り、その上に新しく積まれます。
(gitではrevertすることを「打ち消す」という表現が使われるようです)
直近のコミットをrevertしてみる
まずは検証環境で作成したリポジトリの「見出しC」を追加した直近のコミットをrevertしてみます。
すると、見出しCが削除され(見出しCを追加したコミット履歴は残る)、revertした旨のコミット履歴が積まれます。
$ git revert @
# コミットメッセージを編集し、保存して完了
- revert後のREADME
## A
A
A
## B
B
B
連続したコミットをrevertしてみる
続いて連続したコミットをrevertしてみます。その前に一度revertしてしまっているため、検証環境で作成した状態、revert前に戻しておきます。
# コミットのハッシュ値を確認
$ git log --oneline
f20946b (HEAD -> revert) Revert "追加: 見出しC"
37cbc2e (master) 追加: 見出しC
11acd6f 追加: 見出しB
4ea1d6d 追加: 見出しA
c9c6945 initial commit
# revert前の「37cbc2e」に戻す
$ git reset --hard 37cbc2e
連続したコミットをrevertします。ここでは見出しC, Bをrevertしてみます。
連続したコミットをrevertする場合は、現在のコミットからrevert対象となるコミットまでを"..."で繋ぎ、相対的に記述すれば良いようです。ここで右に指定するコミット-1のコミットまでrevertされるため、右側に指定したコミットの状態に戻ります。
# 現在のコミットから4個前のコミットまでをrevertする
$ git revert @...@~5
# 4個分のコミットメッセージを編集、保存して完了
実際にコマンドを叩いてみるとrevertする数だけ、上記例だと4回分のコミットメッセージを修正する画面が開きます。数が多いとさすがに面倒なので、--no-edit
を指定することでメッセージの編集をスキップする事ができます。
$ git revert --no-edit @...@~2
revert後に自動コミットさせない
revertをデフォルトで実行すると、「revertしたコミットの分」だけrevertした旨のコミットが積まれます。-n
を指定することでrevertはしても自動的にコミットまではされなくなります。
まずは検証前の環境に戻します。
$ git reset --hard 37cbc2e
$ git revert -n @...@~2
$ git status
On branch revert
You are currently reverting commit 11acd6f.
(all conflicts fixed: run "git revert --continue")
(use "git revert --skip" to skip this patch)
(use "git revert --abort" to cancel the revert operation)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: README.md
"-n"を指定した場合は、まずrevertでコミットの状態を戻します。この時、作業内容が戻った状態でかつgit add
された状態となります。そのため、「revertした」という複数のコミットは自動的に積まれず、手動でコミットすることでそれら複数のコミットを1つにまとめることができます。
戻したことを示すコミットが何もないと後々「あの件どうなったんだっけ」とよくわからなくなる可能性があるため、最低でも1つrevertしたコミットはある方が良いかもしれません。
特定のコミットをrevertする
特定のハッシュ値を指定してrevertすることも可能です。ただ、注意としてrevertで作業内容を取り消されるファイルは、特定のコミットの作業内容だけが取り消される訳ではなく、その時点に全て戻されるようです。
また、revertしたことで積まれるコミットですが、こちらは「指定したコミット」だけが積まれます。
作成したREADMEに見出しD, E, Fを追加して次の状態で確認してみます。
## A
A
A
## E
E
E
## F
F
F
## D
D
D
## B
B
B
## C
C
C
$ git revert --no-edit 11acd6f
見出しBから見出しFまで全て取り消されていることが確認できました。範囲を指定した場合と同じ挙動になるようですが、積まれるコミットが1つだけなので明示的にrevertした履歴を残したい場合は範囲を指定する必要がありそうです。
おわりに
試すまでは特定のコミットをrevertするに関して「特定の作業内容だけ」が取り消されるものだと思っていましたが、そういう訳ではないことがわかりました。
今回の学習で「やっちまったorz」がかなり減らせるのではないかな、と思います。幸いなことにまだ事件は起こしてないので、何かあっても冷静に対処ができるようになったかと思います。