章 ▾ 第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の方に移行を開始することをお勧めします。

作業をスタッシュする

スタッシュを実演するために、プロジェクトに入り、いくつかのファイルで作業を開始し、おそらく変更の1つをステージングしてみましょう。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を使用します。古いスタッシュのいずれかを適用したい場合は、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がスタッシュを保存したときに元に戻したファイルを再変更していることがわかります。この場合、スタッシュを適用しようとしたとき、作業ディレクトリはクリーンであり、保存元と同じブランチに適用しようとしました。スタッシュを正常に適用するために、クリーンな作業ディレクトリであることや、同じブランチに適用する必要はありません。あるブランチでスタッシュを保存し、後で別のブランチに切り替えて変更を再適用することもできます。スタッシュを適用するときに、作業ディレクトリに変更済みでコミットされていないファイルがあっても構いません。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オプションは、スタッシュされた作業を適用しようとするだけで、スタックには残り続けます。それを削除するには、削除するスタッシュの名前を指定してgit stash dropを実行します

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

また、git stash popを実行すると、スタッシュを適用し、その後すぐにスタックから削除できます。

スタッシュの応用

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

$ 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は作成されるスタッシュに追跡対象外のファイルを含めます。ただし、スタッシュに追跡対象外のファイルを含めても、明示的に無視されたファイルは含まれません。無視されたファイルも追加で含めるには、--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は変更されたものすべてをスタッシュするのではなく、どの変更をスタッシュしたいか、どの変更を作業ディレクトリに残したいかを対話形式で尋ねます。

$ 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 <new branchname>を実行できます。これは、選択したブランチ名で新しいブランチを作成し、作業をスタッシュしたときのコミットをチェックアウトし、そこに作業を再適用し、正常に適用された場合はスタッシュを削除します

$ 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は「強制」または「本当に実行する」を意味し、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-fに変更して実際に実行する前に、必ず最初に-nを付けて実行し、再度確認してください。このプロセスに慎重になるもう一つの方法は、-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