Чому 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

Наразі катастрофу запобігають, але це викликає деякі питання.

Чому ця помилка сталася спочатку і як мені уникнути її наступного разу?


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?
Danijel

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

Це повністю відновлює відстежуваний вміст (in stash) та невідстежений вміст (in stash^3), а потім видаляє схованку. Кілька приміток:

  • Будьте обережні - це перезапише все вашим схованним вмістом!
  • Відновлення файлів із автоматично git checkoutпризводить до того, що всі git resetвони стартують автоматично, тому я додав, щоб все зняти.
  • Деякі ресурси використовують, stash@{0}і stash@{0}^3під час мого тестування він працює однаково з або без@{0}

Джерела:


1
Чому б вам видалити схованку, чому б просто не залишити її там на деякий час з міркувань безпеки?
Danijel

@Danijel Звичайно, ви могли б зберегти схованку - я думаю, це залежить від вашого випадку використання. Для моїх цілей я закінчив із схованкою, як тільки вона була відновлена.
Ерік

3

крім інших відповідей, я зробив невеличку хитрість

  • Видалено всі нові файли (вже існуючі файли, наприклад у запитанні foo.txt та bar.txt)
  • git stash apply (можна використовувати будь-яку команду, наприклад, застосувати, поп тощо)

Не працює .. Видалені файли просто з’являються знову при застосуванні сховища .. і помилка теж!
Адітя Реварі,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.