Dlaczego git stash pop mówi, że nie może przywrócić niezatwierdzonych plików z wpisu skrytki?

94

Miałem kilka zmian etapowych i niestacjonarnych i chciałem szybko przełączyć się na inną gałąź, a następnie z powrotem.

Więc inscenizowałem swoje zmiany za pomocą:

$ git stash push -a

(Z perspektywy czasu prawdopodobnie mógłbym użyć --include-untrackedzamiast --all)

Potem, kiedy poszedłem do schowka, dostaję mnóstwo błędów w rodzaju:

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

Wygląda na to, że ze skrytki nie przywrócono żadnych zmian.

Też próbowałem, $ git stash branch tempale to pokazuje te same błędy.

Znalazłem sposób na obejście tego, którego miałem użyć:

$ git stash show -p | git apply

Na razie uniknięto katastrofy, ale to rodzi pewne pytania.

Dlaczego ten błąd wystąpił w pierwszej kolejności i jak mogę go uniknąć następnym razem?

steinybot
źródło
29
Musiałem użyć:git stash show -p | git apply --3
xmedeko
2
Jedyne co mi zadziałało to POWYŻEJ KOMENTARZA !!
Mehraj Malik
4
Dzięki @xmedeko, czy ktoś może odróżnić git stash show -p | git apply i git stash show -p | git zastosuj --3?
Deepak Mohandas,
3
Jeśli paniki, po prostu wymienić swoje stashed plików git stash showi ponad plików ratowniczych jeden po drugim: $ git show stash@{0}:your/stashed/file.txt > your_rescued_file.txt. Spowoduje to pobranie pliku ze skrytki i zapisanie go pod inną nazwą. Teraz możesz bezpiecznie eksperymentować z odpowiednimi metodami ratunkowymi (patrz odpowiedzi poniżej). Jeśli coś pójdzie nie tak, zawsze masz uratowane pliki jako ostatni zasób.
Danijel
Wow, dzięki @xmedeko! Po raz kolejny Twój komentarz był jedyną rzeczą, która zadziałała i była taka prosta. +1!
pcdev

Odpowiedzi:

99

Jako dodatkowe wyjaśnienie zwróć uwagę, że git stashpowoduje to dwa lub trzy zatwierdzenia. Wartość domyślna to dwa; otrzymasz trzy, jeśli użyjesz pisowni opcji --alllub --include-untracked.

Te dwa lub trzy zatwierdzenia są wyjątkowe pod jednym ważnym względem: nie znajdują się na żadnej gałęzi. Git lokalizuje je za pomocą specjalnej nazwy stash. 1 Najważniejsze jest jednak to, co Git pozwala - i sprawia, że - robisz z tymi dwoma lub trzema zatwierdzeniami. Aby to zrozumieć, musimy przyjrzeć się temu, co jest w tych zatwierdzeniach.

Co jest w skrytce

Każde zatwierdzenie może zawierać jedno lub więcej zatwierdzeń nadrzędnych . Tworzą one wykres, na którym późniejsze zatwierdzenia wskazują na wcześniejsze. Skrytka normalnie zawiera dwa zatwierdzenia, które lubię wywoływać w iprzypadku zawartości indeksu / obszaru przemieszczania oraz zawartości wdrzewa roboczego. Pamiętaj również, że każde zatwierdzenie zawiera migawkę. W normalnym zatwierdzeniu ten obraz stanu jest tworzony z zawartości indeksu / obszaru przemieszczania. Zatem izatwierdzenie jest w rzeczywistości całkowicie normalnym zatwierdzeniem! Po prostu nie ma go na żadnej gałęzi:

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

Jeśli robisz zwykły magazyn, git stashkod robi to wteraz, kopiując wszystkie śledzone pliki drzewa roboczego (do tymczasowego indeksu pomocniczego). Git ustawia pierwszego rodzica tego wzatwierdzenia, aby wskazywał na HEADzatwierdzenie, a drugi rodzic, aby wskazywał na zatwierdzenie i. Na koniec stashwskazuje na to wzatwierdzenie:

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

Jeśli dodasz --include-untrackedlub --all, Git wykona dodatkowe zatwierdzenie, upomiędzy tworzeniem ii w. Zawartością migawki usą te pliki, które nie są śledzone, ale nie są ignorowane ( --include-untracked) lub pliki, które nie są śledzone, nawet jeśli są ignorowane ( --all). Ten dodatkowy upopełnić ma żadnego rodzica, a następnie, gdy git stashmarki w, ustawia w„s trzeciego rodzica tego udokonać, aby uzyskać:

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

