Пошук уроків, статей та іншого контенту
Відновите коміти після reset, rebase або видалення гілки за допомогою reflog і низькорівневих посилань.
Git не зберігає коміти як частину гілки. Гілка — це лише посилання на останній коміт:
refs/heads/main → CКоміт C посилається на свого батька B, а B — на A. Тому історія формується ланцюжком об’єктів, а гілка лише вказує на його вершину.
Після команди на кшталт:
git reset --hard HEAD~3коміти зазвичай не видаляються одразу. Git просто переміщує посилання HEAD і поточну гілку на інший коміт. Попередні коміти можуть залишатися доступними через журнал переміщень посилань — reflog.
Відновлення зазвичай складається з таких кроків:
знайти потрібний коміт;
перевірити його історію;
створити нове посилання на коміт;
лише після цього продовжити роботу або перенести зміни в основну гілку.
reflog — це локальний журнал змін посилань Git. Він записує, куди переміщувалися:
HEAD;
локальні гілки;
деякі інші локальні посилання.
Переглянути журнал поточного HEAD:
git reflogПриклад:
a1b2c3d HEAD@{0}: reset: moving to HEAD~2
f6e5d4c HEAD@{1}: commit: Add payment validation
9a8b7c6 HEAD@{2}: commit: Add payment modelУ цьому прикладі попередня позиція HEAD — HEAD@{1}. Саме там може бути коміт, який став недоступним після reset.
Для детальнішої інформації:
git reflog show --date=isoАльтернативний формат через журнал комітів:
git log -g --oneline --allОпція -g показує не звичайну батьківську історію комітів, а записи reflog.
Запис можна вказати відносним номером:
git show HEAD@{1}
git show HEAD@{5}Також можна використовувати дату:
git show 'HEAD@{2026-08-20 14:30}'Лапки важливі в Unix-подібних оболонках, тому що фігурні дужки можуть оброблятися оболонкою.
Отримати лише ідентифікатор коміту:
git rev-parse 'HEAD@{1}'У змінну можна зберегти повний SHA-1 або SHA-256 ідентифікатор:
RECOVERED_COMMIT=$(git rev-parse 'HEAD@{1}')resetРозглянемо типову ситуацію:
git log --oneline --decorate --graph -5До помилки історія могла мати такий вигляд:
* f6e5d4c (HEAD -> main) Add payment validation
* 9a8b7c6 Add payment model
* 123abcd Initial commitПісля помилкового скидання:
git reset --hard HEAD~2поточна гілка вказує на 123abcd, але попередня позиція HEAD залишилася в reflog:
git reflogЗнайшовши потрібний коміт, спочатку перевірте його:
git show f6e5d4c
git log --oneline --decorate --graph f6e5d4cПотім створіть окрему рятувальну гілку:
git branch rescue-before-reset f6e5d4cТепер коміт знову має звичайне посилання:
refs/heads/rescue-before-reset → f6e5d4cЦе безпечніше, ніж одразу змінювати main. Після перевірки можна:
продовжити роботу в рятувальній гілці;
перемістити main на відновлений коміт;
вибірково перенести коміти через cherry-pick.
Якщо потрібно повернути main повністю:
git switch main
git reset --hard f6e5d4cПеред переміщенням гілки переконайтеся, що в робочому каталозі немає незбережених змін.
#!/usr/bin/env bash
set -e
rm -rf git-recovery-demo
mkdir git-recovery-demo
cd git-recovery-demo
git init -q
git config user.name "Recovery Demo"
git config user.email "recovery@example.com"
printf "initial\n" > app.txt
git add app.txt
git commit -qm "Initial commit"
printf "model\n" >> app.txt
git commit -qam "Add payment model"
printf "validation\n" >> app.txt
git commit -qam "Add payment validation"
printf "report\n" >> app.txt
git commit -qam "Add payment report"
# Помилково повертаємо гілку на два коміти назад.
git reset --hard HEAD~2 -q
# Попередня позиція HEAD залишилася в reflog.
RECOVERED_COMMIT=$(git rev-parse 'HEAD@{1}')
echo "Коміт для відновлення: $RECOVERED_COMMIT"
git show --stat --oneline "$RECOVERED_COMMIT"
# Створюємо постійне посилання, щоб коміт не залишався недосяжним.
git branch rescue-before-reset "$RECOVERED_COMMIT"
echo
echo "Відновлені гілки:"
git branch --listУ реальному репозиторії не потрібно видаляти каталог або створювати нові коміти. Головна частина відновлення виглядає так:
git reflog
git show 'HEAD@{1}'
git branch rescue-before-reset 'HEAD@{1}'rebaseПід час rebase Git створює нову послідовність комітів. Старі коміти можуть перестати бути частиною поточної гілки, хоча їхні об’єкти ще існують.
Наприклад, після невдалого перебазування:
git rebase mainперевірте reflog:
git reflog --date=isoТиповий фрагмент може виглядати так:
7c8d9e0 HEAD@{0}: rebase (finish): returning to refs/heads/feature
7c8d9e0 HEAD@{1}: rebase (pick): Add validation
4a5b6c7 HEAD@{2}: rebase (start): checkout main
f6e5d4c HEAD@{3}: checkout: moving from main to feature
f6e5d4c HEAD@{4}: commit: Add validationСтарий стан гілки в цьому прикладі — f6e5d4c, який можна перевірити:
git show f6e5d4c
git log --oneline --decorate --graph f6e5d4cСтворіть гілку до старого стану:
git branch feature-before-rebase f6e5d4cПісля цього можна порівняти стару та нову історії:
git log --oneline feature-before-rebase
git log --oneline feature
git diff feature-before-rebase featureORIG_HEADДеякі операції Git зберігають попередню позицію HEAD у спеціальному посиланні ORIG_HEAD. Воно може допомогти після reset, merge, rebase та інших операцій, які суттєво змінюють поточний стан.
Перевірка:
git show ORIG_HEADСтворення рятувальної гілки:
git branch rescue-orig-head ORIG_HEADORIG_HEAD не слід вважати довгостроковою резервною копією. Наступні операції можуть змінити його значення, тому для надійного пошуку перевіряйте також reflog:
git reflog --allКоманда:
git branch -D featureвидаляє посилання refs/heads/feature, але самі коміти можуть ще залишатися в базі об’єктів.
Спочатку перевірте reflog:
git reflog --allІноді потрібний коміт можна знайти серед записів, пов’язаних із HEAD або іншими посиланнями.
Якщо посилання гілки та її reflog уже видалені, використовуйте перевірку об’єктів:
git fsck --full --no-reflogs --unreachableМожливий результат:
unreachable commit f6e5d4c...
unreachable commit 9a8b7c6...Перевірте кожен кандидат:
git show --stat f6e5d4c
git log --oneline --decorate --graph f6e5d4cКоли знайдено правильний коміт, створіть гілку:
git branch feature-recovered f6e5d4cТепер він знову досяжний через звичайне посилання, і Git не розглядатиме його як недосяжний об’єкт.
git fsckКоміти, дерева та блоби Git зберігаються як об’єкти. Об’єкт називають досяжним, якщо його можна знайти, переходячи від певного посилання:
гілки;
тегу;
HEAD;
іншого кореневого об’єкта.
Коміт може бути недосяжним, але ще фізично збереженим у каталозі .git/objects.
Для пошуку недосяжних комітів:
git fsck --full --no-reflogs --unreachableЩоб залишити знайдені коміти у спеціальному просторі посилань:
git fsck --full --no-reflogs --lost-foundПісля цього Git може створити посилання на знайдені об’єкти в:
.git/lost-found/commit/Список таких об’єктів:
find .git/lost-found/commit -type fПеревірка конкретного об’єкта:
git show "$(find .git/lost-found/commit -type f | head -n 1)"Після ідентифікації потрібного коміту створіть нормальну гілку:
git branch recovered-commit <commit-id>Параметр --no-reflogs важливий для такого пошуку: він змушує fsck не вважати записи reflog коренями досяжності. Це дає змогу виявити об’єкти, які доступні лише через reflog або взагалі вже не прив’язані до посилань.
Гілки, теги та віддалені гілки зберігаються як посилання в просторі refs.
Основні простори:
refs/heads/ локальні гілки
refs/remotes/origin/ локальні копії віддалених гілок
refs/tags/ тегиПодивитися всі посилання:
git show-refАбо вивести їх разом із короткою інформацією:
git for-each-ref --format='%(refname) %(objectname:short)' refs/Поточний HEAD зазвичай є символічним посиланням:
git symbolic-ref HEADРезультат для гілки main:
refs/heads/mainОтримати коміт, на який вказує гілка:
git rev-parse refs/heads/maingit update-refКоманда git update-ref безпосередньо змінює посилання Git. Вона корисна для точного та контрольованого відновлення:
git update-ref refs/heads/recovered f6e5d4cПісля цього:
git log --oneline --decorate recoveredБезпечніший варіант — указати очікуване старе значення:
git update-ref refs/heads/recovered f6e5d4c 0000000000000000000000000000000000000000У такому випадку посилання буде створено лише якщо його ще не існує. Для SHA-256-репозиторіїв кількість нулів має відповідати формату ідентифікаторів репозиторію, тому для повсякденного відновлення зручніше використовувати:
git branch recovered f6e5d4cgit branch сам створює потрібне посилання і зазвичай є зрозумілішим. git update-ref варто використовувати, коли потрібно працювати безпосередньо з простором refs або автоматизувати відновлення.
fsck може показати багато комітів, особливо у великому репозиторії. Перевіряйте кандидатів за кількома ознаками:
git show --format=fuller --stat <commit-id>Це покаже:
автора;
дату;
повідомлення;
змінені файли.
Переглянути батьків і структуру:
git log --oneline --decorate --graph --all <commit-id>Порівняти кандидат із поточною гілкою:
git diff HEAD <commit-id>Якщо знайдений коміт є вершиною старої послідовності, перевірте кілька його попередників:
git log --oneline <commit-id> -10Не створюйте основну гілку на першому знайденому об’єкті. Спочатку створіть тимчасове або рятувальне посилання та переконайтеся, що це справді потрібна історія.
reflog є локальним журналом. Він не передається на віддалений сервер під час push або fetch. Якщо коміт ніколи не потрапляв на сервер і був остаточно очищений локально, інший розробник не зможе відновити його з віддаленого репозиторію.
Недосяжні об’єкти з часом можуть бути видалені автоматичним збиранням сміття:
git gcТакож Git періодично очищає старі записи reflog та об’єкти, які більше не мають посилань. Тому після помилки:
не запускайте одразу агресивне очищення;
не видаляйте каталог .git;
якомога швидше знайдіть коміт у reflog;
створіть на нього постійне посилання.
Після створення гілки або тегу коміт стає досяжним:
git branch recovered f6e5d4c# 1. Зупиняємо зміни в репозиторії та перевіряємо стан.
git status
# 2. Переглядаємо всі доступні журнали переміщення посилань.
git reflog --all --date=iso
# 3. Перевіряємо підозрілий коміт.
git show <commit-id>
git log --oneline --decorate --graph <commit-id>
# 4. Створюємо рятувальну гілку.
git branch recovery/<name> <commit-id>
# 5. Перевіряємо відновлену історію.
git log --oneline --decorate --graph recovery/<name>
# 6. Якщо reflog не допоміг, шукаємо недосяжні об’єкти.
git fsck --full --no-reflogs --unreachableПісля цього вже можна приймати рішення щодо основної гілки:
git switch main
git merge recovery/<name>Або, якщо потрібно повністю повернути старий стан:
git reset --hard recovery/<name>Не створюйте гілку на першому SHA, який знайшли у виведенні fsck. Перевірте повідомлення, дату, файли та батьківський ланцюжок через git show і git log.
HEAD@{1} без перевіркиHEAD@{1} означає попередній запис саме для поточного HEAD, але після нових команд позиції reflog змінюються. Спочатку збережіть потрібний ідентифікатор:
git rev-parse 'HEAD@{3}'Потім використовуйте повний SHA.
reset --hard замість рятувальної гілкиКоманда:
git reset --hard <commit-id>змінює поточну гілку та робочі файли. Безпечніше спочатку створити окрему гілку:
git branch recovery <commit-id>Локальний reflog не є спільним журналом команди. Інший клон репозиторію має власний reflog, а віддалений сервер може взагалі не зберігати потрібний запис.
Не запускайте git gc, git prune або ручне видалення файлів у .git/objects, поки не завершили відновлення. Так можна остаточно втратити недосяжні об’єкти.
Гілка Git — це посилання на коміт, а не сама історія.
Після reset, rebase або видалення гілки коміти часто залишаються в базі об’єктів.
git reflog допомагає знайти попередні позиції HEAD і локальних гілок.
ORIG_HEAD може зберігати стан до складної операції, але не замінює reflog.
Після видалення гілки без доступного reflog використовуйте:
git fsck --full --no-reflogs --unreachableЗнайдений коміт потрібно закріпити новою гілкою або іншим посиланням.
git update-ref дає низькорівневий контроль над простором refs.
Якщо об’єкт уже видалений автоматичним очищенням, відновлення з локального репозиторію може бути неможливим.