Git
Chapters ▾ 2nd Edition

5.2 Розподілений Git - Внесення змін до проекту

Внесення змін до проекту

Дуже складно описати як зробити внесок до проекту, через те, що існує неосяжна кількість варіантів того, як це робиться. Оскільки Git дуже гнучкий, люди можуть співпрацювати різноманітними методами, і дуже проблематично описати, як вам слід робити внесок — кожен проект трохи своєрідний. Ось деякі зі змінних, що впливають на це: кількість активних учасників, вибраний процес роботи, чи є у вас доступ на запис, та можливо метод внеску ззовні.

Перша змінна — кількість активних учасників — скільки користувачів активно роблять внески коду до проекту, та як часто? У багатьох випадках, у вас буде два або три розробника з декількома комітами на день, чи навіть менше для дещо неактивних проектів. Для більших компаній чи проектів, кількість розробників може вимірюватись тисячами, з сотнями або тисячами комітів щодня. Це важливо, адже зі збільшенням кількості розробників, виникає все більше проблем з тим, щоб переконатись, що код правильно застосовується та може бути легко злитим. Зміни, які ви надсилаєте, можуть виявитись застарілими, або геть зламаними роботою, яка була злита, доки ви працювали або доки ваші зміни очікували на схвалення та застосування. Як ви можете зберігати ваш код постійно оновленим, а коміти правильними?

Наступною змінною є те, який процес роботи використовується в проекті. Він централізований, та кожен розробник має рівне право запису до головної лінії коду? Чи є в проекті супроводжувач чи менеджер інтеграції, який перевіряє всі латки (patch)? Чи всі латки перевіряються іншими та схвалюються? Чи ви приймаєте участь в цьому процесі? Чи може використовується система лейтенантів, і необхідно спочатку надіслати свою роботу до них?

Наступним питанням є ваш доступ до проекту. Необхідний процес роботи для внеску до проекту є дуже відмінним в залежності від того, чи маєте ви доступ на запис до проекту, чи ні. Якщо у вас немає доступу на запис, як проект воліє приймати вашу роботу? Чи є в нього хоча б якісь правила? Скільки роботи ви збираєтесь надсилати за раз? Як часто ви будете це робити?

Усі ці питання можуть вплинути на те, як робити внесок до проекту ефективно та які процеси роботи є ліпшими чи доступними вам. Ми розглянемо деталі кожного з них у набір прикладів використання, від простого до складніших; ви маєте бути в змозі створювати специфічні процеси роботи, які вам потрібні на практиці, з цих прикладів.

Правила щодо комітів

До того, як розпочати з конкретних прикладів використання, наведемо коротеньку нотатку про повідомлення комітів. Мати добрі правила щодо створення комітів та дотримування їх робить працю з Git та співпрацю з іншими набагато легшою. Проект Git постачає документ, що викладає чимало гарних порад щодо створення комітів, з яких надсилати латки — ви можете прочитати їх у вихідному коді у файлі Documentation/SubmittingPatches.

По-перше, у ваших змінах не має бути помилок пробільних символів. Git надає легкий спосіб перевірити це — перед тим, як створювати коміт, виконайте git diff --check, що знаходить можливі помилки з пробільними символами та надає їх список для вас.

Вивід `git diff --check`.
Рисунок 56. Вивід git diff --check.

Якщо виконати цю команду перед створенням коміту, то можна дізнатись, чи будуть проблеми з пробільними символами в цьому коміті, які можуть дратувати інших розробників.

