Перейти до вмісту

Модуль 4: Мережа безпеки — Скасування та відновлення

Модуль 4: Мережа безпеки — Скасування та відновлення

Розділ «Модуль 4: Мережа безпеки — Скасування та відновлення»

Складність: [СЕРЕДНЯ]
Час на виконання: 60 хвилин
Передумови: Модуль 3 курсу Git Deep Dive

Що ви зможете зробити

Розділ «Що ви зможете зробити»
  • Впровадити git reset із прапорцями --soft, --mixed та --hard для навмисного маніпулювання робочою директорією, областю підготовки та історією комітів з метою виправлення архітектурних помилок.
  • Діагностувати та відновити дані після критичних помилок у Git (таких як невдалий інтерактивний rebase або випадковий hard reset), використовуючи git reflog для відстеження та відновлення “сирітських” комітів (orphaned commits).
  • Оцінити, коли використовувати git revert замість git reset, виходячи з правил захисту гілок, обмежень спільної роботи та необхідності збереження незмінного журналу аудиту.
  • Виконати хірургічні операції з відновлення на рівні файлів за допомогою git restore та git checkout, щоб отримати конкретні стани файлів із минулих комітів, не порушуючи загальну історію гілки.
  • Перенести конкретні коміти між гілками за допомогою git cherry-pick для вирішення проблем невідповідності гілок функціоналу та бекпортування критичних виправлень безпеки.

Була 2:00 ночі п’ятниці, і провідний DevOps-інженер великого фінтех-стартапу намагався впорядкувати історію комітів у гілці main перед критичним релізом у продакшн. Реліз включав тижні складних оновлень маніфестів Kubernetes — нові ConfigMaps, правила маршрутизації Ingress та посилені NetworkPolicies, розроблені для відповідності новому аудиту безпеки. Намагаючись об’єднати (squash) кілька неохайних комітів, щоб історія виглядала ідеально, інженер запустив інтерактивний rebase, заплутався в конфлікті злиття, що стосувався контролера ingress, запанікував і виконав git reset --hard HEAD~5. Миттєво п’ять вирішальних комітів, що представляли три дні колективних зусиль команди, зникли. Локальна робоча директорія оновилася, стерши нові маніфести. Інженер вкрився холодним потом, дивлячись у термінал, який не пропонував негайної кнопки “скасувати”.

Такий сценарій розігрується в інженерних командах по всьому світу щодня. Git неймовірно потужний, фундаментально розроблений як розподілена графова база даних для вашого коду. Однак його команди можуть здаватися зарядженою зброєю, коли ви перебуваєте під тиском. Страх “зламати репозиторій” або назавжди втратити роботу є однією з найпоширеніших тривог серед розробників та інженерів платформ. Коли ви не розумієте глибоко механізми відновлення Git, кожна деструктивна команда здається незворотною, що призводить до культури вагань, надмірно захисних практик контролю версій та покладання на копіювання папок як “.bak” бекапів.

У цьому модулі ми повністю приберемо цей страх. Ви дізнаєтеся, що в Git майже нічого не втрачається назавжди, якщо тільки ви не докладете зусиль для остаточного видалення або не чекатимете місяцями на збирач сміття (garbage collector). Ми зануримося в механіку виправлення помилок: від нюансів у прапорцях reset до головної, прихованої мережі безпеки — reflog. До кінця цієї сесії ви володітимете точними ментальними моделями та практичними навичками командного рядка, щоб впевнено орієнтуватися та відновлюватися навіть після найжахливіших катастроф у Git. Ви перетворитеся з людини, яка сподівається нічого не зламати, на фахівця, який точно знає, як діагностувати та виправити помилки, коли вони неминуче трапляються.

1. Три дерева Git та git reset

Розділ «1. Три дерева Git та git reset»

Щоб по-справжньому опанувати команду git reset і використовувати її передбачувано, ви повинні спочатку зрозуміти архітектуру “Трьох дерев”, яку Git використовує для керування станом ваших файлів. Думайте про ці дерева як про три окремі рівні стану, через які проходить ваш код на шляху до постійної історії.

  1. Робоча директорія (Working Directory): Це файлова система, яку ви бачите та редагуєте у своїй IDE або терміналі. Це ваша чернетка. Вона представляє поточний стан файлів на вашому диску.
  2. Область підготовки (Staging Area / Index): Це вантажна платформа. Ви вибірково переміщуєте файли сюди (за допомогою git add), щоб підготувати їх до наступного коміту. Це точний зріз того, як виглядатиме наступний коміт.
  3. HEAD (Історія комітів): Це постійний запис. Це покажчик на останній коміт у вашій поточній гілці. Він представляє стан вашого репозиторію на момент останнього запуску git commit.

