Основи: продукт, код і робоче середовище

Git: історія, гілки та безпечні зміни

Уявімо, що інженер змінив правило обчислення вільного часу для бронювання, а інша людина того самого дня змінила текст підтвердження. Обидві зберегли…

Глава 4Заморожене українське видання

Уявімо, що інженер змінив правило обчислення вільного часу для бронювання, а інша людина того самого дня змінила текст підтвердження. Обидві зберегли свої версії файлів. Що саме вже записано? Що бачить команда? Чи здатен Git сам вирішити суперечність у бізнес-правилі? Відповіді залежать від різних кроків, які легко назвати одним словом «запушити». Правка у файлі ще не обов’язково є частиною історії, яку бачить команда: вона проходить через робоче дерево, index і commit, а для колег стає доступною після передавання у віддалений репозиторій. У Git кожен крок має окремий стан.

Навчальний результат

Після цієї глави читач може пояснити стан змін у Git словами: від редагування файла до фіксації стану, передавання на сервер, перегляду Pull Request (PR — запит на включення змін) і об'єднання. Він відрізняє локальне збереження від передавання, об'єднання від випуску версії, а компенсувальну зміну від переписування історії. Також уміє прочитати пропозицію агента, перевірити межі різниці між версіями файлів і помітити, коли автоматичне розв'язання конфлікту не відповідає задуму.

Потрібні поняття з глав 2–3: файл у репозиторії, робоча папка, процес і команда термінала. Git тут не розшифровується як абревіатура. PR-процес ширше розглядає глава 5, а ізоляцію робочих папок — глава 47.

Git зберігає історію проєкту

Git — розподілена система контролю версій. Це означає, що у локальному репозиторії є історія проєкту, а зміни можна готувати й перевіряти на власному комп'ютері. Git допомагає відповісти на запитання: які файли змінилися, що саме змінилося, у якому порядку, ким записано та на основі якого попереднього стану. У Pro Git локальна копія описана як така, що містить істотну частину історії: clone створює локальний репозиторій, fetch отримує нові об'єкти й посилання, а push надсилає зафіксовані стани у віддалену копію (Pro Git, Git Basics та Working with Remotes).

Git — не GitHub: GitHub зберігає віддалені репозиторії та надає інструменти для спільної роботи. Файли поза репозиторієм, проігноровані, незакомічені або ще не передані зміни можуть бути відсутні в історії колег, тому Git не є повною системою резервного копіювання.

Три стани: файл, підготовлена зміна, запис історії

Щоб зрозуміти, що буде зафіксовано, розрізняйте три стани.

Стан Просте пояснення Що його показує
Робоче дерево Файли такими, якими їх зараз бачить редактор Незбережені в історії правки та нові файли
Index, або staging area Набір конкретних змін, підготовлених до наступного commit Що саме увійде до наступного commit
Commit Зафіксований стан відстежуваних файлів разом з історичним контекстом Збережену точку, на яку можна посилатися

У Git commit — це запис стану репозиторію, який посилається на знімок файлів і містить історичні дані, як-от автора, повідомлення й батьківський commit або commits. Це не просто «натиснув Save». Він зберігає точку історії; наступні записи спираються на неї. За змістом повідомлення команда пізніше шукає причину зміни. Коротке «виправлення» мало що пояснить; «заборонити подвійний продаж останньої одиниці товару» дає читачеві більше контексту. Commit не засвідчує, що автор перевірив кожен сценарій, що зміна потрапила на GitHub чи що продукт уже працює у робочому середовищі (production). Pro Git описує commit-об'єкт як запис, який пов'язує знімок файлів з автором, повідомленням і батьківськими записами (Git Internals: Git Objects).

Наприклад, автор змінює inventory.md. Поки зміна не потрапила до index, наступний commit її не включить. git add inventory.md готує весь поточний файл; git add -p inventory.md дає змогу вибрати окремі фрагменти. Перед commit перевірте саме підготовлену різницю.

Команда git status підсумовує відмінності між поточним commit, index і робочим деревом, зокрема показує нові невідстежувані файли. git diff порівнює відстежувані зміни робочого дерева з index; невідстежуваний файл сам собою в цій різниці не з'явиться. git diff --cached показує підготовлене до commit. Їх не варто плутати: перегляд unstaged-різниці може бути чистим, хоча в index уже є зміни або поруч лежить новий невідстежуваний файл. Це не помилка інструмента — команди відповідають на різні запитання (git-status, git-diff).

Команди нижче — лише пояснювальний приклад, а не перелік дій для читача:

