Как восстановить утерянный тайник в Git?
Вопрос
Я часто использую git stash
и git stash pop
для сохранения и восстановления изменений в моем рабочем дереве.Вчера у меня были некоторые изменения в моем рабочем дереве, которые я спрятал и извлек, а затем я внес в свое рабочее дерево еще больше изменений.Я бы хотел вернуться и просмотреть вчерашние спрятанные изменения, но git stash pop
похоже, удаляет все ссылки на связанную фиксацию.
Я знаю, что если я использую git stash
затем .git/refs/stash содержит ссылка на коммит, используемый для создания тайника.И .git/logs/refs/stash содержит весь тайник.Но эти ссылки исчезли после git stash pop
.Я знаю, что этот коммит все еще находится где-то в моем репозитории, но не знаю, что это было.
Есть ли простой способ восстановить ссылку на вчерашнюю фиксацию тайника?
Обратите внимание, что сегодня для меня это не критично, поскольку у меня есть ежедневные резервные копии, и я могу вернуться к вчерашнему рабочему дереву, чтобы получить свои изменения.Я спрашиваю, потому что должен быть более простой способ!
Решение
Как только вы узнаете хеш сброшенного вами тайника, вы можете применить его как тайник:
git stash apply $stash_hash
Или вы можете создать для него отдельную ветку с помощью
git branch recovered $stash_hash
После этого вы можете делать все, что захотите, со всеми обычными инструментами.Когда закончите, просто сдуйте ветку.
Нахождение хеша
Если вы только что открыли его, а терминал все еще открыт, вы все еще имеет хеш-значение, напечатанное git stash pop
на экране (спасибо, Долда).
В противном случае вы можете найти его, используя это для 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];}
Это покажет вам все коммиты на кончиках вашего графика коммитов, на которые больше нет ссылок из какой-либо ветки или тега — каждый потерянный коммит, включая каждый тайный коммит, который вы когда-либо создавали, будет где-то на этом графике.
Самый простой способ найти нужный вам тайник — это, вероятно, передать этот список gitk
:
gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )
...или посмотреть ответ от emragins если вы используете Powershell для Windows.
Это запустит браузер репозитория, показывающий вам каждый коммит в репозитории когда-либо, независимо от того, достижим он или нет.
Вы можете заменить gitk
там что-то вроде git log --graph --oneline --decorate
если вы предпочитаете красивый график на консоли отдельному приложению с графическим интерфейсом.
Чтобы обнаружить коммиты в тайнике, найдите сообщения о коммитах такого вида:
НЗП включен какая-то ветка: commithash Какое-то старое сообщение о коммите
Примечание:Сообщение о фиксации будет иметь такую форму (начиная с «WIP on») только в том случае, если вы не предоставили сообщение при этом. git stash
.
Другие советы
Если вы не закрывали терминал, просто посмотрите на вывод 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» и т. д.
Чтобы получить список тайников, которые все еще находятся в вашем репозитории, но больше не доступны:
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.
Я только что создал команду, которая помогла мне найти потерянный коммит тайника:
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
должен показать 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, вы просто измените свой журнал тайника, чтобы добавить старый тайник:
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
Эквивалент Windows PowerShell с использованием gitk:
gitk --all $(git fsck --no-reflog | Select-String "(dangling commit )(.*)" | %{ $_.Line.Split(' ')[2] })
Вероятно, существует более эффективный способ сделать это в одном канале, но он справляется со своей задачей.
Мне понравился подход Аристотеля, но не понравилось использовать GITK...как я привык использовать GIT из командной строки.
Вместо этого я взял висячие коммиты и вывел код в файл DIFF для проверки в своем редакторе кода.
git show $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' ) > ~/stash_recovery.diff
Теперь вы можете загрузить полученный файл diff/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>
в противном случае вы найдете идентификатор из результата вручную с помощью:
$ git fsck --unreachable | grep commit | cut -c 20- | xargs git show
Затем, когда вы найдете идентификатор фиксации, просто нажмите на git-stash, примените {commit-id}
Надеюсь, это поможет кому-то быстро
Почему люди задают этот вопрос?Потому что они еще не знают и не понимают рефлог.
Большинство ответов на этот вопрос содержат длинные команды с опциями, которые почти никто не запомнит.Итак, люди задаются этим вопросом, копируют и вставляют все, что, по их мнению, им нужно, и почти сразу же забывают об этом.
Я бы посоветовал всем, у кого есть этот вопрос, просто проверить рефлог (git reflog), не более того.Как только вы увидите этот список всех коммитов, у вас появится сотня способов узнать, какой коммит вы ищете, и выбрать его или создать на его основе ветку.В процессе вы узнаете о журнале reflog и полезных опциях различных основных команд git.
Вы можете перечислить все недоступные коммиты, написав в терминале эту команду:
git fsck --unreachable
Проверить недостижимый хеш коммита –
git show hash
Наконец, подайте заявку, если найдете спрятанный предмет -
git stash apply hash
Я хочу добавить к принятому решению еще один хороший способ просмотреть все изменения, когда у вас либо нет 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», чтобы перейти к следующему изменению.
Принятый ответ Аристотеля покажет все доступные коммиты, включая коммиты, не похожие на тайники.Чтобы отфильтровать шум:
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"
), это переопределит сообщение по умолчанию «НЗП включено...».
Вы можете отобразить дополнительную информацию о каждом коммите, например.отобразить сообщение о фиксации или передать его 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
Я не смог получить ни одного ответа для работы в 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)
возможно, это не лучшее решение, но мне помогло
Я пришел сюда в поисках того, как на самом деле вернуть тайник, независимо от того, что я проверил.В частности, я что-то спрятал, затем извлек более старую версию, затем извлек ее, но в тот более ранний момент тайник не работал, поэтому тайник исчез;я не мог просто сделать 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.
Восстановил его, выполнив следующие действия:
Определите удаленный хеш-код тайника:
gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )
Черри забери тайник:
git Cherry-Pick -m 1 $stash_hash_code
Разрешите конфликты, если таковые имеются, используя:
git mergetool
Кроме того, у вас могут возникнуть проблемы с сообщением о фиксации, если вы используете gerrit.Пожалуйста, сохраните изменения, прежде чем использовать следующие варианты:
- Используйте полный сброс предыдущей фиксации, а затем повторно зафиксируйте это изменение.
- Вы также можете спрятать изменения, перебазировать и повторно зафиксировать их.
Я случайно удалил тайник в приложении GitUP.Просто нажмите Ctrl+Z, чтобы отменить это действие.
Может кому-то поможет ;)