Коли ви використовуєте git reset, ви явно наказуєте Git перемістити покажчик HEAD на інший, зазвичай старіший, коміт. Але вирішальна частина — і джерело найбільшої плутанини — полягає в тому, що відбувається з областю підготовки та робочою директорією під час цього переміщення. Ця поведінка контролюється прапорцями: --soft, --mixed та --hard.

Уявіть, що ви пакуєте коробки для переїзду в новий дата-центр.

  • HEAD: Коробки, вже завантажені та закріплені у вантажівці для переїзду.
  • Область підготовки (Staging Area): Коробки, заклеєні скотчем, промарковані та розміщені на вантажній платформі, готові до завантаження.
  • Робоча директорія (Working Directory): Окремі сервери, кабелі та компоненти, розкидані по вашій кімнаті підготовки.
+---------------------+ +---------------------+ +---------------------+
| Робоча директорія | ---> | Область підготовки | ---> | HEAD (Історія) |
| (Кімната підготовки)| add | (Вантажна платформа)|commit| (Вантажівка) |
+---------------------+ +---------------------+ +---------------------+
^ ^ ^
| | |
| | |
--hard стирає це --mixed стирає це --soft переміщує це
(а також staging і HEAD) (і переміщує HEAD) (тільки переміщує HEAD)

М’який reset лише переміщує HEAD на попередній коміт. Він повністю ігнорує вашу область підготовки та робочу директорію.

Аналогія: Ви знімаєте коробки з вантажівки і повертаєте їх на вантажну платформу. Речі все ще запаковані, заклеєні та готові до відправлення. Ви нічого не розпаковували; ви просто вирішили, що вони ще не готові до відправлення.

Приклад використання: Ви зробили коміт, що містить Kubernetes Deployment та Service, але зрозуміли, що забули додати важливу змінну оточення в специфікацію контейнера. Ви хочете скасувати коміт, але обов’язково хочете залишити всі зміни підготовленими (staged), щоб ви могли швидко виправити файл, додати його знову і зробити коміт ще раз.

Terminal window
# Переглядаємо поточний лог, щоб побачити коміт, який хочемо скасувати
git log --oneline
# 3a2b1c4 (HEAD -> main) Deploy backend services and DB
# 9f8e7d2 Base project setup
# Ой, забули змінну оточення DB_PASSWORD.
# Скасовуємо коміт, але зберігаємо зміни в області підготовки.
git reset --soft HEAD~1
# Перевіряємо статус. Зміни з 3a2b1c4 знаходяться в області підготовки точно в тому ж стані, що й були.
git status
# On branch main
# Changes to be committed:
# (use "git restore --staged <file>..." to unstage)
# new file: deployment.yaml
# new file: service.yaml

Зупиніться та подумайте: Як ви гадаєте, що станеться, якщо ви виконаєте git commit -m "Same thing" відразу після git reset --soft HEAD~1? Прогноз: Ви відтворите той самий коміт, який щойно скасували, із тими самими станами файлів. Оскільки --soft не чіпає область підготовки, зріз, готовий до наступного коміту, ідентичний тому, який ви щойно відкатали.

git reset --mixed (За замовчуванням)

Розділ «git reset --mixed (За замовчуванням)»

Змішаний reset (який виконується, якщо ви просто введете git reset HEAD~1 без прапорця) переміщує HEAD, а також очищує область підготовки. Однак він залишає вашу робочу директорію повністю недоторканою.

Аналогія: Ви знімаєте коробки з вантажівки, приносите їх назад у кімнату підготовки і розриваєте їх. Усі речі все ще там, на підлозі, але їх потрібно знову відсортувати та запакувати в нові коробки.

Приклад використання: Ви закомітили величезну порцію YAML-файлів разом — Deployment, ConfigMap, Ingress та Secret. Під час перегляду коду (code review) старший інженер каже вам, що їх потрібно логічно розділити на чотири атомарні коміти.

Terminal window
# У нас є монолітний коміт, який ми хочемо розділити на менші частини
git reset HEAD~1
# Тепер файли змінені у вашій робочій директорії, але вони більше не в області підготовки.
git status
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git restore <file>..." to discard changes in working directory)
# modified: deployment.yaml
# modified: configmap.yaml
# modified: ingress.yaml
# modified: secret.yaml
# Тепер ви можете додавати їх окремо і створювати чисті атомарні коміти
git add deployment.yaml
git commit -m "Configure backend deployment"
git add configmap.yaml
git commit -m "Add backend environment configuration"

Жорсткий reset — це радикальний варіант. Він переміщує HEAD, очищує область підготовки і примусово перезаписує вашу робочу директорію, щоб вона точно відповідала цільовому коміту. Будь-які незакомічені зміни у відстежуваних файлах будуть безповоротно знищені.

Аналогія: Ви заганяєте вантажівку назад у кімнату підготовки, викидаєте все геть і підпалюєте будівлю. Залишається лише те, що спочатку було запаковано у вантажівці. Усе інше — попіл.

