ステージングされた変更とステージングされていない変更がたくさんあったので、すぐに別のブランチに切り替えてから元に戻したいと思いました。
そこで、次の方法で変更をステージングしました。
$ 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
災害は今のところ回避されましたが、これはいくつかの疑問を提起します。
最初にこのエラーが発生したのはなぜですか?次回はどうすれば回避できますか?
少し追加の説明として、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はu
とi
を作成する間に追加のコミットw
を作成します。 u
のスナップショットの内容は、追跡されないが無視されないファイル(--include-untracked
)、または無視されても追跡されないファイル(--all
)です。この余分なu
コミットにはno parentがあり、git stash
がw
を作成すると、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 apply
はi
コミットを完全に無視します。
Stashにコミットが2つしかない場合、git stash apply
はw
コミットを適用できます。 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>
を意味するバリアントも必要です。
問題を再現できました。未追跡ファイルを隠してからそれらのファイル(例ではfoo.txt
とbar.txt
)を作成すると、git stash pop
を適用すると上書きされる未追跡ファイルにローカル変更が加えられたようです。
この問題を回避するには、次のコマンドを使用できます。
git checkout stash -- .
これにより、保存されていないローカルの変更が上書きされるため、注意してください。 前のコマンドで見つけた詳細情報があります 。
ダニエル・スミスの答え を展開するには:--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}
の有無にかかわらず動作します出典:
stash^3
コミットに関する情報)