По-друге, намагайтесь робити кожен коміт логічно окремим набором змін. Якщо можете, спробуйте робити ваші зміни легкими для сприйняття — не працюйте всі вихідні над пʼятьма різними завданнями та потім створюйте з них усіх один величезний коміт у понеділок. Навіть якщо ви не робили комітів впродовж вихідних, скористайтесь індексом у понеділок щоб розділити свою роботу принаймні на один коміт для кожного завдання, зі змістовним повідомленням кожного коміту. Якщо деякі зміни редагують один файл, спробуйте використати git add --patch, щоб частково проіндексувати файли (розглянуто докладно в Інтерактивне індексування). Відбиток проекту наприкінці гілки однаковий, хоч ви зробите один коміт, хоч пʼять, доки всі зміни додані в якийсь момент, отже спробуйте спростити своїм колегам розробникам перегляд ваших змін. Цей підхід також сприяє легшому вилученню чи вивертанню (revert) змін, якщо це буде потрібно пізніше. Переписування історії описує чимало корисних хитрощів Git для переписування історії та інтерактивного індексування файлів — використовуйте ці інструменти для виготовлення чистої та зрозумілої історії перед тим, як надсилати вашу роботу іншим.

Нарешті, не варто забувати про повідомлення комітів. Якщо взяти за звичку створювати якісні повідомлення комітів, то використання та співпраця з Git стає значно легшою. За загальним правилом, повідомлення мають починатися з єдиного рядка, який містить не більш ніж приблизно 50 символів та описує набір змін змістовно, після якого є порожній рядок, за яким іде більш докладне пояснення. Проект Git вимагає, щоб докладніше пояснення включало мотивацію зміни та описувало різницю з попередньою поведінкою — це правило варте наслідування. Також слушно використовувати доконаний вид теперішнього часу в цих повідомленнях. Іншими словами, використовуйте команди. Замість “Я додав тести для” чи “Додаю тести для,” використовуйте “Додати тести для.” Ось шаблон, автором якого є Тім Поуп:

Стислий (50 символів або менше) підсумок змін

Докладніший пояснювальний текст, якщо потрібно. Розбивайте
рядки по приблизно 72 символи. У деяких ситуаціях, перший
рядок сприймається як заголовок електронного листа, а
решта як текст тіла листа. Порожній рядок відділяє підсумок
від тіла і є необхідним (хіба ви взагалі не пишете тіло);
такі інструменти як перебазування можуть заплутатися, порожнього
рядка не буде.

Подальші параграфи йдуть після порожнього рядка.

  - Маркери елементів списку теж можна використовувати.

  - Зазвичай як маркер використовують дефіс або зірочку,
    перед якими є єдиний пробіл, з порожніми рядками між
    елементами, проте домовленості щодо цього різняться

Якщо ваші повідомлення комітів відповідають цьому шаблонові, то всім, хто працює над проектом, буде значно легше співпрацювати. Проект Git має добре оформлені повідомлення комітів — спробуйте виконати для нього git log --no-merges, і побачите, як виглядає історія відформатованих комітів проекту.

Зауваження
Робіть як ми кажемо, а не як ми робимо.

For the sake of brevity, many of the examples in this book don’t have nicely-formatted commit messages like this; instead, we simply use the -m option to git commit. Заради стислості, в багатьох прикладах цієї книжки повідомлення комітів будуть оформлені не так добре; натомість ми просто використовуватимемо опцію -m команди git commit.

In short, do as we say, not as we do. Тобто робіть як ми кажемо, а не як ми робимо.

Маленька закрита команда

Найпростіший випадок, який ви можете зустріти — це закритий проект з одним чи двома іншими розробниками. “Закритий,” у цьому контексті, означає зі закритим вихідним кодом — недоступним зовнішньому світу. Ви та інші розробники всі мають доступ на запис до репозиторія.

У такому середовищі, ви можете працювати за процесом роботи, схожим на той, за яким ви могли працювали при користуванні Subversion чи іншої централізованої системи. Ви все одно отримуєте такі переваги, як створення комітів поза мережею та неймовірно простіше галуження та зливання, проте процес роботи може бути дуже схожим: головною відмінністю є те, що зливання здійснюються на клієнті, а не на сервері під час створення коміту. Погляньмо, як це працює, коли два розробника починають працювати разом над спільним сховищем. Перший розробник, Джон, клонує репозиторій, робить зміни й створює коміти локально. (Повідомлення протоколу замінені на ... у цих прикладах, щоб дещо скоротити їх.)

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'remove invalid default value'
[master 738ee87] remove invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