Приклад використання: Ви витратили дві години на експерименти зі складною конфігурацією Istio VirtualService. Усе зламано, синтаксис жахливий, маршрутизація не працює, і ви просто хочете повернутися до чистого, перевіреного стану.

Terminal window
# ПОПЕРЕДЖЕННЯ: Це знищить усі незакомічені зміни у відстежуваних файлах
git reset --hard HEAD
# Або скасуйте останній коміт І всі зміни в робочій директорії одночасно
git reset --hard HEAD~1

Реальна історія: Випадкове знищення

Розділ «Реальна історія: Випадкове знищення»

Молодшому інженеру платформи доручили оновити запити ресурсів CPU та пам’яті в усіх просторах імен Kubernetes. Він написав складний sed-скрипт, який вніс масштабні зміни в 50 різних маніфестів. Перед комітом він запустив git status і побачив величезний список модифікацій. Схвильований, стурбований можливою друкарською помилкою у своєму скрипті та не впевнений у безпечності змін, він ввів git reset --hard, маючи намір просто очистити область підготовки (для чого знадобився б --mixed). Прапорець --hard миттєво знищив усі 50 незакомічених змін у файлах. Оскільки зміни ніколи не були закомічені, Git не мав жодних записів про них. Інженер втратив години ретельного налаштування скрипта і мусив починати все з нуля.

2. git reflog: Мережа безпеки під мережею безпеки

Розділ «2. git reflog: Мережа безпеки під мережею безпеки»

Коли ви виконуєте деструктивну команду, таку як git reset --hard HEAD~3, здається, що ці три коміти зникли назавжди. Якщо ви введете git log, їх ніде не буде видно. Історія виглядає так, ніби їх ніколи не існувало. Але Git за своєю природою ледачий і неруйнівний за лаштунками. Він рідко видаляє дані негайно. Він просто видаляє покажчики на ці дані.

Тут на сцену виходить лог посилань, відомий як reflog.

Reflog — це хронологічно впорядкований прихований щоденник кожного разу, коли вершина гілки (HEAD) оновлювалася у вашому локальному репозиторії. Кожен коміт, який ви робите, кожна гілка, на яку ви переходите, кожен reset, який ви виконуєте, кожне злиття та кожен rebase записуються тут, незалежно від того, чи відображаються вони в git log.

+-------------------------------------------------------------+
| ЩОДЕННИК REFLOG |
+-------------------------------------------------------------+
| HEAD@{0}: reset: moving to HEAD~2 |
| HEAD@{1}: commit: Add horizontal pod autoscaler |
| HEAD@{2}: commit: Update readiness probes |
| HEAD@{3}: checkout: moving from feature-auth to main |
| HEAD@{4}: pull origin main: Fast-forward |
+-------------------------------------------------------------+

Відновлення втраченого коміту

Розділ «Відновлення втраченого коміту»

Давайте змоделюємо жахливу катастрофу. Ви робите важливий коміт, що містить конфіденційні конфігурації інфраструктури, а потім випадково знищуєте його за допомогою hard reset.

Terminal window
# Створюємо та комітимо критично важливий файл
echo "apiVersion: v1" > secret-config.yaml
git add secret-config.yaml
git commit -m "Add critical database credentials template"
# Вивід: [main 7a8b9c0] Add critical database credentials template
# СТАЛАСЯ КАТАСТРОФА: Ви хотіли скинути іншу гілку, але були на main
git reset --hard HEAD~1
# Вивід: HEAD is now at 1d2e3f4 Previous stable state
# Коміт повністю зник зі стандартної історії
git log --oneline
# 1d2e3f4 (HEAD -> main) Previous stable state

На цьому етапі більшість новачків панікують. Але ви, як інженер KubeDojo, звертаєтеся до щоденника.

Terminal window
git reflog
# 1d2e3f4 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
# 7a8b9c0 HEAD@{1}: commit: Add critical database credentials template
# 1d2e3f4 HEAD@{2}: commit: Previous stable state

Уважно подивіться на вивід. Ви бачите, що 7a8b9c0 — це коміт, який ми втратили. Він усе ще існує в об’єктній базі даних Git у повній цілості; йому просто не вистачає покажчика гілки, який вказував би, де він знаходиться. Щоб повернути його, ми просто скидаємо нашу поточну гілку так, щоб вона вказувала на цей “сирітський” хеш.

Terminal window
# Рятуємо сирітський коміт
git reset --hard 7a8b9c0
# Вивід: HEAD is now at 7a8b9c0 Add critical database credentials template

Коміт, файл secret-config.yaml та вся історія повністю відновлені. Ви обманули смерть.

