10.7. Maintenance and Data Recovery
Это типа конспект, который я веду для себя, чтобы получше разобраться и запомнить. Вероятно, вам лучше сразу читать оригинал: 10.7 Maintenance and Data Recovery.
Тут некоторые разделы зубодробительные, поэтому обязетально проделвываем их своими руками.
Maintenance and Data Recovery
Maintenance
Время от времени Git вызывает «auto gc», который чаще всего ничего не делает,
но при некоторых условиях может вызвать полноценный git gc.
«Auto-gc» запускается командой git gc --auto. Эта команда скорее всего ничего не будет делать. Нужно иметь более 7000 потерянных объектов или 50 пак-файлов, чтобы запустился нормальный GC. Эти пределы настраиваются в настройках gc.auto и gc.autopacklimit.
Помимо прочего GC пакует ветки и тэги в файл.git/packed-refs, удаляя их с обычного места. Если ветка обновляется, то гит пишет её новую ссылку на обычное место. Если вы не видите свою ветку на обычном месте, то может быть она запакована в packed-refs и с тех пор не обновлялась.
Эээксперимент про «auto-gc» (провалился)
С полпинка мне не удалось заставить вызвать настоящий GC через «auto-gc» на мелком свежесозданном репозитории на два коммита и один файл.
Эээксперимент про «packed-refs»
Имеем мелкий репозиторий:
$ git log --pretty=oneline --abbrev-commit
dfaeeb0 (HEAD -> master) second commit
cb57f46 (origin/master) first commit
В нём какие-то рефы:
$ find .git/refs -type f -exec grep -H "" {} \;
.git/refs/heads/master:dfaeeb0640856f64b748822476b64d9293e33fe0
.git/refs/remotes/origin/master:cb57f4675eceda2faaf89e0ec6fe6f1dbacdd069
Делаем GC, рефы исчезли, но зато появился packed-refs:
$ git gc
$ find .git/refs -type f -exec grep -H "" {} \;
$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled sorted
dfaeeb0640856f64b748822476b64d9293e33fe0 refs/heads/master
cb57f4675eceda2faaf89e0ec6fe6f1dbacdd069 refs/remotes/origin/master
Делаем коммит в мастер, смотрим, что произошло с рефами:
$ find .git/refs -type f -exec grep -H "" {} \;
.git/refs/heads/master:28067320323036fd93ba7b2050153715be571813
$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled sorted
dfaeeb0640856f64b748822476b64d9293e33fe0 refs/heads/master
cb57f4675eceda2faaf89e0ec6fe6f1dbacdd069 refs/remotes/origin/master
Видим, что снова появился файл с рефом, а packed-refs не изменились.
Data Recovery
В этом разделе рассматривается пример, когда вы грохнули ветку или хард-ресетнули и хотите восстановить доступ к пропавшим коммитам, хотя не помните их айдишники.
Reflog
Чаще всего вас спасёт команда git reflog или git log -g, она покажет все перестановки ссылки HEAD с места на место. В том числе поэтому при изменении веток стоит использовать
команду git update-ref вместо прямой записи в файл ветки.
У меня такой лог:
$ git log --pretty=oneline --abbrev-commit
2806732 (HEAD -> master) third commit
dfaeeb0 second commit
cb57f46 (origin/master) first commit
$ git reflog
2806732 (HEAD -> master) HEAD@{0}: commit: third commit
dfaeeb0 HEAD@{1}: commit: second commit
cb57f46 (HEAD -> master, origin/master) HEAD@{2}: commit (initial): first commit
Делаем ресет на первый коммит:
$ git log --pretty=oneline --abbrev-commit
cb57f46 (HEAD -> master, origin/master) first commit
$ git reflog
cb57f46 (HEAD -> master, origin/master) HEAD@{0}: reset: moving to cb57f
2806732 HEAD@{1}: commit: third commit
dfaeeb0 HEAD@{2}: commit: second commit
cb57f46 (HEAD -> master, origin/master) HEAD@{3}: commit (initial): first commit
git log -g тоже что-то весёленькое показывает.
Так мы видим коммит 2806732 и можем ресетнуться обратно.
Fsck
Если же рефлог тоже пропал или коммита там не найти, то можно
попытать команду git fsck --full, которая анализирует целостность
и показывает недостижимые объекты.
Грохаем reflog, теперь он ничего не показывает, просто гит-лог работает:
$ rm -Rf .git/logs/
$ git log -g
$ git reflog
$ git log --pretty=oneline --abbrev-commit
cb57f46 (HEAD -> master, origin/master) first commit
Пробуем fsck:
$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (6/6), done.
dangling commit 28067320323036fd93ba7b2050153715be571813
Нашёл. Пока мы не грохнули reflog, он ничего не показывал. Финальный шаг:
$ git reset --hard 28067
$ git log --pretty=oneline --abbrev-commit
2806732 (HEAD -> master) third commit
dfaeeb0 second commit
cb57f46 (origin/master) first commit
$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (6/6), done.
Removing Objects
Тут рассматривают технику удаления больших файлов. Добавим и удалим большой файл и ещё чего-нибудь накоммитим. Итак, имеем следующую картину:
$ git log --pretty=oneline --abbrev-commit
2fe3403 (HEAD -> master) modify README one more time
2a89080 delete big file
122eb13 modify README
f9d2b8a add big file
2806732 third commit
dfaeeb0 second commit
cb57f46 (origin/master) first commit
Сделаем gc и count-objects:
$ git gc
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (20/20), done.
Total 20 (delta 1), reused 6 (delta 0)
$ git count-objects -v
count: 0
size: 0
in-pack: 20
packs: 1
size-pack: 2009
prune-packable: 0
garbage: 0
size-garbage: 0
size-pack в килобайтах, значит у нас 2М где-то завалялось.
Такая команда позволяет найти большие объекты:
$ git verify-pack -v .git/objects/pack/pack-60…72.idx \
| sort -k 3 -n \
| tail -3
2a89080072875ddb3cdaa318fc0ad9264e4b13e3 commit 218 150 177
2fe3403bd6d7e71c5619a00b87a1c20ec5a3e76e commit 230 165 12
51c3cecc446f7c624434e41a30bf498c057499fa blob 2054279 2054713 1392
Ищем, что это за файл:
$ git rev-list --objects --all | grep 51c3ce
51c3cecc446f7c624434e41a30bf498c057499fa setuptools-54.2.0.tar.gz
Так мы ищем в каких коммитах этот файл менялся:
$ git log --oneline --branches -- setuptools-54.2.0.tar.gz
2a89080 delete big file
f9d2b8a add big file
Так мы выпиливаем его из коммитов, я сейчас ничерта не понимаю, почему это работает, подробности должны быть в Rewriting History):
$ git filter-branch --index-filter \
'git rm --ignore-unmatch --cached setuptools-54.2.0.tar.gz' -- f9d2b8a^..
Rewrite f9d2b8a0b922c6ab07bb24aa9013a3ecb60b7744 (1/4) (0 seconds passed, remaining 0 predicted) rm 'setuptools-54.2.0.tar.gz'
Rewrite 122eb13fb499ebe127755c02d8ac6e963bcafad6 (2/4) (0 seconds passed, remaining 0 predicted) rm 'setuptools-54.2.0.tar.gz'
Rewrite 2fe3403bd6d7e71c5619a00b87a1c20ec5a3e76e (4/4) (0 seconds passed, remaining 0 predicted)
Ref 'refs/heads/master' was rewritten
Он переписал историю, коммиты теперь другие:
$ git log --pretty=oneline --abbrev-commit
159e33a (HEAD -> master) Modify README one more time
628f4cc delete big file
ba9bc41 Modify README
2714bdd add big file
2806732 third commit
dfaeeb0 second commit
cb57f46 (origin/master) first commit
Исходную ветку он спрятал под refs/original и reflog всё помнит:
$ find .git/refs -type f -exec grep -H "" {} \;
.git/refs/heads/master:159e33a5619a718fd56aa376bead03f5fb553931
.git/refs/original/refs/heads/master:2fe3403bd6d7e71c5619a00b87a1c20ec5a3e76e
$ git reflog
159e33a (HEAD -> master) HEAD@{0}: filter-branch: rewrite
2fe3403 (refs/original/refs/heads/master) HEAD@{1}: commit: Modify README one more time
2a89080 HEAD@{2}: commit: delete big file
122eb13 HEAD@{3}: commit: Modify README
f9d2b8a HEAD@{4}: commit: add big file
2806732 HEAD@{5}: reset: moving to 28067
Репозиторий всё ещё гигант:
$ git count-objects -v
count: 4
size: 16
in-pack: 20
packs: 1
size-pack: 2009
prune-packable: 0
garbage: 0
size-garbage: 0
Грохаем старую ветку и логи:
$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Размер переместился из пака в объекты:
$ git count-objects -v
count: 7
size: 2032
in-pack: 17
packs: 1
size-pack: 2
prune-packable: 0
garbage: 0
size-garbage: 0
Я грохнул объект командой git prune, хотя в книжке пишут, что нужна команда git prune --expire now. Значения по умолчанию для --expire я с полпинка не нашёл.