Git入門:バージョン管理のきほん

変更を取り消す(restore)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • git restore が作業フォルダの編集を、どこの状態まで戻すのか
  • git restore --staged でステージから降ろす方法と、git restore との違い
  • 古い git checkout -- との関係と、いま使うべきコマンド

取り消したい場面は 2 種類ある

第 1 章で、ファイルの状態には 3 つの場所があると学びました。編集している 作業フォルダ、コミットの下書きを置く ステージ、確定した記録である コミット の 3 つです。

取り消したい、と思う場面はこの構造に対応して 2 種類に分かれます。

場面状態使うコマンド
編集を無かったことにしたい編集しただけ(git add していない)git restore ファイル名
git add を取り消したいステージに載っているgit restore --staged ファイル名

どちらも git restore ですが、--staged が付くかどうかで動く場所が変わります。ここを取り違えると意図しないファイルを壊すので、まず git status で自分がどちらの状態にいるか確認する習慣をつけてください。

なお、コミットまで済ませたものを取り消すのは、また別のコマンドです。それは コミットを取り消す(reset の 3 モード) で扱います。

編集を捨てる(git restore)

まず、編集しただけの状態を戻す使い方です。src/app.js を書き換えたものの、やはり要らなかったとします。

ターミナル

$ git status

プレーンテキスト

On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/app.js no changes added to commit (use "git add" and/or "git commit -a")

注目してほしいのは 3 行目です。Git 自身が「作業フォルダの変更を捨てるなら git restore <file> を使え」と案内しています。git status は次に打つべきコマンドまで教えてくれるので、迷ったら読んでください。

案内どおりに実行します。

ターミナル

$ git restore src/app.js $ git status

プレーンテキスト

On branch main nothing to commit, working tree clean

src/app.js は編集前の状態に戻りました。ここでの「編集前」とは、最後にコミットした時点の内容 です。HEAD(いま自分がいるコミットを指す目印)が持っているファイルの中身で、作業フォルダのファイルを上書きしている、という動きになります。

現在のフォルダ以下をまとめて戻したいときは . を渡します。

ターミナル

$ git restore .

git restore で消した編集は取り戻せません。 コミットもステージもしていない内容は Git のどこにも保存されていないため、reflog を使っても復旧できません。まだ捨てる決心がつかないなら、作業を一時退避する(stash) で退避してください。

ステージから降ろす(git restore --staged)

次は git add を取り消したい場面です。コミットするつもりで git add したものの、そのファイルは今回に含めたくなかった、というときに使います。

ターミナル

$ git add src/app.js config/secret.json $ git status

プレーンテキスト

On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: src/app.js new file: config/secret.json

ここでも案内が出ています。ステージから降ろすなら git restore --staged <file> です。

ターミナル

$ git restore --staged config/secret.json $ git status

プレーンテキスト

On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: src/app.js Untracked files: (use "git add <file>..." to include in what will be committed) config/secret.json

config/secret.json がステージから外れ、追跡されていないファイルに戻りました。

大事なのは、--staged を付けたときはファイルの中身に一切触らない ことです。動いたのは「次のコミットに含める」という印だけで、書いた内容はそのまま残ります。だから --staged は安全に使えます。

2 つの動きを並べると違いがはっきりします。

コマンドステージ作業フォルダのファイル
git restore ファイル名変えない最後のコミットの内容で上書き
git restore --staged ファイル名コミットの内容に戻す変えない
git restore --staged --worktree ファイル名戻す上書き

3 つ目は両方まとめて実行する形です。編集内容が失われるので、本当に全部捨ててよいときだけ使ってください。

過去のコミットの内容を取り出す

git restore は既定で HEAD の内容を使いますが、--source を付けると別のコミットから取り出せます。

ターミナル

$ git restore --source 3b7d21e src/app.js

3b7d21e という古いコミットの時点の src/app.js が、作業フォルダに書き出されます。この時点ではまだコミットされていない編集扱いなので、git statusmodified として出ます。内容を確認して、良ければ git add してコミットします。

1 つ前のコミットに戻したいときは HEAD~1 と書けます。~1 は「1 つ前」を意味する書き方で、HEAD~2 なら 2 つ前です。

ターミナル

$ git restore --source HEAD~1 src/app.js

「あのときのこのファイルだけ戻したい」という場面は実務でよくあります。履歴全体を巻き戻さずにファイル単位で取り出せるのが、この使い方の利点です。

削除してしまったファイルを取り戻すのにも同じ考え方が使えます。

ターミナル

$ rm src/util.js $ git status

プレーンテキスト