Зупиніться та подумайте: Який вивід ви очікуєте побачити, якщо знову запустіте git reflog відразу після відновлення? Прогноз: У reflog з’явиться абсолютно новий запис у самому верху (HEAD@{0}), що фіксує дію відновлення: reset: moving to 7a8b9c0. Старі записи зсунуться до HEAD@{1}, HEAD@{2} і так далі. Reflog завжди додає записи; він ніколи не видаляє власну історію (поки не закінчиться термін її зберігання).

3. git revert: Безпечне скасування для спільної історії

Розділ «3. git revert: Безпечне скасування для спільної історії»

git reset — це неймовірно потужний інструмент для маніпулювання локальною історією. Він буквально переписує час, вдаючи, що майбутнього ніколи не було. Це цілком прийнятно і навіть заохочується для локальних гілок функціоналу, які існують лише на вашому ноутбуці.

Однак git reset є катастрофічним для спільних гілок, таких як main, develop або production. Якщо ви скинете коміт, який ваші колеги вже затягнули (pull) на свої машини, їхні локальні історії раптово розійдуться з віддаленим репозиторієм. Коли вони спробують зробити pull або push, вони зіткнуться з хаотичними та заплутаними конфліктами злиття, а конвеєр безперервного розгортання (CD pipeline) команди, швидше за все, зламається.

Коли невдалий коміт уже був відправлений на віддалений сервер і став спільним для інших, ви ніколи не повинні використовувати git reset. Ви повинні використовувати git revert.

Замість того, щоб стирати коміт, git revert створює новий коміт, який вносить зміни, прямо протилежні цільовому коміту. Це “скасування”, яке рухається вперед, зберігаючи незмінний журнал аудиту того, що сталося.

+-------------------+ +-------------------+ +-------------------+
| Коміт A | ---> | Коміт B (ПОГАНИЙ) | ---> | Коміт C |
| Add Deployment | | Break Ingress | | Revert of B |
+-------------------+ +-------------------+ +-------------------+

Скасування невдалої конфігурації

Розділ «Скасування невдалої конфігурації»

Уявіть, що розробник зливає pull request, який випадково спрямовує весь веб-трафік продакшну на staging API бекенду. Сайт не працює.

Terminal window
# Визначаємо хеш поганого коміту, що спричинив збій
git log --oneline
# 5f6g7h8 (HEAD -> main) Merge pull request #102
# 9a8b7c6 Update ingress routing rules (ЦЕЙ ПОГАНИЙ КОМІТ)
# 1d2e3f4 Add new payment gateway service

Зупиніться та подумайте: Якщо ви запустите git revert 9a8b7c6 у гілці main, що покаже git log --oneline відразу після цього? Прогноз: Лог покаже новий коміт на вершині гілки з повідомленням на кшталт “Revert ‘Update ingress routing rules’”. Оригінальний поганий коміт 9a8b7c6 залишиться в історії безпосередньо під ним.

Terminal window
# Скасовуємо конкретний поганий коміт, щоб відновити роботу сервісу
git revert 9a8b7c6

Коли ви запускаєте цю команду, Git розрахує інверсний патч коміту 9a8b7c6. Якщо рядки були додані, він їх видалить. Якщо рядки були видалені, він їх відновить. Потім Git відкриє ваш текстовий редактор за замовчуванням, щоб ви могли завершити повідомлення коміту (яке за замовчуванням виглядає як “Revert ‘Update ingress routing rules’”). Після збереження новий коміт додається на вершину main.

Історія залишається недоторканою, журнал аудиту показує, хто саме припустився помилки і хто її виправив, а продуктивна система безпечно відновлена до попереднього стану без порушення роботи локальних репозиторіїв інших розробників.

Реальна історія: Неревертований Merge

Розділ «Реальна історія: Неревертований Merge»

Команда злила в main величезну гілку функціоналу, над якою працювала багато місяців. Майже відразу після розгортання функціонал спричинив каскадні блокування бази даних у продакшні, що призвело до відмови основної програми. У паніці техлід запустив git revert -m 1 <merge-commit-hash>. Скасування спрацювало, поганий код був видалений, і ситуація в продакшні стабілізувалася.

Через два тижні команда ретельно налагодила та виправила проблему з блокуваннями у своїй локальній гілці. Вони знову спробували злити гілку функціоналу в main. На їхній жах, Git повідомив: “Already up to date” (Уже оновлено). Жодних змін не було злито. Чому? Тому що оригінальні коміти технічно все ще були в історії main (їх просто нейтралізував коміт revert). Git побачив хеші комітів і подумав: “У мене це вже є”. Команді довелося виконати дуже заплутаний маневр: їм довелося скасувати коміт revert (revert the revert), щоб знову ввести оригінальний зламаний код, а потім застосувати свої нові виправлення поверх нього.

4. Відновлення на рівні файлів: git restore та git checkout

Розділ «4. Відновлення на рівні файлів: git restore та git checkout»