git status --short
git diff -- inventory.md
git add inventory.md
git diff --cached -- inventory.md
git commit -m "Prevent inventory overselling"
git log --oneline --decorate -5

git log показує п’ять останніх записів і відповідні покажчики гілок та HEAD. Це огляд історії, не доказ чистого робочого дерева чи push.

До commit варто прочитати diff очима людини. Чи є в ньому лише задумана задача? Чи не потрапили туди чужі локальні зміни, згенеровані файли, секрети або випадкове форматування великого файла? Команда git add означає «внести вибрані зміни до наступного commit», а не «затвердити їхню правильність».

Гілка — рухомий покажчик на історію

Звичайний commit посилається на попередній, merge commit може мати кількох батьків, а кореневий commit не має батьків. Назва гілки, наприклад main чи inventory-alert, — рухоме посилання на її останній commit. Створення гілки задає нове ім’я для поточної точки; наступні commits пересувають покажчик. HEAD зазвичай позначає вибрану гілку, а іноді вказує просто на commit.

main часто називають основною гілкою, але це домовленість про спільну історію, а не особлива магічна властивість Git. Команда може встановити перевірки й правила для main на GitHub; сам Git не знає, яка гілка в компанії вважається production або прийнятою. Pro Git пояснює HEAD як посилання на поточну гілку й показує, що дві гілки можуть розійтися від спільного commit (Branches in a Nutshell).

Позначки A, B і C у таблиці — умовні commits. Останні два рядки — альтернативні результати злиття з одного стану, а не послідовні дії.

Крок main вказує на inventory-alert вказує на Що сталося
Початок A — Є початкова спільна історія
Створили робочу гілку A A Обидві назви вказують на той самий стан
Автор змінив правило A B Новий commit з'явився лише на робочій гілці
Об'єднали швидким просуванням (fast-forward) B B main пересунувся з A на вже наявний commit B; нового merge commit немає
Об'єднали з окремим merge commit C B git merge --no-ff inventory-alert створив C з батьками A і B; робоча гілка лишилася на B

Тривалість гілки — командна домовленість: її можна присвятити одній задачі або вести довше. У матеріалі IT Revolution, адаптованому з книги Accelerate, серед можливостей, пов'язаних із результативністю доставки, названо контроль версій усіх production-артефактів, коротке життя гілок і невеликі порції роботи. Автори пояснюють, що малі порції скорочують час від початку роботи до готового результату й пришвидшують зворотний зв'язок. Це опис висновків досліджень State of DevOps, а не доказ того, що саме Git або конкретна стратегія гілок самі створюють швидку чи якісну поставку (IT Revolution, “24 Key Capabilities”). Жоден формат гілок сам по собі не гарантує швидкої чи якісної поставки.

Локальна історія та віддалений репозиторій

Локальна й віддалена копії — окремі стани, між якими змінюються дані.

  • clone створює початкову локальну копію репозиторію; опції можуть обмежити отриману історію.
  • fetch отримує з віддаленого репозиторію commits та оновлює відомі локально посилання на віддалені гілки. Він не мусить об'єднувати нову історію з поточними файлами.
  • pull спершу отримує нову історію, а потім інтегрує її до вибраної локальної гілки. Конкретний спосіб інтеграції може залежати від налаштувань і аргументів.
  • push передає commits і пересуває віддалене посилання, якщо історія та права доступу дозволяють це.

Наприклад, після локального commit інженер може бачити нову зміну у себе, а колега — ні. Колега побачить її після push до спільного віддаленого репозиторію або іншого узгодженого передавання. Це саме по собі ще не означає, що гілка стала частиною main. Pro Git розрізняє fetch, який отримує й оновлює локальні відомості про віддалені гілки, pull, який після отримання зазвичай виконує інтеграцію, та push, що відправляє commits у віддалену копію (Working with Remotes).

Remote-tracking branch — локальне уявлення про те, де гілка віддаленого репозиторію була на момент останнього fetch. Воно може відстати, якщо інші вже передали нові commits. Тому повідомлення «нема нових змін» стосується відомого Git стану, а не всього інтернету: спершу треба зрозуміти, коли саме були отримані відомості.

Merge поєднує історії, але не приймає бізнес-рішень

merge поєднує дві лінії історії. Якщо одна гілка просто просунулася вперед і відгалуження не було, Git часто може зробити fast-forward: пересунути покажчик основної гілки на новіший commit без додаткового merge commit. Якщо дві гілки незалежно додали commits, Git може створити merge commit із двома батьківськими commits. В обох випадках логіка застосунку має лишитися правильною. Механіка описана у довідці git-merge.