W tym momencie Git usuwa również wszystkie pliki drzewa roboczego, które znalazły się w uzatwierdzeniu (używając git cleando tego).

Przywracanie skrytki

Kiedy idziesz odzyskać skrytkę, możesz jej użyć --indexlub nie. To mówi git stash apply(lub któregokolwiek z poleceń, które używają wewnętrznie apply, jak pop), że powinien on używaći popełnić próbować zmodyfikować swój aktualny indeks. Ta modyfikacja jest wykonywana za pomocą:

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

(mniej więcej; jest kilka drobiazgów, które przeszkadzają w realizacji podstawowej idei).

Jeśli pominiesz --index, git stash applycałkowicie zignoruje izatwierdzenie.

Jeśli skrytka ma tylko dwa zatwierdzenia, git stash applymoże teraz zastosować wzatwierdzenie. Robi to przez wywołanie git merge2 (bez zezwalania na zatwierdzenie lub traktowanie wyniku jako normalnego scalania), używając oryginalnego zatwierdzenia, na którym został utworzony schowek ( irodzica i wpierwszego rodzica) jako podstawy scalania, wjako --theirscommit, a Twoje aktualne (HEAD) commit jako cel scalenia. Jeśli połączenie się powiedzie, wszystko jest w porządku - cóż, przynajmniej tak uważa Git - i git stash applysamo się powiedzie. Jeśli git stash popużywałeś skrytki, kod teraz ją upuszcza . 3 Jeśli scalanie nie powiedzie się, Git deklaruje, że zastosowanie nie powiodło się. Jeśli użyłeśgit stash pop, kod zachowuje skrytkę i dostarcza ten sam stan błędu, co w przypadku git stash apply.

Ale jeśli masz trzecie zatwierdzenie - jeśli uw skrytce, którą stosujesz, jest zatwierdzenie - wszystko się zmienia! Nie ma możliwości udawania, że uzatwierdzenie nie istnieje. 4 Git nalega na wyodrębnienie wszystkich plików z tego uzatwierdzenia do bieżącego drzewa roboczego. Oznacza to, że pliki nie mogą w ogóle istnieć lub mieć taką samą zawartość, jak w uzatwierdzeniu.

Aby to się stało, możesz użyć git cleansiebie - ale pamiętaj, że nieśledzone pliki (zignorowane lub nie) nie istnieją w repozytorium Git, więc upewnij się, że wszystkie te pliki można zniszczyć! Możesz też utworzyć katalog tymczasowy i przenieść tam pliki na przechowanie - lub nawet zrobić inny git stash save -ulub git stash save -a, ponieważ będą one działać git cleanza Ciebie. Ale to po prostu pozostawia ci inny ustyl, którym możesz zająć się później.


1 Tak jest w rzeczywistości refs/stash. Ma to znaczenie, jeśli utworzysz gałąź o nazwie stash: pełna nazwa gałęzi to refs/heads/stash, więc nie są one w konflikcie. Ale nie rób tego: Git nie będzie miał nic przeciwko, ale będziesz się mylić. :-)

2git stash kod faktycznie korzysta git merge-recursivebezpośrednio tutaj. Jest to konieczne z wielu powodów, a także ma efekt uboczny polegający na tym, że Git nie traktuje tego jako scalenia podczas rozwiązywania konfliktów i zatwierdzania.

3 Dlatego też zalecamy unikanie git stash pop, na korzyść git stash apply. Masz szansę przejrzeć, co zostało zastosowane, i zdecydować, czy faktycznie zostało zastosowane poprawnie. Jeśli nie, nadal masz swoją skrytkę, co oznacza, że ​​możesz jej użyć git stash branchdo perfekcyjnego odzyskania wszystkiego. Cóż, zakładając brak tego nieznośnego uzobowiązania.

4 Naprawdę powinno być: git stash apply --skip-untrackedczy coś. Powinien też istnieć wariant, który oznaczałby upuszczenie wszystkich tych uplików ze zmianami do nowego katalogu , np git stash apply --untracked-into <dir>. Być może.