Часто ви не хочете маніпулювати цілими комітами. Ви просто хочете відкинути локальні, неохайні зміни в одному файлі або вам потрібно взяти стару, робочу версію файлу з минулого коміту, тому що ви випадково видалили важливий блок коду.

Відкидання локальних змін

Розділ «Відкидання локальних змін»

Ви працювали над складним файлом Kubernetes statefulset.yaml протягом години, додаючи шаблони запитів на об’єм (volume claim templates) та правила афінності (affinity rules). Ви розумієте, що ваш архітектурний підхід повністю помилковий. Ви хочете викинути свою роботу і повернути файл до стану, у якому він був під час останнього коміту (HEAD).

Історично розробників навчали використовувати для цього git checkout:

Terminal window
git checkout -- statefulset.yaml

Хоча це працює, це семантично заплутано, оскільки checkout також використовується для перемикання гілок. Щоб вирішити цю проблему, у Git 2.23 було введено чіткішу, спеціалізовану команду саме для маніпуляцій із файлами: git restore.

Terminal window
# Відкидаємо всі зміни в робочій директорії для конкретного файлу
git restore statefulset.yaml

Зупиніться та подумайте: Якщо statefulset.yaml наразі змінений та доданий в область підготовки, що покаже git status відразу після запуску git restore --staged statefulset.yaml? Прогноз: Вивід git status перемістить statefulset.yaml із розділу “Changes to be committed” до розділу “Changes not staged for commit”. Фактичні модифікації у файлі залишаться недоторканими у вашій робочій директорії.

Terminal window
# Що, якщо ви вже додали файл до області підготовки?
# Ви можете прибрати файл із підготовки (схоже на те, що reset --mixed робить для всього репо)
git restore --staged statefulset.yaml

Отримання файлу з минулого

Розділ «Отримання файлу з минулого»

Що, якщо хтось випадково видалив критично важливу конфігурацію JSON для панелі Grafana три коміти тому, і вона вам потрібна назад без скасування всього коміту та порушення інших змін? Ви можете націлитися на цей конкретний файл у конкретний момент часу.

Terminal window
# Спочатку знайдіть коміт, де файл ще існував і працював
git log --oneline -- dashboard.json
# Вивід:
# a1b2c3d Update dashboard layout
# 8f7e6d5 Initial dashboard creation
# Отримуємо файл із того конкретного хешу коміту
git checkout a1b2c3d -- dashboard.json

Файл dashboard.json тепер миттєво з’являється у вашій робочій директорії і автоматично потрапляє в область підготовки (staged), готовий до того, щоб бути закоміченим назад у поточну історію.

5. git cherry-pick: Хірургічна точність

Розділ «5. git cherry-pick: Хірургічна точність»

Іноді виникає потреба в конкретному коміті з іншої гілки, але ви категорично не хочете зливати всю гілку повністю.

Можливо, колега виправив критичну вразливість нульового дня у розгалуженій, нестабільній гілці функціоналу. Вам негайно потрібне це виправлення безпеки в гілці main, щоб розгорнути hotfix, але решта функціоналу зламана і не може бути злита.

git cherry-pick — це хірургічний скальпель у Git. Він бере точний набір змін (diff) із конкретного коміту в будь-якій гілці та застосовує його як абсолютно новий коміт у вашій поточній гілці.

Гілка: feature-x
+---+ +---+ +---+
| A | ---> | B | ---> | C | (Виправлення безпеки)
+---+ +---+ +---+
|
| cherry-pick C
v
Гілка: main +---+
+---+ +---+ | C'| (Новий коміт, ідентичні зміни, новий хеш)
| D | ---> | E | ---> +---+
+---+ +---+

Застосування виправлення між гілками

Розділ «Застосування виправлення між гілками»
Terminal window
# Переконайтеся, що ви перебуваєте в цільовій гілці
git branch
# * main
# feature-x
# Вам потрібен патч безпеки, який є комітом C (хеш 8f9g0h1) з feature-x
git cherry-pick 8f9g0h1

Якщо контекст файлу навколо змін достатньо схожий, Git плавно створить новий коміт у main, що міститиме ці зміни.

Однак, якщо контекст занадто розійшовся (наприклад, якщо файл був сильно змінений у main з моменту відгалуження feature-x), Git призупинить cherry-pick і покаже конфлікт злиття. Ви повинні вирішити конфлікт вручну у своєму редакторі, виконати git add, щоб позначити його як вирішений, а потім запустити git cherry-pick --continue, щоб завершити операцію.