Друга розробниця, Джесіка, робить те саме — клонує сховище та створює коміт зі змінами:

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

Тепер, Джесіка надсилає свою працю до сервера, і тут не виникає жодних помилок:

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

Останній рядок цього виводу є доволі корисним повідомленням від операції push. Основний формат — <старе-посилання>..<нове-посилання> посилання-від -> посилання-до, де посилання-від позначає локальне посилання, що надсилається, а посилання-до — назва віддаленого посилання, що оновлюється. Ви зустрічатимете такі рядки далі в дискусіях, тому загальне розуміння допоможе розібратися, в якому стані різноманітні сховища. More details are available in the documentation for Докладніше про це в документації git-push.

Повертаємося до нашого прикладу: невдовзі Джон щось змінює, зберігає ці зміни в локальному сховищі та намагається надіслати їх до того ж серверу:

# John's Machine
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

Цього разу операція завершується помилкою через те, що Джесіка вже надіслала власні зміни. Це особливо важливо зрозуміти, якщо ви звикли до Subversion, оскільки ви помітите, що два розробники не редагували один і той самий файл. Хоча Subversion автоматично робить таке злиття на сервері, якщо різні файли були редаговані, у Git ви змушені спочатку зливати коміти локально. Іншими словами, Джон має спочатку отримати зміни Джесіки та злити їх разом у своєму локальному сховищі, і лише після цього йому буде дозволено їх надіслати.

Як перший крок, Джон отримує працю Джесіки (лише отримує, ще не зливає з власними змінами):

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

Наразі, локальне сховище Джона виглядає приблизно так:

Історія Джона
Рисунок 57. Історія Джона, що розбіглася.

Тепер Джон може злити щойно отриману роботу Джесіки з власною локальною:

$ git merge origin/master
Merge made by the 'recursive' strategy.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Якщо локальне злиття проходить без проблем, то оновлена історія матиме такий вигляд:

Репозиторій Джона після зливання `origin/master`.
Рисунок 58. Репозиторій Джона після зливання origin/master.

Тепер Джонові, можливо, варто запустити тести й переконатися, що зміни Джесіки не вплинули на його власні. Якщо все гаразд, він нарешті може надіслати щойно злиту роботу до серверу:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

Після цього історія комітів Джона матиме такий вигляд:

Історія Джона після надсилання до сервера `origin`.
Рисунок 59. Історія Джона після надсилання до сервера origin.

Тим часом, Джесіка створила тематичну гілку під назвою issue54 та створила в ній три коміти. Вона не отримувала зміни Джона покищо, отже її історія комітів виглядає так:

Тематична гілка Джесіки.
Рисунок 60. Тематична гілка Джесіки.

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

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

Це стягує роботу Джона, яку він встиг надіслати. Історія Джесіки тепер виглядає так:

Історія Джесіки після отримання змін Джона.
Рисунок 61. Історія Джесіки після отримання змін Джона.

Джесіка вважає, що її тематична гілка готова, проте бажає знати, яку частину отриманих змін Джона їй доведеться зливати зі своєю роботою, щоб мати можливість надіслати зміни. Вона виконує git log, щоб дізнатися:

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   remove invalid default value

Синтаксис issue54..origin/master є фільтром журналу, який просить Git відображати лише ті коміти з другої гілки (у цьому випадку origin/master), яких немає в першій гілці (у цьому випадку issue54). Ми розглянемо цей синтаксис докладно в Інтервали комітів.

З цього виводу можна побачити, що існує єдиний коміт, який створив Джон, та Джесіка ще не злила зі своїми локальними змінами. Якщо вона зіллє origin/master, це єдиний коміт, який змінить її локальну працю.

