理系学生日記

おまえはいつまで学生気分なのか

Gitでの各種操作の取り消し・修正方法

VCS を使ったときのメリットは多々ありますが、そのうちの一つは、いつでも修正内容をもとに戻せることだと思います。

ローカルで修正した内容をもとに戻したい

$ git checkout -- <pathspec>…​

あるいは、

$ git restore <pathspec>…​ 

によって、指定したファイル filename が修正前の状態に戻ります。

git checkout

git checkout には大きく分けると2つの責務があり、branch の切り替え、あるいは、working tree 下のファイルの復元 です。 何らかの変更を加えたあとでその変更を取り消したい、というときに使うのはこのうちの後者に該当します。

公式的には git checkout [<tree-ish>] [--] <pathspec>…​ を使おう、というのが一番通りが良いと思うのですが、多くの場合は git checkout -- <pathspec>... を使うことになると思います。具体的には

$ git checkout -- fileA.c

というような形でしょうか。 tree-ish を省略した上記の形式においては、git の index 領域の fileA.c の内容で working tree 上のファイル内容を復元する(更新する)、という意味になります。 (tree-ish を明示的に指定すると、指定した tree-ish に紐づくファイル内容で、working tree 領域および index 領域の内容が更新されます)

git restore

git checkout に複数の責務があると分かりづらい、という理由で、git の v2.23 から git switchgit restore が導入 されました。 名前の通り、「取り消し」に使用するのは git restore の方です。

git checkout -- fileA.c と同じことを git restore で明示的に行おうとすると、

$ git restore --worktree <pathspec>…​

になります。--worktree は、復元する対象領域を worktree にする、という意味です (index 領域に復元したいのであれば、--staged を付与する必要があります)。

直前の commit の内容を変更したい

直前に行った commit の内容を変更したいというときは、index 領域に変更内容 (ファイルの追加や削除、コンテンツの変更) を登録 (git add) した上で、

$ git commit --amend

を使います。コミットメッセージは index 領域とは関係ありませんが、上記コマンド実行時に再指定が可能です。コミットメッセージだけ変更したい、ということであれば、git commit --amend -m <new-commit-msg> というように CLI で完結できます。

commit を取り消したい

コミットをなかったことにしたい場合は、git reset を使います。

$ git reset [<mode>] [<commit>]

上記形式での git reset は、current branch の HEAD をcommit に変更するとともに、その commit で示されるファイルツリーの内容を working tree、および、index 領域に反映したりしなかったりします。 詳細な動作は mode の指定で行います。ちなみに、mode 指定を省略すると、--mixed を指定したものとして解釈されます。

例えば、直前の commit だけ取り消して作業を続行したいということであれば、 git reset HEAD~ だったりを実行することになるでしょう。

mode 振る舞い
--soft working tree も index 領域も変更しない。commit したという事実だけが取り消される。つまり、working tree 上の変更、および、Index 領域上の変更は reset によって失われない。
--mixed index 領域だけ commit のファイルツリーの内容に置き換え、working tree はそのままとなる。つまり、working tree 上の変更は reset によって失われない。
--hard working tree も index 領域も commit のファイルツリーの内容に置き換えられる。もう全部きれいにしたい!というときに使う。結果として、取り消した commit の内容は(頑張らないと)見えなくなる。
--keep コミットを無かったことにしたい、という文脈においては --mixed と同じ。正直、存在理由がよくわからない。Index 領域は commit で置き換えられ、working tree の内容はそのままになる。
--soft との違いとしては、reset 実行タイミングにおける working tree 上のファイルに変更が存在しており、かつ、当該ファイルの commitHEAD に差分がある場合、reset が失敗する。詳細は、 git-reset マニュアルの DISCUSSIONS セクション を読んでくれ。
--merge マニュアル精読したけどさっぱりわからん。助けてくれ。

git reset --hard を取り消したい

git reset --hard によって誤って特定のコミットを削除してしまい、これを復元したい場合。 git の gc によって回収されていない限りは git reflog によって参照を取得できるので、この参照をもとに操作すれば良いです。

$ git reflog 

git reflog を実行すると、HEAD の変更ログ (branch の切り替え、reset の実行、commit など) 一覧と識別子(例えば HEAD@{1}) が取得できます。これを引数にして、git reset --hard HEAD@{1} を実行すれば、そのときのファイルツリーの内容が復元できます。

push したコミットを変更したい

基本的には、

$ git revert <commit>

をしたあとで git push を使うことになると思います。git revert は、対象 commit を打ち消す新たなコミットを生成するサブコマンドですね。

他にももし master ブランチを対象とすると絶対に怒られる方法としては以下などがあり得るでしょうが絶対に怒られると思います。

$ git reset --hard HEAD~
$ git push --force

参考文献