Конфлікт з'являється, коли Git не може безпечно визначити фінальний текст сам. Наприклад, одна гілка замінила в правилі бронювання «до 17:00» на «до 18:00», інша — на «до 19:00». Git не знає, чи команда двічі змінила той самий графік, чи дві зміни представляють різні узгоджені вимоги. Людина має знайти потрібне рішення в Jira, договорі, продуктовій специфікації чи розмові з власником правила. Конфлікт не свідчить, що хтось працював неправильно; він лише показує, що зміну не можна безпечно звести до однієї відповіді за формою тексту. У прикладі нижче обидва цінові варіанти також виявляються неповними, тож власник правила має уточнити їхній зміст.

Git може залишити позначки <<<<<<<, ======= та >>>>>>>. Після узгодження треба прибрати їх і переглянути повний diff; не приймайте «наш» чи «їхній» варіант лише за назвою. Навіть без текстового конфлікту дві зміни можуть бути несумісні за змістом (GitHub Docs: Merge conflicts).

Rebase і squash змінюють подання історії

rebase бере commits робочої гілки й повторно застосовує їх поверх іншої бази. Результат може бути простішою на вигляд лінійною історією, але повторно застосовані commits отримують нові ідентифікатори, бо змінився їхній батьківський commit. Це не косметичне перейменування і не нейтральна операція для всіх учасників. Якщо інші люди вже будували власні зміни на старих commits, переписування опублікованої історії змусить їх узгоджувати свої гілки з новою лінією.

Практичне правило: перед rebase на приватній робочій гілці перевірте її базу й домовтеся, якщо хтось інший уже використовує ці commits. Не переписуйте спільну гілку мовчки. Якщо віддалений стан змінився, push може бути відхилений; силове передавання історії особливо небезпечне для спільної гілки. GitHub окремо пояснює, що його rebase-and-merge створює нові commit-ідентифікатори та не в усьому тотожне локальному git rebase (GitHub Docs: Pull request merges). Варіанти злиття Pull Request на GitHub і їхні результати докладніше розглянуто в главі 5.

Squash об'єднує кілька дрібних змін в один логічний commit. Наприклад, робоча гілка могла містити commits «чернетка», «помилка в тексті» та «уточнення назви», а команді потрібен один зрозумілий запис про завершену функцію. На GitHub стратегія squash-and-merge додає зміни PR як один commit до базової гілки. Це спрощує її історію, але проміжні commits більше не відображаються там як окремі кроки. Команда обирає формат під власну потребу; немає одного правильного стилю для всіх репозиторіїв.

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

Схожі за назвою команди Git мають різні наслідки.

  • revert створює новий commit, який намагається протилежно застосувати зміни вибраного commit. Це залишає попередню подію в історії та додає пояснення, як її скасовано.
  • reset переміщує HEAD чи index до вибраного стану. За певних режимів змінюється й робоче дерево. Переміщення покажчика може прибрати commits з поточної історії, а режим --hard ще й замінює файли; це може знищити незбережені правки.
  • restore копіює вибраний стан файла до index або робочого дерева. Так можна, наприклад, прибрати файл зі staging area, але відновлення вмісту може затерти незбережену роботу.

Перед будь-якою з цих дій перевірте git status і git diff, чи історія вже спільна, та чи не затрете чужі незакомічені правки (Git: Reset, restore and revert).

Якщо неправильна зміна вже з'явилася у спільній main, новий revert часто безпечніший для історії, бо всі можуть побачити і початкову зміну, і компенсувальну. Якщо ж приватний commit ще ніде не передавався й ніхто не залежить від нього, команда може погодити інше виправлення історії. Критерій — не естетика графа, а хто вже отримав цю історію і які локальні зміни треба зберегти.

Force push — передавання, що може замінити віддалений покажчик історії новою лінією, навіть якщо звичайний push відхилив би цю зміну. Це може витіснити commits, на яких працює колега. Навіть варіант із перевіркою, що покажчик не змінився після останнього читання, не замінює домовленості. Спільну історію переписують лише після перевірки авторства, домовленості та способу відновлення.

Cherry-pick, tag, merge, release і deploy — різні події

cherry-pick застосовує зміну з одного наявного commit до поточної гілки й створює новий commit. Це корисно, коли в іншу гілку треба перенести одну вибрану правку, але може залишити за кадром її залежності. Треба перевірити, чи потрібні попередні commits, конфігурація або супутні зміни. Довідка Git прямо визначає cherry-pick як застосування змін наявних commits (git-cherry-pick).