Тепер, Джесіка може злити її тематичну працю до своєї гілки master, зливає роботу Джона (origin/master) до гілки master, а потім надсилає зміни назад до сервера знову.

Спершу (після того, як зберегти всі зміни в комітах у тематичній гілці issue54), вона переходить назад до своєї гілки master, щоб підготуватися до інтеграції:

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Джесіка може злити спочатку хоч origin/master, хоч issue54 — вони обидві готові, отже порядок не є важливим. Відбиток у результаті буде однаковим незалежно від порядку, який вона вибере; лише історія буде трохи різною. Вона вирішує злити спочатку гілку issue54:

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

Жодні проблеми не трапляються; як бачите, це просте злиття перемотуванням вперед (fast-forward). Тепер Джесіка pавершує локальне злиття: зливає вже отриману роботу Джона, що чекає в гілці origin/master:

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

Усе зливається чисто, і тепер історія Джесіки має такий вигляд:

Історія Джесіки після зливання змін Джона.
Рисунок 62. Історія Джесіки після зливання змін Джона.

Тепер origin/master є досяжним з гілки master Джесіки, отже вона має бути в змозі успішно надіслати зміни (припускаючи, що Джон тим часом не надіслав ще якихось змін):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

Кожен розробник створив декілька комітів та успішно зливав роботу іншого.

Історія Джесіки після надсилання змін назад до сервера.
Рисунок 63. Історія Джесіки після надсилання змін назад до сервера.

Це один з найпростіших процесів роботи. Ви працюєте деякий час (зазвичай, переважно у тематичній гілці) та зливаєте цю працю до своєї гілки master, коли все готово. Коли ви бажаєте надати доступ до своєї роботи, треба отримати (fetch) та злити до вашого master гілку origin/master, якщо вона змінилася, та нарешті надіслати гілку master до сервера. Загальна послідовність виглядає приблизно так:

Загальна послідовність подій для простого процесу роботи декількох розробників з Git.
Рисунок 64. Загальна послідовність подій для простого процесу роботи декількох розробників з Git.

Закрита керована команда

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

Скажімо, Джон та Джесіка працюють разом над однією функцією (назвімо це “featureA”), у той час як Джесіка з Джосі (це третій розробник) працюють над іншою (скажімо, “featureB”). У цьому випадку, компанія використовує різновид процесу роботи з менеджером інтеграції, в якому праця окремих груп інтегрується виключно окремими інженерами, і гілка master головного сховища може бути оновленою лише цими інженерами. У цьому сценарії, вся праця відбувається у командних гілках, а пізніше стягується разом інтеграторами.

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

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

Тепер їй треба поділитись своєю роботою з Джоном, отже вона надсилає коміти своєї гілки featureA до сервера. Джесіка не має доступу на запис до гілки master — він є лише в інтеграторів — отже вона має надсилати до іншої гілки, щоб співпрацювати з Джоном:

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Джесіка пише листа Джону, щоб повідомити про створену нею гілку featureA з її роботою, і тепер він може переглянути її. Доки вона чекає на відгук від Джона, Джесіка вирішує розпочати працю над featureB разом з Джосі. Спочатку, вона створює нову функціональну гілку, засновану на гілці master з сервера:

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

Тепер, Джесіка робить декілька комітів у гілці featureB:

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

Сховище Джесіки тепер має такий вигляд:

Початкова історія комітів Джесіки.
Рисунок 65. Початкова історія комітів Джесіки.

Вона готова надсилати свою роботу, проте отримує листа від Джосі про те, що гілка з розпочатою роботою над “featureB” вже надіслана до сервера з ім’ям featureBee. Джесіка має злити ці зміни зі своїми, щоб мати змогу надіслати до сервера. Спочатку Джесіка здобуває зміни Джосі за допомогою git fetch:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Якщо Джесіка досі на гілці featureB, то вона тепер може злити зміни Джосі в цю гілку за допомогою git merge:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

