Пошук уроків, статей та іншого контенту
Порівняєте merge і rebase, їхній вплив на граф комітів та сценарії практичного застосування.
merge і rebaseУ Git гілки — це вказівники на коміти. Коли робота в окремій гілці завершена, її зміни потрібно об’єднати з іншою гілкою, наприклад main.
Для цього найчастіше використовують:
git merge — об’єднує історії гілок;
git rebase — переносить коміти однієї гілки на вершину іншої.
Обидві команди можуть привести до однакового стану файлів, але створюють різну історію комітів.
mergeКоманда merge об’єднує поточну гілку з іншою.
Припустімо, історія має такий вигляд:
A---B---C main
\
D---E featureЯкщо перейти до main і виконати:
git switch main
git merge featureGit створить окремий коміт злиття:
A---B---C-------M main
\ /
D---E--- featureКоміт M має двох батьків:
попередній коміт гілки main;
останній коміт гілки feature.
mergeЗберігає повну історію розвитку гілок.
Не змінює вже наявні коміти.
Безпечний для гілок, якими користуються кілька людей.
Чітко показує, коли саме дві гілки було об’єднано.
Іноді Git може виконати злиття без створення окремого коміту.
Наприклад:
A---B main
\
C---D featureЯкщо в main після створення feature не з’явилося нових комітів, Git просто пересуне вказівник main уперед:
A---B---C---D main, featureЦе називається fast-forward merge.
Щоб завжди створювати коміт злиття, можна використати:
git switch main
git merge --no-ff featureТоді історія матиме окремий merge-коміт, навіть якщо fast-forward можливий.
rebaseКоманда rebase переносить коміти поточної гілки на іншу основу.
Початкова історія:
A---B---C main
\
D---E featureПісля виконання в гілці feature:
git switch feature
git rebase mainGit зробить так:
Тимчасово відкладе коміти D і E.
Перемістить основу гілки feature до коміту C.
Повторно застосує зміни з D і E.
Результат:
A---B---C---D'---E' featureКоміти D' і E' містять ті самі зміни, що й D та E, але технічно це нові коміти з новими ідентифікаторами.
Після цього можна перейти до main і виконати звичайне злиття:
git switch main
git merge featureОскільки main є предком feature, найчастіше відбудеться fast-forward:
A---B---C---D'---E' main, featureОкремий merge-коміт не створюється.
Нижче наведено приклад, який створює дві гілки та показує різницю між підходами.
merge#!/usr/bin/env bash
set -e
rm -rf git-merge-demo
mkdir git-merge-demo
cd git-merge-demo
git init -b main
git config user.name "Demo User"
git config user.email "demo@example.com"
echo "Перша версія" > app.txt
git add app.txt
git commit -m "Створити застосунок"
git switch -c feature
echo "Функція профілю" >> app.txt
git add app.txt
git commit -m "Додати профіль"
git switch main
echo "Документація" > README.md
git add README.md
git commit -m "Додати документацію"
git merge --no-ff feature -m "Об'єднати feature з main"
echo
echo "Історія після merge:"
git log --oneline --graph --decorate --allПриблизна форма історії:
* 1234567 (HEAD -> main) Об'єднати feature з main
|\
| * 2345678 (feature) Додати профіль
* | 3456789 Додати документацію
|/
* 4567890 Створити застосунокУ цій історії видно окрему гілку та момент її об’єднання.
rebase#!/usr/bin/env bash
set -e
rm -rf git-rebase-demo
mkdir git-rebase-demo
cd git-rebase-demo
git init -b main
git config user.name "Demo User"
git config user.email "demo@example.com"
echo "Перша версія" > app.txt
git add app.txt
git commit -m "Створити застосунок"
git switch -c feature
echo "Функція профілю" >> app.txt
git add app.txt
git commit -m "Додати профіль"
git switch main
echo "Документація" > README.md
git add README.md
git commit -m "Додати документацію"
git switch feature
git rebase main
git switch main
git merge feature
echo
echo "Історія після rebase та merge:"
git log --oneline --graph --decorate --allІсторія буде лінійною:
* 1234567 (HEAD -> main, feature) Додати профіль
* 2345678 Додати документацію
* 3456789 Створити застосунокУ цьому прикладі merge після rebase лише пересунув вказівник main, тому окремого merge-коміту немає.
mergeГраф зберігає розгалуження:
A---B---C-------M
\ /
D---E----Це корисно, якщо важливо бачити:
які коміти належали окремій функції;
коли гілку було приєднано;
як паралельно розвивалися різні частини проєкту.
rebaseГраф стає лінійним:
A---B---C---D'---E'Це спрощує перегляд історії:
легше читати git log;
простіше знаходити коміт за допомогою git bisect;
немає додаткових merge-комітів.
Однак лінійність досягається зміною історії комітів.
mergemerge зазвичай підходить, коли:
гілка вже опублікована у спільному репозиторії;
над гілкою працюють кілька людей;
потрібно зберегти реальну структуру розробки;
важливо явно зафіксувати факт об’єднання;
зміни вже використовуються іншими гілками або розробниками.
Типовий сценарій:
git switch main
git pull
git merge feature
git pushПеред злиттям переконайтеся, що локальна main містить актуальні зміни з віддаленого репозиторію.
rebaserebase зручно використовувати, коли:
ви працюєте над власною локальною гілкою;
потрібно оновити гілку останніми змінами з main;
хочеться підготувати чисту лінійну історію;
гілка ще не опублікована або над нею не працюють інші люди.
Типовий сценарій:
git switch feature
git fetch origin
git rebase origin/mainПісля цього коміти вашої гілки будуть розташовані поверх актуального стану origin/main.
Не слід виконувати звичайний rebase над гілкою, яку вже завантажили до спільного репозиторію та яку використовують інші розробники.
Причина в тому, що rebase створює нові коміти з іншими ідентифікаторами. У інших розробників залишаться старі коміти, і історії розійдуться.
Наприклад, після публікації:
A---B---C origin/feature
\
D---E локальна featureПісля rebase локальна гілка може мати такий вигляд:
A---B---C---D'---E' локальна feature
\
D---E origin/featureGit більше не сприймає D і D' як один коміт, навіть якщо вони містять однакові зміни.
Якщо переписування вже опублікованої гілки справді необхідне, для відправлення зазвичай використовують:
git push --force-with-lease--force-with-lease безпечніший за звичайний --force: Git перевіряє, що віддалена гілка не змінилася несподівано. Проте навіть цей варіант потрібно застосовувати обережно та узгоджувати з командою.
merge і rebaseКонфлікти можуть виникнути в обох операціях, якщо різні гілки змінюють ті самі рядки.
Під час merge після виправлення конфліктів потрібно:
git add шлях/до/файлу
git commitПід час rebase після виправлення конфліктів потрібно:
git add шлях/до/файлу
git rebase --continueЩоб скасувати операцію:
git merge --abortабо:
git rebase --abortКоманда --abort повертає репозиторій до стану, який був до початку поточної операції.
Це змінює ідентифікатори комітів і змушує інших розробників повторно узгоджувати свої локальні гілки.
Для спільних гілок безпечнішим вибором зазвичай є merge.
git merge feature об’єднує feature саме в поточну гілку. Перед операцією перевірте її:
git branch --show-currentmerge і rebasemerge додає історії гілок разом і може створити новий коміт.
rebase переносить коміти на нову основу та переписує їх ідентифікатори.
Перед merge або rebase перевірте незбережені зміни:
git statusНезбережені зміни можуть перешкодити операції або ускладнити розв’язання конфліктів.
git push --force без потребиПримусове відправлення може перезаписати віддалену історію. Якщо переписування необхідне, краще використовувати:
git push --force-with-leaseКоротке практичне правило:
власна непублікована гілка — можна використовувати rebase;
спільна або вже опублікована гілка — переважно використовуйте merge;
потрібна прозора історія з гілками — merge;
потрібна проста лінійна історія — rebase, а потім fast-forward merge.
Жодна з команд не є універсально кращою. Вибір залежить від того, чи потрібно зберегти структуру гілок і чи можна безпечно переписувати історію.
merge об’єднує дві історії та зберігає їхнє розгалуження.
rebase переносить коміти на нову основу й створює нові ідентифікатори комітів.
merge безпечніший для спільних і вже опублікованих гілок.
rebase допомагає підтримувати чисту лінійну історію власної гілки.
Після rebase злиття часто виконується як fast-forward без окремого merge-коміту.
Перед операціями перевіряйте поточну гілку та стан робочої директорії.
Не переписуйте історію спільної гілки без узгодження з командою.