Git
Chapters ▾ 2nd Edition

7.3 Git のさまざまなツール - 作業の隠しかたと消しかた

作業の隠しかたと消しかた

何らかのプロジェクトの一員として作業している場合にありがちなのですが、ある作業が中途半端な状態になっているときに、ブランチを切り替えてちょっとだけ別の作業をしたくなることがあります。 中途半端な状態をコミットしてしまうのはいやなので、できればコミットせずにしておいて後でその状態から作業を再開したいものです。 そんなときに使うのが git stash コマンドです。

これは、作業ディレクトリのダーティな状態 (追跡しているファイルのうち変更されたもの、そしてステージされた変更) を受け取って未完了の作業をスタックに格納し、あとで好きなときに再度それを適用できるようにするものです。

自分の作業を隠す

例を見てみましょう。自分のプロジェクトでいくつかのファイルを編集し、その中のひとつをステージしたとします。 ここで git status を実行すると、ダーティな状態を確認することができます。

$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

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:   lib/simplegit.rb

ここで別のブランチに切り替えることになりましたが、現在の作業内容はまだコミットしたくありません。そこで、変更をいったん隠すことにします。 新たにスタックに隠すには git stashgit stash save を実行します。

$ git stash
Saved working directory and index state \
  "WIP on master: 049d078 added the index file"
HEAD is now at 049d078 added the index file
(To restore them type "git stash apply")

これで、作業ディレクトリはきれいな状態になりました。

$ git status
# On branch master
nothing to commit, working directory clean

これで、簡単にブランチを切り替えて別の作業をできるようになりました。これまでの変更内容はスタックに格納されています。 今までに格納した内容を見るには git stash list を使います。

$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log

この例では、以前にも二回ほど作業を隠していたようです。そこで、三種類の異なる作業にアクセスできるようになっています。 先ほど隠した変更を再度適用するには、stash コマンドの出力に書かれていたように git stash apply コマンドを実行します。 それよりもっと前に隠したものを適用したい場合は git stash apply stash@{2} のようにして名前を指定することもできます。 名前を指定しなければ、Git は直近に隠された変更を再適用します。

$ git stash apply
On branch master
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:   index.html
	modified:   lib/simplegit.rb

no changes added to commit (use "git add" and/or "git commit -a")

このコマンドによって、さきほど隠したファイルが元に戻ったことがわかるでしょう。 今回は、作業ディレクトリがきれいな状態で変更を書き戻しました。また、変更を隠したときと同じブランチに書き戻しています。しかし、隠した内容を再適用するためにこれらが必須条件であるというわけではありません。 あるブランチの変更を隠し、別のブランチに移動して移動先のブランチにそれを書き戻すこともできます。また、隠した変更を書き戻す際に、現在のブランチに未コミットの変更があってもかまいません。もしうまく書き戻せなかった場合は、マージ時のコンフリクトと同じようになります。

さて、ファイルへの変更はもとどおりになりましたが、以前にステージしていたファイルはステージされていません。 これを行うには、git stash apply コマンドに --index オプションをつけて実行し、変更のステージ処理も再適用するよう指示しなければなりません。 先ほどのコマンドのかわりにこれを実行すると、元の状態に戻ります。

$ git stash apply --index
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

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:   lib/simplegit.rb

apply オプションは、スタックに隠した作業を再度適用するだけで、スタックにはまだその作業が残ったままになります。 スタックから削除するには、git stash drop に削除したい作業の名前を指定して実行します。

$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

あるいは git stash pop を実行すれば、隠した内容を再適用してその後スタックからも削除してくれます。

ファイルを隠す機能の応用

ファイルの隠しかたは何パターンかあり、役立つものがあるかもしれません。まずひとつ目、stash save コマンドの --keep-index オプションです。これはよく使われているオプションで、git add コマンドでインデックスに追加した内容を隠したくないときに用います。

あれこれと変更したうちの一部だけをコミットして、残りは後ほど処置したい場合、この機能が役立つでしょう。

$ git status -s
M  index.html
 M lib/simplegit.rb

$ git stash --keep-index
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
M  index.html

変更を隠すときの要望といえば、追跡しているファイルとそうでないファイルをどちらも一緒に隠してしまいたい、というのもあるでしょう。デフォルトでは、git stash コマンドが保存するのは追跡しているファイルだけです。けれど、--include-untracked(短縮形は -u)オプションを使うと、追跡していないファイルも一緒に保管して隠してくれます。