Тепер Джесіка хоче надіслати свою злиту роботу над “featureB” назад до сервера, проте вона не хоче просто надсилати свою гілку featureB. Натомість, оскільки Джосі вже створила на сервері гілку featureBee, Джесіка хоче надіслати зміни до цієї гілки, що вона й робить командою:

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

Це називається специфікація посилань (refspec). Дивіться Специфікація посилань (refspec) задля докладнішої дискусії про визпоси Git та різноманітні речі, які з ними можна робити. Також зверніть увагу на опцію -u; це скорочення для --set-upstream, яка налаштовує гілки для легшого подальшого надсилання та отримання змін.

Раптом Джесіка отримує листа від Джона про те, що він надіслав деякі зміни до гілки featureA, над якою вони працюють разом, та просить Джесіку подивитися на них. Джесіка знову виконує простий git fetch, щоб отримати всі нові дані з серверу, включно з (звісно) останніми змінами Джона:

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

Джесіка може продивитися журнал змін Джона, порівнявши щойно отриману гілку featureA зі своєю локальною копією тієї ж гілки:

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    changed log output to 30 from 25

Якщо Джесиці до вподоби те, що вона бачить, то вона може злити нові зміни Джона до локальної гілки featureA за допомогою:

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Нарешті, Джесіка може захотіти внести кілька невеличких змін у злиті файли, тож вона може зробити ці зміни, зберегти їх у коміт в гілці featureA та надіслати кінцевий результат назад до серверу.

$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

Історія комітів Джесіки тепер виглядає так:

Історія Джесіки після створення комітів у функціональних гілках.
Рисунок 66. Історія Джесіки після створення комітів у функціональних гілках.

Якось Джесіка, Джосі та Джон повідомляють інтеграторів, що гілки featureA та featureBee на сервері готові для інтеграції до стрижневої гілки. Після того, як інтегратори зіллють ці гілки до стрижневої, отримання змін додасть новий коміт злиття, що зробить історію такою:

Історія Джесіки після зливання обох її тематичних гілок.
Рисунок 67. Історія Джесіки після зливання обох її тематичних гілок.

Чимало груп переходять на Git через цю можливість декільком командам паралельно працювати, та зливати різні лінії роботи пізніше. Здатність менших підгруп команди співпрацювати за допомогою віддалених гілок без необхідності залучати чи затримувати всю команду є великою перевагою Git. Послідовність для процесу роботи, який ви бачили тут, виглядає приблизно так:

Базова послідовність цього процесу роботи керованої команди.
Рисунок 68. Базова послідовність цього процесу роботи керованої команди.

Відкритий проект з форками

Внески до відкритих проектів роблять дещо інакше. Оскільки у вас немає права оновлювати гілки проекту напряму, ви маєте доправити свої зміни до супроводжувачів іншим чином. Перший приклад описує внески за допомогою форків на хостах Git, які підтримують легке створення форків. Багато сайтів розгортання підтримують це (включно з GitHub, BitBucket, repo.or.cz тощо), та багато супроводжувачів проектів очікують внесків у такому вигляді. Наступна секція розгляне проекти, які бажають приймати внески латками (patches) через електронну пошту.

Спочатку, ви вірогідно забажаєте створити клон головного сховища, створити тематичну гілку для латки чи низки латок, які ви плануєте внести, та попрацювати там. Ось базова послідовність для цього:

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit
Зауваження

Можливо, ви забажаєте використати rebase -i, щоб зчавити (squash) свою працю до єдиного коміту, або переставити коміти, щоб супроводжувачу було легше переглянути латку — дивіться Переписування історії задля детальнішої інформації про інтерактивне перебазування.

Коли ваша робота в гілці завершена, і ви готові направити внесок до супроводжувачів, перейдіть до сторінки оригінального проекту та натисніть кнопку “Форк”, що створить ваш власний форк проекту, в який ви маєте право писати. Потім вам потрібно додати URL цього сховища як нове віддалене сховище до вашого локального репозиторію; у цьому прикладі, назвімо його myfork:

