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

    09 · Git і CI/CD

    Базовий цикл Git: clone, commit, push, pull

    Зміст

    Базовий цикл Git — це та петля, яку інженер прокручує десятки разів на день, майже не думаючи: узяв код, змінив, зафіксував, віддав, підтягнув чуже. Для AQA це не «навички розробника десь поруч», а щоденний інструмент: автотести живуть у git-репозиторії, правки їдуть у CI через push, а свіжий стан гілки приходить через pull. Не володіти цим циклом на автоматі — те саме, що тестувальнику не вміти відкрити DevTools.

    На співбесіді рівня trainee цей цикл питають майже завжди, і провалюються на ньому не через складність, а через плутанину: людина «закомітила», а на GitHub нічого немає; зробила git add і думає, що вже все у коміті; натиснула pull і зламала собі гілку чужими змінами. Усі ці ситуації — не про команди, а про модель: що де лежить і що куди рухається. Тому спершу розберемо механізм, а вже потім — команди. Ця глава спирається на модель трьох зон і поняття «знімок замість діфа» з глави Системи контролю версій і модель Git; якщо ці слова ще нічого не значать — почни звідти.

    Дві точки входу: clone і init

    У цикл є рівно два способи потрапити. git init перетворює звичайну теку на репозиторій: створює приховану підтеку .git, куди Git складатиме всю історію. До цього моменту теки для Git не існувало; після — вона стала робочою директорією репозиторію, але поки що порожнього, без жодного коміту і без звʼязку з будь-яким сервером.

    git clone <url> розвʼязує іншу задачу — коли репозиторій уже існує на сервері (GitHub, GitLab, внутрішній Git-хостинг). Clone качає повну історію, розгортає останній стан у робочу директорію і — що важливо — автоматично налаштовує звʼязок із джерелом під іменем origin та створює гілку, яка «стежить» за віддаленою. Тобто після clone ти вже підключений і можеш одразу pull/push, а після init — ще ні.

    # з нуля: локальна тека стає репозиторієм
    git init
    
    # копія існуючого репозиторію разом з історією і налаштованим origin
    git clone https://github.com/company/e2e-tests.git

    Типовий шлях AQA — майже завжди clone: тестовий проєкт уже є в репозиторії команди. init знадобиться, коли заводиш новий репозиторій сам.

    Три зони і навіщо між ними staging

    Ключ до всього циклу — розуміти, що зміна проходить три зони, перш ніж стати частиною історії:

    git add

    git commit

    git push

    git fetch

    git merge

    Робоча директорія

    Staging / індекс

    Локальний репозиторій

    Віддалений origin

    origin/main

    git add

    git commit

    git push

    git fetch

    git merge

    Робоча директорія

    Staging / індекс

    Локальний репозиторій

    Віддалений origin

    origin/main

    Робоча директорія (working directory) — це файли, які ти реально редагуєш у редакторі. Staging (index, «індекс») — проміжна зона, чернетка наступного коміту. Локальний репозиторій — уже зафіксована історія у .git на твоїй машині. І лише push виносить її на віддалений (remote) сервер, звідки її побачить решта команди.

    Найважливіша і найменш очевидна для новачка зона — staging. Здається зайвою: навіщо окремий крок між «змінив файл» і «зафіксував»? Відповідь — контроль над тим, що саме потрапить у коміт. Ти міг наредагувати п'ять файлів, але зараз хочеш зафіксувати лише два, що стосуються однієї логічної зміни. Staging дозволяє зібрати коміт як чернетку — покласти в нього рівно потрібне, переглянути й лише тоді зафіксувати. Це те, що робить коміти атомарними: одна зміна — один коміт, а не «звалка за день».

    status і diff: спершу подивись, що відбувається

    Перш ніж щось фіксувати, треба бачити стан. git status — найчастіша команда в циклі, її запускають буквально перед кожною дією. Вона показує, які файли не відстежуються (untracked), які змінені, але ще не в staging, а які вже підготовані до коміту.

    git status

    git diff показує зміст змін, а не лише імена файлів, і тут криється пастка, на якій спотикаються всі. За замовчуванням git diff показує різницю між робочою директорією і staging — тобто те, що ти ще НЕ додав через add. А git diff --staged (синонім --cached) показує різницю між staging і останнім комітом — тобто те, що вже підготоване й піде в наступний коміт.

    git diff           # що змінено, але ще не в staging
    git diff --staged  # що вже в staging і піде в коміт

    Через це буває плутанина: людина робить git add ., потім git diff — і бачить порожнечу, вирішивши, що зміни зникли. Насправді вони просто переїхали в staging, і дивитися на них треба вже через --staged.

    add: staging як чернетка

    git add переносить зміну з робочої директорії в staging. Розумова модель тут — не «додати файл у Git», а «покласти цю зміну в чернетку наступного коміту». Файл при цьому не «зберігається» кудись назавжди — ти лише формуєш склад майбутнього знімка.

    git add tests/login.spec.ts   # конкретний файл
    git add .                      # усе змінене в поточній теці й нижче

    git add . — зручно, але саме воно породжує «звалку»: у коміт залітає все підряд, включно з випадковими правками й дебаг-логами. Дисциплінованіший підхід — додавати свідомо, а для складних правок є git add -p (patch), який пропонує додати зміни шматками й запитує по кожному: брати чи ні. Це і є сенс staging як чернетки — ти автор коміту, а не пасивний фотограф усього підряд.

    Важливо: staging тримає знімок зміни на момент add. Якщо після git add file.ts ти ще раз відредагуєш file.ts, у staging залишиться стара версія, а нова правка знову висітиме в робочій директорії — доти, доки не зробиш add ще раз. Саме тому git status після повторної правки покаже файл одночасно і як staged, і як modified.

    commit: знімок і хороший меседж

    git commit бере те, що лежить у staging, і записує це в історію як знімок (snapshot) — незмінний запис із власним ідентифікатором (хешем), автором, датою, посиланням на батьківський коміт і повідомленням. Підкреслимо: коміт фіксує вміст staging, а не робочої директорії. Усе, що ти не додав через add, у коміт не потрапить.

    git commit -m "Add retry to flaky checkout test"

    Тепер — про повідомлення, бо це не формальність, а те, за що беруться на рев'ю і в майбутньому дебазі. Через півроку git log або git blame (див. главу Git для AQA: bisect, blame, хуки й теги) покажуть саме ці повідомлення — і від них залежить, чи зрозумієш ти, навіщо зроблено зміну. Усталена конвенція хорошого меседжу:

    • Заголовок — короткий (орієнтир: до 50 символів), у наказовому способі: Add, Fix, Remove, а не Added/Fixing. Читається як «цей коміт [зробить те-то]».
    • Порожній рядок, потім за потреби тіло, яке пояснює чому зроблено зміну, а не що (що й так видно з diff).
    • Не «пофіксив», а конкретика: Fix login test failing on slow CI due to missing wait.

    Багато команд формалізують це через Conventional Commits (feat:, fix:, chore: тощо), щоб з історії автоматично генерувати changelog. Але суть незмінна: повідомлення пише майбутньому читачеві, часто — тобі ж.

    origin і tracking-гілки

    Перш ніж штовхати коміти на сервер, розберемось із двома поняттями, які постійно плутають. origin — це просто імʼя за замовчуванням для віддаленого репозиторію, яке clone присвоює автоматично. Не «GitHub», не щось магічне — псевдонім для URL, щоб не писати повну адресу щоразу. Їх може бути кілька (origin, upstream), і назвати можна як завгодно.

    Tracking-гілка (відстежувана, upstream) — це звʼязок між твоєю локальною гілкою і гілкою на сервері. Коли локальна main «стежить» за origin/main, Git знає, куди штовхати push і звідки тягнути pull без зайвих аргументів, а git status підказує на кшталт «твоя гілка попереду origin/main на 2 коміти».

    Окремо існують remote-tracking-гілки — це origin/main, origin/develop і подібні. Вони — локальна копія (кеш) того, яким Git востаннє бачив стан сервера. origin/main не оновлюється сам собою: він змінюється тільки коли ти явно синхронізуєшся через fetch чи pull. Це та деталь, яку варто засвоїти одразу: origin/main — це не «зараз на сервері», а «сервер на момент останнього fetch». Детальніше про гілки як вказівники — у главі Гілки та злиття.

    push і pull

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

    git push               # відправити коміти в origin
    git push -u origin main   # перший push + одразу налаштувати tracking
    git pull               # підтягнути й влити зміни з origin

    Класична ситуація, яку варто розуміти: push відхиляється (rejected, non-fast-forward), якщо на сервері зʼявилися коміти, яких немає в тебе, — хтось запушив раніше. Git не дасть перетерти чужу роботу і скаже спершу підтягнути зміни. Правильна реакція — pull (або fetch + розбір), інтегрувати чуже, а тоді пушити знову. Це не помилка Git, а захист від втрати чужих комітів.

    fetch vs pull — різниця, яку люблять питати

    Це одне з найчастіших питань на trainee-співбесіді, бо відрізняє того, хто розуміє модель, від того, хто завчив команди.

    git fetch

    Оновлює origin/main
    твою гілку не чіпає

    git pull

    git fetch

    git merge origin/main
    у поточну гілку

    git fetch

    Оновлює origin/main
    твою гілку не чіпає

    git pull

    git fetch

    git merge origin/main
    у поточну гілку

    git fetch лише завантажує новий стан сервера в remote-tracking-гілки (origin/main) — і зупиняється. Твоя робоча гілка, твої файли, твій staging залишаються недоторканими. Після fetch можна спокійно подивитися, що прилетіло (git log origin/main, git diff main origin/main), і лише тоді вирішити, зливати чи ні.

    git pull — це fetch + одразу merge (за замовчуванням; можна налаштувати на rebase). Тобто pull не просто качає, а негайно вливає чужі коміти у твою поточну гілку, потенційно створюючи merge-коміт або — гірше — merge-конфлікт (див. Merge-конфлікти: причини й розв'язання) просто в мить, коли ти цього не чекав.

    Практичний висновок: fetch безпечний і не змінює твою роботу — це «подивитися, що там нового». pull зручний, коли ти впевнений, що хочеш злити просто зараз. Досвідчені інженери часто роблять fetch, дивляться, а потім свідомо мержать — саме щоб pull не «прилетів» несподівано посеред роботи.

    log і show: читаємо історію

    Коли треба зрозуміти, що вже сталося, є дві команди. git log показує історію комітів — від нового до старого, з хешами, авторами, датами й повідомленнями.

    git log                    # повна історія
    git log --oneline          # по одному рядку на коміт — швидкий огляд
    git log --oneline --graph  # з візуалізацією гілок і мержів

    git show <хеш> розкриває один конкретний коміт: його метадані і повний diff змін, які він приніс. Якщо log відповідає на «що відбувалося», то show — на «що саме змінив ось цей коміт».

    git show a1b2c3d           # деталі й diff конкретного коміту
    git show HEAD              # останній коміт

    Для AQA це щоденний інструмент розслідування: тест почав падати — дивишся git log --oneline по файлу тесту, знаходиш підозрілий коміт, розкриваєш його git show і бачиш, що саме змінилось. Це базис, на якому далі стоять bisect і blame.

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

    Виглядає як «я закомітив, а на GitHub нічого немає» — а насправді коміт лежить лише локально. commit пише в .git на твоїй машині; поки не зробиш push, сервер про нього не знає. Коміт і публікація — два різні кроки.

    Виглядає як «git add вже додав зміну в коміт» — а насправді add лише кладе її в staging. Staging — це чернетка; поки немає commit, у історії нічого не зʼявилося. git add без наступного commit не фіксує нічого.

    Виглядає як «git diff показує порожнечу, зміни пропали» — а насправді вони переїхали в staging. git diff дивиться робочу директорію проти staging; після git add дивитися треба через git diff --staged.

    Виглядає як «origin — це і є GitHub» — а насправді origin лише псевдонім URL. Це імʼя віддаленого репозиторію, яке можна перейменувати чи мати кілька. Git — не GitHub; сам Git працює й без жодного сервера.

    Виглядає як «pull підтягнув чуже і зламав мені гілку» — а насправді pull = fetch + merge, і merge стався несподівано. Якщо не хочеш раптового злиття, роби fetch, дивись, що прилетіло, і мерж свідомо.

    Виглядає як «push відхилило — Git зламався» — а насправді на сервері є коміти, яких у тебе немає. Це захист від затирання чужої роботи: спершу інтегруй віддалені зміни (pull/fetch), потім пуш.

    Підсумок

    • Зміна проходить три зони: робоча директорія → staging (add) → локальний репозиторій (commit), і лише push виносить її на сервер.
    • Staging — це чернетка коміту: він існує, щоб ти зібрав атомарний коміт зі свідомо обраних змін, а не звалку всього підряд.
    • commit фіксує вміст staging (не робочої директорії) як незмінний знімок; хороше повідомлення пише майбутньому читачеві — наказовий спосіб, коротко, з поясненням «чому».
    • fetch лише оновлює origin/main і нічого не змінює в твоїй роботі; pull — це fetch + merge, тобто негайне вливання чужих комітів у поточну гілку.
    • origin — це імʼя віддаленого репозиторію, а origin/main — локальний кеш стану сервера на момент останнього fetch, а не «те, що на сервері зараз».

    Що питають на співбесіді

    • «Розкажи базовий цикл роботи з Git». Очікують не список команд, а модель: змінив у робочій директорії → add у staging → commit у локальну історію → push на сервер → pull, щоб підтягнути чуже. Інтервʼюер дивиться, чи розумієш ти зони, а не чи завчив послідовність.
    • «Чим fetch відрізняється від pull?» Сильна відповідь: fetch тільки завантажує стан сервера в remote-tracking-гілки й нічого не мержить; pull — це fetch плюс автоматичний merge у поточну гілку. Плюс — коли який доречний.
    • «Навіщо потрібен staging / що робить git add?» Перевіряють, чи розумієш ти сенс проміжної зони: зібрати коміт як чернетку, контролювати, що саме туди піде, робити атомарні коміти.
    • «Зробив commit, а змін немає на GitHub — чому?» Класична пастка. Правильно: коміт локальний, потрібен push.
    • «origin — це що?» Дивляться, чи не плутаєш ти імʼя remote з самим GitHub і чи розумієш, що Git ≠ GitHub.
    • «Як подивитись, що змінив конкретний коміт?» Очікують git show <хеш>; бонус — згадка git log --oneline для навігації історією.

    Джерела