3.2 Розгалуження в Git - Основи розгалуження та злиття
Давайте розглянемо простий приклад робочого процесу, який може бути корисним у вашому проекті. Вашу роботу побудовано так:
- Ви працюєте над веб-сайтом.
- Ви створюєте гілку для реалізації нової функціональності відповідно до історії користувача.
- Ви працюєте у цій гілці.
У цей момент ви отримуєте повідомлення, що виявлена критична помилка, яка потребує якнайшвидшого виправлення. Ваші дії:
- Перейти на основну гілку.
- Створити гілку додавання виправлення.
- Після тестування злити гілку, що містить виправлення, з основною гілкою.
- Перейти назад у гілку для реалізації історії користувача і продовжити працювати.
Основи розгалуження
Припустимо, що ви працюєте над проектом і вже маєте кілька коммітів.
Ви обрали завдання #53 з якась там-у-вас-система-відстеження-завдань. Щоб створити гілку і відразу перейти на неї, можна виконати команду git checkout з параметром -b :
$ git checkout -b iss53 Перейти до нової брами "iss53"
Це те саме, що й:
$ git branch iss53 $ git checkout iss53
Ви працюєте над сайтом та робите комміти. Це призводить до того, що гілка iss53 рухається вперед, тому що ви переключилися на неї раніше (HEAD вказує на неї).
$ vim index.html $ git commit -a -m 'Create new footer [issue 53]'
І тут ви отримуєте повідомлення про виявлення на сайті вразливості, і цю вразливість потрібно усунути негайно. Завдяки Git вам не доведеться ні намагатися реалізувати виправлення разом із змінами, які ви зробили в ході розробки iss53, ні докладати зусиль для відкату цих змін та повернення до вихідного стану перед початком розробки виправлення. Все, що вам потрібно - перейти на гілку master.
Майте на увазі, що якщо робочий каталог або індекс містять незафіксовані зміни, що конфліктують з гілкою, на яку ви хочете перейти, то Git не дозволить переключити гілки. Найкраще перемикатися з чистого робочого стану проекту: усі змінені файли додати до індексу і зробити коміт. Є способи обійти це (приховати зміни (stash) або додати їх до останнього комміту (amend)), але про це ми поговоримо пізніше в розділі Приховування та очищення глави 7. Тепер припустимо, що ви зафіксували всі свої зміни і можете переключитися на гілку master :
$ git checkout master Switched to branch 'master'
З цього моменту ваш робочий каталог має такий самий вигляд, який був перед початком роботи над завданням #53, і ви можете зосередитися на роботі над виправленням. Важливо запам'ятати: коли ви перемикаєте гілки, Git повертає стан робочого каталогу до того виду, який він мав у момент останнього комміта в гілку, що перемикається. Він додає, видаляє та змінює файли автоматично, щоб стан робочого каталогу відповідав тому, коли було зроблено останній коміт.
Тепер можна перейти до написання виправлення. Давайте створимо нову гілку, у якій реалізуємо виправлення.
$ git checkout -b hotfix Поточна до нової branch 'hotfix' $ vim index.html $ git commit -a -m 'Fix broken email address' [hotfix 1fb7853] Fix broken email address 1 file changed, 2 insertions(+)
Ви можете прогнати тести, щоб переконатися, що ваша вразливість справді виправлена. І якщо це так - виконати злиття гілки hotfix з гілкою master для включення змін до продукту. Це робиться командою git merge:
$ git checkout master $ git merge hotfix Updating f42c576..3a0874c Fast-forward index.html | 2 ++ 1 file changed, 2 insertions(+)
Помітили фразу "fast-forward" у цьому злитті? Git просто перемістив покажчик гілки вперед, тому що коміт C4, на який вказує злита гілка hotfix, був прямим нащадком коміту C2, на якому ви знаходилися до цього. Іншими словами, якщо комміт зливається з тим, до якого можна дістатися, рухаючись по історії вперед, Git спрощує злиття, просто переносячи покажчик гілки вперед, тому що в цьому випадку немає різноспрямованих змін, які потрібно було б звести докупи. Це називається "fast-forward".
Тепер ваші зміни включені до комміта, на який вказує гілка master , і виправлення можна впроваджувати.
Після впровадження архіважливого виправлення ви готові повернутися до роботи над тим, що були змушені відкласти. Але спочатку потрібно видалити гілку hotfix, тому що вона більше не потрібна - гілка master вказує на те саме місце. Для видалення гілки виконайте команду git branch з параметром -d:
$ git branch -d hotfix Deleted branch hotfix (3a0874c).
Тепер ви можете перейти назад на гілку iss53 і продовжити роботу над завданням #53:
$ git checkout iss53 Перемикається до брами "iss53" $ vim index.html $ git commit -a -m 'Finish the footer [issue 53]' [iss53 ad82d7a] Finish the footer [issue 53] 1 file (+)
Варто звернути увагу на те, що всі зміни з гілки hotfix не включені до вашої гілки iss53 . Якщо їх потрібно включити, ви можете влити гілку master у вашу гілку iss53 командою git merge master, а можете відкласти злиття цих змін до завершення роботи, а потім влити гілку iss53 в master.
Основи злиття
Припустимо, ви вирішили, що робота з проблеми #53 закінчена і її можна влити у гілку master . Для цього потрібно виконати злиття гілки iss53 точно так, як ви робили це з гілкою hotfix раніше.Все, що потрібно зробити - перейти на гілку, в яку ви хочете включити зміни, і виконати команду git merge:
$ git checkout master Поточний до branch 'master' $ git merge iss53 Merge made by the 'recursive' strategy. index.html | 1 + 1 file changed, 1 insertion(+)
Результат цієї операції відрізняється від результату злиття гілки hotfix. В даному випадку процес розробки відповів у більш ранній точці. Оскільки коміт, на якому ми знаходимося, не є прямим батьком гілки, з якою ми виконуємо злиття, Git доведеться трохи попрацювати. У цьому випадку Git виконує просте тристороннє злиття, використовуючи останні коміти гілок, що об'єднуються, і спільного для них батьківського комміту.
Замість того, щоб просто пересунути вказівник гілки вперед, Git створює новий результуючий знімок тристороннього злиття, а потім автоматично робить коміт. Цей особливий коміт називають коммітом злиття, оскільки він має більше одного предка.
Тепер, коли зміни злиті, гілка iss53 більше не потрібна. Ви можете закрити завдання в системі відстеження помилок та видалити гілку:
$ 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 . " to mark resolution) both modified: index.html не змінюється added to commit (use "git add" and/or "git commit -a" )
Все, де є невирішені конфлікти злиття, перераховується як незлите. До конфліктуючих файлів Git додає спеціальні маркери конфліктів, щоб ви могли виправити їх вручну. У вашому файлі з'явився розділ, який виглядає приблизно так:
contact: [email protected] ======= >>>>>>> iss53:index.html
Це означає, що версія з HEAD (вашої гілки master, оскільки саме її ви витягли перед запуском команди злиття) - це верхня частина блоку (все, що над ======= ), а версія з вашої гілки iss53 представлена в нижній частини. Щоб вирішити конфлікт, доведеться вибрати один із варіантів або об'єднати вміст по-своєму. Наприклад, ви можете вирішити конфлікт, замінивши весь блок таким:
У цьому розділі є трохи від кожної частини, а рядки >>>>>> повністю видалені. Вирішивши кожен конфлікт у всіх файлах, запустіть git add для кожного файлу, щоб відзначити конфлікт як вирішений. Додавання файлу до індексу означає для Git, що всі конфлікти в ньому виправлені.
Якщо ви хочете використовувати графічний інструмент для вирішення конфліктів, можна запустити git mergetool, який проведе вас по всіх конфліктах:
$ git mergetool Цей message is displayed because 'merge.tool' is not configured. See 'git mergetool --tool-help' або 'git help config' для more details. 'git mergetool' буде зараз пристосуватися до одного з наступних інструментів: Opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge 'index.html': : modified file : modified file Hit return to start merge resolution tool (opendiff):
Якщо ви хочете використовувати інструмент злиття не за замовчуванням (в даному випадку Git вибрав opendiff , оскільки команда запускалася на Mac), список всіх інструментів, що підтримуються, представлений вгорі після фрази «one of the following tools». Просто введіть назву інструмента, який потрібно використовувати.
Ми розглянемо більш просунуті інструменти для вирішення складних конфліктів злиття у розділі Просунуте злиття розділу 7.
Після виходу з інструменту злиття Git запитає про успішність процесу. Якщо ви відповісте скрипту ствердно, він додасть файл в індекс, щоб відзначити його як дозволений. Тепер можна знову запустити git status, щоб переконатися у відсутності конфліктів:
$ git status On branch master All conflicts fixed but you are still merging. (use "git commit" до заключної merge) Зміни до комітету: 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 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 #
Якщо ви вважаєте, що коміт злиття вимагає додаткових пояснень — опишіть, як були вирішені конфлікти і чому були застосовані саме такі зміни, якщо це не очевидно.
3.1 Розгалуження в Git - Про розгалуження у двох словах
Майже кожна система контролю версій у тій чи іншій формі підтримує розгалуження.Використовуючи розгалуження, Ви відхиляєтесь від основної лінії розробки та продовжуєте роботу незалежно від неї, не втручаючись у основну лінію. У багатьох системах контролю версій створення гілок — це дуже витратний процес, який часто вимагає створення нової копії каталогу з вихідним кодом, що може зайняти багато часу для великого проекту.
Деякі люди, говорячи про модель розгалуження Git, називають її "кілер-фіча", що вигідно виділяє Git на тлі інших систем контролю версій. Що в ньому такого особливого? Розгалуження Git дуже легковажно: операція створення гілки виконується майже миттєво, перемикання між гілками туди-сюди, як правило, також швидко. На відміну від багатьох інших систем контролю версій, Git заохочує процес роботи, при якому розгалуження та злиття виконується часто, навіть кілька разів на день. Розуміння та володіння цією функціональністю дає вам унікальний та потужний інструмент, який може повністю змінити звичний процес розробки.
Про розгалуження у двох словах
Для точного розуміння механізму розгалужень необхідно повернутися назад і вивчити те, як Git зберігає дані.
Як ви можете пам'ятати з Що таке Git?, Git не зберігає дані у вигляді послідовності змін, він використовує набір знімків (snapshot).
Коли ви робите коміт, Git зберігає його у вигляді об'єкта, що містить покажчик на знімок (snapshot) підготовлених даних. Цей об'єкт також містить ім'я автора та email, повідомлення та покажчик на комміт або комміти, що безпосередньо передують даному (його батьків): відсутність батька для початкового комміту, один з батьків для звичайного комміту, і кілька батьків для результатів злиття двох і більше гілок.
Припустимо, у вас є каталог з трьома файлами і ви додаєте їх у індекс і створюєте комміт.Під час індексації обчислюється контрольна сума кожного файлу (SHA-1, як ми дізналися з Що таке Git?), потім кожен файл зберігається в репозиторій (Git називає такий файл блоб — великий бінарний об'єкт), а контрольна сума потрапить до індексу:
$ git add README test.rb LICENSE $ git commit -m 'Initial commit'
Коли ви створюєте коміт командою git commit, Git обчислює контрольні суми кожного підкаталогу (у нашому випадку, лише основний каталог проекту) і зберігає його в репозиторії як об'єкт дерева каталогів. Потім Git створює об'єкт комміту з метаданими та вказівником на основне дерево проекту для можливості відтворити цей знімок у разі потреби.
Ваш репозиторій Git тепер зберігає п'ять об'єктів: три блоби об'єкта (по одному на кожен файл), об'єкт дерева каталогів, що містить список файлів і відповідних їм блобів, а також об'єкт комміта, що містить метадані та вказівник на об'єкт дерева каталогів.
Якщо ви зробите зміни та створите ще один коміт, то він міститиме покажчик на попередній коміт.
Гілка в Git - це простий вказівник, що переміщується, на один з таких коммітів. За замовчуванням, ім'я основної гілки Git - master . Як тільки ви почнете створювати комміти, гілка master завжди вказуватиме на останній комміт. Щоразу під час створення комміта покажчик гілки master пересуватиметься на наступний комміт автоматично.
Гілка "master" в Git - це не якась особлива гілка. Вона така сама, як і всі інші гілки. Вона існує майже у всіх репозиторіях тільки тому, що її створює команда git init, а більшість людей не змінюють її назву.
Створення нової гілки
Що ж насправді відбувається під час створення гілки? Лише створюється новий покажчик для подальшого переміщення.Допустимо ви хочете створити нову гілку з ім'ям testing. Ви можете це зробити командою git branch:
$ git branch testing
Через війну створюється новий покажчик поточний комит.
Малюнок 12. Дві гілки вказують на ту саму послідовність коммітів.
Як Git визначає, в якій галузі ви знаходитесь? Він зберігає спеціальний покажчик HEAD. Майте на увазі, що Git концепція HEAD значно відрізняється від інших систем контролю версій, які ви могли використовувати раніше (Subversion або CVS). У Git це покажчик на поточну локальну гілку. У нашому випадку ми все ще знаходимося у гілці master. Команда git branch тільки створює нову гілку, але не перемикає на неї.
Ви можете легко це побачити за допомогою простої команди git log, яка покаже вам, куди вказують покажчики гілок. Ця опція називається --decorate.
$ git log --oneline --decorate f30ab (HEAD -> master, testing) Add feature #32 - здатність до нових форматів до центральної interface 34ac2 Fix bug #1328 - stack overflow under certain conditions 98ca9
Тут можна побачити вказівки на коміт f30ab гілки: master і testing.
Перемикання гілок
Для перемикання на існуючу гілку виконайте команду git checkout. Давайте перейдемо на гілку testing :
$ git checkout testing
В результаті покажчик HEAD переміститься на гілку testing.
Який у цьому сенс? Давайте зробимо ще один коміт:
$ vim test.rb $ git commit -a -m 'made a change'
Цікава ситуація: покажчик на гілку testing перемістився вперед, а master вказує на той же коміт, де ви були до перемикання гілок командою git checkout. Давайте перейдемо назад на гілку master :
$ git checkout master
Якщо виконати команду git log прямо зараз, то в її висновку щойно створена гілка "testing" фігурувати не буде.
Гілка нікуди не зникла; просто Git не знає, що саме вона вас цікавить, і виводить найкориснішу на його думку інформацію. Інакше кажучи, за умовчанням git log відобразить історію коммітів лише поточної гілки.
Щоб переглянути історію коммітів іншої гілки, необхідно явно вказати її ім'я: git log testing Щоб подивитися історію по всіх гілках — виконайте команду з додатковим прапором: git log --all .
Ця команда зробила дві речі: перемістила покажчик HEAD назад на гілку master і повернула файли в робочому каталозі в стан, на знімок якого вказує master . Це також означає, що всі зміни, що вносяться з цього моменту, будуть відноситися до старої версії проекту. Іншими словами, ви відкотили всі зміни гілки testing та можете продовжувати в іншому напрямку.
Важливо запам'ятати, що при перемиканні гілок Git відбувається зміна файлів у робочому каталозі. Якщо ви переключаєтеся на стару гілку, то робочий каталог виглядатиме так само, як виглядав на момент останнього комміту в гілку. Якщо Git з якихось причин не може цього зробити, він не дозволить вам переключитися взагалі.
Давайте зробимо ще кілька змін і створимо черговий коміт:
$ vim test.rb $ git commit -a -m 'зроблені інші зміни'
Тепер історія вашого проекту розійшлася (див. Розгалужена історія). Ви створили гілку і переключилися на неї, попрацювали, а потім повернулися в основну гілку та попрацювали в ній. Ці зміни ізольовані одна від одної: ви можете вільно перемикатися туди й назад, а коли знадобиться об'єднати їх. І все це робиться простими командами: branch, checkout та commit.
Усі описані дії можна візуалізувати за допомогою команди git log. Для відображення історії коммітів, поточного положення вказівників гілок та історії розгалуження виконайте команду git log --oneline --decorate --graph --all .
$ git log -- oneline -- decorate --graph --all * c2b9e (HEAD, master) Made other changes | * 87ab2 (testing) Зміна зміни |/ * f30ab Додайте перевагу #32 - здатність до нових форматів до центрального interface * 34ac2 Fix bug #1328 - stack overflow under certain conditions * 98ca9
Гілка Git — це простий файл, що містить 40 символів контрольної суми SHA-1 комміта, на який вона вказує; тому операції з гілками є дешевими з погляду споживання ресурсів чи часу. Створення нової гілки в Git відбувається так само швидко і просто як запис 41 байта у файл (40 знаків та переклад рядка).
Це принципово відрізняє процес розгалуження Git від більш старих систем контролю версій, де всі файли проекту копіюються в інший підкаталог. Залежно від розміру проекту операції розгалуження в таких системах можуть займати секунди або навіть хвилини, коли в Git ці операції миттєві. Оскільки при комміті ми зберігаємо покажчик на батьківський коміт, то пошук підходящої бази для злиття гілок робиться автоматично і, як правило, дуже простий. Ці можливості спонукають розробників частіше створювати та використовувати гілки.
Давайте подивимося, чому і вам має сенс робити так само.
Як правило, при створенні нової гілки ви хочете відразу на неї перейти - це можна зробити використовуючи команду git checkout -b .
Починаючи з Git версії 2.23, ви можете використовувати git switch замість git checkout , щоб:
- Перейти на існуючу гілку: git switch testing-branch .
- Створити нову гілку і перейти на неї: git switch -c new-branch . Прапор -c означає створення, але також можна використовувати повний формат: --create.
- Повернутися до попередньої гілки: git switch - .
18. Створення гілки
Розробка нової функціональності завжди пов'язана з ризиком: технологія може зайняти багато часу, ви можете зрештою відмовитися від неї і т.д.Тому краще всього ізолювати розробку фічі в окремій гілці. Коли фіча буде готова, ви зможете злити цю гілку з гілкою main. До того часу гілка main буде захищена від ризикованого та неперевіреного коду. Крім того, ви можете працювати над кількома фічами паралельно, над кожною у власній гілці. Ви також можете в будь-який момент вносити зміни в гілці main , наприклад, щоб виправити помилку у стабільному коді.
01 Створіть гілку
Настав час зробити нашу сторінку стильнішою за допомогою CSS. Ми будемо розвивати цю можливість у новій гілці під назвою style.
Виконайте
git switch -c style
git status
Старожили можуть заперечити, що їх вчили створювати гілки командою git checkout -b style. Пам'ятаєте, я згадував, що команда checkout перевантажена функціями та прапорами? Старий спосіб ще працює, але він не рекомендується. Нова команда git switch виразніша і менш сприйнятлива до помилок. Крім того, у ній менше прапорів та опцій, тому її легше запам'ятати.
Результат
$ git switch -c style
Switched to a new branch 'style'
$ git status
On branch style
nothing to commit, working tree clean
Зверніть увагу, що команда git status повідомляє про те, що ви знаходитесь у гілці style .
02 Додати файл стилів style.css
Виконайте
Файл: style.css
Виконайте
git add style.css
git commit -m "Added css stylesheet"
03 Змініть hello.html, щоб він використовував style.css.
Файл: hello.html
html>
head>
link
type="text/css"
rel="stylesheet"
media="all"
href="style.css"
/>
head>
body>
h1>Hello, World!h1>
body>
html>
Виконайте
git add hello.html
git commit -m "Included stylesheet into hello.html"
04 Далі
Тепер у нас є нова гілка під назвою style з двома новими комітами. Далі ми дізнаємося, як перемикатися між гілками.