Модуль 9: Автоматизація та налаштування — Hooks та Rerere
Модуль 9: Автоматизація та налаштування — Hooks та Rerere
Розділ «Модуль 9: Автоматизація та налаштування — Hooks та Rerere»Складність: [MEDIUM] Час на виконання: 75 хвилин Пререквізити: Попередній модуль у Git Deep Dive Наступний модуль: Модуль 10: Міст до GitOps
Що ви зможете зробити
Розділ «Що ви зможете зробити»До кінця цього модуля ви зможете:
- Проєктувати клієнтські Git hooks, які автоматично перевіряють якість коду та запобігають витоку конфіденційних даних до завершення commit.
- Впровадити стратегію примусового використання conventional commits за допомогою hook
commit-msgдля стандартизації історії репозиторію. - Оцінити, коли та як використовувати Git Rerere (Reuse Recorded Resolution) для автоматизації вирішення повторюваних конфліктів під час merge.
- Порівняти різні методи стандартизації конфігурацій Git у команді, включаючи глобальні конфігурації та шаблонні директорії Git.
- Діагностувати помилки Git hooks та налагоджувати контекст виконання середовища для скриптів hooks.
Чому це важливо
Розділ «Чому це важливо»Це був спокійний ранок вівторка у фінтех-компанії середнього розміру, коли система автоматичного сповіщення раптово спалахнула, як різдвяна ялинка. Основний мікросервіс обробки платежів, що працює у продуктовому кластері Kubernetes, щойно отримав нову конфігурацію з гілки main і миттєво перейшов у стан crash-loop. Команда реагування на інциденти швидко зібралася, гарячково вивчаючи логи, поки черги служби підтримки клієнтів почали переповнюватися звітами про невдалі транзакції.
Причина, виявлена після двадцяти хвилин болісного пошуку несправностей, була абсурдно простою: зайвий пробіл у ключі YAML всередині критичного маніфесту Kubernetes ConfigMap у поєднанні з випадково захардкодженим ключем доступу AWS, який було відправлено у тому самому commit. Розробник, який опублікував код, був пригнічений. Він протестував логіку локально, але в поспіху забув запустити локальний linter перед виконанням фінального git commit.
Конвеєр CI/CD врешті-решт виявив помилку синтаксису YAML, але лише після того, як commit потрапив у спільний репозиторій. Що ще гірше, через дещо неправильно налаштований тригер розгортання, зламаний маніфест був завзято синхронізований з продуктовим середовищем контролером GitOps ще до того, як CI pipeline зміг повністю зупинити розгортання. Команда витратила три виснажливі години на ротацію скомпрометованих облікових даних AWS, виправлення синтаксису YAML, примусовий rollback та відновлення сервісу. Цей інцидент коштував компанії тисячі доларів через простій, підірвав довіру клієнтів і поглинув величезні зусилля на ручне виправлення.
Якби та інженерна команда використовувала Git hooks для впровадження перевірок безпеки та лінтингу за принципом «shift left» (зсув ліворуч) — запускаючи їх локально на машині розробника в ту саму мілісекунду виклику git commit — зайвий пробіл та відкритий секрет були б виявлені миттєво. Commit був би жорстко заблокований самим Git, розробник виправив би його локально за лічені секунди, і катастрофічного інциденту ніколи б не сталося.
Цей модуль присвячений фундаментальній зміні вашого ставлення до Git: від пасивної системи зберігання до активного, інтелектуального партнера. Ви навчитеся створювати автоматичних «вишибал», які забезпечують дотримання стандартів якості, використовувати потужні приховані функції, такі як rerere, для автоматизації марудного вирішення конфліктів, та налаштовувати своє локальне середовище для максимальної продуктивності як платформенного інженера.
Частина 1: Рівень перехоплення: розвінчання міфів про Git Hooks
Розділ «Частина 1: Рівень перехоплення: розвінчання міфів про Git Hooks»Git hooks — це власні скрипти, що виконуються автоматично перед або після певних подій життєвого циклу Git, таких як створення commit, push або отримання коду. Це вбудовані нативні функції, які не потребують встановлення додаткового програмного забезпечення і непомітно живуть у прихованій директорії .git/hooks вашого репозиторію.
Уявіть Git hooks як автоматизовану службу безпеки, що стоїть біля дверей історії вашого репозиторію. Вони ретельно перевіряють кожного і все, що намагається увійти (commit або push), і мають повноваження або пропустити зміни, або категорично відхилити їх, якщо вони не відповідають безкомпромісним стандартам клубу.
Існує дві основні архітектурні категорії hooks:
- Клієнтські hooks: виконуються виключно на локальній робочій станції розробника. Вони тригеряться локальними операціями, такими як commit, merge або rebase. Приклади:
pre-commit,commit-msgтаpre-push. - Серверні hooks: працюють виключно на сервері віддаленого репозиторію (наприклад, GitHub, GitLab або власний Git-сервер). Вони використовуються для забезпечення мережевих політик, відхилення вхідних push на основі аналізу вмісту або запуску складних CI/CD конвеєрів. Приклади:
pre-receive,updateтаpost-receive.
У цьому модулі ми зосередимося насамперед на клієнтських hooks, оскільки вони є першою лінією оборони для розробника, який прагне підтримувати високу якість коду.
Архітектура клієнтських hooks
Розділ «Архітектура клієнтських hooks»+-----------------------------------------------------------------------+| РОБОЧА СТАНЦІЯ РОЗРОБНИКА || || [ Робоча директорія ] [ Область підготовки (Index) ] || | | || | (git add) | || v v || +-------------------+ +-------------------+ || | | | | || | Змінені файли | | Підготовлений зріз| || | | | | || +-------------------+ +-------------------+ || | || | (git commit) || v || +-------------------+ || | | || | pre-commit | <--- Hook 1 || | (Валідація) | || | | || +--------+----------+ || | || [Успіх/Помилка] || | || v || +-------------------+ || | | || | commit-msg | <--- Hook 2 || | (Форматування) | || | | || +--------+----------+ || | || [Успіх/Помилка] || | || v || [ Об'єкт Commit створено ] || | || | (git push) || v || +-------------------+ || | | || | pre-push | <--- Hook 3 || | (Фінальні перевірки) || | | || +--------+----------+ || | || v || [ Передача по мережі ] |+-----------------------------------------------------------------------+Три головні локальні hooks
Розділ «Три головні локальні hooks»- Hook
pre-commit: цей скрипт запускається першим, ще до того, як вам запропонують ввести повідомлення до commit. Він призначений для перевірки конкретного зрізу даних, що підготовлений (staged) і готовий до фіксації. Якщо цей скрипт завершується з ненульовим кодом повернення (що вказує на помилку), Git негайно перериває процес commit. Це ідеальне місце для лінтингу коду, запуску швидких модульних тестів, форматування файлів або перевірки на наявність зайвих пробілів у кінці рядків. - Hook
commit-msg: цей hook приймає один параметр: шлях до тимчасового файлу, що містить текст повідомлення, написаний розробником. Якщо скрипт завершується з помилкою, Git зупиняє процес commit. Це загальноприйняте в індустрії місце для програмного забезпечення форматів повідомлень, таких як специфікація Conventional Commits. - Hook
pre-push: цей hook виконується під час операціїgit push, після оновлення віддалених посилань, але суворо до того, як будь-які об’єкти будуть передані по мережі. Він ідеально підходить для виконання важких, тривалих інтеграційних тестів або перевірки того, чи не намагаєтеся ви випадково відправити експериментальний код у захищену гілку.
Зупиніться та подумайте: як ви вважаєте, що станеться, якщо розробник явно обійде hook
pre-commitза допомогою прапорця--no-verify? Якщо розробник запуститьgit commit --no-verify(або-n), Git повністю проігнорує виконання hookspre-commitтаcommit-msg. Це підкреслює критичне правило: клієнтські hooks існують виключно для зручності розробника та швидкого зворотного зв’язку; вони НЕ є жорсткою межею безпеки. Зловмисник або лінивий розробник може легко їх обійти. Справжнє, непорушне забезпечення безпеки завжди має відбуватися через серверні hooks або централізовані CI/CD конвеєри.
Частина 2: Створення pre-commit hook для валідації YAML та сканування секретів
Розділ «Частина 2: Створення pre-commit hook для валідації YAML та сканування секретів»Перетворімо теорію на практику. Уявіть, що ви керуєте репозиторієм інфраструктури, заповненим сотнями маніфестів Kubernetes. Поламаний синтаксис YAML та випадково додані ключі API — це справжнє прокляття вашого життя. Створімо власний hook pre-commit, який автоматично перевіряє всі змінені файли YAML та агресивно шукає захардкоджені секрети до того, як вони назавжди закарбуються в історії вашого репозиторію.
Спочатку перейдіть до директорії hooks вашого репозиторію. Щоразу, коли ви ініціалізуєте новий репозиторій Git, він автоматично заповнює директорію .git/hooks серією прикладів скриптів (усі вони мають розширення .sample).
# Перейти до прихованої директорії hookscd .git/hooks
# Переглянути стандартні приклади від Gitls -lЗупиніться та подумайте: якби ви писали псевдокод для hook
pre-commit, який перевіряє наявність зайвих пробілів у кінці рядків, які кроки він мав би виконати, щоб гарантувати перевірку лише того коду, який готується до commit? Йому потрібно було б спочатку ідентифікувати лише ті файли, що зараз знаходяться в області підготовки (staging area). Потім, замість того, щоб читати ці файли безпосередньо з диска (робочої директорії), йому потрібно було б витягти точний зріз файлу з індексу Git для сканування, щоб гарантувати, що непідготовлені (unstaged) зміни не будуть випадково перевірені.
Щоб створити активний hook, ми просто створюємо новий файл, названий точно як відповідна фаза (без жодного розширення), і надаємо йому права на виконання.
Створімо наш скрипт pre-commit. Хоча ми напишемо цей приклад на стандартному Bash, Git hooks абсолютно не залежать від мови програмування; вони можуть бути написані на Python, Ruby, Go, Node.js або у будь-якому іншому виконуваному форматі, який підтримує ваша система.
# Створити файл та надати йому права на виконанняtouch pre-commitchmod +x pre-commitТепер відкриємо файл pre-commit і спроєктуємо нашу логіку валідації. Скрипт має виконати три послідовні завдання:
- Ідентифікувати всі файли, які зараз підготовлені до commit.
- Якщо ці підготовлені файли є маніфестами
.yamlабо.yml, ретельно перевірити їх за допомогою linter. - Просканувати точний вміст усіх підготовлених файлів на наявність заборонених рядків, таких як “password”, “secret” або ключі доступу AWS.
#!/bin/bash# Перенаправити весь вивід на стандартний потік помилок, щоб він був видимим у Git GUIexec 1>&2
echo "🛡️ Запуск перевірок KubeDojo pre-commit..."
# Витягти список усіх доданих, скопійованих або змінених файлів (staged)# --name-only: отримати лише імена файлів# --diff-filter=ACM: ігнорувати видалені файли (ми не можемо перевірити те, чого немає)STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
# Якщо файли не підготовлені (наприклад, порожній commit), вийти без помилокif [ -z "$STAGED_FILES" ]; then exit 0fi
# Ініціалізація глобального прапорця помилокERROR_FOUND=0
# ---------------------------------------------------------# Фаза 1: Суворий лінтинг YAML# ---------------------------------------------------------echo "--> Початок перевірки синтаксису YAML..."for FILE in $STAGED_FILES; do if [[ "$FILE" == *.yaml ]] || [[ "$FILE" == *.yml ]]; then # Перевірити, чи доступний бінарний файл yamllint у системному PATH if command -v yamllint &> /dev/null; then # УНИКАЄМО НЕБЕЗПЕКИ: Використовуємо 'git show :$FILE' для перевірки # саме ПІДГОТОВЛЕНОЇ версії файлу, ігноруючи зміни у робочій директорії. git show ":$FILE" | yamllint -d "{extends: relaxed, rules: {line-length: disable}}" -
# $? отримує код виходу попередньої команди (yamllint) if [ $? -ne 0 ]; then echo "❌ КРИТИЧНО: Валідація YAML не вдалася для маніфесту -> $FILE" ERROR_FOUND=1 fi else echo "⚠️ ПОПЕРЕДЖЕННЯ: 'yamllint' не знайдено, пропуск валідації для $FILE" fi fidone
# ---------------------------------------------------------# Фаза 2: Агресивне сканування на секрети# ---------------------------------------------------------echo "--> Сканування підготовлених об'єктів на наявність секретів..."for FILE in $STAGED_FILES; do # Пошук заборонених ключових слів у вмісті підготовленого об'єкта. # Примітка: Це спрощений приклад. У реальних проєктах краще використовувати # спеціалізовані інструменти, такі як TruffleHog або Gitleaks. if git show ":$FILE" | grep -iE 'password\s*[:=]|secret\s*[:=]|api_key|aws_access_key_id'; then echo "❌ КРИТИЧНО: Виявлено потенційний секрет у файлі -> $FILE" ERROR_FOUND=1 fidone
# ---------------------------------------------------------# Фаза 3: Фінальна оцінка# ---------------------------------------------------------if [ $ERROR_FOUND -ne 0 ]; then echo "" echo "🛑 COMMIT СКАСОВАНО: Виявлено порушення безпеки або синтаксису." echo "Будь ласка, виправте помилки вище, підготуйте зміни (git add) і спробуйте знову." # Ненульовий код виходу вказує Git перервати процес commit exit 1fi
echo "✅ Усі перевірки якості пройдено успішно."exit 0Повчальна історія: пастка «Staging area проти робочої директорії»
Розділ «Повчальна історія: пастка «Staging area проти робочої директорії»»Зверніть увагу на код вище. Помітили, як ми використовуємо git show ":$FILE" замість простого запуску yamllint $FILE?
Класична помилка інженерів, які тільки починають писати Git hooks, — запускати linter або тести безпосередньо для шляху до файлу в робочій директорії. Чому це катастрофічна архітектурна помилка?
Уявіть, що ви ідеально підготували (staged) цілком коректний маніфест deployment.yaml. Потім, перед тим як виконати git commit, ви продовжуєте працювати в редакторі, відволікаєтеся і випадково ламаєте синтаксис у робочій директорії — але, що важливо, ви не додаєте ці нові зламані зміни до області підготовки.
Якщо ваш hook сліпо запустить yamllint deployment.yaml, він прочитає зламану версію з диска, провалить перевірку і відхилить commit ідеально валідної підготовленої версії, яку Git насправді намагався зберегти. Використовуючи git show ":$FILE", ми наказуємо Git вивести точний вміст бінарного об’єкта, який зараз зафіксований в області підготовки (індексі), що гарантує точність нашої валідації відносно того, що справді потрапить у commit.
Перевіримо наш новостворений hook. Ми навмисно створимо маніфест Kubernetes із серйозними помилками:
apiVersion: apps/v1kind: Deploymentmetadata: name: test-appspec: replicas: 3 selector: # ПОМИЛКА: Неправильний відступ YAML matchLabels: app: test template: metadata: labels: app: test spec: containers: - name: app image: nginx:1.24 env: - name: DATABASE_PASSWORD value: "super_secret_production_123!" # ПОМИЛКА: Відкритий секретСпробуйте додати та зафіксувати це неподобство:
git add broken-deploy.yamlgit commit -m "feat: introduce new production deployment manifest"Вивід у консолі:
🛡️ Запуск перевірок KubeDojo pre-commit...--> Початок перевірки синтаксису YAML...stdin 7:5 error wrong indentation: expected 2 but found 4 (indentation)
❌ КРИТИЧНО: Валідація YAML не вдалася для маніфесту -> broken-deploy.yaml--> Сканування підготовлених об'єктів на наявність секретів... - name: DATABASE_PASSWORD value: "super_secret_production_123!" # ПОМИЛКА: Відкритий секрет❌ КРИТИЧНО: Виявлено потенційний секрет у файлі -> broken-deploy.yaml
🛑 COMMIT СКАСОВАНО: Виявлено порушення безпеки або синтаксису.Будь ласка, виправте помилки вище, підготуйте зміни (git add) і спробуйте знову.Commit миттєво скасовано! Наш автоматичний «вишибала» спрацював ідеально, запобігши катастрофі ще до того, як вона потрапила до локальної історії репозиторію.
Частина 3: Впровадження стандартів за допомогою commit-msg hooks
Розділ «Частина 3: Впровадження стандартів за допомогою commit-msg hooks»Чиста, структурована історія Git — це справжнє задоволення під час налагодження; хаотична та заплутана історія — це нічне жахіття під час сесії git bisect або спроби автоматично згенерувати опис релізу. Для боротьби з ентропією багато успішних команд використовують «Conventional Commits» — сувору специфікацію для додавання зрозумілого людям та машинам значення до повідомлень commit.
Стандартний Conventional Commit чітко відповідає такій структурі:
<type>[optional scope]: <description>
- Приклад:
feat(api): add robust user authentication endpoint via JWT - Приклад:
fix(database): resolve postgresql connection pool timeout under load
Ми можемо математично забезпечити дотримання цього формату за допомогою hook commit-msg.
#!/bin/bash# Перший аргумент ($1), який Git передає цьому скрипту — це шлях# до тимчасового файлу, що містить повідомлення commit.MESSAGE_FILE=$1MESSAGE=$(cat "$MESSAGE_FILE")
# Визначаємо дозволені типиTYPES="build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test"
# Визначаємо регулярний вираз для conventional commits# ^($TYPES) : має починатися з дозволеного типу# (\([a-z0-9\-]+\))? : може містити необов'язкову область (scope) у дужках# !? : може містити знак оклику для breaking change# : \s+ : має містити двокрапку та принаймні один пробіл# .* : має містити описREGEX="^($TYPES)(\([a-z0-9\-]+\))?!?: .+"
# Перевірка повідомлення на відповідність регулярному виразуif ! echo "$MESSAGE" | grep -qE "$REGEX"; then echo "🛑 COMMIT ВІДХИЛЕНО: Неправильний формат повідомлення." echo "Ваше повідомлення: '$MESSAGE'" echo "" echo "Цей репозиторій вимагає дотримання специфікації Conventional Commits." echo "Будь ласка, змініть повідомлення відповідно до шаблону:" echo " <type>[optional scope]: <description>" echo "" echo "Дозволені типи: $TYPES" echo "Приклад: feat(auth): implement OIDC provider integration" exit 1fi
exit 0Перед тим як запустити це, як ви гадаєте, що станеться, якщо ви поспіхом введете
git commit -m "WIP: fixing stuff"? Hookcommit-msgмиттєво перехопить ваше повідомлення, порівняє його з жорстко визначеним рядком$REGEX, провалить перевіркуgrep, виведе детальну помилку з поясненням причин відмови та завершиться зі статусом1. Ваш необдуманий commit не буде зафіксований в історії репозиторію, що змусить вас замислитися над описом змін.
Частина 4: Охорона віддаленого репозиторію за допомогою pre-push hook
Розділ «Частина 4: Охорона віддаленого репозиторію за допомогою pre-push hook»У той час як pre-commit захищає вашу локальну історію від синтаксичних помилок та відкритих секретів, hook pre-push виступає останнім вартовим перед тим, як код фізично залишить вашу робочу станцію і потрапить у мережу до спільного віддаленого репозиторію.
Типовий сценарій у великих компаніях — активне запобігання випадковим push безпосередньо в гілки main або production. Навіть якщо правила захисту гілок налаштовані на GitHub або GitLab, розробник, який випадково ввів git push origin main, все одно передасть об’єкти Git по мережі, чекатиме на їх обробку сервером, щоб врешті отримати відмову від віддаленого сервера. Локальний hook pre-push перехоплює цю критичну помилку до того, як почнеться передача даних, заощаджуючи трафік, час та нерви.
Створимо hook pre-push, який прямо забороняє push у гілку main.
#!/bin/bashPROTECTED_BRANCH="main"
# Hook pre-push отримує дані про посилання через стандартний ввід у форматі:# <local ref> <local sha1> <remote ref> <remote sha1>while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHAdo # Перевірка, чи цільове віддалене посилання є нашою захищеною гілкою if [[ "$REMOTE_REF" == *"refs/heads/$PROTECTED_BRANCH" ]]; then echo "🛑 КРИТИЧНО: Прямий push у '$PROTECTED_BRANCH' заборонено." echo "Будь ласка, відправте зміни у свою гілку (feature branch) та створіть Pull Request." exit 1 fidone
exit 0Якщо ви неуважно спробуєте виконати git push origin main, цей hook обробить вхідний потік від Git, виявить ціль refs/heads/main і чисто скасує push локально ще до того, як хоч один байт коду залишить вашу машину.
Частина 5: Rerere (Reuse Recorded Resolution) — автоматизація вирішення конфліктів
Розділ «Частина 5: Rerere (Reuse Recorded Resolution) — автоматизація вирішення конфліктів»Конфлікти під час merge — це неминучий і часто болісний факт життя інженера. Ви ретельно їх вирішуєте, фіксуєте результат і рухаєтеся далі. Але що відбувається, якщо ви працюєте над довгостроковою гілкою, яку потрібно постійно оновлювати (rebase) відносно гілки main, що стрімко розвивається?
Щоразу, коли ви виконуєте такий rebase, Git механічно відтворює ваші commit один за одним. Якщо той самий рядок коду був змінений у main, ви можете опинитися в ситуації, коли змушені болісно вирішувати один і той самий складний конфлікт знову і знову для кожного окремого commit у вашій гілці.
Саме тут стає у пригоді rerere (Reuse Recorded Resolution — повторне використання записаного рішення). Це прихована, майже магічна суперсила, вбудована безпосередньо в Git.
Коли цю функцію активовано, Git діє як інтелектуальна губка. Щоразу, коли ви успішно вирішуєте конфлікт merge, Git ретельно записує, як виглядав файл до конфлікту, як саме виглядали маркери конфлікту та як ви вручну їх виправили. Наступного разу, коли Git зустріне ідентичну геометричну структуру конфлікту, він автоматично і непомітно застосує ваше попереднє рішення, не турбуючи вас.
Глобальне ввімкнення Rerere
Розділ «Глобальне ввімкнення Rerere»За замовчуванням Rerere вимкнено у стандартних інсталяціях Git. Вам варто ввімкнути її глобально для всієї системи:
git config --global rerere.enabled trueЯк це працює: візуальна модель
Розділ «Як це працює: візуальна модель»1. Перший конфлікт 2. Rerere записує стан 3. Конфлікт у майбутньому (Main проти Feature) (Preimage проти Postimage) (Rebase Feature на Main)
<<<<<<< HEAD [Git запам'ятовує: <<<<<<< HEADspec.replicas: 3 Сигнатуру конфлікту spec.replicas: 3======= + =======spec.replicas: 5 Фінальний стан] spec.replicas: 5>>>>>>> feature >>>>>>> feature
Ви вручну виправляєте на: Git впізнає сигнатуру!spec.replicas: 5 Автоматично виправляє на: spec.replicas: 5Rerere у дії
Розділ «Rerere у дії»Зімітуємо складний сценарій rebase, щоб побачити магію:
- Створіть файл
app-config.yamlу гілціmain. - Створіть паралельну гілку
feature. - Змініть рядок 1 у
mainі зробіть commit. - Змініть рядок 1 у
featureі зробіть commit. - Спробуйте виконати merge гілки
featureуmain, щоб викликати конфлікт.
# У гілці mainecho 'version: "1.0"' > app-config.yamlgit add app-config.yaml && git commit -m "chore: add initial config"
# Перейти до нової гілки featuregit checkout -b featureecho 'version: "2.0-beta"' > app-config.yamlgit commit -am "feat: update config to beta release"
# Повернутися в main, хтось робить конфліктну змінуgit checkout mainecho 'version: "1.1"' > app-config.yamlgit commit -am "chore: bump version to 1.1"
# Викликати конфліктgit merge featureGit негайно зупиниться з помилкою конфлікту. Але подивіться уважно на вивід, якщо rerere активовано:
Auto-merging app-config.yamlCONFLICT (content): Merge conflict in app-config.yamlRecorded preimage for 'app-config.yaml'Automatic merge failed; fix conflicts and then commit the result.Зверніть увагу на критично важливий рядок: Recorded preimage for 'app-config.yaml'. Git проактивно зробив зріз структури конфлікту.
Тепер виправимо його вручну. Припустимо, ми вирішили, що правильною версією є 2.0-beta.
Ми відкриваємо файл, видаляємо маркери <<<<<<< і зберігаємо файл як version: "2.0-beta".
git add app-config.yamlgit commit -m "Merge feature branch"Вивід у консолі:
Recorded resolution for 'app-config.yaml'.[main 7f3a8b2] Merge feature branchНаступний рядок: Recorded resolution. Git назавжди зберіг результат вашої інтелектуальної праці.
Тепер уявімо, що ми зрозуміли: цей merge був помилковим. Ми скасовуємо його за допомогою hard reset і вирішуємо натомість зробити rebase нашої гілки feature, щоб зберегти лінійну історію.
# Скасувати операцію mergegit reset --hard HEAD~1Зупиніться та подумайте: якби ви зараз виконали
git rerere forget app-config.yaml, що зробив би Git, зустрівши конфлікт знову? Git повністю стер би попередньо збережене рішення зі свого внутрішнього кешу. Коли операція rebase знову дійде до того самого зіткнення файлів, Git зупиниться. Він вставить стандартні маркери конфлікту у файл і змусить вас вирішувати проблему вручну, наче вперше.
# Повернутися до гілки feature та почати rebase на maingit checkout featuregit rebase mainRebase механічно відтворює наші commit. Неминуче він знову наштовхується на той самий конфлікт. Але подивіться на результат:
Auto-merging app-config.yamlCONFLICT (content): Merge conflict in app-config.yamlResolved 'app-config.yaml' using previous resolution.Git автоматично виправив файл за вас! Вам все ще потрібно виконати git add app-config.yaml та git rebase --continue, щоб вручну підтвердити автоматичне рішення, але болісна когнітивна праця з розшифровки маркерів повністю усунена.
Який підхід ви оберете тут і чому? Якщо колега запропонує увімкнути
rerere.autoupdate = true, щоб Git автоматично готував (staged) рішення без вашої згоди, чи варто це робити? Хоча це спокусливо для швидкості, зазвичай безпечніше залишатиautoupdateвимкненим. Вам потрібна хоча б мить, щоб перевіритиgit diffі переконатися, що автоматичне рішення Git є семантично правильним у новому контексті коду, перш ніж сліпо продовжувати rebase. Текстуально ідентичний конфлікт може вимагати іншого рішення залежно від оточуючих змін.
Частина 6: Git аліаси та глобальна конфігурація для складних робочих процесів
Розділ «Частина 6: Git аліаси та глобальна конфігурація для складних робочих процесів»Коли ви перетворюєтеся з новачка на досвідченого користувача Git, введення довгих складних команд стає марудним. Git дозволяє створювати аліаси (aliases) — потужні скорочення для складних ланцюжків команд — безпосередньо у вашому глобальному файлі .gitconfig.
Відкрийте редактор глобальної конфігурації:
git config --global --editОсь кілька рекомендованих, перевірених часом аліасів, спеціально підібраних для робочих процесів DevOps/SRE:
[alias] # Найкращий графік логів. Красиво відображає гілки, теги та хеші commit у терміналі. lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
# М'яке скасування останнього commit, видалення commit із збереженням змін у staging. # Ідеально, коли ви забули додати файл або зробили друкарську помилку в повідомленні. undo = reset --soft HEAD~1
# Жорстке очищення робочої директорії до абсолютно чистого стану. # УВАГА: Безповоротно знищує всі незафіксовані зміни. nuke = !git clean -fd && git reset --hard && git checkout .
# Швидка перевірка, які гілки зараз містять певний хеш commit contains = branch --contains
# Відправити поточну гілку та автоматично налаштувати відстеження (upstream) pub = push -u origin HEADЗ цими аліасами замість введення git log --color --graph ... ви просто пишете git lg.
Механіки розширеної глобальної конфігурації
Розділ «Механіки розширеної глобальної конфігурації»Крім простих аліасів, ваша глобальна конфігурація може фундаментально змінити поведінку за замовчуванням для автоматизації процесів:
git config --global pull.rebase true: за замовчуваннямgit pullвиконуєfetch, а потімmerge, що часто призводить до появи некрасивих commit-повідомлень типу “Merge branch ‘main’ of…”. Встановлення цього параметра уtrueзмушуєgit pullавтоматично виконуватиrebase, зберігаючи вашу локальну історію чистою та лінійною.git config --global push.default current: коли ви ліниво вводитеgit pushбез уточнення імені гілки, Git автоматично відправить вашу поточну гілку в гілку з такою ж назвою у віддаленому репозиторії.
Частина 7: Шаблонні директорії: стандартизація середовищ команд
Розділ «Частина 7: Шаблонні директорії: стандартизація середовищ команд»Ми успішно розробили чудові клієнтські hooks, але тепер стикаємося з серйозною архітектурною проблемою: hooks фундаментально не відстежуються системою контролю версій Git. Вони живуть ізольовано в директорії .git/hooks, яка за дизайном ігнорується. Коли ваш колега клонує репозиторій на свою машину, він абсолютно не отримує hooks, які ви так старанно писали.
Як стандартизувати ці hooks в усій інженерній організації?
В індустрії існує два основні методи:
- Метод фреймворків-обгорток (на рівні репозиторію): це сучасний стандарт для спільних проєктів. Ви використовуєте інструменти типу Husky (для Node.js) або фреймворк pre-commit (
pre-commit.com). Ці інструменти зберігають файл конфігурації (наприклад,.pre-commit-config.yaml) безпосередньо в репозиторії. Розробники запускають команду встановлення один раз, і фреймворк автоматично перехоплює шлях виконання Git hook, керуючи linters в ізольованих середовищах. - Метод шаблонних директорій Git (на рівні машини): це нативна функція Git, яка математично гарантує, що кожен репозиторій, який ви особисто створюєте або клонуєте на своїй локальній машині, автоматично успадковує стандартний набір конфігурацій та hooks.
Дослідімо нативну функцію шаблонних директорій (Template Directories).
Коли ви виконуєте git init або git clone, Git створює директорію .git, копіюючи файли з головної шаблонної директорії вашої системи. Ви можете вказати власну шаблонну директорію, щоб автоматично впроваджувати ваші стандарти у кожен новий проєкт.
Крок 1: Створення структури шаблонної директорії
# Створити централізоване місце у вашій домашній директоріїmkdir -p ~/.git-templates/hooksКрок 2: Додавання стандартних hooks
Скопіюйте bash-скрипти pre-commit та commit-msg, які ми створили раніше, безпосередньо в ~/.git-templates/hooks/ і переконайтеся, що вони мають права на виконання (chmod +x).
Крок 3: Налаштування Git на використання цієї директорії Налаштуйте глобальну конфігурацію:
git config --global init.templatedir '~/.git-templates'Тепер, щоразу під час git clone <url> або git init, Git мовчки та автоматично копіюватиме абсолютно все з ~/.git-templates/ безпосередньо в папку .git/ нового репозиторію. Ваші автоматичні «вишибали» тепер постійно стоять на варті кожного коду, яким ви керуєте.
Чи знали ви?
Розділ «Чи знали ви?»- Hooks можна легко обійти: будь-який клієнтський hook можна повністю ігнорувати, додавши прапорець
--no-verify(або-n) до команди Git. Наприклад,git commit -m "термінове виправлення" -n. Саме тому логіка безпеки має врешті-решт забезпечуватися у централізованих конвеєрах CI, а не лише на локальних машинах розробників. - Rerere зберігає дані майже вічно: за замовчуванням успішні вирішення конфліктів, записані підсистемою
rerere, зберігаються в кеші протягом 60 днів, а нерозв’язані закинуті конфлікти — 15 днів, після чого внутрішній механізм збирання сміття Git видаляє їх. Ви можете змінити ці пороги за допомогоюgc.rerereresolvedтаgc.rerereunresolved. - Шаблонні директорії замінюють усе: коли ви вказуєте власну шаблонну директорію у глобальній конфігурації, Git не об’єднує її з системною директорією за замовчуванням. Ваша директорія повністю замінює стандартну під час ініціалізації.
- Hooks не знають мовних бар’єрів: Git hook — це просто виконуваний файл. Ви можете написати свій складний hook на Python (
#!/usr/bin/env python3), використовувати Node.js або навіть скомпілювати високоефективний бінарний файл на Rust, покласти його в.git/hooks/pre-commit, і Git справно його виконає.
Типові помилки
Розділ «Типові помилки»| Помилка | Чому це стається | Як виправити |
|---|---|---|
| Hooks не запускаються | У файлі скрипта відсутні права на виконання. Git мовчки ігнорує hooks, які не позначені як виконувані операційною системою. | Запустіть chmod +x .git/hooks/<hook-name> відразу після створення файлу. |
| Лінтинг робочого дерева замість індексу | Hook pre-commit запускає linter безпосередньо для файлу на диску, перевіряючи непідготовлені експериментальні зміни замість точного зрізу для commit. | Використовуйте git show ":$FILE", щоб витягти вміст підготовленого об’єкта з індексу та передати його безпосередньо у linter. |
Очікування, що hooks синхронізуються через git pull | Директорія .git ніколи не відстежується системою Git. Нові розробники після клонування не отримають ваші власні hooks автоматично. | Зберігайте hooks у папці, що відстежується (наприклад, scripts/githooks), та використовуйте фреймворк (pre-commit або Husky) для їх встановлення. |
| Rerere сліпо застосовує зламані виправлення | Конфлікт був вирішений невдало втомленим розробником, і це хибне рішення було записане та автоматично застосоване під час наступних rebase. | Використовуйте git rerere forget <file>, щоб стерти записане рішення для цього файлу і змусити Git знову вимагати ручного вирішення. |
Запуск важких тестів у pre-commit | Запуск тривалих інтеграційних тестів у pre-commit сповільнює роботу розробника, що змушує його постійно використовувати --no-verify. | Перенесіть важкі тести у hook pre-push або, що краще, у CI/CD. Тримайте час виконання pre-commit у межах 3 секунд. |
| Забування про повернення кодів помилок | Скрипт виявляє помилку валідації, але завершується за замовчуванням зі статусом 0 (успіх), дозволяючи зламаному commit пройти далі. | Переконайтеся, що ваш скрипт явно викликає exit 1 у разі виявлення будь-якої внутрішньої помилки валідації. |
Контрольні запитання
Розділ «Контрольні запитання»Запитання 1: Ви написали чудовий hook `pre-commit` на Python для перевірки маніфестів Kubernetes. Ви поклали файл `pre-commit.py` у `.git/hooks/` і надали йому права на виконання. Проте під час `git commit` hook не запускається. У чому проблема?
Виконання Git hooks базується на зумовлених іменах файлів без розширень. Оскільки внутрішня система Git налаштована на виконання файлу з точною назвою `pre-commit`, вона ігнорує `pre-commit.py`. Щоб виправити це, потрібно видалити розширення `.py`. Операційна система все одно зрозуміє, що це скрипт Python, завдяки рядку shebang `#!/usr/bin/env python3` на початку файлу.Запитання 2: Команда використовує hook `pre-push` для запуску тестів. Ви знайдете критичну помилку на продуктиві й підготували виправлення локально. Проте тести в гілці main зараз падають через інший нестабільний тест. Вам потрібно негайно відправити виправлення. Як це зробити, не чекаючи тестів?
Ви можете обійти виконання будь-якого клієнтського hook, додавши прапорець `--no-verify` (або `-n`) до вашої команди. Запустивши `git push origin HEAD --no-verify`, ви наказуєте Git пропустити скрипт `pre-push`. Клієнтські hooks створені для зручності та швидкого фідбеку, а не як непроникний захист. В екстрених ситуаціях у інженерів має бути можливість обійти локальні перевірки.Запитання 3: Ви хочете, щоб кожен новий інженер, який клонує репозиторій, автоматично отримував hook `commit-msg`. Чому додавання файлу hook у папку `.git/hooks` і відправлення його у віддалений репозиторій не спрацює?
Структура директорії `.git/`, включаючи підпапку `hooks/`, є суворо локальною для вашої машини й не передається в систему контролю версій. Коли інший інженер виконує `git clone`, Git створює нову, стандартну директорію `.git` на його робочій станції. Ваші локальні hooks не будуть завантажені. Щоб вирішити це, потрібно використовувати фреймворки на кшталт `pre-commit` або налаштувати шаблонні директорії (Git Template Directories) для всієї команди.Запитання 4: Ваш hook `pre-commit` містить команду: `yamllint deployment.yaml`. Розробник готує валідний файл, але перед самим commit випадково робить друкарську помилку у файлі, не додаючи її до staging. Commit відхиляється. Чому так сталося і як переписати hook?
Hook помилково перевірив файл у робочій директорії на диску, ігноруючи чисту версію, яка вже була зафіксована в індексі (staging area). Коли розробник ввів помилку, він змінив робоче дерево, яке і прочитав hook. Hook має бути переписаний так, щоб перевіряти вміст підготовленого об'єкта. Це зазвичай робиться через команду `git show ":deployment.yaml" | yamllint -`, щоб переконатися, що перевіряється саме те, що Git збирається зберегти.Запитання 5: Ви робите rebase довгої гілки на `main`. Ви стикаєтеся зі складним конфліктом у `deployment.yaml`, виправляєте його вручну і продовжуєте. Через три commit rebase знову зупиняється з тим самим конфліктом у тому самому файлі. Яка функція Git могла б запобігти цій повторній роботі?
Ця функція — `rerere` (Reuse Recorded Resolution). Якби ви ввімкнули її командою `git config --global rerere.enabled true` до початку rebase, Git записав би структуру конфлікту та ваше рішення під час першої зустрічі. Коли rebase дійшов би до ідентичного конфлікту в наступних commit, підсистема `rerere` розпізнала б його і автоматично застосувала б ваше попереднє виправлення.Запитання 6: Ви ввімкнули `rerere.autoupdate=true`. Під час rebase Git автоматично вирішує конфлікт і відразу додає файл до staging. Пізніше ви бачите, що програма не компілюється, бо автоматичне рішення видалило потрібний import. У чому небезпека `autoupdate`?
Параметр `autoupdate` наказує `rerere` не тільки вирішити конфлікт, а й автоматично виконати `git add`, сліпо вважаючи рішення правильним. Це пропускає критичний крок перевірки людиною через `git diff`. Контекст має величезне значення, і той самий текстуальний конфлікт може вимагати іншого семантичного рішення залежно від нових змін навколо нього. Автоматичне додавання позбавляє вас шансу помітити такі помилки логіки.Практична вправа
Розділ «Практична вправа»У цій вправі ви налаштуєте локальне середовище Git, створите шаблонну директорію для глобальної стандартизації hooks і напишете потужний hook pre-commit, який діятиме як безкомпромісний фільтр якості для маніфестів Kubernetes.
Етап підготовки
Розділ «Етап підготовки»- Створіть робочу директорію для вправи:
Terminal window mkdir -p ~/git-hooks-labcd ~/git-hooks-lab
Операційні завдання
Розділ «Операційні завдання»Завдання 1: Створення глобальної шаблонної директорії
Розділ «Завдання 1: Створення глобальної шаблонної директорії»Щоб гарантувати, що всі майбутні репозиторії на вашій машині автоматично отримуватимуть ваші стандарти, налаштуйте шаблонну директорію.
- Створіть структуру папок
~/.git-templates/hooks. - Налаштуйте Git глобально на використання цієї директорії під час ініціалізації.
Рішення: Завдання 1
```bash mkdir -p ~/.git-templates/hooks git config --global init.templatedir '~/.git-templates' ```Завдання 2: Створення глобального «вишибали» pre-commit
Розділ «Завдання 2: Створення глобального «вишибали» pre-commit»Створіть виконуваний Bash-скрипт у вашій шаблонній директорії, який буде hook pre-commit.
Hook має надійно виконувати дві речі:
- Забороняти commit будь-якого файлу, розмір якого перевищує рівно 1 мегабайт (щоб запобігти випадковому додаванню важких бінарних файлів або дампів баз даних).
- Сканувати всі підготовлені файли на наявність захардкодженого рядка
AWS_SECRET_ACCESS_KEY.
Рішення: Завдання 2
Створіть файл та надайте права: ```bash touch ~/.git-templates/hooks/pre-commit chmod +x ~/.git-templates/hooks/pre-commit ```Додайте таку логіку до ~/.git-templates/hooks/pre-commit:
#!/bin/bash
# Витягти підготовлені файлиSTAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)if [ -z "$STAGED_FILES" ]; then exit 0; fi
ERROR=0
for FILE in $STAGED_FILES; do # 1. Перевірка розміру файлу. Використовуємо wc -c для підрахунку байтів STAGED об'єкта. SIZE=$(git show ":$FILE" | wc -c) if [ "$SIZE" -gt 1048576 ]; then echo "❌ КРИТИЧНО: Файл $FILE завеликий. Він перевищує 1MB ($SIZE байтів)." ERROR=1 fi
# 2. Перевірка на секрети if git show ":$FILE" | grep -q "AWS_SECRET_ACCESS_KEY"; then echo "❌ КРИТИЧНО: Виявлено відкритий секрет AWS у файлі $FILE" ERROR=1 fidone
if [ $ERROR -ne 0 ]; then echo "🛑 COMMIT СКАСОВАНО: Перевірки якості не пройдені." exit 1fi
echo "✅ Валідація pre-commit пройдена."exit 0Завдання 3: Ініціалізація, запуск та тестування
Розділ «Завдання 3: Ініціалізація, запуск та тестування»Створіть новий репозиторій, перевірте роботу шаблону та протестуйте hooks на різних сценаріях помилок.
- Ініціалізуйте новий репозиторій
k8s-manifests-repo. - Переконайтеся, що файл
.git/hooks/pre-commitбув автоматично скопійований. - Спробуйте зробити commit файлу з рядком
export AWS_SECRET_ACCESS_KEY="xyz123". Переконайтеся, що його відхилено. - Спробуйте зробити commit файлу розміром понад 1MB (підказка: використайте
dd if=/dev/urandom of=massive_binary.bin bs=1M count=2). Переконайтеся, що його відхилено. - Створіть чистий YAML, підготуйте його та переконайтеся, що commit проходить успішно.
Рішення: Завдання 3
Крок 1 та 2: Ініціалізація та перевірка
cd ~/git-hooks-labgit init k8s-manifests-repocd k8s-manifests-repocat .git/hooks/pre-commit # Ви маєте побачити ваш скрипт!Крок 3: Тест на секрети
echo 'export AWS_SECRET_ACCESS_KEY="xyz123"' > aws-credentials.shgit add aws-credentials.shgit commit -m "chore: add local aws credentials script"# Очікуваний результат: ❌ КРИТИЧНО: Виявлено відкритий секрет AWS...# А потім: 🛑 COMMIT СКАСОВАНО: Перевірки якості не пройдені.Крок 4: Тест на розмір файлу
dd if=/dev/urandom of=massive_binary.bin bs=1M count=2git add massive_binary.bingit commit -m "chore: add massive database dump binary"# Очікуваний результат: ❌ КРИТИЧНО: Файл massive_binary.bin завеликий...Крок 5: Тест успішного commit
echo "apiVersion: v1" > clean-deployment.yamlgit add clean-deployment.yamlgit commit -m "feat: add initial clean deployment manifest"# Очікуваний результат: ✅ Валідація pre-commit пройдена.Завдання 4: Додаткове завдання — контроль номерів тікетів через commit-msg
Розділ «Завдання 4: Додаткове завдання — контроль номерів тікетів через commit-msg»Додайте до шаблонної директорії hook commit-msg, який забезпечує простежуваність. Hook має перевіряти, чи повідомлення починається з ID тікета Jira (наприклад, PROJ-1234: або [KUBE-99] ). Якщо ID відсутній, відхиляти commit.
Рішення: Завдання 4
Створіть файл та надайте права: ```bash touch ~/.git-templates/hooks/commit-msg chmod +x ~/.git-templates/hooks/commit-msg ```Додайте логіку до ~/.git-templates/hooks/commit-msg:
#!/bin/bashMESSAGE_FILE=$1MESSAGE=$(cat "$MESSAGE_FILE")
# Перевірка, чи повідомлення починається з великих літер проєкту та номераif ! echo "$MESSAGE" | grep -qE "^\[?[A-Z]+-[0-9]+\]?:? "; then echo "🛑 COMMIT ВІДХИЛЕНО: Відсутній ID тікета Jira." echo "Повідомлення має починатися з ID (наприклад, 'PROJ-123: Текст повідомлення')." exit 1fiexit 0Тестування у репозиторії:
git commit -m "update readme" --allow-empty# Результат: 🛑 COMMIT ВІДХИЛЕНО: Відсутній ID тікета Jira.
git commit -m "KUBE-42: update readme" --allow-empty# Commit успішний.Критерії успіху
Розділ «Критерії успіху»- Ви налаштували глобальний параметр
init.templatedirу вашому.gitconfig. - Нові репозиторії автоматично успадковують виконуваний скрипт
pre-commitпісля ініціалізації. - Hook
pre-commitуспішно відхиляє commit, що містять рядокAWS_SECRET_ACCESS_KEY. - Hook
pre-commitуспішно відхиляє файли, розмір яких перевищує 1MB. - Валідні файли відповідного розміру без секретів успішно зберігаються в історії.
- (Додатково) Hook
commit-msgуспішно відхиляє повідомлення без валідного ID тікета Jira.
Наступний модуль
Розділ «Наступний модуль»Модуль 10: Міст до GitOps — перехід від ручних операцій Git до автоматизованого, повністю декларативного керування станом кластера Kubernetes за допомогою ArgoCD та Flux.