Git
Chapters ▾ 2nd Edition

2.4 Git の基本 - 作業のやり直し

作業のやり直し

どんな場面であっても、何かをやり直したくなることはあります。 ここでは、行った変更を取り消すための基本的なツールについて説明します。 注意点は、ここで扱う内容の中には「やり直しのやり直し」ができないものもあるということです。 Git で何か間違えたときに作業内容を失ってしまう数少ない例がここにあります。

やり直しを行う場面としてもっともよくあるのは、「コミットを早まりすぎて追加すべきファイルを忘れてしまった」「コミットメッセージが変になってしまった」などです。 そのコミットをもう一度やりなおす場合は、--amend オプションをつけてもう一度コミットします。

$ git commit --amend

このコマンドは、ステージングエリアの内容をコミットに使用します。 直近のコミット以降に何も変更をしていない場合 (たとえば、コミットの直後にこのコマンドを実行したような場合)、 スナップショットの内容はまったく同じでありコミットメッセージを変更することになります。

コミットメッセージのエディタが同じように立ち上がりますが、既に前回のコミット時のメッセージが書き込まれた状態になっています。 ふだんと同様にメッセージを編集できますが、前回のコミット時のメッセージがその内容で上書きされます。

たとえば、いったんコミットした後、何かのファイルをステージするのを忘れていたのに気づいたとしましょう。そんな場合はこのようにします。

$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend

最終的にできあがるのはひとつのコミットです。二番目のコミットが、最初のコミットの結果を上書きするのです。

ステージしたファイルの取り消し

続くふたつのセクションでは、ステージングエリアと作業ディレクトリの変更に関する作業を扱います。 すばらしいことに、これらふたつの場所の状態を表示するコマンドを使用すると、変更内容を取り消す方法も同時に表示されます。 たとえば、ふたつのファイルを変更し、それぞれを別のコミットとするつもりだったのに間違えて git add * と打ち込んでしまったときのことを考えましょう。 ファイルが両方ともステージされてしまいました。 ふたつのうちの一方だけのステージを解除するにはどうすればいいでしょう? git status コマンドが教えてくれます。

$ git add *
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README
    modified:   CONTRIBUTING.md

“Changes to be committed” の直後に、"use git reset HEAD <file>... to unstage" と書かれています。このアドバイスに従って、CONTRIBUTING.md ファイルのステージを解除してみましょう。

$ git reset HEAD CONTRIBUTING.md
Unstaged changes after reset:
M	CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

ちょっと奇妙に見えるコマンドですが、きちんと動作します。 CONTRIBUTING.md ファイルは、変更されたもののステージされていない状態に戻りました。

Note

git reset は、危険なコマンドに_なりえます_。その条件は、「--hard`オプションをつけて実行すること」です。 ただし、上述の例はそうしておらず、作業ディレクトリにあるファイルに変更は加えられていません。 `git reset をオプションなしで実行するのは危険ではありません。 ステージングエリアのファイルに変更が加えられるだけなのです。

今のところは、`git reset`については上記の魔法の呪文を知っておけば十分でしょう。リセットコマンド詳説で、より詳細に、`reset`の役割と使いこなし方について説明します。色々とおもしろいことができるようになりますよ。

ファイルへの変更の取り消し

CONTRIBUTING.md に加えた変更が、実は不要なものだったとしたらどうしますか? 変更を取り消す (直近のコミット時点の状態、あるいは最初にクローンしたり最初に作業ディレクトリに取得したときの状態に戻す) 最も簡単な方法は? 幸いなことに、またもや git status がその方法を教えてくれます。 先ほどの例の出力結果で、ステージされていないファイル一覧の部分を見てみましょう。

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

とても明確に、変更を取り消す方法が書かれています 。 ではそのとおりにしてみましょう。

$ git checkout -- CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

変更が取り消されたことがわかります。

Important

ここで理解しておくべきなのが、`git checkout — [file]`は危険なコマンドだ、ということです。 あなたがファイルに加えた変更はすべて消えてしまいます。変更した内容を、別のファイルで上書きしたのと同じことになります。そのファイルが不要であることが確実にわかっているとき以外は、このコマンドを使わないようにしましょう。

やりたいことが、「ファイルに加えた変更はとっておきつつ、一時的に横に追いやっておきたい」ということであれば、Git のブランチ機能 で説明する stash やブランチを調べてみましょう。一般にこちらのほうがおすすめの方法です。

Git にコミットした内容のすべては、ほぼ常に取り消しが可能であることを覚えておきましょう。 削除したブランチへのコミットや --amend コミットで上書きされた元のコミットでさえも復旧することができます (データの復元方法については データリカバリ を参照ください)。 しかし、まだコミットしていない内容を失ってしまうと、それは二度と取り戻せません。