vyvchy
    Теми розділу

    09 · Git і CI/CD

    Pull Request і стратегії гілкування

    Зміст

    Pull Request (PR) — це точка, де зміна проходить рев'ю людей і автоматичні перевірки перед тим, як потрапити у спільну . Сам Git такого поняття не має: він уміє лише зливати гілки локально. PR — це надбудова платформи (GitHub, GitLab, Bitbucket), яка обгортає звичайне злиття в процес: дифф, обговорення, апрув, статуси CI і правила, за яких кнопка «злити» взагалі стає доступною.

    Для AQA це тема з подвійним дном. По-перше, ваші власні автотести їдуть у репозиторій рівно тим самим шляхом — через PR і рев'ю. По-друге, QA часто сам рев'ювер продуктових PR (тестовий погляд на зміну) і власник gate, який блокує злиття, поки не зелений smoke. А стратегія визначає, скільки гілок живе, як довго і як швидко зміни доходять до продакшену — і саме звідси ростуть й «інтеграційне пекло». Стратегію гілкування й спосіб злиття тримають у голові разом: перша визначає, скільки розбіжностей накопичиться, другий — який слід вони лишать в історії.

    Pull Request: чому його немає в Git

    Git знає команди merge і rebase, але не знає, що таке «запит на злиття з обговоренням». PR — це сутність платформи, яка каже: «ось гілка feature/login, я хочу влити її в main; подивіться, обговоріть, схваліть». На GitHub і Bitbucket це Pull Request, у GitLab — Merge Request (MR). Це те саме, просто GitLab-івська назва точніша: ми справді просимо злити (merge), а не підтягнути (pull). Термін «pull» історичний — колись контриб'ютор просив мейнтейнера відкритого проєкту «підтягнути» його зміни до себе.

    У PR завжди дві гілки: базова (base) — куди зливаємо (частіше main), і head-гілка — звідки беремо зміни. Платформа рахує дифф між ними, показує обговорення, прив'язує коментарі до конкретних рядків і збирає статуси перевірок CI. Механіка гілок і тристороннього злиття — у главі Гілки та злиття.

    Життєвий цикл PR і draft

    PR проходить кілька станів, і плутанина в них — типова причина, чому «готова» зміна тижнями висить .

    відкрито як чернетку

    позначено Ready for review

    рецензент просить правки

    нові коміти і повторне рев'ю

    апрув плюс зелені перевірки

    злито в базову гілку

    закрито без злиття

    Draft

    Open

    ChangesRequested

    Approved

    Merged

    Closed

    відкрито як чернетку

    позначено Ready for review

    рецензент просить правки

    нові коміти і повторне рев'ю

    апрув плюс зелені перевірки

    злито в базову гілку

    закрито без злиття

    Draft

    Open

    ChangesRequested

    Approved

    Merged

    Closed

    Ключова деталь — draft-режим (чернетка). каже команді: «ще не готово, не рев'юй усерйоз і не мерджи». GitHub фізично блокує кнопку злиття для draft; GitLab додає префікс Draft: до заголовка і теж не дає злити. Навіщо відкривати PR раніше готовності:

    • рання видимість — команда бачить, що фіча в роботі, і не робить те саме;
    • запустити CI на неготовому коді, щоб раніше зловити падіння;
    • обговорити напрям рішення, поки не пізно переробляти;
    • не смикати рев'юерів завчасним запитом на апрув.

    Важливо: кожен push у head-гілку оновлює PR — переобчислюється дифф і перезапускаються перевірки CI. Тобто PR не «фотографія», а живе відображення гілки. Виправив зауваження рев'юера, запушив — той самий PR оновився, статуси пішли по новій.

    Code review автотестів

    Рев'ю тестового коду — той самий стандарт, що й продуктового: читабельність, відсутність дублювання, осмислені імена. Але в автотестів є свій список того, на що рев'юер дивиться першим:

    • Стабільні . data-testid і ролі, а не крихкі CSS/XPath, прив'язані до верстки чи згенерованих класів (стратегія локаторів — канон розділу «Автоматизація»).
    • Жодних hard wait. page.waitForTimeout(3000) — червоний прапор: тест або флакітиме, або марно гальмуватиме. Механіку правильних очікувань розібрано у web-главі Практичні сценарії AQA: флак і синхронізація.
    • Незалежність і атомарність. Тест не залежить від порядку запуску й перевіряє один інваріант (Arrange–Act–Assert).
    • Без секретів у коді. Паролі й токени тестових акаунтів — у секретах CI, а не в репозиторії (див. Секрети та конфігурація в CI).
    • перевіряє інваріант, а не факт «сторінка відкрилась». expect(page).toHaveURL(...) без перевірки вмісту — часто тест.
    • Назва описує перевірку, а не дію; жодних закоментованих чи безпричинно skip-нутих тестів.

    Класичний приклад того, що рев'ю має завернути:

    // погано: так рев'юер завертає
    await page.click('#submit');
    await page.waitForTimeout(2000);
    expect(await page.locator('.toast').count()).toBe(1);
    
    // добре: web-first assertion сам чекає появи, без магічних затримок
    await page.getByRole('button', { name: 'Submit' }).click();
    await expect(page.getByTestId('toast')).toBeVisible();

    Окреме правило: нові тести мають бути реально зеленими в CI, а не лише «локально в мене працює». Тому branch protection часто вимагає, щоб job з тестами був зеленим перед злиттям — про це нижче.

    Стратегії гілкування

    Стратегія (branching strategy) — це домовленість про те, які гілки існують, скільки живуть і як зливаються. Трьох моделей вистачить, щоб орієнтуватися.

    GitHub Flow

    Найпростіша. Одна вічнозелена гілка main, яка завжди придатна до . Будь-яка зміна — це коротко-живуча feature-гілка від main, PR назад у main, деплой з main. Жодних develop, release, hotfix. Модель ідеально лягає на continuous delivery: злив — задеплоїв.

    Git Flow

    Модель Вінсента Дрізена (Vincent Driessen, 2010) з п'ятьма типами гілок: main (реліз), develop (інтеграція), feature/*, release/*, hotfix/*.

    maindevelopfeaturerelease0-874a2351-5753c712-a33ff383-a8a6d5c5-1c4e767v1.0maindevelopfeaturerelease0-db5949f1-35ddff82-6bd567d3-db80c145-240023bv1.0

    Модель потужна для продуктів із явними версіонованими релізами — коробкового ПЗ, застосунків із довгим циклом. Але для веба, який деплоять по кілька разів на день, вона важка: довгоживучі гілки develop і feature/* розходяться і породжують merge-конфлікти («merge hell»), а release-гілки додають церемоній. Показово, що сам автор у 2020 році додав до статті примітку: git-flow створювався для епохи явних релізів, і командам на безперервній доставці він радить простіший процес — прямо називає GitHub Flow. Саме тому Git Flow і «відходить» — не тому що поганий, а тому що для багатьох команд змінилася модель доставки.

    Trunk-based development

    Усі працюють з однією гілкою-стовбуром (trunk, зазвичай main), а гілки живуть години, максимум день. Кожен інтегрується у trunk щонайменше раз на день. Це не про хаос — це про дисципліну частої інтеграції: чим менша й свіжіша гілка, тим менший конфлікт при злитті. Замість довгих feature-гілок незавершені фічі ховають за (feature flags), тож недороблений код мирно лежить у main вимкненим.

    Trunk-based нерозривно пов'язаний із CI: часта інтеграція має сенс лише тоді, коли кожен PR за хвилини проганяється автоматичними перевірками (див. CI/CD: інтеграція, доставка, розгортання). Для AQA звідси два наслідки: перевірки на PR мають бути швидкими (інакше вони гальмують усю команду), а фіча-флаги треба тестувати в обох станах — і ввімкненому, і вимкненому.

    Branch protection

    Branch protection () — це правила платформи на базову гілку, які не дають злити «як попало». Типовий набір на main:

    • вимагати PR і щонайменше N апрувів (жодних прямих push у main);
    • вимагати зелені required status checks — конкретні job'и CI мають пройти;
    • вимагати, щоб гілка була оновлена відносно бази (up-to-date) перед злиттям;
    • вимагати лінійну історію (заборонити );
    • заборонити у захищену гілку;
    • поширювати правила навіть на адміністраторів.

    Найважливіший для QA пункт — required status checks: це прямий місток до CI. Поки job з автотестами червоний, кнопка злиття заблокована платформою. Так стає не «домовленістю», а технічним бар'єром (розкладку перевірок і що саме блокує merge розібрано в главі Стратегія якості в пайплайні).

    Зворотний бік: якщо required-перевіркою зробити нестабільний e2e-набір, один блокує злиття всій команді. Тому політика довкола (, , що взагалі ставити в блокувальні перевірки) — окрема тема канону автоматизації.

    Хто саме має рев'ювити, автоматизує файл CODEOWNERS: він прив'язує ділянки коду до власників, і платформа сама запитує в них рев'ю.

    # CODEOWNERS: власники автоматично додаються в рев'юери
    /tests/e2e/          @qa-team
    /src/payments/       @payments-team @qa-team
    *.spec.ts            @qa-team
    

    Способи злиття: merge, squash, rebase

    Коли PR схвалено, платформа пропонує кілька способів «покласти» зміни в базову гілку. На GitHub це три різні кнопки, і різниця — у тому, який слід лишиться в історії.

    СпосібЩо робить з історієюКоли доречно
    Merge commit (--no-ff)Зберігає всі коміти PR плюс окремий merge-коміт із двома батькамиРелізні гілки, коли важлива повна історія інтеграції
    Squash and mergeСтискає всі коміти PR в один новий коміт на базіFeature-гілки: один PR = один коміт, чиста лінійна історія
    Rebase and mergeПереграє кожен коміт PR на базу без merge-комітуКоли потрібна лінійна історія, але окремі коміти варто зберегти

    Merge commit нічого не втрачає, але залишає в історії «діамант» з двома гілками — на активному репозиторії граф стає шумним. Squash дає найчистішу історію (один на PR, легко читати git log), але «розчиняє» проміжні коміти — і разом з ними гранулярність для bisect (див. главу про bisect і blame). and merge дає лінійну історію без merge-комітів, але переписує коміти на нові SHA (нові хеші) — механіку й цього розібрано в главі Rebase, cherry-pick і переписування історії, включно із золотим правилом спільних гілок і --force-with-lease.

    Практичний дефолт багатьох команд: squash для feature-гілок (кожен PR — атомарний коміт), merge-коміт для злиття релізних гілок.

    Тести в репо продукту чи окремо

    Питання, де живуть автотести, — архітектурне, і відповідь на нього визначає, чи їдуть тести в одному PR зі змінами продукту.

    Разом із продуктомОкремий репозиторій
    ВерсіонуванняТести і код завжди синхронніРизик версійного дрейфу
    Зміна фічі + тестуОдин атомарний PRДва PR у двох репо
    CIТой самий пайплайнВласний, треба тригерити
    ВолодінняРозробники бачать червоне одразуЧиста межа, володіє QA
    Крос-сервісні e2eНезручноПриродно

    Разом із кодом тести версіонуються з продуктом: PR, який ламає поведінку, у тому ж PR оновлює й тест, а CI проганяє їх на одному коміті. Це і є shift-left — розробник бачить червоне негайно. Мінуси: важкі e2e роздувають репо продукту, можуть бути іншою мовою/тулінгом, і постає питання, хто їх підтримує.

    Окремий репозиторій дає власний темп релізів, чисту межу відповідальності й зручність для e2e, що ходять по кількох сервісах одразу. Головна ціна — версійний дрейф: проти якої версії продукту ганяються тести? Легко отримати ситуацію, коли зелений прогін означає лише, що тести відповідають вчорашньому продукту. Плюс крос-репо тригери й неможливість атомарно оновити код і тест в одному PR.

    Загальний орієнтир: unit/integration/component-тести майже завжди живуть із кодом продукту; окремий репозиторій виправданий для крос-сервісних e2e й контрактних тестів (стратегію для мікросервісів і розібрано в розділах «Автоматизація» та «API-тестування»).

    Типові помилки

    • Виглядає як «PR готовий», а насправді він забутий у стані draft — рев'юери його навіть не бачать у своїй черзі, і зміна тижнями висить.
    • Виглядає як «зелений PR злився штатно», а насправді required status checks не налаштовані, і merge пройшов із червоним CI — захист гілки був лише на словах.
    • Виглядає як «squash усе — чиста історія», а насправді ви втратили гранулярність для bisect: коли тест упаде за місяць, винним буде один жирний коміт на весь PR замість точного.
    • Виглядає як «тести в окремому репо незалежні й це добре», а насправді вони ганяються проти застарілої версії продукту — версійний дрейф робить зелений прогін безглуздим.
    • Виглядає як «rebase and merge безпечний», а насправді він переписує SHA; якщо хтось відгалузився від вашої гілки до злиття, ви створили йому розбіжну історію.
    • Виглядає як «підправлю гілку force-push прямо в PR», а насправді ви можете «відв'язати» коментарі рев'юера від старих комітів і збити CI — під час активного рев'ю форс-пуш робіть обережно.

    Підсумок

    • PR/MR — це надбудова платформи над Git-злиттям: дифф, рев'ю, статуси CI і правила, за яких злиття взагалі дозволене; draft явно каже «ще не рев'юй».
    • Стратегія гілкування = час життя гілок: Git Flow (довгі гілки, версійні релізи) відходить на користь GitHub Flow і trunk-based, бо веб деплоять безперервно.
    • Trunk-based має сенс лише разом зі швидким CI і фіча-флагами — часта інтеграція гасить merge-конфлікти в зародку.
    • Required status checks у branch protection перетворюють гейт якості з домовленості на технічний бар'єр — і роблять -тест проблемою всієї команди.
    • Спосіб злиття — це вибір форми історії: squash (чисто, але без гранулярності), merge-коміт (повно, але шумно), rebase (лінійно, але нові SHA).

    Можливі питання

    • «Чим відрізняється Pull Request від merge-команди Git?» — інтерв'юер перевіряє, чи ви розумієте, що PR не з Git, а з платформи, і додає процес поверх звичайного злиття.
    • «Порівняйте Git Flow, GitHub Flow і trunk-based. Що обрали б для веб-застосунку на щоденних деплоях?» — дивиться на зв'язок стратегії з моделлю доставки й на аргумент про довжину життя гілок і merge-конфлікти.
    • «Merge commit, squash чи rebase — що і коли?» — хоче почути не «улюблене», а тредоф: чистота історії проти повноти й гранулярності bisect.
    • «Що перевіряєте на code review автотесту?» — очікує конкретику: локатори, відсутність hard wait, незалежність, секрети, осмислені асерти, а не «щоб працювало».
    • «Як зробити, щоб не можна було злити PR із червоними тестами?» — перевіряє знання branch protection і required status checks як містка до CI.
    • «Тести тримати в репо продукту чи окремо?» — сильна відповідь називає версійний дрейф як головний ризик окремого репо і shift-left як плюс спільного.

    Джерела

    Pull Request: чому його немає в Git

    • GitHub Docs — About pull requests — PR як пропозиція злити зміни, призначена для обговорення й рецензії до злиття; технічно платформа заводить тимчасові Git-рефи на head-гілку й симульований результат злиття.
    • GitLab Docs — Merge requests — MR як центральне місце рецензії, обговорень і відслідковування змін; той самий предмет під іншою назвою.

    Життєвий цикл PR і draft

    • GitHub Docs — About pull requests — draft-PR не можна злити, і code owners автоматично не запрошуються; вкладка Checks — місце статусів CI.
    • GitLab Docs — Merge requests — аргумент за ранній MR названий прямо: у команди зʼявляється час зловити баги й проблеми якості коду.
    • GitHub Docs — About protected branches — новий push у гілку PR скидає апрув як застарілий і перезапускає вимоги, тобто PR є живим відображенням гілки.

    Code review автотестів

    • Playwright — Locators — рекомендація пріоритезувати user-facing атрибути й роль; про CSS і XPath сказано прямо: not recommended, бо DOM часто змінюється.
    • Cypress — Best Practices — власне правило про виділені тест-атрибути замість селекторів, привʼязаних до верстки.
    • Playwright — Best Practices — ізоляція тестів покращує відтворюваність, спрощує дебаг і запобігає каскадним падінням.
    • The Twelve-Factor App — III. Config — облікові дані до зовнішніх сервісів належать оточенню, а не коду в репозиторії.
    • GitHub Docs — About protected branches — «зелений у CI» стає вимогою через required status checks, а не через домовленість.

    Стратегії гілкування

    • Vincent Driessen — A successful Git branching model — дві головні гілки з безкінечним життям плюс допоміжні, і нота автора від 5 березня 2020: для continuous delivery він радить простіший (як GitHub flow).
    • Trunk Based Development — одна гілка й опір довгоживучим; вимога «комітити в trunk щонайменше раз на 24 години», короткоживучі гілки лише під рецензію й перевірку .
    • Martin Fowler — Continuous Integration — межа кількісна: «no code sits on a branch longer than a few hours without being pushed into the mainline».
    • Feature Toggles (Pete Hodgson, martinfowler.com) — фіча-флаги як спосіб тримати незавершену роботу в основній гілці вимкненою.

    Branch protection

    • GitHub Docs — About protected branches — склад правила: апруви, required status checks (стан successful, skipped, or neutral — тобто skipped теж проходить), strict проти loose, лінійна історія, заборона force push і опційне поширення на адміністраторів.
    • GitHub Docs — About merge methods — з вибір методу злиття зникає: його контролює черга.

    Способи злиття: merge, squash, rebase

    • GitHub Docs — About merge methods — merge-коміт робиться опцією --no-ff; squash зливає -ом і коштує авторства, повторних конфліктів і втрачених SHA; rebase-and-merge завжди оновлює комітера й створює нові SHA.
    • GitHub Docs — About protected branches — вимога лінійної історії забороняє merge-коміти, тож PR у таку гілку мусить зливатися squash-ом або rebase-ом.
    • Vincent Driessen — A successful Git branching model — аргумент за --no-ff із першоджерела git-flow: без нього з історії неможливо побачити, які коміти разом реалізували фічу.

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.