Fri, Jan 1, 2016

git resetとrevertを図解する

git resetとrevertを図解する

この記事を読んだ、またはGitのオブジェクトモデルを理解していることを前提に、Gitgit revertgit resetというコマンドについて説明する。 この二つはしばしばコミットを取り消すコマンドとして同じ文脈で説明されることが多いのでこのエントリでも一緒に説明するが、実際は全く異なるコマンドだし、そもそもどちらもコミットを取り消すコマンドではない。


git revert

git revertは、指定したコミットが持ち込んだ変更を打ち消すコミットを追加する。 リバースパッチを適用すると言ってもよい。 コミットを追加しかしないので、このコマンドによって既存のコミットが消えたり変わったりすることはない。

図にすると以下の感じ。単純。

git reset

git resetには二つの機能がある。 インデックスを再設定する(i.e. resetする)機能と、HEADを付け替える(i.e. resetする)機能だ。

インデックスの再設定

インデックスの再設定をするコマンドはgit reset <ワーキングディレクトリ内のファイルのパス(複数可)>。 これを実行すると、指定したファイルについて、HEADが指すコミットが指すツリー内のブロブを指すようインデックスを更新する。

何を言っているのかわからないので図にする。

(この図では便宜的にHEAD、つまり参照をオブジェクト格納領域内に書いているが、実際には別の場所にあることに注意。)

図を見ると、git add Readme.mdgit reset Readme.mdがだいたい逆のことをしていることがわかる。 要するに、git add <パス>は指定したファイルをステージし、git reset <パス>は指定したファイルをアンステージする。

HEADの付け替え

HEADの付け替えをするコマンドはgit reset <コミット>。 これを実行すると、HEADが指しているコミットを指すようORIG_HEADを作成または更新し、指定したコミットを指すようHEADを更新する。 オプションによってはさらにインデックスやワーキングディレクトリを指定したコミットが指すツリーと同期するよう更新する。

このオプションには--soft--mixed (デフォルト)、--hardの三種類があり、それぞれのオプションを付けた時の更新対象を次の表に示す。

オプション HEAD インデックス ワーキングディレクトリ
--soft
--mixed
--hard


この三者の違いについては面倒だしだいたい分かるはずなので図にしないが、git reset <コミット>したときのHEAD動きについて次に図示する。

スライド中でgit reset HEAD^した時点で、コミットDは実質的に削除されたに近い状態になる。 ORIG_HEADという一時的なシンボリック参照で指されているだけで、どの参照からもたどり着けなくなるからだ。 コミットDはいずれgit gcによって実際に削除されるはずだし、git pushしてもコミットD、それが指すツリー、そのツリーの下にしかないブロブはリモートリポジトリに送られない。

よって、git reset <コミット>は普通コミットを削除したいときに使われる。 使われはするが、このコマンド自体がコミットを削除するわけではなくて、あくまでHEADを付け替えるコマンドであることを覚えていた方がいざというときに助かる。

因みに上のスライドでやった操作は、git commit --amendがやることとほぼ同じ。