Git
目次 ▾ 第2版

7.3 Gitツール - スタッシュとクリーン

スタッシュとクリーン

プロジェクトの一部に取り組んでいると、作業ディレクトリが散らかっていて、別の作業を行うためにブランチを切り替えたい場合があります。問題は、後でこの時点に戻れるように、中途半端な作業をコミットしたくないことです。この問題に対する答えは、git stashコマンドです。

スタッシュは、作業ディレクトリの変更されていない状態(つまり、変更された追跡ファイルとステージングされた変更)を取得し、いつでも再適用できる(別のブランチでも)未完了の変更のスタックに保存します。

注記
git stash pushへの移行

2017年10月下旬以降、Gitメーリングリストで活発な議論が行われており、git stash saveコマンドは既存の代替コマンドであるgit stash pushに置き換えられる予定です。その主な理由は、git stash pushが選択したパススペックのスタッシュというオプションを導入するのに対し、git stash saveはそれをサポートしていないためです。

git stash saveはすぐに廃止されるわけではないので、突然消えてしまうことを心配する必要はありません。しかし、新しい機能を使用するために、pushの代替案に移行することを検討しても良いでしょう。

作業のスタッシュ

スタッシュのデモンストレーションとして、プロジェクトに入り、いくつかのファイルで作業を開始し、変更をステージングします。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 stashまたはgit stash pushを実行します。

$ git stash
Saved working directory and index state \
  "WIP on master: 049d078 Create index file"
HEAD is now at 049d078 Create 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 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log

この場合、以前は2つのスタッシュが保存されていたため、3つの異なるスタッシュされた作業にアクセスできます。元のスタッシュコマンドのヘルプ出力に示されているコマンドを使用して、ちょうどスタッシュしたものを再適用できます。git stash applyです。古いスタッシュの1つを適用する場合は、次のように名前を指定して指定できます。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")

stashを保存した際に、Gitがrevertしたファイルを再変更することがわかります。この場合、stash適用を試みた際に作業ディレクトリはクリーンであり、stashを保存したのと同じブランチで適用を試みました。しかし、stashを正常に適用するために、作業ディレクトリがクリーンであることや、同じブランチで適用することが必須ではありません。あるブランチでstashを保存し、後で別のブランチに切り替えて、変更を再適用することもできます。また、stashを適用する際に、作業ディレクトリに修正済みでコミットされていないファイルがあっても構いません。クリーンに適用できないものがあれば、Gitはマージコンフリクトを発生させます。

ファイルへの変更は再適用されましたが、事前にステージングされたファイルは再ステージングされませんでした。そのためには、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オプションはstashされた作業を適用しようとするだけで、スタック上には残ったままです。削除するには、git stash dropコマンドにstashの名前を指定して実行します。

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

stashを適用してすぐにスタックから削除するには、git stash popコマンドを実行することもできます。

stashの高度な活用

役立つ可能性のあるstashのバリエーションがいくつかあります。非常に人気のある最初のオプションは、git stashコマンドの--keep-indexオプションです。これは、Gitに対し、作成中のstashにステージングされたすべてのコンテンツを含めるだけでなく、インデックスにも同時に残しておくように指示します。

$ 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

stashでやりたいことのもう一つのよくあることは、追跡されているファイルだけでなく、追跡されていないファイルもstashすることです。デフォルトでは、git stashは修正済みでステージングされた追跡済みファイルのみをstashします。--include-untrackedまたは-uを指定すると、Gitは作成中のstashに追跡されていないファイルを含めます。ただし、stashに追跡されていないファイルを含めても、明示的に無視されたファイルは含まれません。無視されたファイルも追加で含めるには、--all(または-a)を使用します。

$ 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するのではなく、stashする変更と作業ディレクトリに残しておく変更を対話的にプロンプト表示します。

$ 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

stashからブランチを作成する

作業をstashし、しばらくそこに残しておいて、stashした作業と同じブランチで作業を続けた場合、作業を再適用する際に問題が発生する可能性があります。適用によって、その後修正したファイルを修正しようとすると、マージコンフリクトが発生し、解決する必要があります。stashされた変更を簡単に再テストしたい場合は、git stash branch <新しいブランチ名>を実行できます。これは、選択したブランチ名で新しいブランチを作成し、作業をstashしたときのコミットをチェックアウトし、そこで作業を再適用し、正常に適用された場合はstashを削除します。

$ 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)

これは、stashされた作業を簡単に復元し、新しいブランチで作業するための便利なショートカットです。

作業ディレクトリのクリーンアップ

最後に、作業ディレクトリの一部の作業やファイルをstashしたくない場合、単にそれらを削除したい場合があります。それがgit cleanコマンドの用途です。

作業ディレクトリをクリーンアップする一般的な理由としては、マージや外部ツールによって生成された不要なファイルを削除すること、またはクリーンビルドを実行するためにビルドアーティファクトを削除することがあります。

このコマンドは、追跡されていないファイルを作業ディレクトリから削除するように設計されているため、注意が必要です。考えを変えた場合、これらのファイルの内容を取り戻すことはほとんどできません。より安全な方法は、git stash --allを実行して、すべてを保存してstashに保存することです。

不要なファイルを削除するか、作業ディレクトリをクリーンアップする必要があると仮定すると、git cleanを使用して実行できます。作業ディレクトリの追跡されていないすべてのファイルを削除するには、git clean -f -dを実行します。これにより、ファイルと、結果として空になるサブディレクトリが削除されます。-fは「強制」または「本当にこれを実行する」という意味で、Git設定変数clean.requireForceが明示的にfalseに設定されていない場合に必要です。

何をするのかを確認したい場合は、--dry-run(または-n)オプションを付けてコマンドを実行できます。これは、「ドライランを実行し、削除する内容を伝える」という意味です。

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

デフォルトでは、git cleanコマンドは無視されていない追跡されていないファイルのみを削除します。.gitignoreまたはその他の無視ファイルのパターンと一致するファイルは削除されません。すべての.oファイル(ビルドによって生成されたファイル)を削除して完全にクリーンなビルドを実行するなど、これらのファイルも削除する場合は、cleanコマンドに-xを追加します。

$ 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>

この方法で、各ファイルを個別にステップ実行したり、削除のパターンを対話的に指定したりできます。

注記

作業ディレクトリのクリーンアップをGitに強制的に要求する必要があるかもしれない奇妙な状況があります。他のGitリポジトリ(おそらくサブモジュールとして)をコピーまたはクローンした作業ディレクトリにいる場合、git clean -fdでもこれらのディレクトリの削除を拒否します。このような場合は、強調するために2番目の-fオプションを追加する必要があります。

scroll-to-top