取り消し操作の基本(restore / reset)
Gitでは操作を取り消す方法が状況ごとに分かれています。作業ツリーの編集を捨てて元に戻すには git restore、ステージに載せた変更を下ろす(ステージから外す)には git restore --staged を使います。直前のコミットを取り消して変更を作業ツリーに戻したいときは git reset を使いますが、履歴を巻き戻す操作なので慎重に扱います。まずは「どの段階を取り消したいのか」を見極めることが大切です。
Git の本当の価値は「安心して戻せる」ことにあります。間違えてもやり直せると分かっていれば、思い切って編集や実験ができます。ただし Git では、「どの領域を・どこまで戻したいか」によって使うコマンドが変わります。作業ツリーの編集だけを捨てたいのか、ステージング(git-staging)に載せたものを降ろしたいのか、確定したコミット(git-commit)そのものを巻き戻したいのか——目的が違えば手段も違います。やみくもに強力なコマンドを使うと、取り返しのつかない事故につながることもあります。そこでまず大切なのは、「いま自分はどの段階を取り消したいのか」を見極めることです。順に、軽い取り消しから重い取り消しへと整理して覚えていきましょう。
編集を捨てる・ステージから降ろす git restore
いちばん身近な取り消しが git restore です。これには2つの使い方があり、どちらも理解しておくと混乱しません。1つ目はファイル名だけを指定する形で、git restore readme.txt とすると、そのファイルに加えた「まだ add していない編集」を捨てて、最後のコミットの状態に戻します。たとえば echo "mistake!!!" >> readme.txt と誤って書き足してしまっても、git restore readme.txt を打てば、その編集はなかったことになります。実行後に git diff を打って何も表示されなければ、元の状態に戻っている証拠です。
2つ目は --staged を付ける形で、git restore --staged readme.txt とすると、git add でステージに載せた変更を、中身はそのまま残してステージから降ろすだけにします。つまり「add の取り消し」です。ここがポイントで、ファイル名だけの restore は編集内容そのものを捨ててしまうのに対し、--staged を付けたほうは編集した中身は手元に残し、ステージから外すだけなので安全です。前者は捨てた編集が戻らない破壊的な操作なので、本当に要らないと確信できるときだけ使ってください。どちらを使うか迷ったら、まず git status を見れば、いまファイルがどの段階にあり、どう取り消せるかの案内が出ています。
直前のコミットをやり直す git commit --amend
「コミットした直後にメッセージの打ち間違いに気づいた」「1ファイル add し忘れたままコミットしてしまった」というときは、git commit --amend で直前のコミットを作り直せます。たとえばメッセージを "Add thrid line" と打ち間違えたら、git commit --amend -m "Add third line" とすれば正しいメッセージに直せます。--amend は新しいコミットを増やすのではなく、直前の1つを置き換えます(その結果ハッシュは変わります)。ファイルを add し忘れていた場合も、そのファイルを git add してから --amend すれば、直前のコミットに含め直せます。ただし注意点があり、--amend はコミットを「作り直す」操作なので、すでに push して他人と共有したコミットには使わないのが原則です。まだ手元にしかない、共有前のコミットを整えるときに使ってください。
コミットごと巻き戻す git reset
コミットそのものを巻き戻したいときは git reset を使います。reset は HEAD(git-head、現在の基準位置)を過去のコミットへ移して履歴を巻き戻す操作で、このとき作業ツリーとステージングをどう扱うかで3つのモードがあります。--soft は履歴だけ戻し、変更はステージに残します(コミットし直したいとき向き)。既定の --mixed はコミットとステージを取り消しつつ、編集内容は作業ツリーに残します。--hard は履歴・ステージ・作業ツリーのすべてを巻き戻し、編集内容も消してしまいます。よく使う指定が HEAD~1 で、これは「現在の1つ前のコミット」を意味します。たとえば git reset --soft HEAD~1 は、最新のコミットだけを取り消して変更をステージに残すので、コミットをやり直したいときに便利です。
ここで強く意識してほしいのが、git reset --hard は手元の編集を問答無用で消す破壊的な操作だということです。git reset --hard HEAD と打てば、ステージも作業ツリーも最新コミットの状態まで巻き戻り、それ以降に書き足した内容は跡形もなく消えます。実行する前に、対象のコミットとモード(とくに --hard かどうか)を二度確認する慎重さが必要です。消したつもりのコミットも git reflog という記録でしばらくは追える場合がありますが、それに頼り切るのは禁物です。reset は強力なぶん、cli-basics で学んだ rm と同じく「実行前に目で確かめる」姿勢が効いてきます。
共有済みなら git revert、そして使い分け
reset には大きな弱点があります。すでに push して他人と共有したコミットを reset で消すと、自分の履歴と他人の履歴が食い違い、深刻な事故につながります。そこで、共有済みの変更を取り消したいときは git revert を使います。revert は過去のコミットを履歴から消すのではなく、「その変更を打ち消す新しいコミットを上に積む」操作です。たとえば git revert --no-edit HEAD とすると、直前のコミットの内容を打ち消すコミットが新たに作られ、「いつ何を取り消したか」も履歴として残ります(--no-edit はメッセージ編集を省く指定)。整理すると、reset は履歴を書き換えるためまだ共有していない手元の整理に向き、revert は履歴を残すため共有済みの変更の取り消しに向きます。「まだ自分しか見ていないなら reset、もう共有したなら revert」と覚えておくと、取り消しで迷わなくなります。