torek
źródło
8
Niezwykle szczegółowa odpowiedź. Dziękuję za poświęcenie czasu na napisanie tego. Dużo się nauczyłem!
steinybot
To wyjaśnienie zasługuje na odznakę bohatera. Dzięki za umieszczenie tak wielu szczegółów!
Lucas Fowler
1
@seelts Niestety, Git ciągle się rozwija, więc książki szybko się dezaktualizują. Ale Git należy rozumieć jako zestaw narzędzi - poleceń, które manipulują zatwierdzeniem plików, manipulują wykresem zatwierdzania lub cokolwiek innego - które łączysz w coś, co jest dla ciebie przydatne , i nie wydaje się, aby zbyt wiele książek do tego zbliżało się sposób albo.
torek
3
Nie rozumiem rozwiązania problemu. Jest to po prostu dodając --index: git stash apply --index?
Danijel
1
Dzięki @torek. Najpierw to zrobiłem git stash save --all, potem natychmiast to zrobiłem git stash apply, ale niektórych plików brakowało, ponieważ zmieniłem ich nazwy, a następnie utworzyłem ponownie (przed przechowywaniem). Pomogło: git checkout stash@{0} -- .nawet nie będę się tym przejmować, git checkout stash^3 -- .ponieważ teraz wszystko wydaje się OK. Szkoda, że ​​nie mam czasu, aby naprawdę zrozumieć, co się dzieje. Dzięki.
Danijel
100

Udało mi się odtworzyć Twój problem. Wygląda na to, że jeśli ukryjesz niezamierzone pliki, a następnie utworzysz te pliki (w swoim przykładzie foo.txti bar.txt), będziesz mieć lokalne zmiany w nieśledzonych plikach, które zostaną nadpisane po zastosowaniu git stash pop.

Aby obejść ten problem, możesz użyć następującego polecenia. Spowoduje to zastąpienie wszelkich niezapisanych zmian lokalnych, więc bądź ostrożny.

git checkout stash -- .

Oto dodatkowe informacje, które znalazłem na temat poprzedniego polecenia .

Daniel Smith
źródło
Moje drzewo robocze było zdecydowanie czyste, chociaż mogły wystąpić zmiany w zignorowanych plikach.
steinybot,
1
Wygląda na to, że użycie --all/ -a będzie obejmować ignorowane pliki , więc może to być istotne.
Daniel Smith,
1
Zakładam, że ta sama logika dotyczy ignorowanych plików i oznaczam to jako odpowiedź (chociaż myślę, że git merge --squash --strategy-option=theirs stashw tym przypadku podejście jest lepsze).
steinybot,
Zgoda, podoba mi się to drugie podejście! Powodzenia w pracy!
Daniel Smith,
Było to pomocne, ale nie przywróciło nieśledzonych plików - jeśli masz ten sam problem ( already exists, no checkout), sprawdź moją odpowiedź poniżej.
Erik Koopmans
25

Aby rozwinąć odpowiedź Daniela Smitha : ten kod przywraca tylko śledzone pliki, nawet jeśli użyłeś --include-untracked(lub -u) podczas tworzenia skrytki. Wymagany pełny kod to:

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

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

To w pełni przywraca śledzoną zawartość (in stash) i nieśledzoną zawartość (in stash^3), a następnie usuwa skrytkę. Kilka uwag:

  • Uważaj - to spowoduje nadpisanie wszystkiego zawartością twojego skrytki!
  • Przywrócenie plików za pomocą git checkoutpowoduje, że wszystkie stają się automatycznie git resetprzemieszczane , więc dodałem, aby wszystko usunąć ze sceny.
  • Niektóre zasoby zużywają, stash@{0}a stash@{0}^3podczas moich testów działa tak samo z lub bez@{0}

Źródła:

Erik Koopmans
źródło
1
Dlaczego miałbyś usunąć skrytkę, dlaczego nie zostawić go tam na chwilę ze względów bezpieczeństwa?
Danijel
@Danijel Jasne, że możesz zatrzymać skrytkę - to chyba zależy od twojego przypadku użycia. Dla moich celów skończyłem ze skrytką po jej przywróceniu.
Erik Koopmans
3

oprócz innych odpowiedzi zrobiłem małą sztuczkę

  • Usunięto wszystkie nowe pliki (już istniejące pliki, np. Foo.txt i bar.txt w pytaniu)
  • git stash apply (można użyć dowolnego polecenia, np. zastosuj, pop itp.)
Sanjay Nishad
źródło
Nie działa… Usunięte pliki po prostu pojawiają się ponownie w skrytce… tak samo jak błąd!
Aditya Rewari