Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Яка різниця між git merge та git rebase?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**git merge проти git rebase** - merge створює коміт з двома батьками, зберігаючи повну історію гілок; rebase відтворює коміти поверх цільової гілки і переписує їхні SHA, формуючи лінійну послідовність. ```bash git merge feature # Merge commit, два батьки, розгалужена історія git rebase main # Нові коміти, ті самі дифи, лінійна історія ``` **Ключове:** merge для спільних гілок, rebase для локальних feature-гілок перед PR. Ніколи не роби rebase на гілці, яку вже підтягнули колеги.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**git merge** інтегрує гілку, створюючи новий коміт з двома батьками і зберігаючи повну записану історію обох гілок. **git rebase** відтворює твої коміти поверх іншої гілки, переписуючи їхні SHA і формуючи лінійну послідовність. ## Теорія ### TL;DR - Аналогія: merge це зшивання двох звітів разом, обидва шляхи залишаються видимими. Rebase це переписування своїх нотаток у кінець основного звіту так, щоб виглядало як один документ. - Головна різниця: merge додає один коміт з двома батьками; rebase створює нові коміти з новими SHA для кожного відтвореного. - Merge зберігає точки розходження і злиття гілок. Rebase робить так, наче робота йшла послідовно від початку. - Правило вибору: спільна або публічна гілка? Merge. Локальна feature-гілка перед PR? Rebase. - Після push на спільний remote: тільки merge. Rebase після push ламає клони команди. ### Швидкий приклад ```bash # main має коміт B, feature має D і E git checkout main && echo "B" >> app.js && git commit -m "B" git checkout -b feature echo "D" >> app.js && git commit -m "D" echo "E" >> app.js && git commit -m "E" # Варіант 1: merge git checkout main && git merge feature # Історія: A-B-M (M має двох батьків: B і E) # Варіант 2: rebase (на feature-гілці, перед мержем) git checkout feature && git rebase main # Історія: A-B-D'-E' (D' і E' мають нові SHA) ``` Після merge в логу видно розгалуження і з'єднання. Після rebase пряма лінія. Код однаковий, але записана версія подій різна. ### Ключова різниця Merge знаходить спільного предка двох гілок, обчислює three-way diff і записує новий коміт з обома верхівками як батьками. Оригінальні коміти не змінюються. Rebase бере кожен коміт між точкою розгалуження і верхівкою feature-гілки, відв'язує його від оригінального батька і по черзі відтворює на новій базі через cherry-pick. Оскільки змінюється SHA батька, змінюється і SHA коміту, навіть якщо diff ідентичний. Саме тому не можна безпечно робити rebase на гілці, яку вже клонували колеги. ### Коли що використовувати - Спільна гілка (`main`, `develop`): тільки merge. Rebase на гілці, яку вже підтягнули інші, створює дублікати комітів і змушує команду вручну виправляти локальний стан. - Локальна feature-гілка перед PR: зробити rebase на main спочатку. Reviewer отримає чисту послідовну історію. - Публічна історія вже запушена і підтягнута колегою: merge. Навіть якщо це твоя власна гілка. - Інтерактивний rebase (`git rebase -i`): підходить для squash або редагування комітів до першого push. ### Таблиця порівняння | Аспект | git merge | git rebase | |---|---|---| | Форма історії | Розгалужена, показує відхилення | Лінійна, коміти послідовно | | Нові коміти | Один merge commit (два батьки) | Новий коміт для кожного відтвореного, нові SHA | | Переписує історію? | Ні | Так | | Безпечний після push? | Так | Ні | | Вирішення конфліктів | Один раз, в точці злиття | По одному разу на кожен відтворюваний коміт | | Коли використовувати | Спільні гілки, аудит | Локальні feature-гілки, чисті PR | ### Як це працює всередині Merge обчислює three-way diff: спільний предок плюс дві верхівки гілок. Без конфліктів Git записує один коміт з двома батьками автоматично. Rebase йде по комітах між точкою розгалуження і верхівкою feature-гілки, робить cherry-pick кожного на нову базу і записує нові коміти. Оскільки батьківський SHA змінився, змінюється і SHA дочірнього коміту, навіть якщо код у ньому байт-у-байт однаковий. ### Типові помилки **Rebase спільної гілки.** ```bash # Неправильно git checkout main && git rebase feature # Переписує публічний main ``` Кожен, хто вже підтягнув `main`, тепер має розбіжну історію. `git pull` впаде. Їм потрібно `git reset --hard origin/main` щоб відновитись. Для гілок, якими активно користується команда, тільки `git merge`. **Очікування одного кроку вирішення конфліктів під час rebase.** ```bash git rebase main # Конфлікт на D -> виправляємо -> git rebase --continue # Конфлікт на E -> виправляємо -> git rebase --continue ``` Merge видає всі конфлікти одразу. Rebase зупиняється на кожному конфліктному коміті. Ті, хто чекає одного кроку, перериваються думаючи що щось зламалось. Це нормально: `git rebase --continue` після кожного конфлікту, або `git rebase --abort` щоб повернутись до початкового стану. **Force-push після rebase коли колега вже підтягнув гілку.** ```bash git push origin feature # Колега підтягує git rebase main # Нові SHA на feature git push --force-with-lease # Перезаписує remote # Локальна feature-гілка колеги тепер осиротіла ``` `--force-with-lease` перевіряє що верхівка remote збігається з останнім SHA, отриманим при fetch. Але він не захищає колегу, який підтягнув між твоїм fetch і push. На спільній гілці це завжди проблема. **Merge після rebase без розуміння fast-forward.** Після rebase feature-гілки на main, верхівка feature напряму попереду main. `git merge feature` зробить fast-forward автоматично і не додасть merge commit. Якщо потрібен merge commit щоб позначити межу PR, треба явно вказати `--no-ff`: ```bash git checkout main && git merge --no-ff feature ``` ### Реальне використання - React (facebook/react): контрибьютори роблять rebase feature-гілок локально перед PR. Main залишається лінійним. - Node.js core (nodejs/node): merge commits для стабільних гілок (`v20.x`), інтерактивний rebase для особистого WIP. - Linux kernel (torvalds/linux): суворий rebase перед злиттям у Linus. Лінійна історія потрібна щоб `git bisect` коректно працював на тисячах комітів. - Express.js: rebase + `merge --no-ff` при злитті PR, щоб межа PR залишалась видною в логу. Більшість команд зупиняється десь між цими підходами. "Rebase перед PR, merge в main" вирішує переважну більшість ситуацій і тримається без зайвого контролю. ### Питання для поглиблення **Q:** Як перевірити що rebase створив нові коміти? **A:** `git reflog` одразу після rebase покаже старі SHA до операції і нові після. `git log --oneline --graph` також показує лінійний результат. **Q:** Що робить `--force-with-lease` і коли це безпечно? **A:** Push відбудеться тільки якщо верхівка remote збігається з останнім SHA, отриманим при fetch. Безпечно коли ти єдиний на гілці і ніхто не підтягував її після твого останнього fetch. **Q:** Чим вирішення конфліктів при rebase відрізняється від merge? **A:** Rebase зупиняється на кожному коміті що викликає конфлікт. Виправляєш і запускаєш `git rebase --continue`. Merge збирає всі конфлікти і вирішуєш їх за один раз. **Q:** Що таке `git pull --rebase origin main` і чим відрізняється від `git rebase main`? **A:** `git pull --rebase` спочатку отримує нові коміти з remote, потім робить rebase локальної гілки на оновлену верхівку. `git rebase main` робить rebase тільки на локальний `main`, який може відставати від remote. **Q:** Чому в команді з багатьма контрибьюторами merge масштабується краще ніж rebase? **A:** Відтворення сотень комітів від багатьох розробників генерує часті конфлікти на кожен коміт окремо. Merge це один коміт на інтеграцію. До того ж merge зберігає хронологію контрибьюторів, що важливо для `git blame` і аудиту. ## Приклади ### Merge в Node.js Express додатку ```bash git checkout main echo "app.get('/', (req,res) => res.send('Home'));" > server.js git add . && git commit -m "Initial Express server" git checkout -b auth-feature echo "app.use('/admin', authMiddleware);" >> server.js git add . && git commit -m "Add auth route guard" echo "function authMiddleware(req,res,next){ next(); }" >> server.js git add . && git commit -m "Implement auth check" git checkout main git merge auth-feature # Результат: merge commit з двома батьками # git log --graph показує точку розгалуження і злиття ``` Обидва оригінальні коміти залишились з оригінальними SHA. В логу видно коли `auth-feature` відгалузилась від main і коли потрапила назад. ### Rebase перед відкриттям PR ```bash # Поки ти був на auth-feature, main отримав нові коміти git checkout auth-feature git rebase main # Коміти auth-feature тепер відтворені поверх останнього main # git log --graph: лінійно - Initial -> нові коміти main -> auth коміти git push origin auth-feature git push --force-with-lease # Тільки якщо гілку вже пушили раніше ``` Код той самий що і після merge. Але історія читається так, наче auth-коміти написані після останніх змін в main. PR стає простіше ревьюити.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.