Теорія
Злиття (git merge): мердж-коміт, squash і rebase на GitHub
Що робить мердж (git merge), три варіанти за кнопкою мерджу на GitHub (merge commit, squash and merge, rebase and merge) і що кожен лишає в історії, fast-forward простими словами і як перевірити, чи зміна справді в main.
~9 хв читання · 10 вправ · 250 балів
Перед початком
Зелена кнопка
Новини для продуктової команди Котокорму щодо запиту фінвідділу: pull request #133 від Daniel, що піднімає поріг безкоштовної доставки до 1000 грн, схвалено на рев'ю. Його назва — твоє повідомлення коміту з першого модуля. Лишився один крок: змерджити його.
У цьому уроці ти побачиш, що робить злиття, які три варіанти ховаються за кнопкою merge на GitHub і що кожен лишає в історії, а також як перевірити, чи зміна справді потрапила в main.
Що робить мердж
Знову в Котокормі: фінвідділ попросив підняти поріг безкоштовної доставки. Pull request #133 від Daniel, «Raise free-delivery threshold to 1000 UAH», отримав схвалення на рев'ю. Лишився один крок: змерджити його.
Мердж (merge), або злиття, — це перенесення змін з однієї гілки в іншу. У мерджу є напрямок: тут — із feature/free-delivery-1000 у main. Доки його не зробили, на сайті й далі 800.
У гілці три коміти: нове число в конфігурації, текст на сторінці доставки й підказка в кошику. Після мерджу в main будуть усі три зміни разом, хоч би яких файлів вони стосувалися; мерджити файл за файлом не треба. Сама гілка лишається такою, як була, і зазвичай її потім видаляють.
8be2f70Add cat treats bundlemain5c1d7e2Set freeDeliveryFrom to 1000a8f40b9Update delivery page textd36b9c5Update cart hint textfeature/free-delivery-1000
Граф комітів, від найстарішого: Pull request #133 перед мерджем 1. коміт 8be2f70 у main "Add cat treats bundle" 2. гілка feature/free-delivery-1000 починається від коміту 8be2f70 3. коміт 5c1d7e2 у feature/free-delivery-1000 "Set freeDeliveryFrom to 1000" 4. коміт a8f40b9 у feature/free-delivery-1000 "Update delivery page text" 5. коміт d36b9c5 у feature/free-delivery-1000 "Update cart hint text"
У терміналі розробники записують напрямок навпаки: стають на гілку, що приймає, і називають ту, що входить: git switch main, а потім git merge feature/free-delivery-1000. На GitHub pull request каже це прямо вгорі, наприклад «wants to merge 3 commits into main from feature/free-delivery-1000».
- Так«#133 мерджить
feature/free-delivery-1000уmain: усі три зміни приходять разом». - Не так«Після мерджу гілка отримала зміни з
main».Напрямок тут — із гілки в
main.
Одна кнопка, три варіанти
Унизу pull request є зелена кнопка Merge pull request, а поруч маленька стрілка. Стрілка відкриває три варіанти:
Create a merge commitSquash and mergeRebase and merge
Котрий дає правильний результат? Усі три. Після будь-якого з них файли в main однакові: поріг безкоштовної доставки 1000 у кожному разі, а сторінка доставки й підказка в кошику мають той самий текст. Відрізняється історія, яку ти потім читатимеш: скільки записів отримає main, чи з'являться початкові коміти гілки і чи покаже граф дві доріжки, що сходяться, чи одну пряму лінію.

