Пошук уроків, статей та іншого контенту
Розбираємо конфлікти злиття, узгоджуємо командний workflow і безпечно інтегруємо паралельні зміни.
Конфлікт злиття виникає, коли Git не може автоматично об’єднати зміни з різних гілок. Найчастіше це трапляється, коли двоє розробників змінили одні й ті самі рядки або один із них змінив файл, який інший видалив.
Наприклад:
у гілці main заголовок змінено на Welcome;
у гілці feature/login той самий заголовок змінено на Sign in;
Git не може самостійно визначити, який варіант має залишитися.
Конфлікт — це не помилка в історії Git. Це сигнал, що потрібно прийняти рішення щодо несумісних змін.
Під час злиття Git зупиняє операцію та повідомляє про конфлікт:
Auto-merging src/header.js
CONFLICT (content): Merge conflict in src/header.js
Automatic merge failed; fix conflicts and then commit the result.У файлі з конфліктом з’являються спеціальні маркери:
const title = '<<<<<<< HEAD
Dashboard
=======
User profile
>>>>>>> feature/profile';Маркери мають таке значення:
<<<<<<< HEAD — початок змін із поточної гілки;
======= — роздільник між двома варіантами;
>>>>>>> feature/profile — кінець змін із гілки, яку ми намагаємося злити.
У реальному JavaScript-файлі це виглядало б так:
const title = '<<<<<<< HEAD
Dashboard
=======
User profile
>>>>>>> feature/profile';Однак Git вставляє маркери як звичайний текст у файл, тому правильніше побачити конфлікт можна на такому прикладі:
<<<<<<< HEAD
const title = 'Dashboard';
=======
const title = 'User profile';
>>>>>>> feature/profileПотрібно вручну залишити правильний варіант або створити третій:
const title = 'Account';Після цього маркери конфлікту необхідно видалити.
Розглянемо ситуацію: розробник працює у своїй гілці та хоче інтегрувати останні зміни з main.
git statusКоманда покаже:
чи триває операція злиття;
які файли мають конфлікти;
які зміни вже підготовлені до коміту.
Файли з конфліктами зазвичай мають статус both modified.
git diff --name-only --diff-filter=UПрапорець U означає unmerged — файл ще не об’єднано.
Для перегляду самого конфлікту використовуйте:
git diffДля кожного конфліктного фрагмента потрібно:
зрозуміти зміни обох гілок;
визначити потрібну поведінку;
відредагувати файл;
видалити маркери <<<<<<<, =======, >>>>>>>.
Не варто механічно залишати весь варіант однієї гілки. Часто правильне рішення — об’єднати обидві зміни.
Після редагування додайте файл до індексу:
git add src/header.jsgit add у цьому випадку не створює коміт. Він повідомляє Git, що конфлікт у файлі розв’язано.
Перевірте стан:
git statusЯкщо всі конфлікти розв’язані, Git покаже, що потрібно завершити злиття.
Перед комітом перегляньте підготовлені зміни:
git diff --stagedЗапустіть перевірки проєкту:
npm test
npm run lintКонкретні команди залежать від проєкту, але перевіряти потрібно не лише відсутність маркерів, а й правильність поведінки програми.
git commitGit запропонує повідомлення для merge-коміту. За потреби його можна змінити.
Або повідомлення можна вказати одразу:
git commit -m "Merge main into feature/profile"Нижче наведено повний локальний приклад із двома гілками. Він створює конфлікт у файлі config.txt, допомагає знайти його та завершити злиття.
#!/usr/bin/env bash
set -e
# Створюємо тимчасовий репозиторій
rm -rf git-conflict-demo
mkdir git-conflict-demo
cd git-conflict-demo
git init -b main
git config user.name "Demo Developer"
git config user.email "demo@example.com"
# Створюємо початковий файл
printf "theme=light\n" > config.txt
git add config.txt
git commit -m "Add initial configuration"
# Створюємо гілку паралельної роботи
git switch -c feature/dark-theme
printf "theme=dark\n" > config.txt
git add config.txt
git commit -m "Set dark theme"
# Повертаємося в main і вносимо несумісну зміну
git switch main
printf "theme=system\n" > config.txt
git add config.txt
git commit -m "Use system theme"
# Починаємо злиття — Git зупиниться через конфлікт
if git merge feature/dark-theme; then
echo "Конфлікту не виникло"
else
echo "Конфлікт виник у config.txt"
fi
# Замінюємо конфліктний файл узгодженим варіантом
printf "theme=system\n" > config.txt
# Позначаємо конфлікт розв’язаним і завершуємо merge
git add config.txt
git commit -m "Merge feature/dark-theme and resolve configuration conflict"
# Показуємо результат
printf "\nІсторія репозиторію:\n"
git log --oneline --graph --all
printf "\nВміст config.txt:\n"
cat config.txtУ реальній роботі не потрібно автоматично перезаписувати конфліктний файл. У прикладі це зроблено лише для демонстрації. Під час командної роботи рішення має ґрунтуватися на вимогах до функціональності та обговоренні з автором змін.
Git дозволяє вибрати один із готових варіантів під час конфлікту.
Для злиття через git merge:
git checkout --ours path/to/file
git checkout --theirs path/to/file--ours залишає версію з поточної гілки;
--theirs залишає версію гілки, яку зливають.
Після цього файл потрібно додати:
git add path/to/fileУ сучасних версіях Git також можна використовувати:
git restore --ours path/to/file
git restore --theirs path/to/fileВажливо: ours і theirs залежать від операції та її контексту. Під час звичайного merge ours — це поточна гілка, а theirs — гілка, яку приєднують. Тому перед використанням цих команд перевірте, яку саме операцію виконуєте.
Механічний вибір одного варіанта може видалити важливу частину логіки. Використовуйте його лише тоді, коли ви справді розумієте наслідки.
Якщо під час розв’язання стало зрозуміло, що злиття почалося передчасно або зміни важко об’єднати, його можна скасувати:
git merge --abortРепозиторій повернеться до стану, який був до початку merge.
Скасування злиття не видаляє коміти з гілок. Воно лише припиняє поточну незавершену операцію.
Якщо конфлікт виник під час rebase, використовуйте:
git rebase --abortІнші команди для rebase:
git add path/to/file
git rebase --continueЩоб пропустити поточний коміт під час перебазування:
git rebase --skipgit merge --abort і git rebase --abort не взаємозамінні: використовуйте команду відповідно до поточної операції.
Команда має заздалегідь домовитися, як інтегрувати зміни.
Розробник оновлює локальну інформацію та зливає main у свою робочу гілку:
git switch feature/profile
git fetch origin
git merge origin/mainКонфлікт розв’язується у робочій гілці, після чого зміни перевіряються та надсилаються на сервер:
git add .
git commit
git push origin feature/profileПереваги:
не змінюється історія вже опублікованої гілки;
процес добре підходить для спільних гілок;
злиття явно видно в історії.
Розробник переносить власні коміти поверх актуального main:
git switch feature/profile
git fetch origin
git rebase origin/mainЯкщо виник конфлікт:
git status
# Редагування конфліктних файлів
git add path/to/file
git rebase --continueПісля успішного rebase локальна історія була переписана. Якщо гілка вже публікувалася, для надсилання потрібен безпечний варіант примусового push:
git push --force-with-lease origin feature/profile--force-with-lease перевіряє, що віддалена гілка не отримала нових змін від іншого розробника. Це безпечніше за безумовний --force, але все одно потребує уважності.
Не слід без узгодження робити rebase гілки, у якій працюють інші люди. Переписування спільної історії може ускладнити роботу всій команді.
Один із практичних варіантів роботи:
Створюйте окрему гілку для кожної задачі.
Регулярно синхронізуйте її з актуальним main.
Робіть невеликі коміти з логічними змінами.
Перед створенням pull request оновіть гілку та розв’яжіть конфлікти локально.
Запускайте тести й перевірки після розв’язання.
Просіть автора конфліктної зміни допомогти прийняти рішення, якщо контекст незрозумілий.
Не зливайте неперевірений код лише для того, щоб прибрати конфлікт.
Після успішної інтеграції видаляйте непотрібні локальні та віддалені гілки за правилами команди.
Конфлікт часто виникає через довгоживучі гілки. Чим довше гілка відхиляється від основної, тим більше змін може накопичитися та тим складніше їх узгодити.
Під час розв’язання конфлікту дотримуйтеся такого порядку:
Прочитайте контекст обох змін. Перегляньте коміти та за потреби поспілкуйтеся з автором.
Визначте очікувану поведінку. Питання не в тому, який текст залишити, а в тому, який результат має отримати користувач.
Змініть код вручну. Об’єднайте варіанти, якщо це необхідно.
Перевірте сусідню логіку. Конфліктний фрагмент може впливати на імпорти, типи, формат даних або тести.
Перегляньте staged diff.
Запустіть тести та статичні перевірки.
Зробіть зрозумілий коміт.
Для складного конфлікту корисно переглянути історію конкретного файлу:
git log --oneline -- path/to/fileА зміни конкретного коміту:
git show <commit>Це допомагає зрозуміти не лише поточний текст, а й причину кожної зміни.
Якщо не виконати git status, легко пропустити конфлікт в іншому файлі або випадково змінити не пов’язані файли.
git status
git diff --name-only --diff-filter=UРядки <<<<<<<, ======= і >>>>>>> не повинні потрапити у фінальний код. Пошук можна виконати так:
git grep -n -E '<<<<<<<|=======|>>>>>>>'Якщо команда нічого не вивела, маркери в робочих файлах не знайдено.
Файл може успішно пройти злиття на рівні синтаксису Git, але містити неправильну бізнес-логіку. Після конфлікту обов’язково запускайте доступні тести та перевірки.
ours або theirs без розуміння контекстуЦі параметри вирішують текстовий конфлікт, але не гарантують правильність результату. Один із варіантів може містити потрібний імпорт або важливе виправлення з іншої гілки.
git add . без перегляду змінКоманда додасть до індексу всі зміни в поточному каталозі, включно з випадковими. Безпечніше спочатку перевірити:
git status
git diffПотім додати конкретні розв’язані файли:
git add src/header.js src/navigation.js--forceБезумовний --force може перезаписати чужі коміти. Якщо переписування історії необхідне, використовуйте:
git push --force-with-leaseі попередьте команду.
Мета розв’язання конфлікту — не зробити файл схожим на один із варіантів, а зберегти потрібну функціональність. Якщо вимоги суперечать одна одній, спочатку узгодьте рішення з командою.
Конфлікт виникає, коли Git не може автоматично об’єднати паралельні зміни.
Конфліктні файли потрібно перевірити через git status і git diff.
Після ручного редагування маркери конфлікту видаляються, а файл додається через git add.
Злиття завершується git commit, а перебазування — git rebase --continue.
Для скасування використовуйте git merge --abort або git rebase --abort.
ours і theirs слід застосовувати лише після перевірки контексту.
Команда має домовитися про правила merge, rebase, публікації гілок і перевірки перед інтеграцією.
Безпечне розв’язання конфлікту включає обговорення поведінки, перегляд diff і запуск тестів.