はじめに
こんにちは。クラウド事業部の野本です。
プロジェクトのコードを Git でバージョン管理する際は、基本的に ファイル編集 → git add → git commit を繰り返してファイルを更新・保存していきます。ただ、たまに操作を間違えてしまい、これらの手順を取り消したくなることがあります。
本記事では、これらの操作について Git の仕組みを交えて整理したうえで、その逆を行う方法についてまとめます。
(確認した Git バージョン: 2.47.1 )
図によるまとめ
先に、本記事の内容を詰め込んだ図を示します。

四角で囲った縦列は、各操作時点での Git の状態を表しています。内部ではファイルの内容を管理する場所として、これまで作成した「コミット」に加えて「ステージングエリア」と「ワーキングディレクトリ」という領域が存在します。左端が commit Y をチェックアウトした状態、右端がそこへ変更 commit Z を追加した状態です。両端はコミットが1つ増えたこと以外は同じなので、右端になったらまた左端から次の作業を行い、コミットを増やしていけます。
コミットを増やすには3つの段階を踏む必要があります。上部には、左端から右端へ進むための各段階のコマンドを記載しています。下部には逆に、各段階の操作を取り消すコマンドを記載しています。各段階を繋ぐ曲線矢印は、コマンドによってどこの領域の内容がどこへ反映されるかを示しています。
(簡単のため対象ファイルの指定はディレクトリ丸ごと . とし、また他のオプションと混同しないよう -- を明示しています)
なお、中間状態で次に実行すべきコマンドは git status で表示されるため、コマンドを完全に暗記する必要はありません。
ファイルを管理する領域の種類
ファイル編集 、 git add 、 git commit を行う際は、3種類のファイル管理スペースが関わります。
- ワーキングディレクトリ(ワーキングツリー)
- ファイルエクスプローラーなどで普通に見て触るディレクトリ
Git からチェックアウトした内容がベース - ステージングエリア(インデックス)
- 次のコミットとなる予定の、ディレクトリの内容のコピー
- コミット
- これまでに Git へ保存した、各時点でのディレクトリのスナップショット
特にHEADが指すものは現在チェックアウトしている対象
CD イメージの作成で例えれば、ワーキングディレクトリはファイル編集作業用のディレクトリ、ステージングエリアはこれから CD-R へ焼く内容を登録したもの、コミットは焼き終わった CD-R 、という感じです。
全ての変更を Git へ保存し終わった状態では、3つのスペースは全て同じ内容です。(ステージングエリアは空になるのでなく、次に備えてファイルを置きっぱなしとなります)
コミット作成時の操作
図の左から右へ進む流れについて説明します。
ファイル編集
好きなエディタやコマンドなどで、ディレクトリ内のファイルを編集します。ここは Git は関係ありません。
ステージングエリアは何も触っていないため、ワーキングディレクトリとステージングエリアとで内容に差が生じます。この差分は git diff で確認できます。
ステージングエリアへの反映
Git では、いきなりコミットを作るのではなく、一旦ステージングエリアにファイルを登録します。
この操作には git add を用います。編集したファイルを全て指定することが多いですが、一部のファイルだけだったり(残りのファイルは以前のまま)、ファイル内の一部の行だけだったりという指定も可能です。複数回に分けて実行しても構いません。
git add -- . で全て登録すれば、ワーキングディレクトリとステージングエリアの内容は同じになります(したがって git diff では何も表示されなくなります)。一方でステージングエリアと最新のコミット( HEAD が指すもの)とでは内容に差が生じます。この差分は git diff --staged で確認できます1。
コミットの作成
ステージングエリアの内容を Git に保存します。ワーキングディレクトリやステージングエリアは内容が流動的ですが、コミットは一旦作れば内容が固定され、後からいつでも当時の内容を再取得できます。
この操作には git commit を用います。これによってステージングエリアと同じ内容のコミットが作られます。また、これが最新のコミットになるため HEAD も移動します。
取り消しの操作
図の右から左へ戻る流れについて説明します。
エディタの undo 機能のような直接取り消す方法はありませんが、 Git に保存済みの内容を取り出して上書きすることで以前の状態を復元していきます。
コミットの取り消し
git commit と逆の操作というのは、 HEAD の位置を親コミットに戻すことです。一度作ったコミットは削除されるわけではありませんが、履歴が分岐して参照されなくなれば実質的に消えたことになります。
HEAD を戻すには、 git reset --soft HEAD~ というコマンドを実行します。
git reset をパス指定無しで使った場合、 HEAD の位置を指定のコミットへ移動させます。今回は HEAD の親であればいいので、コミットは ID などでなく HEAD~ と指定すれば十分です。
また、このときにモード --soft を指定すると、 HEAD を移動させるだけでステージングエリアやワーキングディレクトリの内容は維持します。
add の取り消し
git add を取り消すのは、コミットの内容をステージングエリアに反映し直すことで実現します。
具体的には、 git add -- . の逆は git reset -- . となります。
git reset をパス指定ありで使った場合、 HEAD の位置は動かさず、単に指定のコミット(省略時は HEAD )の内容をステージングエリアに反映します。
なお、同じ操作は Git 2.23.0 で追加されたコマンドを使って git restore --staged -- . でも可能です。今はこちらで覚えたほうが分かりやすいでしょう。
編集の取り消し
ワーキングディレクトリ上の変更を取り消す方法はいくつかありますが、ステージングエリアの内容を反映させると他への影響が少なく済みます。
この操作は git checkout -- . または git restore -- . で可能です。
git checkout をパス指定ありで使った場合2、一般には指定のコミットの内容をステージングエリアとワーキングディレクトリに反映します。ただしコミットを指定しなければ、ステージングエリアの内容をワーキングディレクトリに反映します。
git restore は復元に特化させたものであり、パス指定無しだとエラーになるためより安全に使えます。
redo 可能な取り消し
コミットしたことのある情報は簡単に復元できますが、コミット前であるワーキングディレクトリやステージングエリアの内容はそうでないため、操作の際は少し注意が必要です。
他の内容で上書きする方法の欠点として、例えば「編集の取り消し」を取り消したいとなったときは、もう元の情報が残っていないので自力で再度作成するしかなくなります。
もしワーキングディレクトリやステージングエリアを一時的に綺麗にしたいだけなら、 git stash push -- . などとして変更を退避する方法が適切です。これは Git が即席のコミットを作って内容を記憶しておいてくれます。そして必要なときに git stash apply --index などで変更を復元できます。
git stash の詳しい使い方は、コマンドのドキュメントや公式解説を参照してください。
(あるいはそもそも、細かいミスは気にせず頻繁にコミットしておくのも手です。リモートへ push する前に rebase でコミットを整理すれば、綺麗な履歴だけを他の人と共有できます。)
複数段階の一括取り消し
ここまで図の1段階分ずつ取り消す方法を説明しましたが、複数段階を一括で行う方法もあります。参考までにそれらのコマンドを記載します。
HEADの復元+ステージングエリアの復元(=git commit -aの取り消し)git reset HEAD~
HEADの復元+ステージングエリアの復元+ワーキングディレクトリの復元git reset --hard HEAD~
- ステージングエリアの復元+ワーキングディレクトリの復元
git checkout HEAD -- .git restore --staged --worktree -- .git stash push -- .(変更を退避)
まとめ
Git で新しいコミットを作る際の ファイル編集 → git add → git commit という流れについて、その仕組みと取り消し方を整理しました。
- 概念として「ステージングエリア」が登場しますが、これは CD イメージを作成する際に一旦内容を登録するのと同じように考えられます
- 取り消し方は undo は無いため、 Git に保存済みの内容を取り出して反映し直すことで状態を復元していきます
- ステージングエリアやワーキングディレクトリの復元には
git restoreコマンドを使います(覚えなくてもgit statusで確認できます) - コミットの取り消しは
HEADの移動が必要なためgit resetコマンドを使います
- ステージングエリアやワーキングディレクトリの復元には
これらを示した図は記事冒頭のとおりです。
参考
- Pro Git 2nd Edition
- Git Reference
- checkout
- reset
- restore
-
オプション
--cachedも同じ意味です↩ -
パス指定無しの場合はブランチの切り替えであり、
git switchと同じです↩ - 英語版には git restore で説明し直したものがあります↩