1. ホーム
  2. git

[解決済み] git stash pop で stash エントリから未追跡のファイルを復元できなかったと表示されるのはなぜですか?

2022-02-27 21:28:52

質問

ステージングとアンステージの変更が大量にあり、素早く別のブランチに切り替えてから戻したいと思っていました。

というわけで、私の変更をステージングしてみました。

$ git stash push -a

(今にして思えば、おそらく --include-untracked の代わりに --all )

そして、スタッシュをポップしようとすると、以下のようなエラーが大量に発生します。

$ git stash pop
foo.txt already exists, no checkout
bar.txt already exists, no checkout
...
Could not restore untracked files from stash entry

隠し場所から復元された変更点はないようです。

また、試しに $ git stash branch temp が、同じエラーが表示されます。

私はこれを回避する方法を考え出しました。

$ git stash show -p | git apply

とりあえず災害は回避されましたが、これにはいくつかの疑問が残ります。

なぜこのようなエラーが発生したのか、次回からどのように回避すればよいのか。

解決方法は?

少し補足説明として、以下のことに注意してください。 git stash は、2つのコミット、または3つのコミットを作成します。 デフォルトは2回で、3回になるのは --all または --include-untracked オプションを使用します。

この2つ、あるいは3つのコミットは、ある重要な点において特別です。 いいえ ブランチです。 Git はこれらのブランチを、特別な名前 stash . 1 しかし、最も重要なことは、Git があなたに何をさせるかということです。 作る この2~3回のコミットで、あなたは何をするのでしょうか。 これを理解するためには、これらのコミットの中身を見る必要があります。

スタッシュの中身

すべてのコミットには、1つまたは複数の コミットです。 これらはグラフを形成し、後のコミットが前のコミットを指し示すことになります。 通常、隠し場所には二つのコミットがあります。 i はインデックスとステージングエリアのコンテンツ、そして w はワークツリーの内容です。 また、各コミットはスナップショットを保持することも覚えておいてください。 通常のコミットでは、このスナップショットは以下のように作成されます。 から インデックスとステージング・エリアの内容です。 そのため i のコミットは、実際には完全に正常なコミットです! ただ、どのブランチにもないだけです。

...--o--o--o   <-- branch (HEAD)
           |
           i

通常のスタッシュを作る場合は git stash のコードでは w ここで、追跡したすべてのワークツリー・ファイルを(一時的な補助インデックスに)コピーします。 Git は、このファイルの最初の親を w を指すようにコミットします。 HEAD コミットを指し、2番目の親をコミットするために i . 最後に stash を指すようにします。 w コミットします。

...--o--o--o   <-- branch (HEAD)
           |\
           i-w   <-- stash

を追加した場合 --include-untracked または --all の場合、Git は余分なコミットを行います。 u を作成する間に iw . のスナップショットコンテンツは u は、追跡されないが無視されないファイル( --include-untracked )、あるいは無視されても追跡されないファイル( --all ). この余分な u コミットには いいえ を親にして、その後に git stash を作る w を設定します。 w 's サード の親がこの u をコミットすることで、得られるようになります。

...--o--o--o   <-- branch (HEAD)
           |\
           i-w   <-- stash
            /
           u

Gitも、この時点で。 を削除します。 にあるワークツリーファイルは、すべて u コミット git clean を実行します)。

スタッシュの復元

に移動すると リストア を使用するオプションがあります。 --index を使用するか、使用しないかです。 これは git stash apply (または、内部で apply のような pop を使用する必要があります。 使用 その i コミットして、現在のインデックスを変更することを試みます。 この修正は

git diff <hash-of-i> <hash-of-i's-parent> | git apply --index

(多かれ少なかれ。ここでは基本的な考え方の邪魔になる細かい部分がたくさんあります)。

を省略した場合 --index , git stash apply は完全に無視されます。 i コミットします。

stashに2つのコミットしかない場合。 git stash apply を適用できるようになりました。 w コミットします。 これを行うには git merge 2 (をコミットしたり通常のマージと同じように扱ったりすることなく)、スタッシュが作成された元のコミット ( i の親であり w の最初の親)をマージベースとする。 w--theirs コミット、そして現在の (HEAD) コミットをマージのターゲットとします。 マージが成功すれば万事解決です。まあ、少なくとも ギット はそう考えており git stash apply は成功します。 もしあなたが git stash pop を適用すると、今のコードは ドロップ を表示します。 3 マージに失敗した場合、Git は適用に失敗したことを宣言します。 もし git stash pop の場合と同じように、コードは隠し場所を保持し、失敗のステータスを提供します。 git stash apply .

しかし、もしあなたがその サード コミット - もし u を適用すると、状況が一変します。 を無視するオプションはありません。 u コミットは存在しません。 4 Git はすべてのファイルの抽出を主張する から その u を現在のワークツリーにコミットします。 これは、ファイルがまったく存在しないか、あるいは u コミットします。

それを実現するために git clean しかし、追跡されていないファイル (無視されているかどうかに関わらず) は Git リポジトリ内では他に存在しないので、これらのファイルがすべて破棄されることを確認してください! あるいは、一時ディレクトリを作ってそこにファイルを移動し、安全に保管することもできます。 git stash save -u または git stash save -a を実行することになるので git clean を使用します。 しかし、これではもう1つの u -スタイルの隠し場所を後で処理する必要があります。


1 これは、実際には refs/stash . という名前のブランチを作成する場合、これは重要です。 stash というブランチの場合、ブランチのフルネームは refs/heads/stash ということで、これらは矛盾しません。 でも、そんなことはしないでください。 ギット は気にしないでしょうが、自分自身を混乱させることになります :-)

2 git stash のコードでは、実際に git merge-recursive を直接使用します。 これは複数の理由から必要であり、また、コンフリクトを解決してコミットする際に Git がこれをマージとして扱わないようにするという副次的な効果もあります。

3 このため、私は git stash pop を使用し、代わりに git stash apply . 適用された内容を確認し、それが正しいかどうかを判断します。 実際に 正しく適用されます。 そうでない場合は まだ隠し場所がある ということは git stash branch を使えば、すべてを完璧にリカバリーできます。 まあ、あの厄介な u コミットします。

4 本当にあるはずです。 git stash apply --skip-untracked などと言う。 また、次のような意味のバリアントもあるはずです。 をすべて削除します。 u コミットファイルを新しいディレクトリに , 例, git stash apply --untracked-into <dir> ということでしょうか。