チャプター ▾ 第2版

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

スタッシュとクリーニング

プロジェクトの一部を作業していると、作業中の状態が散らかった状態になり、一時的に別のブランチに切り替えて別の作業をしたいと思うことがよくあります。問題は、後でこの時点に戻れるように、中途半端な作業をコミットしたくないことです。この問題の解決策がgit stashコマンドです。

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

git stash pushへの移行

2017年10月下旬以降、Gitメーリングリストでgit stash saveコマンドが既存のgit stash pushに置き換えられ、非推奨になるという広範な議論が行われました。主な理由は、git stash pushが選択された_pathspec_をスタッシュするオプションを導入したことで、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はマージの競合を発生させます。

ファイルの変更は再適用されましたが、以前にステージしたファイルは再度ステージされませんでした。これを行うには、--indexオプションを付けてgit stash applyコマンドを実行し、ステージされた変更を再適用するようにコマンドに指示する必要があります。代わりにこれを行っていれば、元の位置に戻っていたでしょう。

$ 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

スタッシュでやりたいもう1つの一般的なことは、追跡ファイルだけでなく、未追跡ファイルもスタッシュすることです。デフォルトでは、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 を実行できます。これは、選択したブランチ名で新しいブランチを作成し、作業をスタッシュしたときのコミットをチェックアウトし、そこに作業を再適用し、正常に適用された場合はスタッシュを破棄します。

$ 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を付けて最初に実行し、-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