Зупиніться та поміркуйте: Який підхід ви б обрали тут і чому? Сценарій: У вас є 10 неохайних, експериментальних комітів у гілці функціоналу. Ви хочете залишити лише 3 з них і перенести їх у main для чистого pull request. Підхід: Вам слід перейти в main і запустити git cherry-pick <hash> тричі, послідовно для кожного конкретного хешу коміту, який ви хочете зберегти. Це значно чистіше, безпечніше та простіше для розуміння, ніж спроба складного інтерактивного rebase та видалення 7 комітів.

  1. Термін дії Reflogs обмежений: git reflog — це мережа безпеки, але вона не вічна. За замовчуванням недосяжні коміти (ті, що не є частиною історії жодної гілки, наприклад, видалені під час reset) видаляються збирачем сміття через 30 днів. Досяжні коміти видаляються через 90 днів. Ви не в безпеці назавжди!
  2. Гілки — це просто крихітні текстові файли: У Git гілка — це не папка або складна структура. Це буквально просто текстовий файл, що містить 40 символів — SHA-1 хеш коміту, на який вона вказує. Коли ви видаляєте гілку, ви видаляєте лише цей крихітний текстовий файл, а не самі коміти.
  3. Походження Cherry-Pick: Назва команди походить безпосередньо від англійської ідіоми “cherry-picking”, що означає вибір лише найкращих, найбажаніших елементів із групи, подібно до збирання найстигліших вишень із дерева та ігнорування кислих.
  4. Revert може бути націлений на злиття (merge): git revert може скасовувати коміти злиття, але ви повинні вказати прапорець -m (mainline). Коміт злиття має двох батьків. Ви повинні сказати Git, яка батьківська гілка повинна вважатися “основною лінією” для збереження (-m 1), а зміни якої гілки слід скасувати.
ПомилкаЧому це трапляєтьсяЯк це виправити
Запуск git reset --hard для незакоміченої роботиНерозуміння того, що --hard агресивно стирає робочу директорію, а не лише область підготовки.Це неможливо легко виправити за допомогою Git. Незакомічена робота, якої не було в staging, зникла. Покладайтеся на локальну історію IDE або бекапи файлової системи, якщо вони є.
Скасування (revert) коміту замість скидання (reset) локальноДумка, що revert — єдиний спосіб скасування, що створює засмічену історію в локальних гілках функціоналу.Використовуйте git reset, поки гілка локальна і не відправлена в репо. Використовуйте git revert лише після push у спільний репозиторій.
Втрата гілки після випадкового видаленняВидалення незлитої гілки через git branch -D та паніка через те, що git log більше не показує роботу.Запустіть git reflog, знайдіть хеш, де востаннє відбувався перехід на цю гілку, і відтворіть її через git branch <name> <hash>.
Cherry-picking коміту кілька разівЗапуск cherry-pick для коміту, який уже був злитий, що призводить до порожніх комітів або дивних конфліктів.Використовуйте git log --cherry-mark, щоб побачити вже застосовані коміти, або ретельно перевіряйте повідомлення перед cherry-picking.
Думка, що reflog — це віддалений бекапПрипущення, що git reflog на GitHub або на машині колеги дивним чином покаже ваші локальні помилки.Розумійте, що reflog є суворо локальним для вашої конкретної директорії .git на вашому ноутбуці. Він ніколи не синхронізується по мережі.
Використання git checkout для файлів, що спричиняє плутанину з detached HEADЗабування роздільника -- при отриманні файлів, що іноді призводить до переходу на гілку зі схожою назвою та від’єднання HEAD.Використовуйте сучасну команду git restore для всіх маніпуляцій на рівні файлів.

Контрольні запитання