Tag — назване посилання на певний об'єкт, часто commit. Легкий tag — лише назва; annotated tag містить дату, автора tag і повідомлення, інколи цифровий підпис. Наприклад, команда може позначити commit v2.4.0, щоб посилатися на нього як на версію. Сам tag не збирає бінарний файл і не викладає систему користувачам (git-tag).

Важливо розрізняти завершальні етапи:

Подія Що сталося
Commit Стан збережено у локальній історії
Push Commit передано до віддаленого репозиторію
Pull Request Зміну запропоновано для обговорення та review (рецензування)
Merge Історію інтегровано у цільову гілку
Release Команда оголосила або підготувала конкретну версію для використання
Deploy Збірку встановили у вибране середовище
Підтвердження роботи Перевірили потрібний користувацький результат у цьому середовищі

У GitHub можна обрати merge commit, squash або rebase-and-merge; кожен варіант має різний результат для історії. Злиття PR не доводить, що зміну розгорнуто або що вона дала очікуваний результат у продукті. Release і deploy залежать від процесів команди поза самим фактом commit. У главі 5 розглянуто GitHub PR, перевірки та докази release і deployment.

Повністю розібрані приклади

Приклад 1. Дві незалежні зміни в магазині

Ситуація. Марта змінює правило, яке попереджає продаж останньої одиниці товару, а Олег — текст листа про відправлення замовлення. Обоє починають з commit A у main.

Дія Марти. Вона створює гілку inventory-low-stock, змінює серверне правило, перевіряє diff, готує потрібний файл і створює commit B. Після push колеги бачать його в цій гілці, але він ще не є частиною main.

Дія Олега. У гілці shipping-email-copy він змінює шаблон листа, створює commit C і теж робить push. Два commits походять від спільної точки A.

Review і рішення про merge. Рецензент підтверджує обидві зміни, автоматичні перевірки PR проходять, а власник продукту приймає підсумковий diff. Команда об'єднує гілки за погодженим процесом; деталі станів Pull Request, review і перевірок розглянуто в главі 5.

Перевірка після merge. Команда відкриває актуальну main і перевіряє, що обидві зміни ввійшли до неї та їхній спільний результат узгоджений. Оскільки вони торкаються різних файлів, текстового конфлікту немає; однак треба пересвідчитися, що лист не обіцяє відправлення товару, якого немає в наявності. Якщо для однієї зміни потрібні конфігурація або зовнішнє налаштування, саме перебування commits у main не доводить їхньої правильності чи того, що зміни вже працюють у production.

Приклад 2. Конфлікт у тарифі підписки

Ситуація. У навчальному прикладі базовий місячний тариф сервісу аналітики становить п’ятсот норвезьких крон. Продажі змінюють його в одній гілці на п’ятсот п’ятдесят, фінансова команда — в іншій на шістсот. Обидві правки торкаються того самого рядка; числа вигадані.

Дія інструмента. Під час merge Git не вгадує; у файлі може з'явитися приблизно такий фрагмент:

<<<<<<< sales-price
Місячний тариф коштує п’ятсот п’ятдесят норвезьких крон.
=======
Місячний тариф коштує шістсот норвезьких крон.
>>>>>>> operations-price

Що має зробити команда. Спершу з’ясувати, чи одна з правок застаріла, чи за ними стоять різні умови. Тут продажі мали на увазі ціну для нових клієнтів, а фінансова команда — продовження підписки для наявних. Власник продукту вирішує записати обидва правила: п’ятсот п’ятдесят крон для нових клієнтів і шістсот — для продовження.

Результат. Автор прибирає маркери, записує узгоджені умови, перевіряє diff і просить власника підтвердити їхній зміст. Команда перевіряє обидва тарифи на окремих прикладах: для нового клієнта й для продовження підписки. Тепер merge об’єднує дві вимоги, а не залишає випадково одну з них.

Чому не прийняти один бік. Це прибрало б позначки, але могло б стерти другу вимогу. Конфлікт — сигнал узгодити намір із власником правила, а не вибрати варіант за його назвою.

Інша можливість і межа. Якщо власник продукту недоступний, роботу варто лишити незлитою або використати узгоджену попередню політику. Тимчасова ціна з припущенням не має ставати чинною непомітно. Перевірка формату не замінює підтвердження бізнесового змісту.

Приклад 3. Помилка вже в main

Ситуація. Після merge команда виявляє, що нова перевірка в CRM відхиляє давні записи клієнтів, у яких немає необов’язкового номера телефону. Двоє колег уже отримали актуальну main і почали наступну роботу.

