Как восстановить сброшенный тайник в Git?

1740

Я часто использую git stashи git stash popдля сохранения и восстановления изменений в моем рабочем дереве. Вчера у меня были некоторые изменения в моем рабочем дереве, которые я спрятал и вытолкнул, а затем я сделал больше изменений в своем рабочем дереве. Я хотел бы вернуться и просмотреть вчерашние сохраненные изменения, но, git stash popпохоже, удаляет все ссылки на связанный коммит.

Я знаю, что если я использую, git stashто .git / refs / stash содержит ссылку на коммит, использованный для создания тайника. И .git / logs / refs / stash содержит весь тайник. Но эти ссылки исчезли после git stash pop. Я знаю, что коммит все еще находится в моем хранилище, но я не знаю, что это было.

Есть ли простой способ восстановить вчерашнюю ссылку на коммит?

Обратите внимание, что сегодня это не критично для меня, потому что у меня ежедневные резервные копии, и я могу вернуться к вчерашнему рабочему дереву, чтобы получить свои изменения. Я спрашиваю, потому что должен быть более легкий путь!

Грег Хьюгилл
источник
74
Примечание на будущее: если вы не хотите каждый раз терять свои тайники git stash pop, вы можете сделать это git stash applyвместо этого. Он делает то же самое, за исключением того, что не удаляет ссылку на примененный тайник.
Кевин
3
Здесь все перепробовал, не смог найти тайник, который уже выскочил. Так рада за jetbrains.com
Хуан Мендес
У меня была эта проблема. Для того, чтобы обновить свой репозиторий, я побежал git stash, git pull -r upstream, git push -f origin, git stash pop, и поп сказал «фатальный: журнал для рефов / тайник пуст». Tried Я попробовал кучу этих ответов, ничего не получалось. Когда я посмотрел в .git / refs / stash , там был SHA. Может быть проблема с маркировкой сетевого диска Windows для автономной синхронизации? B
brianary

Ответы:

2788

После того, как вы узнаете хэш потерянного коммита, вы можете применить его как тайник:

git stash apply $stash_hash

Или вы можете создать для него отдельную ветку

git branch recovered $stash_hash

После этого вы можете делать все, что хотите со всеми обычными инструментами. Когда вы закончите, просто взорвать ветку.

Нахождение хеша

Если вы только совал его , и терминал по - прежнему открыт, вы по- прежнему имеют значение хеш - функции выведенную git stash popна экран (спасибо, Dolda).

В противном случае вы можете найти его, используя это для Linux, Unix или Git Bash для Windows:

git fsck --no-reflog | awk '/dangling commit/ {print $3}'

... или используя Powershell для Windows:

git fsck --no-reflog | select-string 'dangling commit' | foreach { $bits = $_ -split ' '; echo $bits[2];}

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

Самый простой способ найти нужный stash-коммит - это, вероятно, передать этот список gitk:

gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )

... или посмотрите ответ от emragins, если используете Powershell для Windows.

Это запустит браузер репозитория, который покажет вам каждый коммит в репозитории , независимо от того, доступен он или нет.

Вы можете заменить его gitkчем-то вроде, git log --graph --oneline --decorateесли предпочитаете хороший график на консоли, а не отдельное приложение с графическим интерфейсом.

Чтобы обнаружить коммиты на stash, ищите сообщения коммитов этой формы:

        WIP на somebranch : commithash Некоторое старое сообщение коммита

Примечание . Сообщение коммита будет только в этой форме (начиная с «WIP on»), если вы не предоставили сообщение, когда это сделали git stash.

