Почему git stash pop говорит, что не может восстановить неотслеживаемые файлы из записи в тайнике?

94

У меня была куча поэтапных и неустановленных изменений, и я хотел быстро переключиться на другую ветку, а затем вернуться обратно.

Поэтому я внес свои изменения, используя:

$ 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

Катастрофа пока предотвращена, но это вызывает некоторые вопросы.

Почему вообще возникла эта ошибка и как избежать ее в следующий раз?

Steinybot
источник
29
Пришлось использовать:git stash show -p | git apply --3
xmedeko
2
Единственное, что у меня сработало, это КОММЕНТАРИЙ ВЫШЕ !!
Мехрадж Малик
4
Спасибо @xmedeko, кто-нибудь может отличить git stash show -p | git apply и git stash show -p | git apply --3?
Дипак Мохандас
3
Если вы панику, просто список ваших спрятанных файлов с git stash showи чем спасательными файлы один за другим: $ git show stash@{0}:your/stashed/file.txt > your_rescued_file.txt. Это позволит получить файл из тайника и сохранить его под другим именем. Теперь вы можете безопасно экспериментировать с правильными методами спасения (см. Ответы ниже). Если что-то пойдет не так, спасенные файлы всегда будут последним ресурсом.
Danijel
Вау, спасибо @xmedeko! И снова ваш комментарий был единственным, что сработало, и это было так просто. +1!
pcdev

Ответы:

99

В качестве небольшого дополнительного объяснения обратите внимание, что git stashвыполняется либо две, либо три фиксации. По умолчанию два; вы получите три, если используете любое написание --allили --include-untracked.

Эти два, или три, совершающие специальные в одном важном пути: они находятся на нет отрасли. Git находит их по специальному имени stash. 1 Однако самое важное - это то, что Git позволяет - и заставляет - делать с этими двумя или тремя коммитами. Чтобы понять это, нам нужно посмотреть, что находится в этих коммитах.

Что внутри тайника

Каждая фиксация может содержать одну или несколько родительских коммитов. Они образуют график, где более поздние фиксации указывают на более ранние. Тайник обычно содержит две фиксации, которые я люблю вызывать iдля содержимого индексной / промежуточной области и wдля содержимого рабочего дерева. Помните также, что каждый коммит содержит снимок. При обычной фиксации этот снимок создается из содержимого области индекса / промежуточной области. Таким образом, iфиксация на самом деле является совершенно нормальной фиксацией! Его просто нет ни в одной ветке:

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

Если вы создаете обычный тайник, git stashкод wтеперь копирует все ваши отслеживаемые файлы рабочего дерева (во временный вспомогательный индекс). Git устанавливает, что первый родительский элемент этой wфиксации указывает на HEADфиксацию, а второй родительский элемент указывает на фиксацию i. Наконец, он stashуказывает на этот wкоммит:

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

Если вы добавите --include-untrackedили --all, Git сделает дополнительную фиксацию, uмежду созданием iи w. Содержимое моментального снимка 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фиксацию.

Если в тайнике есть только две фиксации, git stash applyтеперь можно применить wфиксацию. Он делает это, вызывая git merge2 (не позволяя ему фиксировать или обрабатывать результат как обычное слияние), используя исходный коммит, на котором был создан тайник ( iродительский элемент и wпервый родительский элемент) в качестве базы слияния, wкак --theirscommit, а ваш текущий (HEAD) коммит в качестве цели слияния. Если слияние прошло успешно, все в порядке - ну, по крайней мере, Git так думает - и 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за вас. Но это просто оставляет вам uтайник в другом стиле, с которым вы будете разбираться позже.


+1 Это на самом деле refs/stash. Это имеет значение, если вы создаете ветку с именем stash: полное имя ветки refs/heads/stash, поэтому они не конфликтуют. Но не делайте этого: Git не будет возражать, но вы запутаетесь. :-)

2git 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>, возможно.

торек
источник
8
Невероятно подробный ответ. Спасибо, что нашли время написать это. Я многому научился!
steinybot
Это объяснение заслуживает значка героя. Спасибо за такую ​​детальную информацию!
Лукас Фаулер
1
@seelts К сожалению, Git постоянно развивается, поэтому книги быстро устаревают. Но Git следует понимать как набор инструментов-команд , которые манипулируют коммят-FUL файлов, или манипулируют фиксации графика, или какие бы то ни-что вы собираете в любого полезно вам , и не слишком много книг , кажется , подойти к нему , что так или иначе.
Торек
3
Я не понимаю решения проблемы. Является ли это просто добавив --index: git stash apply --index?
Даниел
1
Спасибо @torek. Я сначала сделал git stash save --all, потом сразу сделал git stash apply, но некоторые файлы отсутствовали, потому что я переименовал их, а затем создал заново (перед тем, как спрятать). Что помогло: git checkout stash@{0} -- .я даже не буду беспокоиться, git checkout stash^3 -- .потому что теперь все в порядке. Жалко, что у меня нет времени по-настоящему понять, что происходит. Спасибо.
Danijel
100

Мне удалось воссоздать вашу проблему. Кажется, что если вы храните неотслеживаемые файлы, а затем создаете эти файлы (в вашем примере foo.txtи bar.txt), тогда у вас есть локальные изменения в неотслеживаемых файлах, которые будут перезаписаны при применении git stash pop.

Чтобы обойти эту проблему, вы можете использовать следующую команду. Это отменит любые несохраненные локальные изменения, поэтому будьте осторожны.

git checkout stash -- .

Вот дополнительная информация, которую я нашел по предыдущей команде .

Дэниел Смит
источник
Мое рабочее дерево определенно было чистым, хотя игнорируемые файлы могли быть изменены.
steinybot
1
Похоже, что использование --all/ -a будет включать игнорируемые файлы , так что это может быть актуально.
Дэниел Смит
1
Я предполагаю, что та же самая логика применяется к игнорируемым файлам, и помечаю это как ответ (хотя я думаю, что git merge --squash --strategy-option=theirs stashв этом случае подход лучше).
steinybot
Согласен, мне нравится второй подход! Удачи в работе!
Дэниел Смит
Это было полезно, но не восстановило неотслеживаемые файлы - если у вас такая же проблема ( already exists, no checkout), проверьте мой ответ ниже.
Эрик Купманс
25

Чтобы расширить ответ Дэниела Смита : этот код восстанавливает только отслеживаемые файлы, даже если вы использовали --include-untracked(или -u) при создании тайника. Требуется полный код:

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}

Источники:

Эрик Купманс
источник
1
Почему бы вам удалить тайник, почему бы просто не оставить его там на время из соображений безопасности?
Даниел
@Danijel Конечно, ты можешь оставить тайник - полагаю, это зависит от твоего варианта использования. Для моих целей я закончил с тайником, как только он был восстановлен.
Эрик Купманс
3

Помимо других ответов, я сделал небольшой трюк

  • Удалил все новые файлы (уже существующие файлы, например, foo.txt и bar.txt в вопросе)
  • git stash apply (можно использовать любую команду, например, применить, поп и т. д.)
Санджай Нишад
источник
Не работает .. Удаленные файлы снова появляются в тайнике, применяются .. и ошибка тоже!
Адитья Ревари,