Git入門:バージョン管理のきほん

作業を一時退避する(stash)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • 書きかけの変更をコミットせずに退避する git stash の使い方
  • popapply の違い、複数の退避を stash list で管理する方法
  • 新規ファイルが退避されない落とし穴と、-u オプションでの回避

中途半端な変更を抱えたままブランチを移れない

開発をしていると、こんな場面が必ず来ます。ログイン画面を直している途中に「本番でバグが出ているので今すぐ直してほしい」と連絡が入る。手元のファイルは書きかけで、まだコミットできる状態ではありません。

このまま別のブランチへ移ろうとすると、Git は止めてきます。

ターミナル

$ git switch main error: Your local changes to the following files would be overwritten by checkout: src/login.js Please commit your changes or stash them before you switch branches. Aborting

メッセージの最後に commit your changes or stash them と書かれています。Git 自身が「コミットするか、stash しなさい」と教えてくれているわけです。ここで無理にコミットすると「作業中」という意味のないコミットが履歴に残ります。そのための道具が git stash です。

stash は変更を棚に上げる

git stash は、作業ディレクトリの変更とステージした内容をまとめて退避し、作業ディレクトリを直前のコミットの状態に戻します。イメージとしては、机の上に広げた書きかけの書類を、いったん引き出しにしまう操作です。

まず状態を確認してから実行します。

ターミナル

$ git status On branch feature/login Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/login.js no changes added to commit (use "git add" and/or "git commit -a")

ターミナル

$ git stash Saved working directory and index state WIP on feature/login: 8f3c1a2 add login form

WIP は work in progress の略で「作業中」という意味です。ここでもう一度 git status を見ると、変更が消えてきれいな状態になっています。

ターミナル

$ git status On branch feature/login nothing to commit, working tree clean

「変更が消えた」と不安になるかもしれませんが、消えていません。退避したものはリポジトリの中、具体的には .git ディレクトリの内部に、通常のコミットとよく似た形で保存されています。作業ディレクトリから見えなくなっただけです。

これで安心してブランチを移動できます。急ぎの修正を終えたら、元のブランチに戻って引き出しから書類を取り出します。

ターミナル

$ git switch feature/login $ git stash pop On branch feature/login Changes not staged for commit: modified: src/login.js no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (b9d2f7e6a1c4d8905f3b2e7a6c1d4098f5b3e2a7)

書きかけの変更が戻ってきました。最後の Dropped は「退避リストから取り出して消しました」という報告です。

stash は「コミットしていない変更を一時的に置く場所」であって、成果物を保管する場所ではありません。数日以上残るような作業は、素直に作業用ブランチを切ってコミットしてください。stash に何週間も置いた変更は、ほぼ確実に忘れられます。

退避したものを一覧して管理する

stash は何度でも積めます。積んだものは git stash list で確認します。

ターミナル

$ git stash list stash@{0}: WIP on feature/login: 8f3c1a2 add login form stash@{1}: WIP on feature/search: 4a7e9c0 add search box

stash@{0} がいちばん新しい退避です。数字が大きいほど古くなります。中身を見たいときは git stash show を使います。

ターミナル

$ git stash show stash@{1} src/search.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)

-p を付けると差分そのものが読めます。

ターミナル

$ git stash show -p stash@{1}

番号を指定して取り出すこともできます。

ターミナル

$ git stash pop stash@{1}

なお、退避が積み重なると番号がずれていきます。stash@{0} を取り出すと、それまで stash@{1} だったものが stash@{0} に繰り上がります。番号を指定するときは、必ず直前に git stash list で確認してください。

主なコマンドの一覧

コマンド何をするか
git stash追跡中のファイルの変更を退避する
git stash -u新規ファイル(未追跡)も含めて退避する
git stash -m "説明"説明文を付けて退避する
git stash list退避の一覧を出す
git stash show -p stash@{0}退避の中身を差分で見る
git stash popいちばん新しい退避を戻し、リストから消す
git stash apply退避を戻すが、リストには残す
git stash drop stash@{0}指定した退避を消す
git stash clear退避をすべて消す

説明文を付ける習慣は付けておくと後で助かります。WIP on feature/login だけが 5 個並んだ一覧からは、どれが何だったのか判別できません。

ターミナル

$ git stash -m "ログイン画面のバリデーション書きかけ" Saved working directory and index state On feature/login: ログイン画面のバリデーション書きかけ

コマンドは Windows でも Mac でも同じです。ただし Windows のエクスプローラや Mac の Finder は、git stash の実行結果をすぐには表示に反映しないことがあります。ファイルの中身が変わっていないように見えたら、エディタで開き直してください。ファイル自体は確実に書き換わっています。

pop と apply の使い分け