Аристотель Пагальцис
источник
49
Джейдел вынул слова изо рта. Этот пост спас мою работу :) Я просто хотел бы добавить - запоминание даты, когда вы работали над тем, что вы потеряли, облегчает поиск gitk для того, что вы ищете.
Шридхар Сарнобат
4
@Codey: потому что PowerShell. Я не знаю, отправляет ли MsysGit двоичный файл AWK. Googling говорит мне, что что-то подобное %{ $_.Split(' ')[2]; }должно делать эквивалент команды {print $3}in awkв PowerShell, но у меня нет системы Windows, чтобы проверить это, и вам все еще нужен эквивалент для этой /dangling commit/части. В любом случае, просто бегите git fsck --no-reflogи смотрите на результат. Вам нужны хэши из строк «dangling commit <commitID>».
Аристотель Пагальцис
7
Стоит отметить, что сообщение о коммите будет иметь строку «WIP» только в том случае, если вы не предоставили свое собственное сообщение при копировании (то есть, делая git stash save "<message>").
Самир Агиар
12
Если вы знаете, когда произошло отбрасывание, вы можете использовать этот однострочник, чтобы получить список висячих коммитов за счет увеличения времени: git fsck --no-reflog | awk '/dangling commit/ {print $3}' | xargs -L 1 git --no-pager show -s --format="%ci %H" | sortвероятно, последняя запись - это та, которую вы хотите stash apply.
ris8_allo_zen0
3
git stash apply {ref}восстановил упавший заначку! gitтак здорово, что это должно быть незаконно!
Том Рассел
707

Если вы не закрыли терминал, просто посмотрите на вывод, git stash popи у вас будет идентификатор объекта уроненного тайника. Обычно это выглядит так:

$ git stash pop
[...]
Dropped refs/stash@{0} (2ca03e22256be97f9e40f08e6d6773c7d41dbfd1)

(Обратите внимание, что git stash dropтакже производит ту же строку.)

Чтобы вернуть этот тайник, просто бегите git branch tmp 2cae03e, и вы получите его как ветвь. Чтобы преобразовать это в тайник, запустите:

git stash apply tmp
git stash

Наличие этого как ветви также позволяет вам свободно им манипулировать; например, чтобы выбрать его или объединить.