$ git remote add myfork <url>

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

Хай там як, ви можете надіслати свою працю за допомогою:

$ git push -u myfork featureA

Коли ваша робота надіслана до вашого форку, потрібно повідомити супроводжувачів оригінального проекту, що ви хотіли б, щоб вони злили ці зміни. Це, зазвичай, називають pull request, і, зазвичай, ви можете або згенерувати його за допомогою сайту — GitHub має власний механізм “Pull Request”, який ми розглянемо в GitHub —  або можете виконати команду git request-pull та надіслати її вивід поштою до супроводжувачів проекту вручну.

Команда git request-pull бере базову гілку, до якої ви бажаєте злити тематичну гілку, та URL Git сховища, з якого вони можуть отримати гілку, та виводить стислий опис всіх змін, які ви просите злити. Наприклад, якщо Джесіка бажає надіслати Джонові pull request та вона зробила два коміти в тематичній гілці, які вона щойно надіслала, вона може виконати це:

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
        added a new function

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      add limit to log function
      change log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

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

У проекті, в якому ви не є супроводжувачем, зазвичай легше, щоб гілка master завжди слідкувала за origin/master, та працювати в тематичних гілках, які можна легко скасувати, якщо їх відкинули. Ще однією перевагою ізолювання напрямків роботи в тематичних гілках є те, що це полегшує перебазування вашої праці, якщо вершина головного сховища пересунулась за цей час та ваші коміти більше не застосовуються чисто. Наприклад, якщо ви бажаєте подати на розгляд другу тему до проекту, не продовжуйте працювати в щойно надісланій тематичній гілці — почніть спочатку з гілки master головного сховища.

$ git checkout -b featureB origin/master
  ... work ...
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
  ... email generated request pull to maintainer ...
$ git fetch origin

Тепер, кожна з ваших тем міститься в зерносховищі — щось схоже на чергу латок — їх можна переписувати, перебазовувати, змінювати без взаємних завад чи залежностей, наприклад:

Початкова історія комітів з роботою `featureB`.
Рисунок 69. Початкова історія комітів з роботою featureB.

Скажімо, супроводжувач проекту отримав купу інших латок та спробували вашу першу гілку, проте вона вже не зливається чисто. У цьому випадку, ви можете спробувати перебазувати цю гілку поверху origin/master, розв’язати конфлікти для супроводжувачів, та надати свої зміни знову:

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

Це переписує вашу історію, і тепер вона виглядає як Історія комітів після праці в featureA..

Історія комітів після праці в `featureA`.
Рисунок 70. Історія комітів після праці в featureA.

Оскільки ви перебазували гілку, доводиться задати -f до команди push, щоб замінити гілку featureA на сервері комітом, який не є її нащадком. Альтернативно, можна було надіслати цю нову працю до іншої гілки на сервері (можливо названу featureAv2).

Подивімося на ще один можливий сценарій: супроводжувач подивився на роботу в другій гілці та схвалює концепцію, проте бажає, щоб ви змінили якусь окрему річ у реалізації. Ви також скористаєтесь цією можливістю, щоб перебазувати роботу на поточній гілці master. Ви починаєте з нової гілки, відгалуженої від origin/master, зчавлюєте в ній зміни featureB, розв’язуєте конфлікти, робите зміну в реалізації, та потім надсилаєте зміни назад як нову гілку:

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
  ... change implementation ...
$ git commit
$ git push myfork featureBv2

Опція --squash бере всю роботу з гілки, яку зливають, та зчавлює її в один набір змін, в результаті отримуємо стан репозиторія, ніби справжнє злиття сталося проте без власне створення коміту злиття. Це означає, що майбутні коміти матимуть лише одного батька, та дозволяє додати всі зміни з іншої гілки, а потім зробити ще деякі зміни до запису нового коміту. Також опція --no-commit може бути корисною, щоб відкласти коміт злиття в разі типового процесу зливання.

