Гілки і злиття в Git: switch, merge, конфлікти
Як створювати й перемикати гілки, чим fast-forward відрізняється від merge-коміту, squash merge, і покроковий розбір конфлікту злиття з прикладом.
Навіщо гілки
Гілка — окрема лінія розробки. Поки ви робите нову фічу у своїй гілці, main лишається робочою: її можна деплоїти, від неї можна зробити термінове виправлення. Гілка в Git — лише вказівник на коміт (див. «Як працює Git»), тому створюється миттєво й нічого не коштує.
Правило: кожна задача — своя гілка. Закінчили — влили в main, гілку видалили.
Основні команди
| Дія | Команда |
|---|---|
| список локальних гілок | git branch |
| разом з віддаленими | git branch -a |
| з останнім комітом і upstream | git branch -vv |
| створити і перейти | git switch -c feature/search |
| створити від певного коміту чи гілки | git switch -c hotfix/login origin/main |
| перейти в існуючу | git switch main |
| повернутися в попередню | git switch - |
| перейменувати поточну | git branch -m feature/new-name |
| видалити злиту | git branch -d feature/search |
| видалити незлиту (примусово) | git branch -D feature/experiment |
| які гілки вже злиті в main | git branch --merged main |
| які ще ні | git branch --no-merged main |
switch і checkout
git checkoutуміє все: перемикати гілки, відновлювати файли, переходити на коміти. Через це легко помилитися. З Git 2.23 є дві вужчі команди:git switch— для гілок,git restore— для файлів. Старі команди теж працюють.
| Старе | Нове |
|---|---|
git checkout main |
git switch main |
git checkout -b feat |
git switch -c feat |
git checkout -- file |
git restore file |
Що станеться з незакоміченими змінами при перемиканні
- Якщо зміни не конфліктують з тим, що в іншій гілці, — Git перенесе їх із собою. Зручно, коли почали правити не в тій гілці:
git switch -c правильна-гілка— і зміни вже там. - Якщо конфліктують — Git відмовиться:
error: Your local changes to the following files would be overwritten by checkout:
src/app.py
Please commit your changes or stash them before you switch branches.
AbortingВаріанти: закомітити, або сховати (git stash, потім git stash pop — детально в нотатці «Скасування змін»).
Два види злиття
Злиття (merge) — перенести зміни з однієї гілки в іншу. Завжди вливаємо в поточну: спершу переходимо туди, куди зливаємо.
git switch main
git merge feature/searchЗалежно від історії Git зробить одне з двох:
Fast-forward — у main нічого нового не з'явилося з моменту відгалуження. Git просто пересуває вказівник main вперед. Нового коміту немає, історія лінійна.
Updating c9e8f02..9c0d1e2
Fast-forward
src/search.py | 40 ++++++++++++++++++++++++++++++++++++++++Злиття з merge-комітом (three-way merge) — обидві гілки пішли вперед. Git знаходить спільного предка, поєднує зміни з обох боків і створює коміт M з двома батьками.
Merge made by the 'ort' strategy.
src/search.py | 40 ++++++++++++++++++++++++++++++++++++++++Можна керувати поведінкою:
git merge --ff-only feature # лише fast-forward; якщо не вийде — помилка, нічого не змінено
git merge --no-ff feature # завжди створювати merge-коміт — видно, що це була окрема гілкаSquash merge — усі коміти гілки в один
git switch main
git merge --squash feature/search
git commit -m "Додати пошук по нотатках"Усі зміни гілки потрапляють в індекс як один набір — ви робите з них один коміт. Історія main чиста, але зв'язок із комітами гілки губиться, і Git не вважає гілку злитою (git branch -d попросить -D). Те саме робить кнопка «Squash and merge» на GitHub.
Конфлікти злиття
Конфлікт — це коли в обох гілках по-різному змінили ті самі рядки (або одну видалили, а іншу змінили). Git не вгадує, яка версія правильна, і просить вирішити вас. Це нормальна частина роботи, а не аварія.
Відтворимо конфлікт на практиці
mkdir conflict-demo && cd conflict-demo && git init
echo "timeout = 10" > config.ini
git add . && git commit -m "Початковий конфіг"
git switch -c feature
echo "timeout = 30" > config.ini
git commit -am "Збільшити таймаут до 30"
git switch main
echo "timeout = 15" > config.ini
git commit -am "Збільшити таймаут до 15"
git merge featureAuto-merging config.ini
CONFLICT (content): Merge conflict in config.ini
Automatic merge failed; fix conflicts and then commit the result.Крок 1. Подивитися, що конфліктує
git statusYou have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: config.iniСписок лише файлів з конфліктами: git diff --name-only --diff-filter=U.
Крок 2. Відкрити файл
<<<<<<< HEAD
timeout = 15
||||||| 3b18e51
timeout = 10
=======
timeout = 30
>>>>>>> feature| Маркер | Що між ним і наступним |
|---|---|
<<<<<<< HEAD |
версія поточної гілки (куди вливаємо, тут main) |
||||||| 3b18e51 |
версія спільного предка — показується з merge.conflictStyle = zdiff3 |
======= |
роздільник |
>>>>>>> feature |
версія гілки, яку вливаємо |
Блок предка дуже допомагає: видно, що було 10, одна гілка зробила 15, інша 30. Без нього — лише дві версії і жодного контексту. Увімкніть один раз:
git config --global merge.conflictStyle zdiff3Крок 3. Вирішити
Відредагуйте файл так, як має бути в результаті, і приберіть усі маркери:
timeout = 30Можна взяти одну зі сторін цілком, не редагуючи вручну:
git checkout --ours config.ini # версія поточної гілки (main)
git checkout --theirs config.ini # версія гілки, яку вливаємо (feature)VS CodeНад кожним конфліктом з'являються кнопки Accept Current / Accept Incoming / Accept Both, а для складних випадків є 3-way Merge Editor. Current = HEAD, Incoming = гілка, яку вливаєте.
Крок 4. Позначити вирішеним і завершити
git add config.ini
git commit # повідомлення «Merge branch 'feature'» уже підставленеПередумали зливати — все повернеться, ніби нічого не було:
git merge --abortНе комітьте маркериПошукайте перед комітом:
git diff --checkабоgrep -rn '<<<<<<<' .. Файл з<<<<<<<усередині зламає збірку, а YAML/JSON — ще й деплой.
rerere — Git запам'ятовує ваші рішення
Якщо той самий конфлікт трапляється знову (типово при повторному rebase), Git може вирішити його так само автоматично:
git config --global rerere.enabled trueЯк конфліктувати рідше
- Вливайте свіжий
mainу свою гілку частіше (або робіть rebase) — дрібні конфлікти простіше, ніж один великий через місяць. - Робіть маленькі гілки, що живуть дні, а не тижні.
- Не змішуйте автоформатування всього файлу з логічними змінами — форматування окремим комітом (і бажано окремим PR).
- Домовтеся про форматер і
.editorconfigу команді — тоді пробіли й лапки не стануть причиною конфліктів.
Іменування гілок
Популярна схема — префікс за типом задачі:
| Префікс | Для чого | Приклад |
|---|---|---|
feature/ або feat/ |
нова функціональність | feature/user-search |
fix/ або bugfix/ |
виправлення бага | fix/login-redirect |
hotfix/ |
терміново в прод | hotfix/payment-timeout |
chore/ |
залежності, конфіги | chore/bump-node-24 |
docs/ |
документація | docs/api-auth |
Часто додають номер задачі: feature/PRJ-142-user-search. Без пробілів і кирилиці — латиниця, цифри, -, /.
Прибирання гілок
git branch --merged main # злиті — можна видаляти
git branch -d feature/search # видалити локальну
git push origin --delete feature/search # видалити на сервері
git fetch --prune # прибрати origin/* гілки, яких уже немає на серверіВидалили гілку з незлитими комітами помилково? Git показав її хеш у повідомленні:
Deleted branch feature/experiment (was 9c0d1e2).Повернути: git branch feature/experiment 9c0d1e2. Повідомлення вже не видно — хеш знайдеться в git reflog.
Шпаргалка
| Команда | Що робить |
|---|---|
git switch -c <гілка> |
створити й перейти |
git switch - |
у попередню гілку |
git merge <гілка> |
влити в поточну |
git merge --ff-only / --no-ff |
лише перемотка / завжди merge-коміт |
git merge --squash <гілка> |
усі зміни одним комітом |
git merge --abort |
скасувати злиття з конфліктом |
git checkout --ours/--theirs <файл> |
взяти одну зі сторін у конфлікті |
git branch --merged main |
що вже можна видалити |
git push origin --delete <гілка> |
видалити гілку на сервері |
Збережіть статтю собі
Увійдіть, щоб імпортувати її у власні нотатки, відмічати вивчене, ставити лайки й питати спільноту. Реєстрація безкоштовна: вхід через Google займає кілька секунд.
Увійти через Google Інші способи входу