Гілки та злиття
Про гілки
Щоб по-справжньому зрозуміти, як Git працює з розгалуженнями, нам потрібно зробити крок назад і розглянути, як Git зберігає свої дані. Git зберігає дані не у вигляді серії наборів змін або відмінностей, а у вигляді серії знімків. Коли ви робите коміт, Git зберігає об’єкт коміту, який містить вказівник на створений вами знімок вмісту. Цей об’єкт також містить ім’я та адресу електронної пошти автора, повідомлення, яке ви набрали, і вказівники на комміт або комміти, які безпосередньо передували цьому комміту (його батька або батьків): нульовий батько для початкового комміту, один батько для звичайного комміту і кілька батьків для комміту, який є результатом злиття двох або більше гілок.
Commits tree
Гілка в Git’і - це просто легкий рухомий вказівник на одну з цих коммітів. Стандартна назва гілки у Git’і - master. Коли ви починаєте робити комміти, ви отримуєте головну гілку, яка вказує на останній зроблений вами комміт. Кожного разу, коли ви робите комміт, вказівник головної гілки автоматично пересувається вперед.
Управління гілками
Команда git branch
робить більше, ніж просто створює і видаляє гілки. Якщо ви запустите її без аргументів, ви отримаєте простий список ваших поточних гілок:
$ git branch
devel
feature/contributing
feature/legacy
feature/lib-ms
feature/libms
feature/pdf-builder
* feature/vcs
master
Щоб побачити всі гілки, які містять роботи, до яких ви ще не приєдналися, використовуйте опцію --no-merged
:
$ git branch --no-merged
feature/contributing
feature/lib-ms
feature/libms
У гілках, перелічених у виводі, є напрацювання, які не злито з поточною гілкою. Отже, їх видалення за допомогою git branch -d
не спрацює.
Створення гілок
Існує декілька способів створити нову гілку в Git-сховищі. Найпоширеніший з них - використання команди git branch
.
git branch <branch_name> [parent_commit]
Вказівник на батьківський коміт не є обов’язковим. За замовчуванням батьківський коміт встановлюється на найновіший доступний (вказівник HEAD) на момент створення гілки. Ви також можете переключитися на новостворену гілку одразу після її створення за допомогою:
git checkout -b <branch_name> [parent_commit]
Перемикання гілок
Щоб перейти до існуючої гілки, виконайте команду git checkout
.
$ git branch
devel
feature/contributing
feature/legacy
feature/lib-ms
feature/libms
feature/pdf-builder
* feature/vcs
master
$ git checkout devel
Switched to branch 'devel'
Your branch is up to date with 'origin/devel'.
Основи розгалуження та злиття
По-перше, припустимо, що ви працюєте над своїм проектом і вже маєте кілька комітів на гілці master
. Ви вирішили, що будете працювати над feature #53 в будь-якій системі моніторінгу завдань, яку використовує ваша компанія. Щоб створити нову гілку і одночасно переключитися на неї, ви можете виконати команду git checkout
з ключем -b
:
$ git checkout -b feature/GH-53
Switched to a new branch "feature/GH-53"
Це скорочено означає:
$ git branch feature/GH-53
$ git checkout feature/GH-53
Насправді це створить новий покажчик гілки, спрямований на останню фіксацію, наявну у гілці master
. Але відтепер ваша гілка називається feature/GH-53
. Отже, створення коммітів пересуває вказівник гілки вперед, тому що ви її вилучили (тобто HEAD
вказує на неї).
Тепер припустимо, що працю над «GH-53» завершено, і вам потрібно перенести коміти з гілки feature/GH-53
назад до master
.
Злиття гілок
Вам потрібно повернутися до гілки master
і скористатися командою git merge
, щоб об’єднати зміни (коміти) з вихідної гілки в цільову.
$ git checkout master
$ git merge feature/GH-53
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
Ви помітите фразу «fast-forward» у цьому злитті. Оскільки коміт, на який вказує гілка feature/GH-53
, до якої ви приєдналися, був безпосередньо перед останнім комітом у гілці master
, Git просто пересуне вказівник вперед.
Зміни з feature/GH-53
тепер містяться у знімку коміту, на який вказує гілка master
.
Fast-forward merge
Тепер припустимо, що інший розробник почав працювати над feature/GH-54
в той самий час. І у цій гілці мало комітів. Припустимо, що розробник вирішив, що робота над GH-54 завершена і він хоче об’єднати свою гілку з master
.
$ git branch
master
* feature/GH-54
$ git checkout master
$ git merge feature/GH-54
Merge made by the 'recursive' strategy.
index.html | 1 +
1 file changed, 1 insertion(+)
Це виглядає дещо інакше, ніж злиття feature/GH-53
. У цьому випадку історія розробки розійшлася з якоїсь давнішої точки. Оскільки коміт на гілці, в якій ви перебуваєте, не є прямим предком гілки, в яку ви виконуєте злиття, Git’у доведеться виконати деяку роботу. У цьому випадку Git виконує просте тристороннє злиття, використовуючи два знімки, на які вказують кінчики гілок, і їхнього спільного предка.
Замість того, щоб просто перемістити вказівник гілки вперед, Git створює новий знімок, який є результатом цього тристороннього злиття, і автоматично створює новий коміт, який вказує на нього. Це називається коммітом злиття, і він особливий тим, що має більше одного батька.
Merge commit
Конфлікти об’єднання
Іноді процес злиття відбувається не зовсім гладко. Якщо у двох гілках, які ви об’єднуєте, однакова частина одного і того ж файлу змінена по-різному, Git не зможе об’єднати їх чисто.
$ git merge vcs
Auto-merging branches.txt
CONFLICT (content): Merge conflict in branches.txt
Automatic merge failed; fix conflicts and then commit the result
Git не створив автоматично новий коміт злиття. Він призупинив процес, поки ви вирішуєте конфлікт. Якщо ви хочете побачити, які файли було від’єднано після конфлікту злиття, ви можете запустити git status
:
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: branches.txt
no changes added to commit (use "git add" and/or "git commit -a")
Все, що має конфлікти злиття, які не було вирішено, позначається як не об’єднане. Git додає стандартні маркери вирішення конфліктів до файлів, які мають конфлікти, щоб ви могли відкрити їх вручну і вирішити ці конфлікти. Ваш файл містить розділ, який виглядає приблизно так:
<<<<<<< HEAD:branches.txt
Anything that has merge conflicts and has not been resolved is listed as
=======
Anything that has merge conflicts
and hasn't been resolved is listed as
</div>
>>>>>>> vcs:branches.txt
Це означає, що версія у гілці HEAD
(ваша гілка master
, тому що саме її ви перевірили, коли виконували команду merge) - це верхня частина блоку (все, що вище «=======»), тоді як версія у гілці vcs
виглядає як все, що знаходиться у нижній частині. Для того, щоб вирішити конфлікт, вам доведеться або стати на чийсь бік, або об’єднати вміст самостійно.
Коли конфлікти буде вирішено, ви можете знову запустити команду git status
:
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: branches.txt
Якщо вас все влаштовує, і ви переконалися, що все, що викликало конфлікти, було усунуто, ви можете запустити git commit
для остаточного завершення злиття.
Перебазування
У Git’і існує два основних способи інтеграції змін з однієї гілки в іншу: merge
(злиття) і rebase
(перебазування).
Раніше ми вже наводили приклад розбіжностей у роботі та зобов’язаннях, зроблених на двох різних гілках.
Before rebase
Найпростішим способом об’єднання гілок, як ми вже розглядали, є команда merge
. Вона виконує тристороннє злиття між двома останніми знімками гілок і останнім спільним предком, створюючи новий знімок (і фіксацію).
Втім, є й інший спосіб: ви можете взяти патч зі змінами, які було внесено до master
, і повторно застосувати його поверх feature
. У Git’і це називається rebasing. За допомогою команди rebase
ви можете взяти всі зміни, які були зафіксовані на одній гілці, і відтворити їх на іншій гілці.
$ git checkout feature
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Ця операція виконується шляхом переходу до спільного предка двох гілок, отримання відмінностей, внесених кожним коммітом гілки, на якій ви перебуваєте, збереження цих відмінностей у тимчасових файлах, скидання поточної гілки до того ж комміту, що і гілка, на яку ви перебазуєтесь, і, нарешті, застосування кожної зміни по черзі.
After rebase
Примітка
Інші супроводжувачі надають перевагу перебазуванню (або вилученню) внесених напрацювань на кінці своєї master
гілки, замість того, щоб об’єднувати їх, щоб зберегти переважно лінійну історію.
Cherry-picking
Інший спосіб перенесення внесеної роботи з однієї гілки в іншу - це збирання коммітів (cherry-pick). Збирання вишень у Git’і - це відновлення бази для одного коміту. Він бере патч, який було внесено в комміті, і намагається повторно застосувати його на гілці, на якій ви зараз перебуваєте. Це корисно, якщо ви маєте декілька коммітів у гілці теми і хочете інтегрувати лише один з них, або якщо ви маєте лише один комміт у гілці теми і вважаєте за краще вибрати його замість того, щоб запускати rebase.
Cherry-pick a commit
$ git cherry-pick e43a6
[master 0288270] Important commit
Date: Thu Aug 17 20:30:05 2023 +0300
3 files changed, 17 insertions(+), 3 deletions(-)
Стратегії розгалуження
Стратегія розгалуження в системах контролю версій визначає, як створюються, управляються та використовуються гілки в процесі розробки проекту. Вона допомагає командам ефективно співпрацювати, ізолювати зміни та організовувати зусилля з розробки. Хороша стратегія розгалуження забезпечує чіткість у роботі з гілками, мінімізує конфлікти та уможливлює плавний і структурований робочий процес розробки.
GitFlow - це популярна стратегія розгалуження, яка надає чітко визначену модель для управління гілками в Git-репозиторії. Вона була представлена Вінсентом Дріссеном (Vincent Driessen) і базується на ідеї використання двох основних гілок: «master» і «develop».
Майстер-гілка: «Майстер-гілка» представляє стабільну версію кодової бази. Вона завжди повинна містити готовий до використання код і не містити жодних серйозних проблем. Майстер-гілка захищена, і в неї зливаються тільки релізні версії.
Гілка розробки: Гілка «develop» - це місце, де відбувається постійна розробка та інтеграція функцій. Вона слугує інтеграційною гілкою для різних функціональних гілок, а також повинна містити стабільну версію коду.
Гілки функцій: Для кожної нової функції або виправлення помилки створюється окрема гілка «feature» у гілці «develop». Ці гілки недовговічні і існують лише на час розробки функції.
Гілки релізу: Коли розробка на гілці «develop» готова до релізу, створюється гілка «release». Релізна гілка використовується для тестування, виправлення помилок і підготовки до розгортання.
Гілки виправлень: Якщо у виробничій версії виявлено критичну проблему, на основі основної гілки створюється гілка «hotfix». Це дозволяє швидко виправити проблему, не впливаючи на поточну розробку на гілці «develop».
GitFlow забезпечує структурований підхід до управління розгалуженням у середовищі спільної розробки. Він гарантує, що основні гілки залишаються стабільними, а нові функції та виправлення помилок плавно інтегруються перед випуском. Ця стратегія особливо корисна для проектів з регулярними випусками і командою, яка працює над декількома функціями одночасно.
GitFlow diagram