取り消し操作の基本(restore / reset)
Gitでは操作を取り消す方法が状況ごとに分かれています。作業ツリーの編集を捨てて元に戻すには git restore、ステージに載せた変更を下ろす(ステージから外す)には git restore --staged を使います。直前のコミットを取り消して変更を作業ツリーに戻したいときは git reset を使いますが、履歴を巻き戻す操作なので慎重に扱います。まずは「どの段階を取り消したいのか」を見極めることが大切です。
Git の本当の価値は、「安心して戻せる」ことにある。間違えてもやり直せると分かっていれば、思い切って編集や実験ができる。
ただし Git では、ひとつの「戻す」で済むわけではない。「どの領域を・どこまで戻したいか」によって使うコマンドが変わるからだ。
作業ツリーの編集だけを捨てたいのか、ステージング(git-staging)に載せたものを降ろしたいのか、確定したコミット(git-commit)そのものを巻き戻したいのか。目的が違えば手段も違う。
やみくもに強力なコマンドを使うと、取り返しのつかない事故につながることもある。そこでまず大切なのは、「いま自分はどの段階を取り消したいのか」を見極めることだ。
順に、軽い取り消しから重い取り消しへと整理して覚えていこう。
↩ 編集を捨てる・add を降ろす 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 は手元の編集を問答無用で消す破壊的な操作だということだ。
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」と覚えておくと、取り消しで迷わなくなる。