Які варіанти ти бачиш, залежить від налаштувань репозиторію. Адміністратор може вимкнути будь-який, тож у деяких репозиторіях лишається один. Так команда непомітно закріплює обраний спосіб.
Для продакта висновок простий. Щоб знати, чи зміна вже в main, вибирати варіант не потрібно. Але перш ніж рахувати коміти чи читати граф, треба знати, яким із них користується команда.
- Так«Хоч би який варіант вибрав Daniel, у
mainбуде 1000». - Не так«Вибери
Rebase and merge, інакше текст може вийти не такий».Файли однакові; відрізняється лише історія.
Варіант 1: мердж-коміт
Create a merge commit додає коміти гілки в main такими, як вони є, і зв'язує дві лінії одним додатковим комітом — мердж-комітом (merge commit).
На графі видно, як сходяться дві доріжки. Мердж-коміт особливий: у нього двоє батьків — попередній коміт у main і останній коміт у гілці. Повідомлення для нього пише сам GitHub, наприклад «Merge pull request #133 from catchow/feature/free-delivery-1000».
8be2f70Add cat treats bundle5c1d7e2Set freeDeliveryFrom to 1000a8f40b9Update delivery page textd36b9c5Update cart hint textfeature/free-delivery-1000e4b7a90Merge pull request #133main
Граф комітів, від найстарішого: Варіант 1: мердж-коміт 1. коміт 8be2f70 у main "Add cat treats bundle" 2. гілка feature/free-delivery-1000 починається від коміту 8be2f70 3. коміт 5c1d7e2 у feature/free-delivery-1000 "Set freeDeliveryFrom to 1000" 4. коміт a8f40b9 у feature/free-delivery-1000 "Update delivery page text" 5. коміт d36b9c5 у feature/free-delivery-1000 "Update cart hint text" 6. мердж-коміт e4b7a90: feature/free-delivery-1000 змерджено в main "Merge pull request #133" (виділено)
Тож для гілки Daniel із трьома комітами історія main зростає на чотири записи: три його коміти з початковими хешами плюс мердж-коміт. Нічого не переписується, і завжди видно, де гілка почалася і де повернулася. У терміналі розробники отримують те саме через git merge --no-ff feature/free-delivery-1000, що завжди створює мердж-коміт.
Ціна — строкатіша історія. Кожен дрібний крок кожної гілки потрапляє в main, зокрема коміти на кшталт «oops, typo», а тиждень із десятьма pull requests лишає клубок доріжок, який треба читати.
- Так«У r3 двоє батьків, r2 і t2, отже, це мердж-коміт».
- Не так«Після мердж-коміту в
mainлише один новий запис».Коміти гілки теж потрапляють туди.
Варіант 2: squash and merge
Squash and merge пакує всі коміти pull request в один новий коміт у main. Усі зміни з гілки потрапляють в історію разом, одним кроком.

