Git
Chapters ▾ 2nd Edition

3.2 Галуження в git - Основи галуження та зливання

Основи галуження та зливання

Розглянемо простий приклад галуження та зливання на схемі, котра трапляється в реальності:

  1. Вам потрібно внести зміни до веб-сайту.

  2. Створюєте гілку для своєї задачі.

  3. Працюєте в цій гілці.

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

  1. Переключитеся на головну гілку.

  2. Створите гілку-виправлення.

  3. Після тестування зливаєте гілку-виправлення та надсилаєте в основну гілку.

  4. Переключаєтеся на першопочаткову гілку та продовжуєте роботу над задачею.

Основи галуження

Скажімо, ви працюєте над проектом і вже маєте кілька комітів у гілці master.

Проста історія комітів.
Figure 18. A simple commit history

Тепер вирішили працювати над задачею, котра в системі вашої компанії зареєстрована як №53. Щоб створити нову гілку для цієї задачі та одразу перейти на неї, виконайте команду git checkout з параметром -b:

$ git checkout -b iss53
Switched to a new branch "iss53"

Це скорочений запис наступного:

$ git branch iss53
$ git checkout iss53
Creating a new branch pointer.
Figure 19. Створення нового вказівника гілки

Ви працюєте над змінами до сайту та комітите зміни. Таким чином ваша гілка iss53 починає рухається вперед, оскільки ви на неї раніше переключилися (тобто вказівник HEAD вказує на цю гілку):

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
Гілка `iss53` рухається вперед разом з вашими змінами.
Figure 20. Гілка iss53 рухається

Вам подзвонили та доповіли про проблему на сайті, якої потрібно якнайшвидше позбутися. Завдяки Git, вам не потрібно відсилати це виправлення разом із змінами в iss53, також, ви не докладете багато зусиль для того щоб скасувати поточні зміни та працювати над виправленням в основній гілці. Все що потрібно, це знову переключитися на основну гілку master.

Проте, зверніть увагу на те, що якщо у вашій робочій директорії чи області з підготовленими файлами є незакомічені зміни, це спричинить конфлікт з гілкою master та Git не дозволить зробити це переключення. Найкраще спочатку очистити вашу робочу область перед переключеннями.

Способи як це зробити (сховати (stash) та виправити (commit amend)) ми розглянемо пізніше в Ховання та чищення. Зараз вважаємо, що ми закомітили всі зміни в iss53, отже, можна перейти на гілку master:

$ git checkout master
Switched to branch 'master'

Тепер ваша робоча директорія точно в такому стані, як була до того, як ви почали працювати над №53 і ви можете сфокусуватися на терміновому виправленні. Важливо запам’ятати: коли перемикаєтеся між гілками, Git відновлює вашу робочу директорію, щоб вона виглядала так як після вашого останнього коміту. Git додає, видаляє та змінює файли автоматично, щоб впевнитися що ваша робоча копія точно відповідає тому якою була гілка на час вашого останнього коміту.

Далі вам знову потрібно зробити ще одне швидке виправлення. Створимо гілку hotfix і будемо там працювати поки не закінчимо:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)
Гілка `hotfix`
Figure 21. Гілка hotfix, базована на master

Тепер можете запускати тести, щоб впевнитися що зміна годиться і нарешті злити (merge) гілку hotfix назад до master щоб викласти зміни на виробництво. Робиться це за допомогою команди git merge command:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

Зверніть увагу на фразу “fast-forward” у цьому злитті. Через те, що коміт C4, який зливався, належав гілці hotfix, що була безпосередньо попереду поточного коміту C2, Git просто переміщує вказівник вперед. Іншими словами, коли ви зливаєте один коміт з іншим, і це можна досягнути слідуючи історії першого коміту, Git просто переставляє вказівник, оскільки немає змін-відмінностей, які потрібно зливати разом - це називається “перемоткою” (“fast-forward”).

Тепер ваша зміна міститься в знімку коміту, на який вказує master і ви можете викладати зміни.

`master` перемотаний на `hotfix`.
Figure 22. master перемотаний на hotfix

Після того, як ваше супер важливе виправлення викладено, можна повернутися до роботи, яку було відкладено через швидке виправлення. Але спочатку видалимо гілку hotfix — нам вона більше не потрібна, а master вказує на той самий знімок коду. Для цього виконайте git branch з опцією -d:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

Тепер перемикайтеся на вашу незакінчену гілку для №53 і продовжуйте роботу.

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)
Продовження роботи на `iss53`.
Figure 23. Продовження роботи на iss53

