Теорія
Конфлікти злиття (merge conflict): чому виникають і що робити
Що таке merge conflict на GitHub і чому дві гілки можуть зіткнутися на тому самому рядку, що означає «This branch has conflicts that must be resolved», як читати маркери конфлікту й розв'язати простий конфлікт у браузері і чому вибір правильного тексту це продуктове рішення.
~10 хв читання · 11 вправ · 250 балів
Перед початком
Сіра кнопка Emma
Звичайний день у продуктовій команді Котокорму, аж поки Emma, яка пише тексти для магазину, не надсилає панічне повідомлення. Її pull request #132 повідомляє, що в гілці є конфлікти, які треба розв'язати, кнопка merge сіра, і Emma думає, що зламала сайт.
У цьому уроці ти з'ясуєш, чому дві гілки можуть зіткнутися на одному рядку, прочитаєш маркери конфлікту, розв'яжеш простий конфлікт у браузері й побачиш, чому вибір правильного тексту — продуктове рішення.
Конфлікт блокує один мердж, і більше нічого
Контент-маркетологиня Котокорму Emma відкриває свій pull request #132, «Rewrite delivery page copy», і бачить «This branch has conflicts that must be resolved». Кнопка мерджу сіра. Перша думка: вона зламала сайт.
Rewrite delivery page copy#132
This branch has conflicts that must be resolved: мердж заблоковано. Конфліктний файл: pages/delivery.md
Raise free-delivery threshold to 1000 UAH#133
Змерджено вчора як коміт 6f2c8a1
Ні. Конфлікт (merge conflict) блокує один мердж, і більше нічого. main, живий сайт і робота Emma в її гілці лишаються такими, як були. Pull request просто чекає, доки хтось ухвалить рішення.
Що сталося? Pull request #133 від Daniel, уже змерджений, підняв поріг безкоштовної доставки й змінив одне речення на сторінці доставки. Emma переписала всю сторінку, і це речення теж. Git отримав дві різні версії одного рядка й не став угадувати, яка має перемогти.

Повідомлення ще й називає файл, тут pages/delivery.md, тож відомо, куди дивитися. Для продакта правильна перша реакція — спокій: на сайті нічого не змінилося, нічого не втрачено, а розв'язання — це розмова про те, що має бути на сторінці.
- Так«Нічого не зламано; #132 просто чекає рішення щодо рядка 12».
- Не так«Конфлікт, відкочуйте сайт!»
Конфліктна зміна на сайт навіть не потрапила.
Коли Git зупиняється й питає
Щоб змерджити, Git знаходить коміт, де дві лінії роботи розійшлися, — їхню спільну точку відліку — і порівнює з нею кожну сторону.
- Якщо місце змінила лише одна сторона, Git бере цю зміну.
- Якщо сторони змінили різні файли або різні частини одного файлу, Git об'єднує їх сам. Так минає більшість мерджів.
- Якщо обидві сторони змінили місце на точнісінько той самий текст, суперечити нема чому.
Конфлікт виникає, лише коли обидві сторони по-різному змінили ті самі рядки або сусідні, або коли одна сторона видалила файл, який інша редагувала. Для такого випадку правила немає: яка версія переможе — це рішення, а рішення Git залишає людям.