popapply はどちらも退避を作業ディレクトリに戻します。違いは、戻したあとに退避リストから消えるかどうかだけです。

  • pop は戻して消す。通常はこちらでかまいません
  • apply は戻すが消さない。同じ変更を複数のブランチに当てたいときに使います

apply を使ったときは、確認が済んだら git stash drop で自分で消します。消し忘れると、同じ変更が何度も残った一覧ができ上がります。

ターミナル

$ git stash apply $ git stash drop Dropped refs/stash@{0} (b9d2f7e6a1c4d8905f3b2e7a6c1d4098f5b3e2a7)

pop で戻すときにコンフリクトが起きることがあります。退避してから今のブランチが進んで、同じ行が書き換わっていた場合です。このときは pop であっても退避はリストに残ります。コンフリクトを解決したあと、自分で git stash drop してください。コンフリクトの直し方はコンフリクトを解決するで扱います。

よくある失敗と、そこからの戻し方

初めて stash を使った人がほぼ全員はまるのが、新しく作ったファイルが退避されないことです。

ターミナル

$ git status On branch feature/login Changes not staged for commit: modified: src/login.js Untracked files: (use "git add <file>..." to include in what will be committed) src/validator.js $ git stash Saved working directory and index state WIP on feature/login: 8f3c1a2 add login form $ git status On branch feature/login Untracked files: src/validator.js nothing added to commit but untracked files present

src/validator.js が残っています。git stash は既定で「Git が追跡しているファイルの変更」だけを退避するからです。一度も git add していない新規ファイルは追跡対象ではないので、対象から外れます。

このまま別ブランチに移ると、そのブランチに src/validator.js が紛れ込んで見えます。ビルドが通らない、テストが落ちる、といった原因の分かりにくい混乱につながります。

戻し方は簡単です。もう一度、-u を付けてやり直します。

ターミナル

$ git stash pop $ git stash -u Saved working directory and index state WIP on feature/login: 8f3c1a2 add login form $ git status On branch feature/login nothing to commit, working tree clean

-u--include-untracked の短い形で、未追跡ファイルも一緒に退避します。迷ったら -u を付けておけば安全です。

なお、.gitignore で無視しているファイルはこれでも退避されません。それも含めたいときは -a--all)ですが、node_modules のような巨大なディレクトリまで退避対象になるので、普段は使わないでください。無視ファイルの扱いは追跡しないファイルを決める(.gitignore)で扱っています。

もうひとつ、退避したことを忘れたまま同じファイルを触り直す失敗もよくあります。数日後に git stash pop すると、同じ行を 2 通りに直した状態がぶつかってコンフリクトになります。

ターミナル

$ git stash pop Auto-merging src/login.js CONFLICT (content): Merge conflict in src/login.js The stash entry is kept in case you need it again.

最後の 1 行が重要です。コンフリクトしたときの pop は退避を消しません。ファイルを開いて衝突箇所を直し、動作を確かめてから git stash drop してください。順番を逆にして先に drop すると、直しかけの状態しか残らなくなります。

退避から新しいブランチを作る

退避したあとに元のブランチが進んでしまい、そのまま戻すとコンフリクトだらけになる、という場面があります。そういうときは、退避した時点の状態から新しいブランチを切れます。

ターミナル

$ git stash branch feature/login-validation Switched to a new branch 'feature/login-validation' On branch feature/login-validation Changes not staged for commit: modified: src/login.js Dropped refs/stash@{0} (b9d2f7e6a1c4d8905f3b2e7a6c1d4098f5b3e2a7)

このコマンドは、退避を作った時点のコミットから新しいブランチを作り、そこに変更を戻し、退避を消す、までを一度にやります。コンフリクトを避けたいときの逃げ道として覚えておいてください。

もうひとつ、stash を使ううえで押さえておきたい性質があります。退避は git push でリモートに送られません。あくまで自分のパソコンの中だけのものです。別のパソコンで続きをやりたいなら、stash ではなく作業用ブランチを切ってコミットし、push してください。

最後に、stash を使うべき場面と、そうでない場面を整理しておきます。

  • stash が向くのは 数分から数時間で戻ってくる中断です。急ぎの修正、ブランチの取り違え、git pull の前に手元をきれいにしたいとき
  • ブランチを切るべきなのは 一区切りが付くまで日をまたぐ作業です。名前が付き、履歴に残り、他の人からも見える形の方が安全です

判断に迷ったら、ブランチを切る方を選んでください。stash に置いた変更は一覧を開かないかぎり目に入らないので、そのまま忘れられるのが最大のリスクです。

この章のポイント
  • git stash は書きかけの変更を退避し、作業ディレクトリを直前のコミットの状態に戻す
  • 戻すのは pop(戻して消す)か apply(戻して残す)。普段は pop でよい
  • 新規ファイルは既定で退避されない。-u を付ける習慣にしておくと事故が減る
  • stash は一時置き場。長く置く作業は作業用ブランチを切ってコミットする