git add してステージしてしまったファイルを取り消しする Git コマンドについてです。勢いよく git add したはいいけど・・、あれ?元に戻せない・・・。というのはよくある話です。
今回、git add の取り消し方をマスターして、うまくコミットをコントロールしましょう。
目次
git add とは?
まずはおさらいですが、git add は、作業ツリーの更新内容を、インデックスに反映するための Git コマンドです。インデックスに追加された変更内容は、「git commit」を実行した際に、そのコミットに含まれる事になります。
さて、今回のテーマは「いったん git add してしまったインデックス上の変更内容は、どうやって取り消すのか?」という問題でした。さっそく見ていきましょう。
git add した変更を、全て一括で取り消す方法
git reset で、インデックスを HEAD の状態へ戻す
git add したファイルの変更内容をインデックスから削除するには、git reset コマンドを利用します。
下記のように、git reset の引数に「HEAD」と指定するか、もしくは引数を省略すれば、インデックスへ add した変更が、元の状態に戻ります。このとき、作業ツリーは変更を受けません。
// git add したファイルの更新をインデックスから削除 (2つは同義)
git reset HEAD .
git reset .
git add と git reset の関係性のイメージです。
引数なしの git add
ちなみに、実際は引数にファイルパスを指定せず、このように記載しても、すべてのファイルがリセットされます。
正確には、引数に「. (ドット)」を指定すると、現在のディレクトリ配下を対象としたすべてのファイルが指定されますが、ファイルパスを指定しない「git reset HEAD」は、完全に HEAD のツリーをインデックスに登録されたファイル(= Git 管理下のファイル)すべてを再現します。
今回、説明上の都合であえて「.」を紹介していますが、「すべて一括で取り消し」を実現するためには、こちらのほうが適切かもしれません。
// インデックスのすべてのファイルを取り消す
git reset HEAD
git reset
なお、この引数なしの「git reset」は、前述のパスを指定した「git reset .」とは別の機能を利用している事になります。git reset は引数でファイルパスが指定されるかどうかで、その振る舞いが変わるからです。詳しくは、この記事の末尾の「【参考】git reset の基本動作とオプション」をご一読下さい。
ファイル/ディレクトリを指定して git add を取り消す
上記では、引数に「. (ドット)」を指定してすべてのファイルを指定していましたが、もちろん、ファイルやディレクトリを限定して取り消すこともできます。もちろんこちらも作業ツリーはそのままです。<file_path>と書いた部分に、実際のパスとマッチするパターンを指定して下さいね。
// git add したファイル(index.php)をインデックスから削除(2つは上と同義) $ git reset index.php $ git reset HEAD index.php
Git における、ディレクトリの指定は。「/」をつけてパスを記述することで、ディレクトリ配下のファイル群全体を指定できます。
// ディレクトリ(images)を指定して、配下のファイル群の git add を取り消し $ git reset /images/ $ git reset HEAD /images/
Git では、パスは、パターンマッチングで記述します。スラッシュが末尾にあればディレクトリ配下のファイル群、また、冒頭にあれば Git レポジトリのルートディレクトリを意味します。
また、否定を表す「!」、ワイルドカードの「*」 や「?」も使用できますよ。これはGit における共通の「pathspec(パス仕様)」の記述方式です。
git reset は「git add の取り消し」なのか?
1点注意が必要なのが、例えば「最後の commit から2回以上の git add を実行した」という場合、git reset を実行すると、1回目 git add の変更含めて、インデックスがまとめてリセットされてしまいます。ですので、本当の意味での「git add 実行1回分の取り消し」とはなりません。
[gitc]git reset[/gitc] によるインデックスの操作は、実際「git addの取り消し」と呼ぶよりは、「インデックスを(直前のコミット状態に)リセットしている」と言った方が、実際に起きていることを表しているといえます。
一番最初の git add を取り消す「git rm --cached」
git レポジトリを作成した直後、一番最初のに実行した git add を取り消したい場合、まだ HEAD の参照が存在しないため、上記の git reset を使った取消方法が利用できません。(これは、新規追加したファイルを初めての git add した場合も同様です。)レポジトリ内にリセット対象があってはじめてリセットできるのです。
さて、そのような場合の git add の取り消しは、インデックスのファイルを直接削除するコマンド「git rm --cached」を利用するしかありません。
// インデックスから所望のファイルを削除し、ファイル実態はそのまま
$ git rm --cached <file_name>
// 例:new-dir ディレクトリ内のすべての git add を取り消す。
$ git rm --cached -r new-dir
注意が必要なのは、この「git rm」コマンドは、--cached オプションで作業ツリーにある、ファイルの実体も削除するかどうかの振る舞いが代わります。
git add の取り消し(=インデックスからの削除)だけであれば、忘れず --cached オプションを付けましょう。「git rm XXX」としてキャッシュをつけ忘れると、間違ってまだどこのも保存していないファイル実体を削除してしましますので、注意が必要です。
// インデックスと、ファイル実体を削除する(間違ってファイル削除に注意!)
$ git rm <file_name>
// インデックスからのみ削除し、ファイルはそのまま
$ git rm --cached <file_name>
【補足1】git add された変更内容を確認するコマンド
実際、何度も git add したり、git reset したりしていると「あれ、いまインデックスって、どんな状態だっけ?」となってしまいます。
git status でもステージ済みのファイル名は一覧できますが、より詳細に知るためには、下記を実行すると、HEADから見たインデックスの差分を表示する事ができます。
「HEAD ⇨ インデックス」の差分を確認するコマンド
// HEAD → インデックスの差分を確認する git diff --cached HEAD // 同義 git diff --cached
git diff は、普段は「作業ツリーの変更状況」を確認するコマンドですが、「--cached」のオプションにより、対象を変更し「インデックスへ変更状況」を確認することができるのでした。git diffの振る舞いについては、別の記事に詳しくまとめたので、ぜひご覧下さい。
なお、git init 後の初回の git add 直後で、HEADが存在しない場合、ゼロベースからインデックスへの比較となります。
【補足2】git reset の基本動作とオプション
それでは、良い機会なので、Git の取り消しを司るコマンド「git reset」について、詳しく見ていきます。
git reset の基本動作
まず、基本的なgit reset の機能は下記の2つに分類できます。
- インデックスの状態を更新する
- 現在の作業ブランチの先頭をリセットする(同時にインデックスと作業ツリーも更新)
パラメータにパス・ファイル名までを指定すれば(1)、ブランチ名のみを指定すれば(2)の動作となります。
指定するパラメータによって振る舞いが変わるので、把握しておく必要があります。特に(1)への引数は、普通のファイルパス形式だけでなく、「<branch>:<path>」、「<branch> — <path>」などの形式を取りいずれの方法でも指定できます。
git reset のリセットの適応対象
今回の「git add の取り消し」として使用したのは、主に(1)の機能でしたが、参照するコミットがHEADである時に限っては(1)と(2)は非常によく似た動作となります。
一方で、(2)では、git reset のオプションにより、「モード」の指定が可能です。このモードの変更オプションにより、インデックスだけでなく同時に作業ツリーや、ブランチの先頭(HEAD)をまとめて、指定したコミットの状態にリセットする事ができるのです。
git reset のモードを使い分ける
git reset は、デフォルトでは、引数なしで(mixedモード)とみなされます。その他「soft」「hard」というモードが存在し、用途によって使い分けます。
モード指定 | 動作 |
git reset --soft | 現在のbranchの先頭だけをリセット。(インデックスはそのまま) |
git reset (git reset --mixed) |
現在のbranchの先頭と、インデックスをリセット |
git reset --hard | 現在のbranchの先頭、インデックスも作業ツリーも全部リセット |
このようにリセットされる範囲が異なります。
モードごとに、用途を考えてみます。
git reset --soft はいつ使うの?
このオプションは、上記の通り、ブランチの先頭(HEAD)だけを書き換えて、インデックスと作業ツリーには影響を与えません。
このオプションは、例えば「最新のコミットの内容をちょっとだけ修正したい時」に使う事があります。例えば「git reset --soft HEAD^ 」と実行すると、 インデックスと作業ツリーはそのままで、HEADの参照が一つ前に戻ります。もし「デバッグコード削除し忘れ」の修正であれば、あとはその不要なコードを削除してgit commit を実行するだけで直前のコミットを修正できます。
git reset はいつ使うの?
上述の例のように、git add を取り消してやり直す時など、インデックスを整える時に使うでしょう。
git reset --hard はいつ使うの?
このコマンドは、インデックス、作業ツリーもろとも、すべて指定したコミットと同じ状態に強制的に書き換える、つよい効果をもったコマンドです。
例えば、「ローカルブランチをリモートブランチで強制上書きする」などする時に、一撃必殺技的に使うことが多いでしょう。ローカルブランチの歴史もろとも、不要なものを全部消し去って、特定の基準点に一気に帰りたい時、というケースは、時々発生するものだと思います。これにより、インデックス、作業ツリーの現在の状態を無視して(変更があろうがなかろうが吹き飛ばして)指定されたコミットと全く同じ状態を作り出します。
git resetについて、より詳しくは、こちらをご一読ください。
作業途中の変更は、この--hard モードにより、取り返しがつかなくなりますので、注意を払って使う必要があります。一時的な変更はgit stash などで対比しておくのも1つのやり方だと思います。
git add の取り消しに関する参考情報
git add も、git resetもだいたいマニュアルには書いてありますね。情報が多いため、少々読みにくいのが難点です。
また、より詳しくgit reset についてまとめました。
git commit の取り消しに関しては、こちらを御覧ください
以上、git add の取り消しについてでした。
それでは、楽しいgit ライフを。