Розділ «Контрольні запитання»
Запитання 1: Ви щойно закомітили три маніфести YAML, але зрозуміли, що забули включити важливий ConfigMap у той самий коміт. Ви хочете скасувати коміт, але залишити всі раніше закомічені зміни в області підготовки (staging), щоб просто додати ConfigMap і зробити коміт знову. Який прапорець reset слід використати і чому? Вам слід використати `git reset --soft`. Вирішальна різниця між прапорцями полягає виключно в тому, як вони обробляють область підготовки. `git reset --soft` переміщує покажчик HEAD назад, але залишає область підготовки повністю недоторканою, тобто всі раніше закомічені зміни тепер підготовлені і готові до повторного коміту негайно. Навпаки, `git reset --mixed` переміщує покажчик HEAD ТА примусово очищує область підготовки, повертаючи ці зміни в робочу директорію як непідготовлені модифікації, які ви повинні знову вручну додавати через `git add`. Розуміння цієї різниці є життєво важливим, оскільки воно визначає, чи зможете ви миттєво перекомітити свої зміни, чи вам доведеться прискіпливо сортувати робочу директорію, щоб знову підготувати конкретні файли. Використання правильного прапорця запобігає випадковому пропуску файлів під час швидкого циклу відкату та повторного коміту.
Запитання 2: Ви працюєте над великим файлом `deployment.yaml`. Ви вносите кілька змін протягом години, розумієте, що вони в основі своїй хибні, і хочете повернути файл до стану вашого останнього коміту. Яку команду слід використати? Вам слід використати `git restore deployment.yaml`. Ця команда націлена саме на робочу директорію і замінює файл його початковою версією з поточного HEAD. Альтернативно, `git checkout -- deployment.yaml` дає той самий результат, але `restore` є сучасним, кращим методом, спеціально розробленим для дій на рівні файлів. На відміну від `git reset --hard`, який стер би всі незакомічені зміни в усьому репозиторії, ця команда забезпечує хірургічну точність. Вона гарантує, що лише цільовий файл буде повернутий до початкового стану, залишаючи будь-які інші експериментальні файли, над якими ви працюєте, повністю недоторканими.
Запитання 3: Ваша команда щойно розгорнула новий реліз у продакшн. Одразу починають спрацьовувати сповіщення Datadog, оскільки скрипт міграції бази даних, введений в останньому коміті злиття, має серйозну синтаксичну помилку. Гілка була спільною, і інші команди вже зробили з неї pull. Що ви зробите? Ви повинні використати `git revert `. Оскільки коміт уже був відправлений у спільний віддалений репозиторій (і розгорнутий у продакшн), використання `git reset` переписало б історію і спричинило б хаос синхронізації для решти інженерної команди. `git revert` створює безпечний коміт скасування "вперед", який нейтралізує баг і може бути відправлений негайно. Цей підхід зберігає незмінний журнал аудиту, чітко документуючи як оригінальну помилку, так і її подальше виправлення для майбутнього налагодження. Це гарантує, що коли ваші колеги затягнуть останню гілку `main`, Git зможе плавно оновити (fast-forward) їхні локальні історії без запуску складних і схильних до помилок конфліктів злиття.
Запитання 4: Ви мали намір очистити область підготовки за допомогою `git reset --mixed`, але ваш палець зісковзнув, і ви випадково ввели `git reset --hard HEAD~2`. Два дуже важливих коміти тепер повністю відсутні у вашій історії. Як їх відновити? Вам потрібно скористатися `git reflog`. Спочатку запустіть `git reflog`, щоб переглянути свій локальний хронологічний щоденник рухів HEAD. Знайдіть хеш коміту безпосередньо перед тим, як ви виконали hard reset (ймовірно, він буде за індексом `HEAD@{1}` або `HEAD@{2}`). Потім виконайте `git reset --hard <цей-хеш>`, щоб примусово відновити покажчик вашої гілки та робочу директорію до того безпечного стану. Git рідко видаляє фактичні об'єкти комітів негайно, тому дані все ще надійно зберігаються в об'єктній базі даних вашої локальної директорії `.git`. Скидаючи стан назад до сирітського хешу, знайденого в reflog, ви фактично скасовуєте катастрофічний hard reset і миттєво повертаєте свою гілку до початкового стану.
Запитання 5: У вас є довготривала гілка функціоналу з 5 комітами. Ви розумієте, що коміт №3 насправді містить критичне виправлення витоку пам'яті, яке має потрапити в гілку `main` негайно, раніше решти незавершеного функціоналу. Як його ізолювати та перенести? Вам слід скористатися `git cherry-pick`. Спочатку знайдіть конкретний хеш коміту №3 за допомогою `git log`. Потім перейдіть на гілку `main`. Нарешті, запустіть `git cherry-pick `. Це скопіює точні зміни з цього конкретного коміту в `main` як абсолютно новий коміт, не переносячи решту нестабільної гілки функціоналу. Ця техніка ізолює екстрене виправлення від непов'язаного, потенційно зламаного коду, дозволяючи швидко та цілеспрямовано розгорнути hotfix. Це гарантує, що критичний код потрапить у продакшн швидко, тоді як основна гілка функціоналу продовжує свій нормальний життєвий цикл розробки незалежно.
Запитання 6: Ви запускаєте `git branch -D feature-ingress` і миттєво шкодуєте про це, бо там були три дні незлитих маніфестів Kubernetes. Чи може `git reflog` врятувати вас? Якщо так, то як саме? Так, `git reflog` може легко вас врятувати. Reflog відстежує, де HEAD перебував протягом часу. Навіть якщо мітка гілки `feature-ingress` видалена, самі коміти все ще існують у базі даних. Ви запускаєте `git reflog`, щоб знайти точний хеш останнього коміту, який ви зробили, перебуваючи в тій гілці. Щойно ви знайдете хеш, ви зможете відтворити гілку, що вказує на цей конкретний коміт, за допомогою команди `git branch feature-ingress `. Оскільки гілка Git — це фундаментально просто легкий рухомий покажчик на конкретний коміт, видалення гілки знищує лише покажчик, а не дані зрізу. Відтворивши мітку гілки та прикріпивши її до відновленого хешу, ви плавно відновлюєте доступ до всіх раніше недосяжних комітів у тій історії.

У цій вправі ми змоделюємо катастрофічну помилку в робочому процесі та використаємо просунуті техніки відновлення Git, щоб врятувати проєкт від загибелі.

