Как восстановить сброшенный тайник в Git? - PullRequest
1500 голосов
/ 18 сентября 2008

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

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

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

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

Ответы [ 18 ]

2416 голосов
/ 18 сентября 2008

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

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, если вы предпочитаете хороший график на консоли, а не отдельное приложение с графическим интерфейсом.

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

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

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

666 голосов
/ 21 октября 2011

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

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

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

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

git stash apply tmp
git stash

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

253 голосов
/ 03 марта 2009

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

$ git stash apply ad38abbf76e26c803b27a6079348192d32f52219
1003
80 голосов
/ 04 мая 2011

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

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

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

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

71 голосов
/ 18 сентября 2008

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

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
38 голосов
/ 18 сентября 2008

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

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

27 голосов
/ 23 июня 2011

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

Как предположил Аристотель Пагальцис, 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, вы просто изменяете свой reflog, чтобы добавить старый 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
21 голосов
/ 08 января 2016

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

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

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

16 голосов
/ 08 января 2013

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

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

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

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

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

git stash apply ad38abbf76e26c803b27a6079348192d32f52219
12 голосов
/ 09 июня 2016

В 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}

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

...