Дія та вибір. Власник продукту вирішує тимчасово повернути попередню поведінку. Команда спершу зберігає номер commit і перевіряє, які наступні зміни від нього залежать. Оскільки commit уже спільний, створює окрему компенсувальну зміну через revert та проводить її через PR, review і потрібні перевірки. Початковий commit залишається в історії, а новий запис пояснює, що саме скасовано й чому.

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

Чому не reset --hard на спільній гілці. Він переписав би узгоджену історію й міг би стерти локальні правки. Для спільного commit revert лишає видимими початкову зміну та її скасування.

Чому відкат продукту — інша операція. revert створює нову зміну у репозиторії. Відкат deployment може повторно встановити раніше зібрану версію або іншим способом повернути конфігурацію. Одне не виконує інше автоматично: команда має визначити потрібний безпечний стан, зберегти дані й перевірити його окремо.

Якщо помилковий commit ще приватний і ніхто не залежить від нього, команда може погодити інше виправлення історії. Але зміна файлів Git не скасовує вже виконану міграцію чи зміну production-даних: відновлення тексту або коду не повертає зовнішній стан автоматично. Його треба перевірити та відновити окремо.

Практична дисципліна для агента

Перед зміною перевірте репозиторій, гілку, робоче дерево, index і поточний diff. Збережіть чужі правки: не додавайте весь index і не перезаписуйте незрозумілі файли, доки не з'ясуєте, кому вони належать.

  • Перед commit: звірте підготовлені файли й повідомлення із задачею.
  • Перед push: перевірте, чи саме ця гілка призначена для спільного репозиторію.
  • Перед PR: назвіть цільову гілку, базовий commit, виконані й невиконані перевірки, їхню ревізію та відомі обмеження.
  • Після нових правок: повторіть перевірки, на які вони вплинули. Локальний commit засвідчує зміну історії, але сам не доводить, що її протестовано, передано, змерджено чи встановлено.

Це узгоджується з описаними Drive Capital практиками: поділ великої функції на менші PR може пришвидшити перегляд, а невдалу зміну команда повертала через окремий revert PR. Це приклад робочого процесу однієї команди, а не універсальна політика Git чи доказ, що кнопка revert завжди безпечна (Drive Capital, Git Workflow, commit c67fa12).

Підсумок

  • Робоче дерево, index і commit — окремі стани; diff до й після staging показує різні зміни.
  • Commit зберігає локальну історію, push передає її віддалено, а merge інтегрує її в цільову гілку. Branch — рухомий покажчик; main — домовленість команди.
  • Merge і rebase поєднують історії по-різному. Конфлікти вимагають змістового рішення; revert, reset і restore мають різні наслідки.
  • Tag позначає точку історії, але сам не виконує release чи deploy. Git зберігає зміни, не підтверджуючи їхню бізнесову правильність або поведінку в production.

Ці події відповідають на різні запитання: чи є зміна у локальній історії, чи передали її колегам, чи прийняли до цільової гілки, чи підготували й установили версію та чи отримали потрібний користувацький результат. Git допомагає відстежувати історію, але не визначає бізнесове рішення та не гарантує, що розгорнута система відповідає вимозі.

Джерела та подальше читання

  • Git Project, Git Reference, сторінки довідки станом на Git 2.55.0; прочитано 23 вересня 2026 року. Використано для точної ролі status, diff, add, commit, merge, rebase, revert, restore, reset, cherry-pick і tag. Довідка описує інструмент, але не встановлює командну політику.
  • Scott Chacon і Ben Straub, Pro Git, 2nd Edition, 2014, онлайн-версія з виправленнями спільноти; прочитано 23 вересня 2026 року. Використано глави про Git Basics, Working with Remotes і Git Branching для пояснення commits, гілок, HEAD та передачі історії.
  • GitHub Docs, Merge conflicts та Pull request merges, чинні вебсторінки без сталої дати видання; прочитано 23 вересня 2026 року. Використано для конфліктів і відмінності між merge commit, squash і rebase-and-merge у GitHub.
  • IT Revolution, 24 Key Capabilities to Drive Improvement in Software Delivery, 1 вересня 2020 року; прочитано 23 вересня 2026 року. Автор зазначає, що стаття адаптує уривок з Accelerate. Використано для обережного зв'язку контролю версій production-артефактів і малих порцій роботи з можливостями доставки; це не універсальна гарантія ефекту Git.
  • Drive Capital, Git Workflow, commit c67fa12e5c76510e960257c929b5353989b20144; прочитано 23 вересня 2026 року. Використано як задокументований опис практик однієї команди щодо поділу змін, PR і повернення невдалої зміни; це не загальний стандарт і не емпірична оцінка.