Тепер ви можете повідомити супроводжувача про те, що ви зробили доручені зміни, та ці зміни можна знайти у вашій гілці featureBv2.

Історія комітів після роботи над `featureBv2`.
Рисунок 71. Історія комітів після роботи над featureBv2.

Відкритий проект за допомогою електронної пошти

Багато проектів встановили процедури для прийняття латок — вам треба переглянути специфічні правила для кожного проекту, адже вони будуть різними. Адже існує декілька старших, більших проектів, які приймають латки через поштовий розсилку розробників (developer mailing list), ми розглянемо приклад цього зараз.

Процес роботи схожий на попередній випадок — ви створюєте тематичні гілки для кожного набору латок, над якими працюєте. Різниця в тому, як ви надаєте їх проекту. Замість створення форку проекту та надсилання до власної версії, треба згенерувати поштові версії кожної послідовності комітів та надіcлати їх поштою до поштової розсилки розробників:

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

Тепер у вас є два коміти, які ви бажаєте надіслати до поштової розсилки. Ви використовуєте git format-patch щоб згенерувати файли у форматі mbox, які ви можете відправити електронною поштою до розсилки — вона перетворює кожен коміт на лист, у темі якого зазначено перший рядок повідомлення коміту, а решту повідомлення та латку записує в тіло листа. Це дуже зручно тим, що застосування латки з листа, який згенеровано за допомогою format-patch, відновлює всю інформацію коміту правильно.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

Команда format-patch друкує назви файлів з латками, які створює. Перемикач -M каже Git шукати перейменування. У результаті файли виглядають так:

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

Ви також можете редагувати ці файли латок, щоб додати більше інформації до поштової розсилки, яку ви не бажаєте бачити в повідомленні коміту. Якщо додати текст між рядком --- та початком латки (рядок diff --git), розробники зможуть прочитати його, проте під час застосування латки цей текст ігнорується.

Щоб надіслати цього листа до поштової розсилки, ви можете або вставити файл до вашої поштової програми, або надіслати його за допомогою командного рядка. Вставка тексту здебільшого призводить до помилок формату, особливо з “розумнішими” клієнтами, які не зберігають доречно розриви рядків та інші пробільні символи правильно. На щастя, Git постачає інструмент, щоб допомогти з надсиланням правильно оформлених латок через IMAP, що може бути простішим. Ми продемонструємо, як надіслати латку через Gmail, який є поштовим агентом, з яким ми найкраще знайомі; ви можете прочитати детальні інструкції для багатьох поштових програм наприкінці згаданого раніше файлу Documentation/SubmittingPatches вихідного коду Git.

Спершу, вам треба налаштувати секцію imap свого файлу ~/.gitconfig. Ви можете встановити кожне значення окремо декількома командами git config, або можете додати їх вручну, проте зрештою конфігураційний файл має виглядати приблизно так:

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

Якщо ваш сервер IMAP не використовує SSL, останні два рядки вірогідно зайві, та значення host буде imap:// замість imaps://. Коли це налаштовано, ви можете використати git imap-send, щоб розмістити послідовність латок у директорії Чернетки (Drafts) зазначеного IMAP сервера:

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

Наразі, ви маєте бути в змозі перейти до своєї директорії Чернетки, змінити значення Кому (To) на поштову розсилку, до якої ви надсилаєте латку, можливо додати до Копії (CC) супроводжувача, або людину, відповідальну за цю секцію, та відправити листа.

Ви також можете надіслати латки через SMTP сервер. Як і раніше, ви можете задати кожне значення окремо за допомогою декількох команд git config, або додати їх вручну до секції sendmail свого файлу ~/.gitconfig:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

Відтак, ви можете використати git send-email, щоб надсилати латки:

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

Потім, Git друкує купу журнальної інформації, що виглядає приблизно так для кожної відправленої латки:

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK

Підсумок

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

scroll-to-top