Перейти до змісту
Увійти

Теорія

Злиття (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 будуть усі три зміни разом, хоч би яких файлів вони стосувалися; мерджити файл за файлом не треба. Сама гілка лишається такою, як була, і зазвичай її потім видаляють.

Pull request #133 перед мерджемТри коміти в гілці, і жодного з них поки що немає в main.
  1. 8be2f70Add cat treats bundlemain
  2. 5c1d7e2Set freeDeliveryFrom to 1000
  3. a8f40b9Update delivery page text
  4. d36b9c5Update 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 commit
  • Squash and merge
  • Rebase 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».

Варіант 1: мердж-комітДві доріжки сходяться в e4b7a90, у якого двоє батьків. Історія main отримує чотири записи.
  1. 8be2f70Add cat treats bundle
  2. 5c1d7e2Set freeDeliveryFrom to 1000
  3. a8f40b9Update delivery page text
  4. d36b9c5Update cart hint textfeature/free-delivery-1000
  5. e4b7a90Merge 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. Усі зміни з гілки потрапляють в історію разом, одним кроком.

Три невеликі посилки пакують в одну більшу коробку з однією порожньою наліпкою, і вона їде конвеєром на склад.
Squash-мердж перетворює кроки гілки на один коміт в основній гілці.

На графі гілка з'єднується з цим комітом пунктирною лінією: самі коміти гілки не стали частиною історії main; стали лише їхні об'єднані зміни. Номер pull request GitHub додає до назви коміту сам, як у «Raise free-delivery threshold to 1000 UAH (#133)», тож шлях назад до обговорення — один клік. Перед підтвердженням той, хто мерджить, може відредагувати повідомлення; типово воно бере назву pull request і перелічує під нею назви початкових комітів.

Варіант 2: squash and mergeОдин новий коміт у main, 6f2c8a1, з пунктирним зв'язком із гілкою. Трьох початкових комітів в історії main немає.
  1. 8be2f70Add cat treats bundle
  2. 5c1d7e2Set freeDeliveryFrom to 1000
  3. a8f40b9Update delivery page text
  4. d36b9c5Update cart hint textfeature/free-delivery-1000
  5. 6f2c8a1Raise 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.

Варіант 3: rebase and mergeТри копії комітів, з новими хешами, продовжують пряму лінію main. Оригінали лишаються в гілці, доки її не видалять.
  1. 8be2f70Add cat treats bundle
  2. 5c1d7e2Set freeDeliveryFrom to 1000
  3. a8f40b9Update delivery page text
  4. d36b9c5Update cart hint textfeature/free-delivery-1000
  5. 1f6a3d8Set freeDeliveryFrom to 1000
  6. 2b9e5c4Update delivery page text
  7. 7d0c8f1Update 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 початкові хеші комітів гілки після rebase

    Rebase створює копії з новими хешами.

Чому команда вибирає один спосіб

Якщо файли в підсумку однакові, чому команди сперечаються про способи мерджу? Бо вони щодня читають історію, а кожен спосіб робить її іншою.

  • Прихильники squash хочуть, щоб кожен запис у main був однією завершеною роботою з посиланням на її pull request. Щоб її скасувати, досить revert одного коміту.
  • Прихильники мердж-комітів хочуть повний запис: кожен коміт із початковим хешем і точні місця, де сходилися гілки.
  • Прихильники rebase теж хочуть кожен крок, але прямою лінією.

Переможця немає. Важливо, щоб команда домовилася про один спосіб, і тоді історія читається однаково з тижня в тиждень.

Для продакта це змінює підрахунок. У репозиторії зі squash один коміт може ховати тиждень роботи. У репозиторії з мердж-комітами десять комітів можуть бути однією маленькою фічею. Тож «скільки комітів увійшло в реліз?» — питання з підступом, доки не знаєш, як команда мерджить; «скільки pull requests?» зазвичай краще. А читаючи граф, спершу впізнай спосіб: пряма лінія з копіями — це rebase.

  • Так
    «Скільки pull requests увійшло в цей реліз?»

    Означає те саме за будь-якого способу мерджу.

  • Не так
    Порівнювати продуктивність двох команд за кількістю комітів у main

    Squash і мердж-коміти дають дуже різні числа за ту саму роботу.

Чи зміна вже в `main`?

Питання, яке ти як продакт чутимеш найчастіше: «це вже там?». Відповідь — на позначці вгорі кожного pull request:

  • Open (зелена): запропоновано й обговорюється; зміни досі в гілці.
  • Draft (сіра): автор ще працює; не готово до рев'ю, тим паче до main.
  • Merged (фіолетова): зміни потрапили в цільову гілку. Рядок поруч каже, у яку саме і коли.
  • Closed (червона): закрито без мерджу; зміни так і не потрапили.
Pull requests у catchow/website
  • 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 into main» → зміна вже в main
  • Не так
    Зараховувати червоний Closed pull request як виконаний

    Closed означає, що його закрили без мерджу.

Змерджено — ще не на сайті

Фіолетова позначка Merged — добра новина, але це факт про минуле. Перш ніж казати комусь «це вже на сайті», постав три питання:

  1. Куди змерджено? Рядок поруч із позначкою називає цільову гілку. На сайт Котокорму йде лише мердж у main.
  2. Коли, і чи вже вийшло? У Котокормі мердж у main автоматично викладають за кілька хвилин. Інші команди накопичують мерджі й випускають їх пізніше релізом, тож «змерджено» може означати «чекає четвергового релізу». Про релізи — у модулі 4.
  3. Чи воно досі там? Редизайн кнопки від Daniel, pull request #97, досі має Merged, хоча #98 відкотив його за десять хвилин. Revert не змінює старої позначки.
Посилка з печаткою «доставлено» стоїть на порозі, а кур'єр несе таку саму посилку назад до фургона.
Змерджено куди, коли і чи воно досі там?

Якщо є сумніви, забудь про позначки й відкрий файл у main: якщо в config/prices.json 1000, зміна там. А на питання «це вже на сайті?» відповідає сам сайт або команда, яка знає, як влаштоване викладання.

Ця звичка вбереже від найпоширенішої помилки продакта на GitHub: пообіцяти клієнтам те, що змерджили, а потім скасували.

  • Так
    «#133 змерджено в main сьогодні, і в prices.json у main стоїть 1000».

    Позначка, ціль і сам файл збігаються.

  • Не так
    «На #97 написано Merged, отже, нове оформлення вже на сайті».

    Його відкотив #98 за десять хвилин.

Готові потренуватися?

Виконайте вправи уроку й отримайте 250 балів, коли пройдете його вперше.

Розпочати вправи