web-dev-qa-db-ja.com

git stash popが、追跡されていないファイルをstashエントリから復元できないと言うのはなぜですか?

ステージングされた変更とステージングされていない変更がたくさんあったので、すぐに別のブランチに切り替えてから元に戻したいと思いました。

そこで、次の方法で変更をステージングしました。

$ 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

Stashから復元された変更はないようです。

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

私はこれを回避する方法を見つけました:

$ git stash show -p | git apply

災害は今のところ回避されましたが、これはいくつかの疑問を提起します。

最初にこのエラーが発生したのはなぜですか?次回はどうすれば回避できますか?

29
Steiny

少し追加の説明として、git stashが2つのコミットまたは3つのコミットを行うことに注意してください。デフォルトは2です。 --allまたは--include-untrackedオプションのスペルを使用すると、3が得られます。

これらの2つまたは3つのコミットは、1つの重要な点で特別です:noブランチ上にあります。 Gitは特別な名前stashでそれらを見つけます。1 ただし、最も重要なことは、Gitでできること、そしてmakes youがこれらの2つまたは3つのコミットでできることです。これを理解するには、それらのコミットの内容を調べる必要があります。

隠し場所の中身

すべてのコミットは、1つ以上のparentコミットをリストできます。これらは、後のコミットが前のコミットを指すグラフを形成します。 stashは通常2つのコミットを保持します。これは、インデックス/ステージング領域のコンテンツに対してiを呼び出し、ワークツリーのコンテンツに対してwを呼び出します。また、各コミットにはスナップショットが保持されることも忘れないでください。通常のコミットでは、このスナップショットが作成されますfromインデックス/ステージング領域のコンテンツ。したがって、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はuiを作成する間に追加のコミットwを作成します。 uのスナップショットの内容は、追跡されないが無視されないファイル(--include-untracked)、または無視されても追跡されないファイル(--all)です。この余分なuコミットにはno parentがあり、git stashwを作成すると、wの-​​third parentを設定します。このuコミットに対して、次のようになります:

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

また、Gitは、この時点で、removesuコミットに巻き込まれたワークツリーファイル(git cleanを使用して)します。

スタッシュの復元

restore a stashに移動すると、--indexを使用するか、使用しないかを選択できます。これは、git stash apply(またはapplyなどのpopを内部で使用するコマンドのいずれか)に、seiコミットしようとすることを伝えます現在のインデックスを変更します。この変更は以下で行われます:

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

(多かれ少なかれ、ここで基本的な考え方を邪魔する重要な詳細がたくさんあります)。

--indexを省略すると、git stash applyiコミットを完全に無視します。

Stashにコミットが2つしかない場合、git stash applywコミットを適用できます。 git mergeを呼び出してこれを行います2 (結果を通常のマージとしてコミットまたは処理することを許可せずに)、スタッシュが作成された元のコミット(iの親、およびwの最初の親)をマージベースとして使用しますw--theirsコミットとして、現在の(HEAD)コミットをマージのターゲットとして。マージが成功した場合、すべてが良好です(少なくともGitはそう考えています)。git stash apply自体も成功します。 git stash popを使用してスタッシュを適用した場合、コードはdropsスタッシュになります。3 マージが失敗した場合、Gitは適用が失敗したと宣言します。 git stash popを使用した場合、コードはスタッシュを保持し、git stash applyの場合と同じ障害ステータスを提供します。

しかし、あなたがthird commit—あなたが適用しているスタッシュにu commitがあれば—それから物事は変わります! uコミットが存在しないふりをするオプションはありません。4 Gitは、すべてのファイルfrom that u commitを現在のワークツリーに抽出することを主張します。これは、ファイルがまったく存在しないか、uコミットと同じ内容である必要があることを意味します。

これを実現するには、git cleanを自分で使用できますが、Gitリポジトリ内には追跡されていないファイル(無視されているかどうかに関係なく)が存在しないため、これらのファイルはすべて破棄できることに注意してください!または、一時ディレクトリを作成し、そこにファイルを移動して保管することもできます。または、git stash save -uを実行するため、別のgit stash save -aまたはgit cleanを実行することもできます。ただし、後で対処するために、別のuスタイルのスタッシュが残ります。


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

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>を意味するバリアントも必要です。

26
torek

問題を再現できました。未追跡ファイルを隠してからそれらのファイル(例ではfoo.txtbar.txt)を作成すると、git stash popを適用すると上書きされる未追跡ファイルにローカル変更が加えられたようです。

この問題を回避するには、次のコマンドを使用できます。

git checkout stash -- .

これにより、保存されていないローカルの変更が上書きされるため、注意してください。 前のコマンドで見つけた詳細情報があります

38
Daniel Smith

ダニエル・スミスの答え を展開するには:--include-untracked(または-uを使用した場合でも、コードはtrackedファイルのみを復元)スタッシュを作成するとき。必要な完全なコードは次のとおりです。

git checkout stash -- .
git checkout stash^3 -- .
git stash drop

# Optional to unstage the changes (auto-staged by default).
git reset

これにより、追跡されたコンテンツ(stash)と追跡されていないコンテンツ(stash^3)が完全に復元され、スタッシュが削除されます。いくつかのメモ:

  • 注意してください-これはすべてをスタッシュの内容で上書きします!
  • git checkoutでファイルを復元すると、それらはすべて自動的にステージングされるため、すべてのステージングを解除するためにgit resetを追加しました。
  • 一部のリソースはstash@{0}stash@{0}^3を使用しますが、私のテストでは、@{0}の有無にかかわらず動作します

出典:

5
Erik Koopmans