Dolda2000
источник
54
Вы также можете сделать git stash apply commitidто , git stashчтобы получить новый тайник.
Мэтью Флашен
32
Обратите внимание, что если git автоматически объединяет тайник и имеет конфликты, он не покажет вам хэш.
Джеймс
31
@James: Опять же, если эти конфликты являются результатом запуска git stash pop, он также не сбрасывает тайник, так что обычно это не проблема.
Dolda2000
2
В моем выводе git stash не было SHA. :(
счет
2
@Honey: В этом все дело git stash pop. Если вы хотите применить тайник, не опуская его, используйте git stash applyвместо этого. Кроме того, если вы хотите применить изменения к нескольким веткам, вы также можете выбрать коммит.
Dolda2000
271

Просто хотел упомянуть это дополнение к принятому решению. Для меня это было не сразу очевидно, когда я впервые попробовал этот метод (возможно, так и должно было быть), но чтобы применить тайник из значения хеша, просто используйте «git stash apply»:

$ git stash apply ad38abbf76e26c803b27a6079348192d32f52219

Когда я был новичком в git, мне это было непонятно, и я пробовал разные комбинации «git show», «git apply», «patch» и т. Д.

брод
источник
3
Обратите внимание, что это относится (дух!) Тайник к текущему рабочему дереву. Если дерево загрязнено, вы можете сначала использовать временную ветвь или тайник, применить тайник из SHA-1, снова спрятать, а затем вставить второй в последний тайник (который называется stash @ {1}).
MusiKk
112

Чтобы получить список тайников, которые все еще находятся в вашем хранилище, но больше недоступны:

git fsck --unreachable | grep commit | cut -d" " -f3 | xargs git log --merges --no-walk --grep=WIP

Если вы дали название своему хранилищу, замените «WIP» в -grep=WIPконце команды частью вашего сообщения, например -grep=Tesselation.

Команда выглядит как «WIP», потому что сообщение о фиксации по умолчанию для тайника находится в форме WIP on mybranch: [previous-commit-hash] Message of the previous commit.

Сентил А Кумар
источник
1
echo 'git fsck - недоступно | grep commit | cut -d "" -f3 | xargs git log --merges --no-walk --grep = WIP '> / usr / local / bin / git-stashlog; chmod a + rx / usr / local / bin / git-stashlog # git stashlog
Эрик Мартино
Или вы можете добавить это к вашему .gitconfig как псевдоним (перед командой введите a !).
asmeurer
Спас мой бекон - ну не совсем, но спас меня, перекодировав дни работы - признателен - учитывая то, что я только что бросил, я просто выбрал верхний SHA из вывода вашей команды - тогда .... git stash apply SHA ... как упоминалось в других ответах - многие
спасибо
75

Я только что создал команду, которая помогла мне найти потерянный коммит:

for ref in `find .git/objects | sed -e 's#.git/objects/##' | grep / | tr -d /`; do if [ `git cat-file -t $ref` = "commit" ]; then git show --summary $ref; fi; done | less

Это перечисляет все объекты в дереве .git / objects, находит те, которые имеют тип commit, затем показывает сводку каждого из них. С этого момента достаточно было просто просмотреть коммиты, чтобы найти подходящий «WIP на работе: 6a9bb2» («work» - это моя ветка, 619bb2 - недавний коммит).

Я заметил, что если бы я использовал «git stash apply» вместо «git stash pop», у меня не возникло бы этой проблемы, и если бы я использовал «git stash save message », тогда фиксация могла бы быть легче найти.

Обновление: с идеей Натана, это становится короче:

for ref in `git fsck --unreachable | grep commit | cut -d' ' -f3`; do git show --summary $ref; done | less
Грег Хьюгилл
источник
41

git fsck --unreachable | grep commitдолжен показать sha1, хотя список, который он возвращает, может быть довольно большим. git show <sha1>покажет, если это коммит вы хотите.

git cherry-pick -m 1 <sha1> объединит фиксацию с текущей веткой.

Натан Джонс
источник
37

Эквивалент Windows PowerShell с использованием gitk:

gitk --all $(git fsck --no-reflog | Select-String "(dangling commit )(.*)" | %{ $_.Line.Split(' ')[2] })

Вероятно, есть более эффективный способ сделать это в одной трубе, но это делает работу.

emragins
источник
1
Я очень благодарен за ваш ответ
Виталий Шебаниц
32

Если вы хотите восстановить потерянный тайник, сначала нужно найти хэш потерянного тайника.

Как подсказал Аристотель Пагальцис, git fsckвам должен помочь.

Лично я использую свой log-allпсевдоним, который показывает мне каждый коммит (восстанавливаемые коммиты), чтобы иметь лучшее представление о ситуации:

git log --graph --decorate --pretty=oneline --abbrev-commit --all $(git fsck --no-reflogs | grep commit | cut -d' ' -f3)

Вы можете выполнить еще более быстрый поиск, если ищете только сообщения «WIP on».

Как только вы узнаете свой sha1, вы просто измените свой список хранения stash, чтобы добавить старый stash:

git update-ref refs/stash ed6721d

Вы, вероятно, предпочтете иметь связанное сообщение, чтобы -m

git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721d

И вы даже захотите использовать это как псевдоним:

restash = !git update-ref -m $(git log -1 --pretty=format:'%s' $1) refs/stash $1
Колин Хеберт
источник
2
Однако, -d\\ должно быть -d\ (или даже более ясно -d' ')
joeytwiddle
Получил ошибку: «роковой: неоднозначный аргумент« висящий »: неизвестная ревизия или путь не в рабочем дереве».
Даниэль Райан
Вам также нужно git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721
Андрей Шостик
18

Мне понравился подход Аристотеля, но мне не нравилось использовать GITK ... так как я привык использовать GIT из командной строки.

Вместо этого я взял оборванные коммиты и вывел код в файл DIFF для просмотра в редакторе кода.

git show $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' ) > ~/stash_recovery.diff

Теперь вы можете загрузить полученный файл diff / txt (он находится в вашей домашней папке) в ваш текстовый редактор и увидеть фактический код и полученный SHA.

Тогда просто используйте

git stash apply ad38abbf76e26c803b27a6079348192d32f52219
Шахин Гиасси
источник
17

Вы можете перечислить все недоступные коммиты, написав эту команду в терминале -

git fsck --unreachable

Проверить недостижимый хеш коммита -

git show hash

Наконец примените, если вы найдете спрятанный предмет -

git stash apply hash
Вивек Кумар
источник
15

Почему люди задают этот вопрос? Потому что они еще не знают или не понимают рефлог.

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

Я бы посоветовал всем с этим вопросом просто проверить reflog (git reflog), не намного больше этого. Как только вы видите этот список всех коммитов, есть сотни способов узнать, какой коммит вы ищете, и выбрать его или создать из него ветку. В процессе вы узнаете о reflog и полезных опциях для различных основных команд git.

RobbyD
источник
1
Привет, Робби. Это актуально, если вы работали, попали в тупик и вам нужно вернуться туда, где вы остановились пару недель назад, только чтобы узнать, что вы не можете найти свою скрытую работу - она, вероятно, потерялась где-то в этом другом материале, который вы делали. Reflog хорош, если это недавняя история, но не для длительных промежутков времени.
Emragins
1
Эй, Эмрагинс, я согласен, но это был именно тот случай использования ОП. Я не знаю наверняка, как поведут себя другие команды, размещаемые здесь, но моя проблема в том, что они также перестанут работать после очистки ссылки на его скрытый коммит.
RobbyD
1
Хм ... сценарий, приведенный выше, и привел меня к этому вопросу, и я знаю, что это была, по крайней мере, пара недель, а то и ближе к месяцу между тем, когда я (по незнанию) потерял свой тайник и когда смог его восстановить.
Emragins
15

В OSX с git v2.6.4 я просто случайно запустил git stash drop, а затем нашел его, выполнив следующие шаги

Если вы знаете имя тайника, используйте:

$ git fsck --unreachable | grep commit | cut -c 20- | xargs git show | grep -B 6 -A 2 <name of the stash>

в противном случае вы найдете идентификатор из результата вручную с помощью:

$ git fsck --unreachable | grep commit | cut -c 20- | xargs git show

Затем, когда вы найдете commit-id, просто нажмите git stash apply {commit-id}

Надеюсь, это поможет кому-то быстро

Can Tecim
источник
12

Я хочу добавить к принятому решению еще один хороший способ пройти через все изменения, когда у вас либо нет доступного gitk, либо нет X для вывода.

git fsck --no-reflog | awk '/dangling commit/ {print $3}' > tmp_commits

for h in `cat tmp_commits`; do git show $h | less; done

Затем вы получаете все различия для тех хэшей, которые отображаются один за другим. Нажмите «q», чтобы перейти к следующему различию.

Фил
источник
12

Я не смог получить ни одного ответа для работы в Windows в простом командном окне (Windows 7 в моем случае). awk, grepИ Select-stringне были признаны в качестве команд. Поэтому я попробовал другой подход:

  • первый забег: git fsck --unreachable | findstr "commit"
  • скопировать вывод в блокнот
  • найти заменить "недостижимый коммит" start cmd /k git show

будет выглядеть примерно так:

start cmd /k git show 8506d235f935b92df65d58e7d75e9441220537a4 start cmd /k git show 44078733e1b36962571019126243782421fcd8ae start cmd /k git show ec09069ec893db4ec1901f94eefc8dc606b1dbf1 start cmd /k git show d00aab9198e8b81d052d90720165e48b287c302e

  • сохранить как файл .bat и запустить его
  • скрипт откроет кучу командных окон, показывая каждый коммит
  • если вы нашли тот, который ищете, запустите: git stash apply (your hash)

возможно, не лучшее решение, но сработало для меня

Koen
источник
Вы можете использовать git bash даже в Windows. В git bash у вас есть все (unixoid) инструменты командной строки, которые вам нужны.
Адриан Вт
10

Принятый ответ Аристотеля покажет все достижимые коммиты, включая коммиты, не похожие на тайники. Чтобы отфильтровать шум:

git fsck --no-reflog | \
awk '/dangling commit/ {print $3}' | \
xargs git log --no-walk --format="%H" \
  --grep="WIP on" --min-parents=3 --max-parents=3

Это будет включать только коммиты, которые имеют ровно 3 родительских коммита (которые будут иметь тайник), и чье сообщение включает «WIP on».

Имейте в виду, что если вы сохранили свой тайник с сообщением (например git stash save "My newly created stash"), это переопределит стандартное сообщение «WIP on ...».

Вы можете отобразить дополнительную информацию о каждом коммите, например, отобразить сообщение о коммите или передать его git stash show:

git fsck --no-reflog | \
awk '/dangling commit/ {print $3}' | \
xargs git log --no-walk --format="%H" \
  --grep="WIP on" --min-parents=3 --max-parents=3 | \
xargs -n1 -I '{}' bash -c "\
  git log -1 --format=medium --color=always '{}'; echo; \
  git stash show --color=always '{}'; echo; echo" | \
less -R
Брэд Фихан
источник
6

Мой любимый это одна строка:

git log --oneline  $( git fsck --no-reflogs | awk '/dangling commit/ {print $3}' )

Это в основном та же идея, что и в этом ответе, но гораздо короче. Конечно, вы все равно можете добавить, --graphчтобы получить древовидный дисплей.

Когда вы найдете коммит в списке, примените с

git stash apply THE_COMMIT_HASH_FOUND

Для меня использование --no-reflogsдействительно показало потерянную запись тайника, но --unreachable(как найдено во многих других ответах) не сделало.

Запустите его на git bash, когда вы находитесь под Windows.

Кредиты: Подробности вышеупомянутых команд взяты из https://gist.github.com/joseluisq/7f0f1402f05c45bac10814a9e38f81bf

Адриан В
источник
5

Восстановить его, выполнив следующие действия:

  1. Определите удаленный хэш-код:

    gitk --all $ (git fsck --no-reflog | awk '/ dangling commit / {print $ 3}')

  2. Вишневый пикап:

    git cherry-pick -m 1 $ stash_hash_code

  3. Разрешить конфликты, если они есть, используя:

    git mergetool

Кроме того, у вас могут возникнуть проблемы с сообщением фиксации, если вы используете gerrit. Пожалуйста, сохраните ваши изменения, прежде чем использовать следующие альтернативы:

  1. Используйте hard reset к предыдущему коммиту и затем подтвердите это изменение.
  2. Вы также можете спрятать изменения, перебазировать и подтвердить.
Abhijeet
источник
@ miva2 ваши изменения удалили ссылку на самый правильный ответ в этом вопросе. Добавление ссылки обратно в комментарии stackoverflow.com/questions/89332/…
Abhijeet
4

Что я пришел сюда в поисках, так это как вернуть тайник, независимо от того, что я проверил. В частности, я спрятал что-то, затем проверил более старую версию, затем выскочил, но тайник был недоступен в тот более ранний момент времени, поэтому тайник исчез; Я не мог просто сделать это, git stashчтобы вернуть его обратно в стек. Это сработало для меня:

$ git checkout somethingOld
$ git stash pop
...
nothing added to commit but untracked files present (use "git add" to track)
Dropped refs/stash@{0} (27f6bd8ba3c4a34f134e12fe69bf69c192f71179)
$ git checkout 27f6bd8ba3c
$ git reset HEAD^    # Make the working tree differ from the parent.
$ git stash # Put the stash back in the stack.
Saved working directory and index state WIP on (no branch): c2be516 Some message.
HEAD is now at c2be516 Some message.
$ git checkout somethingOld # Now we are back where we were.

Оглядываясь назад, я использую git stash applyне git stash pop. У меня был bisectнебольшой патч, который я хотел применить на каждом bisectшагу. Сейчас я делаю это:

$ git reset --hard; git bisect good; git stash apply
$ # Run tests
$ git reset --hard; git bisect bad; git stash apply
etc.
Бен
источник
Это ответ или продолжение вопроса?
Алекс Браун
Немного и того, и другого. Я нашел эту страницу, потому что потерял тайник и пытался вернуть его обратно. Вариант использования для меня - это деление пополам, где я хочу применить изменения перед тестированием на каждом этапе. Я усвоил трудный путь, что вы не можете просто вставлять, тестировать, копить, делить пополам, потому что это может оставить другой коммит на тайнике, следовательно stash apply.
Бен