core code helper

Гілки і злиття в Git: switch, merge, конфлікти

Як створювати й перемикати гілки, чим fast-forward відрізняється від merge-коміту, squash merge, і покроковий розбір конфлікту злиття з прикладом.

0 імпортували Імпортувати собі

Навіщо гілки

Гілка — окрема лінія розробки. Поки ви робите нову фічу у своїй гілці, 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 вперед, а справжнє злиття створює merge-коміт M з двома батьками

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 feature
Auto-merging config.ini
CONFLICT (content): Merge conflict in config.ini
Automatic merge failed; fix conflicts and then commit the result.

Крок 1. Подивитися, що конфліктує

git status
You 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 Інші способи входу