Крок 1: Налаштування сценарію

Розділ «Крок 1: Налаштування сценарію»

Давайте побудуємо наше середовище, створимо код інфраструктури та налаштуємо змодельовану катастрофу.

Terminal window
# 1. Створюємо новий репозиторій
mkdir k8s-disaster-recovery
cd k8s-disaster-recovery
git init
# 2. Створюємо початковий стабільний стан
echo "apiVersion: apps/v1" > deployment.yaml
git add deployment.yaml
git commit -m "Init: Base deployment manifest"
# 3. Створюємо гілку функціоналу і робимо хорошу роботу
git checkout -b feature-scaling
echo "replicas: 3" >> deployment.yaml
git commit -am "Feature: Increase replicas to 3"
echo "kind: HorizontalPodAutoscaler" > hpa.yaml
git add hpa.yaml
git commit -m "Feature: Add HPA manifest"
# 4. Створюємо ще один вирішальний коміт
echo "cpu: 80%" >> hpa.yaml
git commit -am "Feature: Set HPA CPU threshold"
# Подивіться на вашу чудову лінійну історію
git log --oneline

Ви мали намір повернутися в main, але відволіклися, заплуталися і випадково виконали hard reset, жорстоко стерши останні три коміти у вашій гілці функціоналу.

Terminal window
# ПОМИЛКА: Ви думали, що скидаєте один файл, але знищили історію гілки
git reset --hard HEAD~3
# Перевірте абсолютне знищення. Коміти функціоналу зникли, hpa.yaml відсутній у директорії.
git log --oneline
ls -la

Крок 3: Рятувальна місія

Розділ «Крок 3: Рятувальна місія»

Ваше завдання — знайти втрачені коміти та відновити гілку до правильного стану з усім функціоналом.

  • Запустіть команду, яка показує прихований щоденник рухів HEAD.
  • Визначте точний хеш коміту, пов’язаного з повідомленням “Feature: Set HPA CPU threshold”.
  • Використайте команду reset, щоб примусово повернути покажчик гілки до того втраченого хешу коміту.
Рішення для кроку 3
Terminal window
# Переглядаємо reflog, щоб знайти втрачену історію
git reflog
# Ви повинні побачити вивід, схожий на цей:
# 1a2b3c4 (HEAD -> feature-scaling) HEAD@{0}: reset: moving to HEAD~3
# 9d8e7f6 HEAD@{1}: commit: Feature: Set HPA CPU threshold
# 5c4b3a2 HEAD@{2}: commit: Feature: Add HPA manifest
# ...
# Виконуємо hard reset до хешу коміту "HPA CPU threshold" (наприклад, 9d8e7f6)
git reset --hard 9d8e7f6
# Перевіряємо, чи повернулися файли у вашу робочу директорію
ls -la
cat hpa.yaml

Крок 4: Хірургічна трансплантація

Розділ «Крок 4: Хірургічна трансплантація»

Тепер, коли ви відновили гілку, ваш техлід пише вам у Slack. Маніфести HPA (Feature: Add HPA manifest та Feature: Set HPA CPU threshold) повинні бути негайно перенесені в main, щоб впоратися з очікуваним піком трафіку, але зміна кількості реплік (Feature: Increase replicas to 3) має баги і ще не схвалена.

  • Перейдіть на гілку main.
  • Перегляньте лог гілки feature-scaling, щоб отримати хеші двох конкретних комітів HPA.
  • Використайте спеціальну команду, щоб застосувати лише ці два коміти до гілки main, переконавшись, що ви застосовуєте спочатку найстаріший.
  • Перевірте, що hpa.yaml існує в main, але deployment.yaml не має рядка replicas: 3.
Рішення для кроку 4
Terminal window
# Перемикаємося на цільову гілку
git checkout main
# Переглядаємо історію гілки функціоналу, щоб взяти потрібні хеші
git log --oneline feature-scaling
# Припустимо, хеші такі:
# 9d8e7f6 Feature: Set HPA CPU threshold
# 5c4b3a2 Feature: Add HPA manifest
# 3b2a1c0 Feature: Increase replicas to 3
# 1a2b3c4 Init: Base deployment manifest
# Cherry-pick два конкретних коміти (найстаріший першим, щоб запобігти конфліктам!)
git cherry-pick 5c4b3a2
git cherry-pick 9d8e7f6
# Перевіряємо точний стан репозиторію
git log --oneline
ls -la
cat hpa.yaml
cat deployment.yaml # НЕ повинен містити рядок "replicas: 3"

Тепер, коли ви знаєте, як надійно відновлюватися після катастроф і маніпулювати історією з хірургічною точністю, настав час навчитися працювати над кількома функціями одночасно, не втрачаючи розуму, у Модулі 5: Майстерність багатозадачності.