Зачастую самый быстрый способ — использование команды git reflog. Дело в том, что во время вашей работы Git записывает все изменения HEAD. Каждый раз при переключении веток и коммитов изменений, добавляется запись в reflog. reflog также обновляется командой git update-ref — это, кстати, хорошая причина использовать именно эту команду, а не вручную записывать SHA-1 в ref-файлы, как было показано в Ссылки в Git. Вы можете посмотреть где находился указатель HEAD в любой момент времени, запустив git reflog:
$git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: modified repo.rb a bit
484a592 HEAD@{2}: commit: added repo.rb
Здесь мы видим два коммита, на которые когда-то указывал HEAD, однако информации не так уж и много. Более интересные выводы можно получить, используя git log -g; получим привычный лог, но для записей из reflog:
$git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon )
Reflog message: updating HEAD
Author: Scott Chacon
Date: Fri May 22 18:22:37 2009 -0700
third commit
commit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon )
Reflog message: updating HEAD
Author: Scott Chacon
Date: Fri May 22 18:15:24 2009 -0700
modified repo.rb a bit
Похоже, что последний коммит — это и есть тот, который мы потеряли; и его можно восстановить, создав ветку, указывающую на него. Например, создадим ветку с именем recover-branch, указывающую на этот коммит (ab1afef):
$git branch recover-branch ab1afef
$git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
Здорово! Теперь у нас есть ветка recover-branch, указывающая туда, куда ранее указывал master, тем самым делая потерянные коммиты вновь доступными. Теперь, положим, потерянная ветка по какой-либо причине не попала в reflog, для этого удалим восстановленную ветку и весь reflog. Теперь два первых коммита недоступны ниоткуда:
$git branch -D recover-branch
$rm -Rf .git/logs/
Данные reflog хранятся в директории .git/logs/, которую мы только что удалили, поэтому теперь у нас нет reflog. Как теперь восстановить коммиты? Один из вариантов — использование утилиты git fsck, проверяющую внутреннюю базу данных на целостность. Если выполнить её с ключом --full, будут показаны все объекты, недостижимые из других объектов:
$git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (18/18), done.
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293
В данном случае потерянный коммит указан после слов "dangling commit" ("висячий коммит"). Его можно восстановить аналогичным образом, добавив ветку, указывающую на этот SHA-1.
Git — замечательный инструмент с кучей классных фич, но некоторые из них способны и навредить. Например, команда git clone загружает проект вместе со всей историей, включая все версии всех файлов. Это нормально, если в репозитории хранится только исходный код, так как Git хорошо оптимизирован под такой тип данных и может эффективно сжимать их. Однако, если когда-либо в проект был добавлен большой файл, каждый, кто потом захочет клонировать проект, будет вынужден скачивать этот файл, даже если он был удалён в следующем же коммите. Он будет в базе всегда, просто потому, что он доступен в истории.
Это может стать большой проблемой при конвертации Subversion или Perforce репозиториев в Git. В этих системах вам не нужно загружать всю историю, поэтому добавление больших файлов не имеет там особых последствий. Если при импорте из другой системы или при каких-либо других обстоятельствах стало ясно, что ваш репозиторий намного больше, чем он должен быть, то как раз сейчас мы расскажем, как можно найти и удалить большие объекты.
Предупреждаем: дальнейшие действия разрушат историю изменений.Каждый коммит, начиная с самого раннего, из которого нужно удалить большой файл, будет переписан. Если сделать это непосредственно после импорта, пока никто ещё не работал с репозиторием, то всё окей, иначе придётся сообщать всем участникам о необходимости перемещения их правок на новые коммиты.
Для примера добавим большой файл в тестовый репозиторий, удалим его в следующем коммите, а потом найдём и удалим его полностью из базы. Для начала добавим большой файл в нашу историю:
$curl https://www.kernel.org/pub/software/scm/git/git-2.1.0.tar.gz > git.tgz
$git add git.tgz
$git commit -m 'add git tarball'
[master 7b30847] add git tarball
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 git.tgz
Упс, мы нечаянно. Нам лучше избавится от этого файла:
Читать дальше