Базовий цикл 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
Ключ до всього циклу — розуміти, що зміна проходить три зони, перш ніж стати частиною історії:
Робоча директорія (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 лише завантажує новий стан сервера в 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для навігації історією.
Джерела
- Pro Git (Scott Chacon, Ben Straub), розділ 2 «Git Basics» — канонічний безкоштовний підручник, покриває clone/add/commit/status/diff/push/pull.
- Pro Git, розділ 2.5 «Working with Remotes» — про origin, fetch, pull і tracking-гілки.
- Офіційна документація Git — довідник команд — точна семантика
git-fetch,git-pull,git-diffта інших. - GitHub Docs — Git basics — практичний бік роботи з віддаленим репозиторієм.
Розкажи базовий цикл роботи з Git
Сильна відповідь описує не команди, а рух зміни між зонами. Ти правиш файли в робочій директорії (working directory), відбираєш потрібне у staging через git add, фіксуєш це як коміт у локальну історію через git commit, віддаєш коміти на сервер через git push і забираєш чуже через git pull. Тобто зміна долає чотири рубежі: диск → чернетка коміту → локальний .git → віддалений репозиторій. Інтервʼюер тут перевіряє саме модель зон: чи розумієш ти, що add і commit — різні кроки, а commit і push — теж різні. Заучена послідовність без цієї моделі розсипається на першому ж уточненні на кшталт «а чому після коміту нічого немає на GitHub».
Чим відрізняються git init і git clone?
Це два різні способи опинитися всередині циклу. git init перетворює звичайну теку на репозиторій: створює приховану підтеку .git під історію, але сам репозиторій поки порожній — без комітів і без будь-якого звʼязку із сервером. git clone <url> розвʼязує зворотну задачу: репозиторій уже є на сервері, і ти качаєш його повну історію, розгортаєш останній стан у робочу теку, а Git при цьому сам налаштовує звʼязок із джерелом під іменем origin і заводить локальну гілку, що стежить за віддаленою. Практичний наслідок: після clone ти одразу можеш pull і push, а після init — ще ні, бо серверу твоя тека невідома. Для AQA це майже завжди clone, бо тестовий проєкт уже лежить у репозиторії команди; init знадобиться, лише коли заводиш новий репозиторій із нуля сам.
Які три зони проходить зміна, перш ніж потрапити в історію?
Робоча директорія → staging → локальний репозиторій. Робоча директорія — це файли, які ти реально редагуєш у редакторі. Staging (його ще звуть index, індекс) — проміжна зона, чернетка наступного коміту, куди зміна лягає командою git add. Локальний репозиторій — уже зафіксована історія в .git, куди чернетку записує git commit. І тільки четвертий крок, git push, виносить зафіксоване на віддалений сервер, звідки його побачить команда. Розуміти ці межі критично, бо майже всі типові помилки новачка — це сплутані зони: «зробив add, думав, що вже закомітив» або «закомітив, а на сервері порожньо».
Зробив commit, а на GitHub нічого немає. Чому?
Бо коміт і публікація — два окремі кроки, і це класична пастка. git commit записує знімок у .git на твоїй власній машині; сервер про цей коміт нічого не знає, доки ти не зробиш git push. Тобто локально історія вже змінилася — git log покаже свіжий коміт, — але віддалений репозиторій лишається на попередньому стані. Правильна реакція проста: виконати push. Якщо це перший push нової гілки, знадобиться git push -u origin <гілка>, щоб заодно налаштувати звʼязок із віддаленою гілкою. Часто на це накладається ще й плутанина Git проти GitHub: сам Git прекрасно працює локально без жодного сервера, а GitHub — лише один із можливих хостингів.
Навіщо потрібен staging і що робить git add?
Staging існує заради контролю над тим, що саме потрапить у коміт. git add не «зберігає файл у Git назавжди», а кладе конкретну зміну в чернетку наступного коміту. Сенс проміжної зони видно, коли ти наредагував пʼять файлів, але одна логічна зміна стосується лише двох: staging дає зібрати коміт вручну — покласти рівно потрібне, переглянути й лише тоді зафіксувати. Саме це робить коміти атомарними: одна логічна зміна — один коміт, а не звалка за весь день. Тому git add . хоч і зручний, але провокує ту саму звалку — у коміт залітають випадкові правки й дебаг-логи; дисциплінованіше додавати свідомо, а для складних правок є git add -p, що пропонує відбирати зміни шматками.
Що фіксує git commit — вміст робочої директорії чи staging?
Тільки вміст staging. Це принциповий момент: commit бере рівно те, що ти поклав через add, і записує в історію; усе, що лишилося в робочій директорії поза staging, у коміт не потрапляє. Звідси наслідок, який ловить новачків: наредагував десять файлів, зробив git add лише на двох — і в комітi опиниться два, а не десять, хоч решта вісім теж змінені на диску. Тому перед комітом варто дивитися git status (що вже в staging, а що ні) і git diff --staged (що саме туди піде). Сам коміт — це незмінний знімок (snapshot) із власним хешем, автором, датою, посиланням на батьківський коміт і повідомленням.
Робиш git add ., потім git diff — і бачиш порожнечу. Зміни зникли?
Ні, вони просто переїхали в staging, а git diff за замовчуванням туди не дивиться. У цьому вся пастка: голий git diff показує різницю між робочою директорією і staging — тобто те, що ти ще НЕ додав. Після git add . усі зміни вже в staging, робоча директорія і staging збіглися — тому diff і порожній. Щоб побачити те, що вже підготоване й піде в коміт, потрібен git diff --staged (синонім --cached), який порівнює staging з останнім комітом. Практичний висновок: до add дивишся git diff, після add — git diff --staged; переплутати ці два погляди — і здається, ніби правки загубилися.
Як подивитися, що саме змінив конкретний коміт?
Через git show <хеш> — він розкриває один коміт: метадані (автор, дата, повідомлення) плюс повний diff змін, які цей коміт приніс. Це відповідь на питання «що саме тут змінилось», на відміну від git log, який відповідає на «що взагалі відбувалося» і дає лінійний список комітів від нового до старого. Типовий тандем для навігації: спершу git log --oneline (по рядку на коміт, швидкий огляд), знаходиш підозрілий хеш, потім git show <хеш> розкриваєш його вміст. Можна взяти й git show HEAD для останнього коміту. Для AQA це щоденний інструмент розслідування: тест почав падати — дивишся історію файлу тесту через git log, знаходиш зміну, розкриваєш її show і бачиш, що саме поїхало.
Що таке хороший commit message і чому на нього звертають увагу?
Хороший меседж пише майбутньому читачеві, яким часто виявляєшся ти сам через півроку. Через git log чи git 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?
Це імʼя за замовчуванням для віддаленого репозиторію, яке git clone присвоює автоматично, — і нічого магічного в ньому немає. По суті origin — псевдонім для URL сервера, щоб не писати повну адресу щоразу при push/pull. Ключове, що варто показати на співбесіді: origin — це не «GitHub» і не сам Git, а всього лише назва, яку можна перейменувати, і таких віддалених може бути кілька (класика — origin для свого форку і upstream для оригінального репозиторію). За цим питанням інтервʼюер перевіряє, чи не плутаєш ти імʼя remote із сервісом і чи розумієш, що Git ≠ GitHub — сам Git працює й без жодного віддаленого сервера взагалі.
origin/main показує те, що зараз на сервері?
Ні — origin/main показує стан сервера на момент твого останнього fetch, а не в реальному часі. Це так звана remote-tracking-гілка: локальна копія (кеш) того, яким Git востаннє бачив віддалену гілку. Вона не оновлюється сама собою — тільки коли ти явно синхронізуєшся через git fetch або git pull. Наслідок, який варто засвоїти одразу: якщо колега щойно запушив коміт, у твоєму origin/main його ще немає, доки ти не зробиш fetch; повідомлення git status на кшталт «твоя гілка попереду origin/main на 2 коміти» рахується саме проти цього кешу, а не проти живого сервера. Плутанина «origin/main = зараз на сервері» — часте джерело сюрпризів при push.
Що таке tracking-гілка і навіщо git push -u origin main?
Tracking-гілка (відстежувана, upstream) — це звʼязок між твоєю локальною гілкою і конкретною гілкою на сервері. Коли локальна main стежить за origin/main, Git знає, куди штовхати push і звідки тягнути pull без додаткових аргументів, а git status може підказувати, на скільки комітів ти попереду чи позаду. Прапорець -u (скорочення від --set-upstream) у git push -u origin main при першому пуші не лише відправляє коміти, а й одразу налаштовує цей звʼязок — після нього достатньо голого git push і git pull. Після clone tracking уже налаштований автоматично, тому -u типово потрібен для нових гілок, які ти створив локально й пушиш уперше.
Чому git push може бути відхилено (rejected, non-fast-forward)?
Бо на сервері зʼявилися коміти, яких немає в тебе — хтось запушив у ту саму гілку раніше. Git навмисно не дає перезаписати чужу роботу і відхиляє push із повідомленням про non-fast-forward, вимагаючи спершу інтегрувати віддалені зміни. Це не поломка Git, а захист від мовчазної втрати чужих комітів. Правильна реакція: підтягнути зміни (git pull або обережніше git fetch з подальшим розбором), злити чуже зі своїм, розвʼязати конфлікти, якщо виникли, — і лише тоді пушити знову. Спроба «продавити» push силою (--force) якраз і затирає ті коміти, від затирання яких Git тебе щойно вберіг, тому в спільній гілці це майже завжди помилка.
Чим git fetch відрізняється від git pull?
Це найчастіше питання про Git на trainee-співбесіді, бо відділяє того, хто розуміє модель, від того, хто завчив команди. git fetch лише завантажує новий стан сервера в remote-tracking-гілки (origin/main) і на цьому зупиняється — твою робочу гілку, твої файли й staging він не чіпає. Після fetch можна спокійно роздивитися, що прилетіло (git log origin/main, git diff main origin/main), і лише тоді свідомо вирішити, зливати чи ні. git pull — це fetch плюс негайний git merge origin/main у поточну гілку (за замовчуванням; можна переналаштувати на rebase), тобто він одразу вливає чужі коміти, потенційно створюючи merge-коміт або merge-конфлікт у мить, коли ти цього не чекав. Сильна відповідь додає, коли який доречний: fetch безпечний, це «подивитися, що нового»; pull зручний, коли ти впевнений, що хочеш злити просто зараз.
Зробив git add file.ts, потім ще раз відредагував file.ts. Що тепер у staging?
У staging лишиться версія на момент першого add, а свіжа правка висітиме окремо в робочій директорії. Це прямий наслідок того, що staging тримає знімок зміни на момент add, а не «живе посилання» на файл. Тому git status після повторної правки покаже той самий файл одночасно у двох станах: як staged (те, що ти додав) і як modified (те, що доредагував після). Якщо просто зробити commit, у коміт піде лише перша версія; щоб включити й нову правку, треба виконати git add file.ts ще раз. Це деталь, яка часто дивує новачків, але вона логічно випливає з ідеї staging як чернетки, зафіксованої в конкретний момент.
Чому досвідчені інженери часто роблять git fetch, а не git pull?
Щоб merge не «прилетів» несподівано посеред роботи. pull зручний, але він поєднує завантаження і злиття в один крок, тому чужі коміти вливаються у твою гілку негайно — а якщо там конфлікт, він виникає саме тоді, коли ти його не чекав, іноді просто на середині власної незавершеної правки. fetch розриває це на два свідомі рішення: спершу тільки подивитися, що зʼявилося на сервері (git log origin/main, git diff main origin/main), оцінити обсяг і ризик, а вже потім свідомо мержити чи ребейзити в зручний момент. Практично це дає контроль: ти сам обираєш, коли інтегрувати чуже, замість того щоб дозволити pull зробити це за тебе автоматично. Особливо цінно на активних гілках, де хтось пушить часто, і на важливих правках, які не хочеться раптово переплітати з чужими змінами.
Три ситуації з робочого дня AQA, де плутанина в зонах перетворює просту дію на «Git зламався»: правка автотесту, яка ніяк не доїде на сервер; відхилений push після колеги; і вибір між fetch і pull, коли не хочеться раптового merge. Скрізь — що показує git status на кожному кроці й чому.
Кейс 1. «Закомітив, а в CI старий тест» — читаємо зони по status
Ти поправив флакі-тест логіну, зробив коміт, але пайплайн у Pull Request усе одно ганяє стару версію. Перш ніж винити CI, треба відповісти на одне питання: на якому рубежі застрягла твоя зміна. git status після кожного кроку показує це буквально.
Наредагували файл — зміна ще в робочій директорії, staging порожній:
$ git status
On branch fix/flaky-login
Changes not staged for commit:
modified: tests/login.spec.ts
$ git diff # бачимо ЗМІСТ правки: додали await для networkidle
Кладемо у staging і одразу перевіряємо саме staged-погляд (голий git diff тут уже покаже порожнечу — це нормально):
$ git add tests/login.spec.ts
$ git status
Changes to be committed:
modified: tests/login.spec.ts
$ git diff --staged # ось що реально піде в коміт
Фіксуємо й читаємо ключовий рядок — «ahead of origin by 1 commit»:
$ git commit -m "Fix flaky login test: wait for networkidle before assert"
$ git status
Your branch is ahead of 'origin/fix/flaky-login' by 1 commit.
(use "git push" to publish your local commits)
Ось і діагноз. Коміт існує, але лежить у локальному .git; сам Git прямо каже — publish your local commits. CI бачить лише те, що на сервері, а туди коміт ще не доїхав:
$ git push
$ git status
Your branch is up to date with 'origin/fix/flaky-login'.
Що дивитися і чому:
- «ahead by N commits» = робота лежить локально. Поки бачиш цей рядок, на сервері (і в CI) твоїх комітів немає. Коміт і публікація — різні кроки;
commitпише в.git, доставляєpush. - Порожній
git diffпісляadd— не втрата правки. Зміна переїхала в staging, дивитися на неї треба через--staged. Плутанина цих двох поглядів — головна причина «а куди зникли мої зміни». - Оракул для CI — не «я ж закомітив», а стан гілки. Перед тим як заводити баг на пайплайн, звір, що
git statusкаже «up to date with origin», інакше розслідуєш проблему, якої на сервері ще не існує.
Кейс 2. Push відхилено (non-fast-forward) — колега запушив раніше
Ти дописав тест, тиснеш push — і отримуєш відмову. Це не поломка, а захист: у спільну гілку хтось влив коміти, яких у тебе немає, і Git не дає їх мовчки затерти.
$ git push
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:company/e2e-tests.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. Integrate the remote changes (e.g. 'git pull')
hint: before pushing again.
Правильна реакція — не --force, а спершу подивитися, що саме прилетіло. Обережний шлях: fetch, потім порівняти:
$ git fetch
$ git log --oneline main..origin/main
9f8e7d6 Add data-testid to checkout button # коміт колеги, якого в тебе не було
$ git diff main origin/main # переконались, що конфлікту з нашим файлом немає
Тепер свідомо інтегруємо чуже й пушимо:
$ git pull # fetch + merge; тут пройшло чисто, конфлікту немає
$ git push # цього разу приймається
Що дивитися і чому:
rejected ... fetch first= на сервері є новіші коміти. Це штатний захист від затирання чужої роботи, а не збій. Git навіть підказує реакцію вhint.git log main..origin/mainпоказує «що в них є, а в мене немає». Дві крапки читаються як «коміти в origin/main, яких нема в моїй main» — так ти бачиш обсяг чужих змін до будь-якого злиття.--forceтут — пастка. Він якраз затирає ті коміти колеги, від затирання яких Git тебе щойно вберіг. У спільній гілці це майже завжди помилка й привід для неприємної розмови.- Якщо
pullдав merge-конфлікт — це не тупик: Git позначить конфліктні місця в файлах, їх треба розвʼязати руками, зробитиaddрозвʼязаних файлів і завершити merge комітом.
Кейс 3. fetch чи pull — таблиця рішень
pull економить один рух, але вливає чуже негайно; fetch дає подивитися перед злиттям. Ось коли який доречний.
| Ситуація | Доречно | Чому |
|---|---|---|
| Почав день, хочу свіжий стан гілки, робочих правок ще немає | pull | Нічого не втрачаєш: локальних змін немає, merge пройде тривіально (fast-forward). |
| Посеред незавершеної правки, але цікаво, що там на сервері | fetch | Оновить origin/main, твою гілку й staging не чіпає — подивишся без ризику раптового merge. |
| Push відхилило (non-fast-forward) | fetch → розбір → pull | Спершу побачити чужі коміти, оцінити ризик конфлікту, і лише тоді свідомо злити. |
| Активна гілка, колега пушить щогодини | fetch + свідомий merge | Сам обираєш момент інтеграції, щоб конфлікт не прилетів у найгірший час. |
| CI зелений, гілка стабільна, просто синхронізуюсь перед push | pull | Ризик мінімальний, зайвий крок ні до чого. |
Швидка перевірка «що прилетить, якщо зроблю pull», без самого злиття:
$ git fetch
$ git log --oneline HEAD..origin/main # коміти, які pull влив би в мене
$ git diff HEAD origin/main # їхній зміст — оцінити ризик конфлікту
Що дивитися і чому:
fetchніколи не змінює твою роботу. Він рухає тількиorigin/main— локальний кеш стану сервера. Файли, гілку і staging лишає недоторканими, тому це завжди безпечний перший крок.pull=fetch+merge. Зручний, коли ти впевнений, що хочеш злити зараз; ризикований, коли не чекаєш чужих змін — merge (а з ним і можливий конфлікт) стається негайно.- Правило для важливих правок: розділяй «подивитися» і «злити».
fetchпоказує,mergeінтегрує — і між ними є твоє свідоме рішення, а не автоматика pull.
Точки входу і три зони
- Знаю різницю
git initvsgit clone: перший робить порожній репозиторій без звʼязку із сервером, другий качає готовий разом з історією й налаштованимorigin. - Можу пояснити, чому після
cloneодразу можнаpull/push, а післяinit— ще ні. - Знаю три зони, які проходить зміна: робоча директорія → staging (index) → локальний репозиторій, і що лише
pushвиносить її на сервер. - Розумію, що
commitіpush— два різні кроки: коміт лягає в локальний.git, а на сервер його доставляє тількиpush.
staging, add і commit
- Можу пояснити, навіщо взагалі потрібен staging: контроль над тим, що саме піде в коміт, і атомарність (одна логічна зміна — один коміт).
- Розумію, що
git addне «зберігає файл назавжди», а кладе зміну в чернетку наступного коміту;git add .провокує «звалку», тож для вибіркового додавання єgit add -p. - Можу пояснити, що
commitфіксує вміст staging, а не робочої директорії — усе поза staging у коміт не потрапляє. - Розумію, що staging тримає знімок на момент
add: повторна правка файлу лишає його одночасно staged і modified, доки не зробишaddще раз. - Знаю правила хорошого commit message: короткий заголовок у наказовому способі (
Add/Fix), порожній рядок, тіло пояснює чому, а не що.
status і diff: спершу подивись
- Використовую
git statusперед кожною дією й розумію його три категорії: untracked, змінене поза staging, підготоване до коміту. - Знаю пастку
git diff: без прапорця він показує робочу директорію проти staging (те, що ще НЕ додано), аgit diff --staged— staging проти останнього коміту (те, що піде в коміт). - Розумію, чому після
git add .голийgit diffпорожній, хоча зміни нікуди не поділись.
origin і віддалені гілки
- Можу пояснити, що
origin— це лише імʼя-псевдонім для URL віддаленого репозиторію, а не «GitHub»; його можна перейменувати й мати кілька (origin,upstream). Git ≠ GitHub — сам Git працює локально без жодного сервера. - Знаю, що
origin/main— це remote-tracking-гілка, локальний кеш стану сервера на момент останньогоfetch, а не «те, що на сервері зараз». - Можу пояснити, що таке tracking-гілка (upstream) і навіщо
git push -u origin <гілка>при першому пуші.
push, pull і fetch
- Знаю різницю
git fetchvsgit pull:fetchлише оновлюєorigin/mainі нічого не мержить,pull— цеfetch+mergeу поточну гілку. - Можу пояснити, чому
pushбуває відхилено (non-fast-forward) — на сервері є коміти, яких немає в тебе, — і правильну реакцію: підтягнути й інтегрувати чуже (pull/fetch), а не продавлювати--force. - Розумію, чому досвідчені інженери часто роблять
fetch+ свідомийmergeзамістьpull: щоб merge-конфлікт не прилетів несподівано посеред роботи.
Читання історії
- Знаю різницю
git logvsgit show:logвідповідає на «що відбувалося» (список комітів;--onelineдля швидкого огляду,--graphдля гілок),show <хеш>— на «що саме змінив цей коміт» (метадані + diff). - Можу описати AQA-флоу розслідування падіння тесту:
git logпо файлу тесту → підозрілий коміт →git showрозкриває зміну.
Чим git init відрізняється від git clone?
Питання
git init vs git clone — у чому різниця?