На графі гілка з'єднується з цим комітом пунктирною лінією: самі коміти гілки не стали частиною історії main; стали лише їхні об'єднані зміни. Номер pull request GitHub додає до назви коміту сам, як у «Raise free-delivery threshold to 1000 UAH (#133)», тож шлях назад до обговорення — один клік. Перед підтвердженням той, хто мерджить, може відредагувати повідомлення; типово воно бере назву pull request і перелічує під нею назви початкових комітів.
8be2f70Add cat treats bundle5c1d7e2Set freeDeliveryFrom to 1000a8f40b9Update delivery page textd36b9c5Update cart hint textfeature/free-delivery-10006f2c8a1Raise free-delivery threshold to 1000 UAH (#133)main
Граф комітів, від найстарішого: Варіант 2: squash and merge 1. коміт 8be2f70 у main "Add cat treats bundle" 2. гілка feature/free-delivery-1000 починається від коміту 8be2f70 3. коміт 5c1d7e2 у feature/free-delivery-1000 "Set freeDeliveryFrom to 1000" 4. коміт a8f40b9 у feature/free-delivery-1000 "Update delivery page text" 5. коміт d36b9c5 у feature/free-delivery-1000 "Update cart hint text" 6. коміт 6f2c8a1: feature/free-delivery-1000 змерджено в main через squash "Raise free-delivery threshold to 1000 UAH (#133)" (виділено)
Котокорм користується squash. Тому main має вигляд прямої лінії: один pull request — один коміт, а «oops, typo» до main не доходить.
Де ж початкові кроки? У вкладці Commits цього pull request, яка зберігає їх навіть після видалення гілки. У терміналі git merge --squash готує таку саму об'єднану зміну для одного коміту.
- ТакБракує комітів? Натисни
(#133)у назві коміту → pull request →Commits - Не так«Два коміти від Daniel загубилися дорогою до
main».Їх склеїли; pull request їх зберігає.
Fast-forward — мердж без нового коміту
Ти почуєш, як розробники кажуть «fast-forward». Це найпростіший мердж із усіх, і стається він, коли main не змінювалася відтоді, як почалася гілка.
Згадай, що гілка — це лише назва, що вказує на коміт. Якщо main досі вказує на коміт, де гілка почалася, гілка просто далі на тій самій лінії. Об'єднувати нічого, тож Git може пересунути назву main уперед, на останній коміт гілки. Жодного нового коміту, жодного другого батька: назва просто наздоганяє.
Саме це за замовчуванням робить git merge feature/free-delivery-1000 у терміналі в такій ситуації. Якщо команда все ж хоче видимий мердж-коміт, додають --no-ff; якщо хочуть дозволити лише fast-forward — --ff-only.
Одна заковика для користувачів GitHub: станом на 2026 рік кнопка мерджу на GitHub звичайного fast-forward не робить. Create a merge commit завжди додає мердж-коміт, а Rebase and merge завжди створює коміти заново з новими хешами. Тож fast-forward в історії зазвичай означає, що хтось мерджив із термінала.
- Так
mainне рухалася →git mergeробить fast-forward: назва наздоганяє, нового коміту немає - Не такЧекати, що зелена кнопка GitHub зробить fast-forward
Вона завжди додає мердж-коміт або створює коміти заново.
Варіант 3: rebase and merge
Rebase and merge відтворює коміти гілки поверх main, один за одним. Без мердж-коміту і без склеювання.
На графі main просто продовжується прямою лінією з трьома новими комітами, які мають ті самі повідомлення, що й коміти гілки, але інші хеші. Це копії: rebase створює кожен коміт заново на новому місці, а заново створений коміт завжди отримує новий хеш. Оригінали лишаються в гілці, доки її не видалять. У терміналі та сама ідея — git rebase main у гілці, а потім fast-forward main.
8be2f70Add cat treats bundle5c1d7e2Set freeDeliveryFrom to 1000a8f40b9Update delivery page textd36b9c5Update cart hint textfeature/free-delivery-10001f6a3d8Set freeDeliveryFrom to 10002b9e5c4Update delivery page text7d0c8f1Update cart hint textmain
Граф комітів, від найстарішого: Варіант 3: rebase and merge 1. коміт 8be2f70 у main "Add cat treats bundle" 2. гілка feature/free-delivery-1000 починається від коміту 8be2f70 3. коміт 5c1d7e2 у feature/free-delivery-1000 "Set freeDeliveryFrom to 1000" 4. коміт a8f40b9 у feature/free-delivery-1000 "Update delivery page text" 5. коміт d36b9c5 у feature/free-delivery-1000 "Update cart hint text" 6. коміт 1f6a3d8 у main "Set freeDeliveryFrom to 1000" 7. коміт 2b9e5c4 у main "Update delivery page text" 8. коміт 7d0c8f1 у main "Update cart hint text" (виділено)
Поклади чотири картинки поруч:
- Мердж-коміт: дві доріжки сходяться в коміті з двома батьками.
- Squash: один новий коміт у
mainіз пунктирним зв'язком із гілкою. - Rebase: копії комітів гілки, одна за одною в
main. - Fast-forward (лише в терміналі): нового коміту немає; назва
mainпереходить на кінець гілки.
Якщо розрізняєш ці чотири на графі, прочитаєш історію будь-якої команди.
- ТакТі самі повідомлення, нові хеші, пряма лінія →
Rebase and merge - Не такШукати в
mainпочаткові хеші комітів гілки після rebaseRebase створює копії з новими хешами.
Чому команда вибирає один спосіб
Якщо файли в підсумку однакові, чому команди сперечаються про способи мерджу? Бо вони щодня читають історію, а кожен спосіб робить її іншою.
- Прихильники squash хочуть, щоб кожен запис у
mainбув однією завершеною роботою з посиланням на її pull request. Щоб її скасувати, досить revert одного коміту. - Прихильники мердж-комітів хочуть повний запис: кожен коміт із початковим хешем і точні місця, де сходилися гілки.
- Прихильники rebase теж хочуть кожен крок, але прямою лінією.
Переможця немає. Важливо, щоб команда домовилася про один спосіб, і тоді історія читається однаково з тижня в тиждень.
Для продакта це змінює підрахунок. У репозиторії зі squash один коміт може ховати тиждень роботи. У репозиторії з мердж-комітами десять комітів можуть бути однією маленькою фічею. Тож «скільки комітів увійшло в реліз?» — питання з підступом, доки не знаєш, як команда мерджить; «скільки pull requests?» зазвичай краще. А читаючи граф, спершу впізнай спосіб: пряма лінія з копіями — це rebase.
- Так«Скільки pull requests увійшло в цей реліз?»
Означає те саме за будь-якого способу мерджу.
- Не такПорівнювати продуктивність двох команд за кількістю комітів у
mainSquash і мердж-коміти дають дуже різні числа за ту саму роботу.
Чи зміна вже в `main`?
Питання, яке ти як продакт чутимеш найчастіше: «це вже там?». Відповідь — на позначці вгорі кожного pull request:
Open(зелена): запропоновано й обговорюється; зміни досі в гілці.Draft(сіра): автор ще працює; не готово до рев'ю, тим паче доmain.Merged(фіолетова): зміни потрапили в цільову гілку. Рядок поруч каже, у яку саме і коли.Closed(червона): закрито без мерджу; зміни так і не потрапили.
Raise free-delivery threshold to 1000 UAH#133
Змерджено в main сьогодні: зміни вже в main
Rewrite delivery page copy#132
Відкритий: зміни Emma досі в її гілці
Fix checkout button after cart edits#134
Чернетка: Daniel ще працює над ним
Тож у списку, де #129 і #133 Merged, #132 Open, #134 Draft, а #127 Closed, зміни в main є лише в #129 і #133.
Читай і рядок поруч із Merged. Pull request може бути спрямований не в main, а «змерджено в release/2.5» — не те саме, що «змерджено в main». У списку pull requests є фільтри на кшталт is:merged і is:open, якщо треба перевірити багато одразу.
- ТакФіолетова
Merged+ «merged intomain» → зміна вже вmain - Не такЗараховувати червоний
Closedpull request як виконанийClosed означає, що його закрили без мерджу.
Змерджено — ще не на сайті
Фіолетова позначка Merged — добра новина, але це факт про минуле. Перш ніж казати комусь «це вже на сайті», постав три питання:
- Куди змерджено? Рядок поруч із позначкою називає цільову гілку. На сайт Котокорму йде лише мердж у
main. - Коли, і чи вже вийшло? У Котокормі мердж у
mainавтоматично викладають за кілька хвилин. Інші команди накопичують мерджі й випускають їх пізніше релізом, тож «змерджено» може означати «чекає четвергового релізу». Про релізи — у модулі 4. - Чи воно досі там? Редизайн кнопки від Daniel, pull request #97, досі має
Merged, хоча #98 відкотив його за десять хвилин. Revert не змінює старої позначки.

Якщо є сумніви, забудь про позначки й відкрий файл у main: якщо в config/prices.json 1000, зміна там. А на питання «це вже на сайті?» відповідає сам сайт або команда, яка знає, як влаштоване викладання.
Ця звичка вбереже від найпоширенішої помилки продакта на GitHub: пообіцяти клієнтам те, що змерджили, а потім скасували.
- Так«#133 змерджено в
mainсьогодні, і вprices.jsonуmainстоїть 1000».Позначка, ціль і сам файл збігаються.
- Не так«На #97 написано Merged, отже, нове оформлення вже на сайті».
Його відкотив #98 за десять хвилин.
Готові потренуватися?
Виконайте вправи уроку й отримайте 250 балів, коли пройдете його вперше.
Розпочати вправи