Зауважте, що зміни з гілки hotfix відсутні в гілці iss53. Якщо вам потрібні ці зміни підчас роботи над №53, ви можете злити master з iss53 командою git merge master, або просто почекати до того моменту коли ви будете інтегровувати iss53 в master.

Основи зливання

Допустимо, ви вирішили, що робота над №53 завершена і готова до злиття з гілкою master. Для цього ви виконаєте злиття гілки iss53 до master саме так, як раніше робили це з гілкою hotfix. Все що потрібно це перемкнутися на вашу робочу гілку і виконати команду git merge:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

Виглядає трошки інакше, ніж те, що ми робили з гілкою hotfix. У цьому випадку історія змін двох гілок почала відрізнятися в якийсь момент. Оскільки коміт поточної гілки не є прямим нащадком гілки, в яку ви зливаєте зміни, Git мусить трохи попрацювати. В цьому випадку Git робить просте триточкове злиття, користуючись двома знімками, що вказують на гілки та третім знімком - їх спільним нащадком.

Три відбитки типового злиття.
Figure 24. Три відбитки типового злиття

Замість того, щоб просто пересунути вказівник гілки вперед, Git створює новий знімок, що є результатом 3-точкового злиття, і автоматично створює новий коміт, що вказує на нього. Його називають комітом злиття (merge commit) та його особливістю є те, що він має більше одного батьківського коміту.

Коміт злиття.
Figure 25. Коміт злиття

Варто зауважити, що Git сам визначає найбільш підходящого спільного нащадка, якого брати за основу зливання; це відрізняє Git від старіших систем таких як CVS чи Subversion (до версії 1.5), де розробник, що виконує зливання, сам повинен вказувати що брати за основу зливання. Це надзвичайно полегшує зливання, порівняно з іншими системами.

Тепер, коли ваші зміни злиті, гілка iss53 вам більше не потрібна. Можете закривати задачу №53 та видаляти гілку:

$ git branch -d iss53

Основи конфліктів зливання

Трапляється, що цей процес не проходить гладко. Якщо ви маєте зміни в одному й тому самому місці в двох різних гілках, Git не зможе їх просто злити. Якщо підчас роботи над №53 ви поміняли ту саму частину файлу, що й у гілці hotfix, ви отримаєте конфлікт, що виглядає приблизно так:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
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:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

Все, що має конфлікти, які не були вирішені є в списку незлитих (unmerged) файлів. У кожен такий файл Git додає стандартні позначки-вирішенння для конфліктів, отже ви можете відкрити ці файли і вирішити конфлікти самостійно. У вашому файлі з конфліктом появиться блок, схожий на таке:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

Розглянемо, як це розуміти. Версія файлу в HEAD (з вашої master гілки, оскільки ви запустили зливання, будучи на ній) у верхній частині блоку (все вище =======), а версія з iss53 - все, що нижче. Щоб розв’язати цю несумісність, вам потрібно вибрати одну із версій, або самостійно (вручну) поредагувати вміст файлу. Наприклад, ви можете вирішити цей конфілікт, замінивши блок повністю:

<div id="footer">
please contact us at email.support@github.com
</div>

В цьому випадку ми взяли потрохи з кожної секції, а стрічки <<<<<<<, ======= та >>>>>>> видалили повністю. Після того, як ви розв’язали подібні несумісності в кожному блоці конфліктних файлів, виконайте для них git add, щоб індексувати та позначити, як розв’язані. Індексуючи файл, ви позначаєте його для Git таким, що більше не має конфлікту. Якщо ви хочете використовувати графічний інструмент для розв’язання конфліктів, виконайте команду git mergetool, яка запустить графічний редактор та проведе вас по всьому процесу:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

Для того, щоб використовувати інструмент-програму іншу, ніж по-замовчуванню (Git обрав opendiff, оскільки команду було запущено з Mac), подивіться на список сумісних зверху одразу після “one of the following tools.” Просто введіть ім’я потрібного інструменту.

Note

Про інструменти для розв’язання більш складних конфліктів ми повернемося в Складне злиття.

Після того, як ви вийшли з програми для зливання, Git спитає вас чи було зливання успішним. Якщо ви відповісте, що так, Git проіндексує файл для того, щоб позначити файл як безконфліктний Можете виконати 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:   index.html

Якщо ви задоволені результатом та перевірили, що всі файли, котрі містили несумісності, проіндексовані, можете виконувати git commit і, таким чином, завершувати злиття. Повідомлення після коміту виглядає приблизно так:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

Якщо ви вважаєте, що це допоможе іншим зрозуміти коміт злиття у майбутньому, можете змінити його повідомлення — докладно розказати, як ви розвʼязали конфлікт, чому ви зробили саме такі зміни, якщо це й без того не є очевидним.