-
1. Вступ
- 1.1 Про систему контролю версій
- 1.2 Коротка історія Git
- 1.3 Основи Git
- 1.4 Git, зазвичай, тільки додає дані
- 1.5 Три стани
- 1.6 Командний рядок
- 1.7 Інсталяція Git
- 1.8 Початкове налаштування Git
- 1.9 Отримання допомоги
- 1.10 Підсумок
-
2. Основи Git
- 2.1 Створення Git-репозиторія
- 2.2 Запис змін до репозиторія
- 2.3 Перегляд історії комітів
- 2.4 Скасування речей
- 2.5 Взаємодія з віддаленими сховищами
- 2.6 Теґування
- 2.7 Псевдоніми Git
- 2.8 Підсумок
-
3. Галуження в git
- 3.1 Гілки у кількох словах
- 3.2 Основи галуження та зливання
- 3.3 Управління гілками
- 3.4 Процеси роботи з гілками
- 3.5 Віддалені гілки
- 3.6 Перебазовування
- 3.7 Підсумок
-
4. Git на сервері
- 4.1 Протоколи
- 4.2 Отримання Git на сервері
- 4.3 Генерація вашого публічного ключа SSH
- 4.4 Налаштування Серверу
- 4.5 Демон Git
- 4.6 Розумний HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Варіанти стороннього хостингу
- 4.10 Підсумок
-
5. Розподілений Git
-
6. GitHub
-
7. Інструменти Git
- 7.1 Вибір ревізій
- 7.2 Інтерактивне індексування
- 7.3 Ховання та чищення
- 7.4 Підписання праці
- 7.5 Пошук
- 7.6 Переписування історії
- 7.7 Усвідомлення скидання (reset)
- 7.8 Складне злиття
- 7.9 Rerere
- 7.10 Зневадження з Git
- 7.11 Підмодулі
- 7.12 Пакування
- 7.13 Заміна
- 7.14 Збереження посвідчення (credential)
- 7.15 Підсумок
-
8. Налаштування Git
-
9. Git and Other Systems
- 9.1 Git як клієнт
- 9.2 Міграція на Git
- 9.3 Підсумок
-
10. Git зсередини
- 10.1 Кухонні та парадні команди
- 10.2 Об’єкти Git
- 10.3 Посилання Git
- 10.4 Файли пакунки
- 10.5 Специфікація посилань (refspec)
- 10.6 Протоколи передачі
- 10.7 Супроводження та відновлення даних
- 10.8 Змінні середовища
- 10.9 Підсумок
-
A1. Додаток A: Git в інших середовищах
- A1.1 Графічні інтерфейси
- A1.2 Git у Visual Studio
- A1.3 Git в Eclipse
- A1.4 Git у Bash
- A1.5 Git у Zsh
- A1.6 Git у Powershell
- A1.7 Підсумок
-
A2. Додаток B: Вбудовування Git у ваші застосунки
- A2.1 Git з командного рядка
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
-
A3. Додаток C: Команди Git
- A3.1 Налаштування та конфігурація
- A3.2 Отримання та створення проектів
- A3.3 Базове збереження відбитків
- A3.4 Галуження та зливання
- A3.5 Поширення й оновлення проектів
- A3.6 Огляд та порівняння
- A3.7 Зневаджування
- A3.8 Латання (patching)
- A3.9 Електронна пошта
- A3.10 Зовнішні системи
- A3.11 Адміністрування
- A3.12 Кухонні команди
3.6 Галуження в git - Перебазовування
Перебазовування
Git має два основні способи інтегрувати зміни з гілки в гілку: merge
(зливання) та rebase
(перебазовування).
У цій секції вивчатимете що таке перебазовування, як його виконувати, чому воно вважається таким чудовим інструментом та випадки, коли його не варто застосовувати.
Просте перебазовування
Поверніться до попереднього прикладу з Основи зливання, де видно, що ваша робота розгалузилася в комітах по двох різних гілках.
![Просте розгалуження історії.](png/basic-rebase-1.png)
Найпростіше інтегрувати гілки, як ми вже розглянули, за допомогою команди merge
.
Вона виконає триточкове зливання між двома останніми знімками гілок (C3
і C4
) та їх найближчим спільним предком (C2
), створивши новий знімок (і коміт).
![Інтеграція розгалуженої історії за допомогою зливання.](png/basic-rebase-2.png)
Проте, існує інший спосіб: можете взяти латку (patch) змін з C4
і накласти її поверху C3
.
У Git це називають перебазовуванням (rebasing).
За допомогою rebase
, ви можете взяти всі зміни, що були в комітах одної гілки та відтворити їх на іншій гілці.
Для цього прикладу, виконайте наступне:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Працює це так: шукається спільний предок двох гілок (поточної та тої, на котру перебазовуєтесь); отримуються відмінності (diff), спричинені кожним комітом поточної гілки; відмінності зберігаються в тимчасові файли; поточна гілка встановлюється (reset) на коміт гілки, на яку перебазовуєтесь; та нарешті, застосовується кожна зміна.
![Перебазовування зміни з `C4` на `C3`.](png/basic-rebase-3.png)
C4
на C3
Тепер ви можете повернутися на master
та зробити злиття перемотуванням (fast-forward merge).
$ git checkout master
$ git merge experiment
![Злиття перемотуванням гілки master.](png/basic-rebase-4.png)
Тепер знімок, що вказує на C4'
є точнісінько таким, на який вказував C5
із нашого прикладу зі зливанням.
Кінцевий результат інтеграції змін нічим не відрізняється, проте, у випадку перебазовування отримуємо чистішу історію.
Якщо подивитеся на журнал перебазованої гілки, то він буде лінійним: здається, що вся робота була послідовною, незважаючи на те, що насправді, вона виконувалася паралельно.
Ви часто використовуватимите це, щоб переконатися в тому, що ваші коміти накладалися на віддалені гілки чисто — наприклад, якщо хочете зробити внесок до проекту, яким самі не керуєте.
У цьому випадку, ви працюватимете в гілці й потім перебазуєтеся на origin/master
, коли будете готові надіслати свої латки до основного проекту.
У свою чергу, керуючий проектом не змушений робити ніякої додаткової інтеграційної роботи — просто перемотати гілку або чисто накласти зміни.
Зауважте, що відбиток, на який вказує фінальний коміт, чи він є останнім з перебазовуваних комітів у випадку перебазовування, чи це фінальний коміт зливання, є одинаковим — відрізняється лише історія. Операція перебазовування відтворює зміни з однієї лінії роботи на іншій лінії в тій же послідовності, в якій ці зміни зустрічалися, у той час, як зливання бере кінцеві точки та зливає їх докупи.
Цікавіше перебазовування
Під час перебазовування ви можете програвати зміни не лише на цільовій гілці.
Для прикладу візьміть історію Історія з тематичною гілкою, відгалуженою від іншої тематичної гілки.
Ви створили тематичну гілку (server
) для того, щоб додати певну серверну функціональність до свого проекту та додали туди коміт.
Потім створили гілку для змін на клієнтській стороні (client
), де встигли зробити кілька комітів.
Нарешті, повернулися до гілки з серверними змінами та додали кілька комітів там.
![Історія з тематичною гілкою](png/interesting-rebase-1.png)
Допустимо, ви вирішили злити зміни клієнтської сторони до головної лінії коду для релізу, але хочете зачекати з серверними, доки вони будуть більше відтестовані.
Для цього можете взяти клієнтські зміни, які ще не є на сервері (C8
і C9
), та відтворити їх на master
, користуючись опцією --onto
команди git rebase
:
$ git rebase --onto master server client
Що означає “Візьми гілку client
, знайди всі латки, відколи вона відгалузилася від гілки server
, та відтвори їх у гілці client
, ніби вона насправді базувалася на гілці master
.”
Це трохи складно, але результат доволі класний.
![Перебазовування тематичної гілки](png/interesting-rebase-2.png)
Тепер можете перемотати свою основну гілку master
(дивіться Перемотування гілки master, для залучення в неї змін з клієнтської гілки):
$ git checkout master
$ git merge client
![Перемотування гілки master](png/interesting-rebase-3.png)
Скажімо, тепер ви також вирішили прийняти зміни з серверної гілки.
Ви можете перебазувати серверну гілку на master
без переходу на неї, користуючись командою git rebase <базова гілка> <тематична гілка>
, що перейде на тематичну гілку (в нашому випадку server
) та застосує її зміни на базовій гілці (master
):
$ git rebase master server
Це відтворить вашу зроблену роботу з server
поверху master
, як відображено в Перебазовування серверної гілки поверху master
.
![Перебазовування серверної гілки поверху `master`.](png/interesting-rebase-4.png)
master
Тоді, можете перемотати базову гілку (master
):
$ git checkout master
$ git merge server
Гілки client
та server
можна вилучати, оскільки вся робота з них вже інтегрована і вам вони більше не знадобляться, що зробить вашу історію такою як Остаточна історія комітів:
$ git branch -d client
$ git branch -d server
![Остаточна історія комітів.](png/interesting-rebase-5.png)
Небезпеки перебазовування
Ох, але все блаженство перебазовування не позбавлене й своїх недоліків, які можна підсумувати одним рядком:
Не перебазовуйте коміти, що існують в інших репозиторіях.
Якщо будете слідувати цьому правилу, то все буде гаразд. Якщо ні, то люди ненавидітимуть вас, а сім’я та друзі зневажатимуть.
Коли ви перебазовуєте, то втрачаєте існуючі коміти, а натомість створюєте нові, які є схожі, проте інші.
Якщо ви виклали (push) коміти та інші забрали (pull) їх, базуючи на них свою роботу, а потім ви перезаписали ці коміти з допомогою git rebase
та виклали (push) їх знову, учасники проекту будуть змушені знову зливати свою роботу, а потім у вас буде плутанина, коли ви будете тягнути (pull) їхню роботу назад у свій репозиторій.
Погляньмо на приклад того, як перебазовування змін, які вже зробили публічними, може створювати проблеми. Нехай ви зклонували репозиторій з центрального сервера і трохи в ньому попрацювали. Історія комітів виглядає десь так:
![Склонували репозиторій та трохи в ньому попрацювали.](png/perils-of-rebasing-1.png)
Тепер ще хтось додав змін, включно із зливанням, та відправив ці зміни на центральний сервер. Ви отримали ці зміни та злили нову віддалену гілку зі своєю роботою, отже ваша історія виглядає десь так:
![Отримали ще коміти та злили їх із своєю роботою.](png/perils-of-rebasing-2.png)
Далі, ті, хто викладали та зливали зміни, вирішили повернутися та перебазувати зроблену роботу, виконавши git push --force
для того, щоб переписати історію на сервері.
Коли ви оновитися (fetch
), то отримаєте з сервера нові коміти.
![Хтось виклав перебазовані коміти, зневажаючи коміти, на яких ви базували свою роботу.](png/perils-of-rebasing-3.png)
Тепер ви обидвоє в неприємній ситуації.
Якщо виконаєте git pull
, то створите новий коміт злиття, який включатиме обидві лінії історії, а ваш репозиторій виглядатиме так:
![Ви знову виконали зливання тої самої роботи в новий коміт зливання.](png/perils-of-rebasing-4.png)
Коли ви виконаєте git log
для такої історії, то побачите два коміти, що мають того ж автора, дату та повідомлення, що є досить заплутаним.
Більше того, коли ви відправите (push) таку історію, ви відновите всі перебазовані коміти на центральному сервері, що й надалі заплутуватиме інших.
Мабуть, інші розробники не хочуть мати C4
та C6
в історії, власне чому вони й ініціювали перебазовування.
Перебазовуйся коли перебазовуєшся
Якщо ж ви опинилися в схожій ситуації, Git має деякі чари, що можуть допомогти. Коли хтось у вашій команді примусово надіслав зміни, що перетерли роботу, на якій ви базувалися, — вам доведеться розібратися котрі зміни ваші, а котрі перетерто.
Виявляється, що крім контрольної SHA-1 суми коміту Git також вираховує контрольну суму, що базується на латці (patch), спричиненою комітом. Вона називається “patch-id”.
Якщо ви візьмете роботу, що була перетертою, та перебазуєте її поверх нових комітів ваших колег, Git часто може сам розібратися що є унікально вашим та застосувати це поверху нової гілки.
Скажімо, для попереднього прикладу, якщо, будучи на Хтось виклав перебазовані коміти, зневажаючи коміти, на яких ви базували свою роботу, ми, замість зливання, виконаємо git rebase teamone/master
, Git зробить таке:
* Визначить які зміни є унікальні для нашої гілки (C2, C3, C4, C6, C7)
* Визначить що не є комітами злиття (C2, C3, C4)
* Визначить які коміти не були переписані в цільовій гілці (лише C2 та C3, оскільки C4 має таку ж латку як і C4')
* Застосує ті коміти поверху teamone/master
Отже, замість результатів, які ми бачили в Ви знову виконали зливання тої самої роботи в новий коміт зливання, ми матимемо щось більш схоже на Перебазовування поверх примусово надісланих (force-pushed) змін..
![Перебазовування поверх змушено надісланих (force-pushed) змін.](png/perils-of-rebasing-5.png)
Це спрацювало лише тому, що C4 та C4' що ви та ваш колега зробили мають майже однакові латки. Інакше перебазовування не могло б визначити що це є дублікат і додало б ще одне C4-подібну латку (яка, швидше за все не могла б успішно застосуватися, оскільки зміни вже були б у хоча б одному місці).
Ви також можете спростити процес виконуючи git pull --rebase
замість звичайного git pull
.
Або "вручну" виконати послідовно git fetch
та git rebase teamone/master
.
Якщо ви використовуєте git pull
та бажаєте щоб --rebase
виконувався за замовчуванням, можете відповідно встановити конфігураційну опцію pull.rebase
, виконавши щось типу git config --global pull.rebase true
.
Якщо ви сприймаєте перебазовування як спосіб очистки комітів перед надсиланням (push) та якщо ви перебазовуєте лише коміти, що не були досі опубліковані, тоді все буде гаразд. Якщо ж ви перебазовуєте коміти, що вже були опубліковані, а інші вже базують свої зміни на них, то легко можете опинитися в жахливих неприємностях та зневажанні колег.
Якщо ж ви, чи ваші колеги, все ж мають таку необхідність, з тих чи інших причин, упевніться що інші виконують git pull --rebase
для того, щоб трохи полегшити неприємну ситуацію.
Перебазовування чи зливання
Побачивши перебазовування та зливання в дії, у вас може виникнути запитання: котре з них краще. Перед тим, як відповісти, повернімося до того, що для нас означає історія змін.
Одна з точок зору, що історія репозиторія, це — запис того, що власне трапилося. Це історичний документ сам по собі, та його не можна підробляти. Під цим кутом, переписування історії є майже блюзнірством; ви брешете про те, що ж відбулося насправді. Ну то й що, як там серія заплутаних комітів зливання? Саме так все сталося, нехай репозиторій тримає це для прозорості нащадків.
Протилежна точка зору — сприймати історію як оповідання про те, як створювали проект. Ви ж не будете опубліковувати чернетки вашого творіння, також інструкція з підтримки вашого програмного забезпечення заслуговує детальної редакції. Представники цього табору активно використовують такі інструменти як перебазовування та фільтрація гілок для представлення історії в спосіб, найзручніший майбутнім читачам.
Повертаючись до питання що краще: перебазовування чи зливання, — сподіваємося ви й самі бачите що воно не таке й просте. Git є могутнім інструментом та дозволяє робити багато речей з вашою історією, проте кожна команда та проект є різними. І тепер, коли ви вже познайомилися з тим, як працюють обидва підходи, рішення про те, який кращий для вашої ситуації, залишається за вами.
Взагалі, щоб отримати найкраще з двох світів, дотримуйтеся правила перебазовувати ті локальні зміни, якими ви ще не поділилися, перед надсиланням для того, щоб тримати свою історію чистішою, але ніколи не перебазовувати те, що вже було кудись надіслано.