$ git status -s
M  index.html
 M lib/simplegit.rb
?? new-file.txt

$ git stash -u
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
$

もうひとつの応用例として、--patch オプションを挙げておきましょう。これを使うと、変更内容をすべて隠してしまうのではなく、隠したい変更を対話的に選択できるようになります。この場合、選択されなかった変更は作業ディレクトリに残ることになります。

$ git stash --patch
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 66d332e..8bb5674 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -16,6 +16,10 @@ class SimpleGit
         return `#{git_cmd} 2>&1`.chomp
       end
     end
+
+    def show(treeish = 'master')
+      command("git show #{treeish}")
+    end

 end
 test
Stash this hunk [y,n,q,a,d,/,e,?]? y

Saved working directory and index state WIP on master: 1b65b17 added the index file

隠した変更からのブランチの作成

作業をいったん隠し、しばらくそのブランチで作業を続けていると、隠した内容を再適用するときに問題が発生する可能性があります。 隠した後に何らかの変更をしたファイルに変更を再適用しようとすると、マージ時にコンフリクトが発生してそれを解決しなければならなくなるでしょう。 もう少しお手軽な方法で以前の作業を確認したい場合は git stash branch を実行します。このコマンドは、まず新しいブランチを作成し、作業をスタックに隠したときのコミットをチェックアウトし、スタックにある作業を再適用し、それに成功すればスタックからその作業を削除します。

$ git stash branch testchanges
M	index.html
M	lib/simplegit.rb
Switched to a new branch 'testchanges'
On branch testchanges
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

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:   lib/simplegit.rb

Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014)

これを使うと、保存していた作業をお手軽に復元して新しいブランチで作業をすることができます。

作業ディレクトリの掃除

最後に、作業ディレクトリにある変更内容やファイルを隠すのではなく、取り除いてしまいたい場合の話をしましょう。これは、 git clean コマンドを使えば実現できます。

このコマンドが役立つのは、マージの結果、あるいは外部ツールによって生成された不要物を取り除いたり、ビルド結果を削除してクリーンな状態でビルドを実行したいときです。

このコマンドを実行するときは十分注意してください。作業ディレクトリにあって追跡されていないファイルは削除されるようになっているからです。後で気が変わっても、削除してしまったデータを取り戻すのは難しいでしょう。代わりに git stash --all を実行して、すべてを隠してしまうほうが安全です。

不要物を本当に取り除きたい、作業ディレクトリを掃除したい、という場合は、git clean を実行しましょう。作業ディレクトリの追跡されていないファイルをすべて削除するには、git clean -f -d を実行します。そうすれば、ファイルをすべて削除し、サブディレクトリを空にしてくれます。-f オプションは force の省略形で、「本当にそうしたい」という意味です。

このコマンドを実行するとどうなるか知りたいなら、-n オプションがいいでしょう。これを使うと、「リハーサルをして、何が消される はず だったのかを教えて」と Git に指示してくれます。

$ git clean -d -n
Would remove test.o
Would remove tmp/

git clean コマンドがデフォルトで削除するのは、追跡されていなくて、かつ無視されてもいないファイルだけです。.gitignore ファイルなどの無視設定に合致するファイルは削除されません。そういったファイルも消したい場合は、clean コマンドに -x オプションを追加するといいでしょう。完全にクリーンなビルドを行うため、以前のビルドで生成された .o ファイルをすべて削除したい、というような場合に使えます。

$ git status -s
 M lib/simplegit.rb
?? build.TMP
?? tmp/

$ git clean -n -d
Would remove build.TMP
Would remove tmp/

$ git clean -n -d -x
Would remove build.TMP
Would remove test.o
Would remove tmp/

git clean コマンドが何を削除するのかわからず不安なら、-n オプションを常につけるようにしましょう。何が削除されるかを前もって確認してから、 -n オプションを -f に変えてファイルを実際に削除すればよいのです。また、このコマンドを慎重に実行するもうひとつの方法として、-i、「対話モード」オプションというのもあります。

これを使えば、clean コマンドを対話モードで実行できます。

$ git clean -x -i
Would remove the following items:
  build.TMP  test.o
*** Commands ***
    1: clean                2: filter by pattern    3: select by numbers    4: ask each             5: quit
    6: help
What now>

この方法であれば、ファイルを個別に選んだり、パターンマッチさせるなど対話モードで範囲を絞り込んだうえでファイルを削除できます。