On branch main Changes not staged for commit: deleted: src/util.js

コミット済みのファイルを消しただけなら、内容は履歴に残っています。

ターミナル

$ git restore src/util.js

これでファイルが戻ります。git restore は「指定した状態でファイルを書き出す」コマンドなので、消えている場合は作り直す形になります。ゴミ箱を漁る前に、まず git status を見てください。

一部の変更だけ取り消す

ファイル全体ではなく、書き換えた中の一部だけ戻したい、という場面もあります。そのときは -p を付けます。

ターミナル

$ git restore -p src/app.js

差分が小さなかたまりごとに表示され、1 つずつ y(捨てる)か n(残す)を選べます。全部消すのが怖いときの安全な進め方として覚えておくと役立ちます。--staged と組み合わせて git restore -p --staged とすれば、ステージから一部だけ降ろすこともできます。

古い書き方 git checkout -- について

Git 2.23 より前は、この操作に git checkout -- ファイル名 を使っていました。ネット上の記事にはまだこの形が多く残っています。動作としては git restore ファイル名 と同じなので、古い記事で git checkout -- を見たら git restore に読み替えて構いません。新しく書くコマンドは restore を使ってください。

分けられた理由は、git checkout がブランチの移動とファイルの復元という別々の仕事を兼ねていて、-- を書き忘れると意図しない動作になりやすかったためです。いまは移動が git switch、復元が git restore に整理されています。

対応関係は次の通りです。

古い書き方いまの書き方役割
git checkout -- ファイル名git restore ファイル名編集を破棄する
git reset HEAD ファイル名git restore --staged ファイル名ステージから降ろす
git checkout ブランチ名git switch ブランチ名ブランチを移動する

古いコマンドもそのまま動くので、覚え直しを急ぐ必要はありません。ただし git status が案内してくるのは新しい書き方なので、そちらに合わせておくほうが読み書きが一致して混乱しません。ブランチの移動については ブランチを作って移動する(switch) で扱います。

やりがちな失敗と、その戻し方

最も多いのは、--staged を付け忘れて編集内容ごと消してしまう 失敗です。

git add を取り消したいだけだったのに、--staged を忘れて次のように打ったとします。

ターミナル

$ git restore src/app.js

このとき Git は何も警告しません。確認も出ず、静かに完了します。そして 1 時間かけて書いたコードは、最後のコミットの内容で上書きされて消えます。

防ぐ方法は 2 つあります。

1 つは、実行前に必ず git status を読むことです。git status は「ステージから降ろすなら --staged を付けろ」と毎回書いています。案内の文言と自分が打とうとしているコマンドが一致しているか、指差し確認してください。

もう 1 つは、消す前に何が消えるのかを見ることです。

ターミナル

$ git diff src/app.js

ここに表示される差分が、そのまま消える内容です。中身を見て「これは捨ててよい」と判断してから実行すれば事故は起きません。

エディタが独自に持っている取り消し履歴(Undo)から復旧できる場合があります。git restore で消したことに気づいたら、まだそのファイルを開いているエディタで Undo を試してください。Git では戻せなくても、エディタなら残っていることがあります。

どれを使うか迷ったときの手順

実務では次の順で考えると迷いません。

  1. git status を打つ
  2. 対象が Changes to be committed にあるなら、git add の取り消しなので --staged を付ける
  3. 対象が Changes not staged for commit にあるなら、編集の破棄なので --staged なしを使う
  4. その前に git diff で、消える内容を目で確認する

--staged はステージという印だけを動かす安全な操作、--staged なしはファイルの中身を書き換える危険な操作」と覚えておけば、判断を間違えません。

もうひとつ、git restore が扱えない範囲もはっきりさせておきます。git restore はコミットに触りません。 対象はあくまで作業フォルダとステージだけです。

やりたいこと使うもの
編集を捨てるgit restore
git add を取り消すgit restore --staged
コミットを取り消すgit reset
共有済みのコミットを打ち消すgit revert

コミットまで済ませてしまった変更を戻したいときは、次の コミットを取り消す(reset の 3 モード) に進んでください。ここまでで、コミット前の 2 つの場所は自由に行き来できるようになっています。

この章のポイント
  • git restore ファイル名 は作業フォルダの編集を、最後のコミットの内容で上書きする(取り戻せない)
  • git restore --staged ファイル名git add を取り消すだけで、書いた内容には触らない
  • --source HEAD~1 のように指定すると、過去のコミットからファイル単位で取り出せる
  • 古い git checkout --git restore に読み替える。実行前に git statusgit diff で確認する