Как восстановить упавший тайник в Git?


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

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

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

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

19 1355

19 ответов:

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

в противном случае, вы можете найти его с помощью этого для Linux и Unix:

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

и для Windows:

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

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

самый простой способ найти тайник фиксации вы хотите, наверное, чтобы передать этот список в gitk:

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

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

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

чтобы определить фиксацию тайника, найдите сообщения фиксации этой формы:

WIP on somebranch:commithash какое-то старое сообщение фиксации

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

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

git stash apply $stash_hash

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

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

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

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

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

git stash apply tmp
git stash

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

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

$ git stash apply ad38abbf76e26c803b27a6079348192d32f52219

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

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

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, а затем показывает сводку каждого из них. С этого момента это был просто вопрос просмотра коммитов, чтобы найти соответствующий "НЗП на работе: 6a9bb2" ("работа" - это моя ветвь, 619bb2-недавняя фиксация).

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

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

for ref in `git fsck --unreachable | grep commit | cut -d' ' -f3`; do git show --summary $ref; done | less

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

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

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

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

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

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

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

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

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

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

вы можете сделать еще более быстрый поиск, если вы ищете только "НЗП на" сообщения.

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

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' ) refs/stash 

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

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

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

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

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

git stash apply ad38abbf76e26c803b27a6079348192d32f52219

в 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>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

git fsck --no-reflog | \
awk '/dangling commit/ {print }' | \
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..." сообщение.

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

git fsck --no-reflog | \
awk '/dangling commit/ {print }' | \
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

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

  • первый запуск: git fsck --unreachable | findstr "commit"
  • скопируйте вывод в блокнот
  • найти замену "unreachable 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

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

может быть, не лучшее решение, но работал для меня

то, что я пришел сюда искать, как на самом деле получить тайник обратно, независимо от того, что я проверил. В частности, я припрятал что-то, затем проверил более старую версию, а затем вытащил ее, но тайник был не-op в тот более ранний момент времени, поэтому тайник исчез; я не мог просто сделать 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 и было немного патч, который я хотел применить на каждом

извлекают его с помощью следующих шагов:

  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. используйте жесткий сброс для предыдущей фиксации, а затем повторите это изменение.
  2. вы также можете спрятать изменения, перебазировать и вновь.

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

git fsck --unreachable

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

git show hash

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

git stash apply hash

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

все, что вы хотите, чтобы отменить поп, но по-прежнему держать его в списке тайников, так что вы можете вытащить его на правильной ветке.

чтобы исправить это, сделайте следующее:

git reset HEAD --hard
git checkout my_correct_branch
git stash pop

готово!

Я случайно удалил тайник в приложении GitUP. Просто нажмите Ctrl+Z, чтобы отменить его.

может быть, это поможет кому-то ;)