У #132 спільна точка казала «Free delivery on orders from 800 UAH.» main змінила це на «...from 1000 UAH.», а гілка Emma — на «Orders from 800 UAH ship free, right to your door.» Той самий рядок, дві різні правки: конфлікт. Ні Daniel, ні Emma нічого не зробили неправильно; вони просто торкнулися того самого речення того самого тижня.
e7c3b19Add summer promo bannerf02a6d4Update FAQ answerse19c3b7Rewrite delivery page copycontent/delivery-copy8be2f70Add cat treats bundle6f2c8a1Raise free-delivery threshold to 1000 UAH (#133)main
Граф комітів, від найстарішого: content/delivery-copy і main сьогодні 1. коміт e7c3b19 у main "Add summer promo banner" 2. гілка content/delivery-copy починається від коміту e7c3b19 3. коміт f02a6d4 у main "Update FAQ answers" 4. коміт e19c3b7 у content/delivery-copy "Rewrite delivery page copy" (виділено) 5. коміт 8be2f70 у main "Add cat treats bundle" 6. коміт 6f2c8a1 у main "Raise free-delivery threshold to 1000 UAH (#133)" (виділено)
- ТакЧекати на конфлікт, коли двоє по-різному редагують те саме речення
- Не такЧекати на конфлікт лише тому, що двоє редагували той самий файл
Різні частини одного файлу зливаються самі.
Немає конфлікту — ще не означає правильно
Git — уважний поєднувач тексту, а не тестувальник. Він порівнює рядки й зупиняється, лише коли ті самі рядки змінили двома способами. Він не знає, який вигляд має сторінка, що рахує кошик і чи не заважають одна одній дві фічі.
Згадай історію Daniel про кнопку. Його гілка з редизайном і панель cookie від колеги змінювали різні файли, тож Git змерджив їх мовчки. На телефонах панель лягла на нову кнопку, натиснути її не міг ніхто, і за десять хвилин мердж відкотили. Конфлікту не було, а результат зламаний.
Тримай у голові обидві половини:
- Відсутність конфлікту не означає, що результат правильний. Після будь-якого мерджу, який поєднує роботу різних людей, перевір сторінку, бажано й на телефоні.
- Конфлікт не означає, що хтось помилився. Він лише означає, що двоє змінили той самий текст, і рішення має ухвалити людина.
Для продакта це корисний розподіл довіри: текст хай поєднує Git, а продукт перевіряй сам.
- Так«Злилося чисто. Перевірмо оформлення на телефоні, перш ніж оголошувати».
- Не так«GitHub не показав конфліктів, отже, точно працює».
Git перевіряє рядки тексту, а не поведінку.
Читаємо маркери конфлікту
Під повідомленням про конфлікт у #132 є кнопка Resolve conflicts («розв'язати конфлікти»). Вона відкриває простий редактор просто на GitHub, уже з конфліктним файлом. Схоже на звичайний текст із дивними стрілками посередині:
## Delivery
<<<<<<< content/delivery-copy
Orders from 800 UAH ship free, right to your door.
=======
Free delivery on orders from 1000 UAH.
>>>>>>> main
Couriers deliver within one day in Kyiv.
Це маркери конфлікту:
<<<<<<<відкриває конфлікт і називає гілку, з якої прийшла верхня версія, тутcontent/delivery-copyвід Emma;=======розділяє дві версії;>>>>>>>закриває конфлікт і називає другу гілку, тутmain, чия версія стоїть одразу над ним.
Усе поза маркерами вже узгоджено: заголовок і рядок про кур'єрів рішення не потребують. На людину чекає лише частина між стрілками. А маркери — не текст для сторінки: усі три рядки з ними треба прибрати. Розробники бачать точнісінько такі маркери у своєму редакторі коду після git merge у терміналі.
- ТакВирішувати лише між
<<<<<<<і>>>>>>>; решту лишити як є - Не такЗаодно переписати в редакторі заголовок і рядок про кур'єрів
Рядки поза маркерами вже узгоджено.
Конфлікт — це питання про намір
Перший порив Emma — залишити свою версію; зрештою, це її pull request. Але тоді сторінка обіцятиме безкоштовну доставку від 800 грн, а кошик після #133 робитиме її безкоштовною лише від 1000. Клієнти платитимуть за доставку, яку сторінка назвала безкоштовною. А якщо залишити рядок Daniel, пропаде новий текст Emma, який погодив маркетинг.
Тож правильна відповідь — жодна зі сторін: слова Emma з числом Daniel, «Orders from 1000 UAH ship free, right to your door.»

У цьому суть розв'язання конфліктів. Конфлікт — не помилка, а питання: що мав на увазі кожен і що тепер має бути на сторінці? Git на це не відповість. Розробники технічно розв'яжуть будь-який конфлікт, але часто не знають, чи фінвідділ знову не передумав і яке формулювання погодив маркетинг. Тут і потрібен продакт.
У редакторі можна залишити одну сторону, обидві або написати щось нове. Вибирай за наміром, а не за тим, чий це pull request, і переконайся, що в результаті сторінка читається як одне ціле.
- Так«Orders from 1000 UAH ship free, right to your door.»
Погоджені слова плюс чинний поріг.
- Не такЗалишити свою сторону, бо це твій pull request
Сторінка суперечитиме кошику.
Розв'язуємо конфлікт на GitHub
Для конфлікту в текстовому файлі вистачає вебредактора GitHub. Кроки по порядку:
- У pull request натисни
Resolve conflicts. - Знайди блок між
<<<<<<<і>>>>>>>. - Заміни весь блок погодженим текстом і видали всі три рядки з маркерами. Тут: «Orders from 1000 UAH ship free, right to your door.»
- Натисни
Mark as resolved(«позначити як розв'язане»). Це каже GitHub, що з файлом усе вирішено. Якщо конфліктних файлів кілька, вибери наступний у списку ліворуч і повтори. - Натисни
Commit merge(«закомітити мердж»). - Перевір вкладку
Files changedу pull request так, як це зробив би рев'юер: там має бути лише переписаний текст Emma, з 1000 у рядку 12 і без жодного сліду 800. Тоді — рев'ю і мердж pull request, як завжди.
Порядок важливий: спершу редагування, потім Mark as resolved, і лише тоді Commit merge. І розв'язання — ще не фініш: воно оновлює тільки гілку. У main і на сайт зміна потрапить, коли сам pull request пройде рев'ю і його змерджать.
- ТакРедагування →
Mark as resolved→Commit merge→ перевіркаFiles changed→ рев'ю і мердж - Не такОголосити новий текст, щойно натиснув
Commit mergePull request ще треба переглянути й змерджити.
Забуті маркери потрапляють на сторінку
Коли комітять розв'язаний мердж, Git довіряє людині. Те, що лежить у файлі в цю мить, і є розв'язанням. Git за тебе не прибирає: забутий <<<<<<<, ======= чи >>>>>>> — просто текст, він комітиться, як будь-який інший рядок, і після мерджу може з'явитися на живій сторінці доставки перед кожним клієнтом.
Тому Daniel і каже «перевір двічі». Вебредактор GitHub трохи допомагає, бо чекає, що маркери зникнуть, перш ніж файл позначать як розв'язаний. У терміналі ніщо нікого не зупинить. Розробники страхуються командою git diff --check, яка помічає забуті маркери конфлікту, або шукають у файлі <<<<<<< перед комітом.
Для продакта страхувальна сітка — рев'ю. Після розв'язання відкрий Files changed і проглянь конфліктний файл: там має бути погоджений текст, без стрілок і рядків зі знаків рівності. Якщо помітиш маркер, залиш коментар до цього рядка, перш ніж хтось змерджить.
- ТакПеред схваленням проглянути
Files changedна<<<<<<<,=======і>>>>>>> - Не так«Git сам прибере маркери під час мерджу».
Маркери комітяться як звичайний текст.
Куди потрапляє розв'язання
Що саме комітить Commit merge і куди? Мердж-коміт у гілці pull request, а не в main. Щоб розв'язати конфлікт, GitHub зливає main у content/delivery-copy, і твоє рішення всередині. Станом на 2026 рік його повідомлення таке: «Merge branch 'main' into content/delivery-copy».
На графі стрілка мерджу йде з main у гілку. Після цього в гілці є все з main плюс переписаний текст Emma, тож вона на нуль behind, а повідомлення про конфлікт зникає. Сама main не змінилася. Попередні коміти Emma всі на місці; нічого не викинуто.
e7c3b19Add summer promo bannerf02a6d4Update FAQ answerse19c3b7Rewrite delivery page copy8be2f70Add cat treats bundle6f2c8a1Raise free-delivery threshold to 1000 UAH (#133)mainc90d4e7Merge branch 'main' into content/delivery-copycontent/delivery-copy
Граф комітів, від найстарішого: Після Commit merge 1. коміт e7c3b19 у main "Add summer promo banner" 2. гілка content/delivery-copy починається від коміту e7c3b19 3. коміт f02a6d4 у main "Update FAQ answers" 4. коміт e19c3b7 у content/delivery-copy "Rewrite delivery page copy" 5. коміт 8be2f70 у main "Add cat treats bundle" 6. коміт 6f2c8a1 у main "Raise free-delivery threshold to 1000 UAH (#133)" 7. мердж-коміт c90d4e7: main змерджено в content/delivery-copy "Merge branch 'main' into content/delivery-copy" (виділено)
Кнопка мерджу повертається й чекає на рев'ю. Оскільки Котокорм робить squash, цей додатковий мердж-коміт в історію main не потрапить: main отримає один чистий коміт для #132.
Тож щоразу, бачачи мердж на графі, читай напрямок. Мердж у гілку означає, що гілку оновили чи розв'язали в ній конфлікт; мердж у main — що робота приземлилася. Сайт змінюється лише від другого.
- Так«Merge branch 'main' into content/about-page» → гілку оновили;
mainне змінилася - Не такЧитати мердж у гілку як «уже на сайті»
На сайт потрапляє лише мердж у
main.
Хто розв'язує і що робить продакт
Вебредактор GitHub уміє розв'язувати один вид конфліктів: різні правки тих самих рядків текстового файлу. Цього вистачає для більшості конфліктів у текстах. Інші види потребують командного рядка, наприклад коли одна гілка видалила файл, який інша редагувала, або обидві замінили те саме зображення. Тоді GitHub вимикає Resolve conflicts і пише, що конфлікт надто складний для вебредактора; це робота розробника.
Хто розв'язує? Зазвичай автор заблокованого pull request разом з автором зміни, з якою сталося зіткнення. Продакт приносить відповідь на питання «що тут має бути написано?». Ця частина роботи така:
- з'ясувати, що мала на увазі кожна сторона, наприклад з іншого pull request і його issue;
- погодити результат із причетними людьми;
- записати погоджений текст і причину в коментарі до pull request;
- потім перевірити
Files changed.
А чого не робити: вибирати сторону, аби кнопка стала зеленою, розв'язувати конфлікти в коді чи конфігурації, яких не розумієш, і редагувати чужу гілку, не спитавши.
- Так«Погоджено з Emma і Daniel: її формулювання, 1000 грн із #133 (запит фінвідділу)».
Намір, рішення й причина — записані.
- Не такРозв'язати конфлікт у JavaScript оформлення замовлення, бо редактор дозволяє
Конфлікти в коді — справа розробників, які за цей код відповідають.
Як мати менше конфліктів
Чому конфлікт Emma взагалі стався? Її гілка жила понад тиждень і переписала цілу сторінку. А main тим часом пішла вперед без неї: коли Emma відкрила pull request, гілка вже відставала на три коміти. Кожен із них був шансом на зіткнення.
Ризик росте з трьома речами, які видно просто зі списку pull requests: скільки гілка живе, скільки файлів зачіпає і наскільки відстає від main. Виправлення в одному файлі, відкрите вчора, зіштовхується рідко. Шеститижневий редизайн на 38 файлів, що відстає на 41 коміт, майже напевно зіштовхнеться, і конфлікти будуть великі.
Звички, що цьому запобігають:
- маленькі гілки, одна задача на гілку, мердж за кілька днів;
- у довшу гілку рано підтягувати свіжу
main, поки конфлікти ще дрібні; - розмовляти: один рядок у командному чаті, «цього тижня я переписую сторінку доставки», — і Daniel написав би Emma, перш ніж чіпати те речення.

Останнє — теж робота продакта: знати, що фінвідділ і маркетинг змінюють ту саму сторінку того самого тижня.
- Так«Попереджаю: цього тижня переписую сторінку доставки».
Один рядок у чаті запобігає зіткненню.
- Не такДати гілці з редизайном на 38 файлів відстати від
mainна 41 комітКожен пропущений коміт — шанс на зіткнення.
Готові потренуватися?
Виконайте вправи уроку й отримайте 250 балів, коли пройдете його вперше.
Розпочати вправи