Git
Chapters ▾ 2nd Edition

9.1 Git and Other Systems - Git як клієнт

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

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

Git як клієнт

Git справляє настільки позитивне враження на розробників, що багато з них вигадують способи використання Git на своєму комп’ютері навіть тоді, коли решта команди використовує іншу систему контролю версій. Для цього розроблено багато спеціальних адаптерів, які називаються "мостами" ("bridges"). Тут ми розглянемо ті адаптери, з якими вам, найімовірніше, доведеться мати справу при роботі над реальними проектами.

Git та Subversion

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

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

git svn

Базова команда Git для всіх команд мосту Subversion — git svn. Вона приймає доволі багато команд, отже ми покажемо найпоширеніші під час розгляду декількох простих процесів роботи.

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

Не переписуйте історії та не намагайтесь знову надіслати зміни, та не надсилайте до паралельного сховища Git для взаємодії зі співробітниками, які використовують Git. Subversion може мати лише єдину лінійну історію, та заплутати його дуже легко. Якщо ви працюєте в команді, і дехто використовує SVN, а інші — Git, переконайтесь, що всі використовують сервер SVN для взаємодії — це зробить ваше життя легшим.

Налаштування

Задля демонстрації цього функціоналу, вам потрібне звичайне сховище SVN, до якого у вас є доступ на запис. Якщо бажаєте виконувати подальші приклади, вам треба створити копію якогось тестового SVN сховища з правом на запис. Щоб зробити це легко, ви можете використати інструмент під назвою svnsync, який постачається разом зі Subversion.

Щоб схоплювати думку, вам спочатку треба створити локальне сховище Subversion:

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

Потім, дозволити всім користувачам змінювати revprops — це просто зробити, якщо додати скрипт pre-revprop-change, який завжди повертає 0:

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

Тепер ви можете синхронізувати цей проект на вашій локальній машині — для цього треба викликати svnsync init з параметрами “до якого” та “з якого” сховища синхронізувати.

$ svnsync init file:///tmp/test-svn \
  http://your-svn-server.example.org/svn/

Це налаштовує властивості (properties) для виконання синхронізації. Потім треба зробити клонування коду, виконавши

$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[…]

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

Розпочинаємо

Тепер, коли у вас є сховище Subversion з доступом на запис, ви можете прослідкувати за типовим процесом роботи. Ви почнете з команди git svn clone, яка імпортує весь репозиторій Subversion до локального сховища Git. Памʼятайте: якщо ви імпортуєте зі справжнього розгорнутого (hosted) сховища Subversion, то маєте замінити file:///tmp/test-svn на URL вашого репозиторія Subversion:

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
    A	m4/acx_pthread.m4
    A	m4/stl_hash.m4
    A	java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
    A	java/src/test/java/com/google/protobuf/WireFormatTest.java
…
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75

Це еквівалентно виклику двох команд — git svn init та потім git svn fetch — з URL, який ви надали. Це може бути довгим процесом. Якщо, наприклад, тестовий проект має лише приблизно 75 комітів та невеликий за розміром код, Git все одно має отримати кожну версію, по одній за раз, та створювати коміти для кожної. Для проекту зі сотнями чи тисячами комітів, це може дійсно потребувати годин або навіть днів, щоб завершитись.

Частина -T trunk -b branches -t tags каже Git, що цей репозиторій Subversion розташовує гілки та теґи як заведено. Якщо у вас trunk, гілки чи теґи називаються інакше, ви можете змінити ці опції. Через те, що ця частина дуже розповсюджена, її всю можна замінити на -s, що означає стандартне розташування та означає всі ці опції. Наступна команда еквівалентна попередній:

$ git svn clone file:///tmp/test-svn -s

Наразі, у вас має бути працюючий репозиторій Git, який містить імпортовані гілки та теґи:

$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk

Завважте, цей інструмент працює з теґами Subversion як з віддаленими посиланнями. Подивімося прискіпливіше за допомогою кухонної команди Git show-ref:

$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk

Git такого не робить, коли клонує з Git сервера; ось як сховище з теґами виглядає відразу після клонування:

$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0

Git отримує теґи напряму до refs/tags, замість того, щоб працювати з ними, як з віддаленими гілками.

Надсилання змін назад до Subversion

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

$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)

Далі, вам треба надіслати свої зміни до першоджерела. Завважте, як це змінює спосіб роботи з Subversion — ви можете створити декілька комітів локально, та лише потім надіслати їх всіх разом до сервера Subversion. Щоб надіслати до сервера Subversion, треба виконати команду git svn dcommit:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r77
    M	README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Вона бере всі коміти, які ви створили поверху коду з сервера Subversion, робить коміт Subversion для кожного, та потім переписує ваші локальні коміти Git, щоб додати до них унікальний ідентифікатор. Це важливо, оскільки означає, що всі SHA-1 суми ваших комітів зміняться. Частково через це, робота з віддаленою версію проекту, яка працює на Git, та одночасно працювати з сервером Subversion, не є гарною ідеєю. Якщо ви подивитесь на останній коміт, то побачите новий доданий git-svn-id:

$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README

    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68

Зверніть увагу, що раніше сума SHA-1 починалася з 4af61fd, а після коміту починається з 95e0222. Якщо ви бажаєте надсилати зміни й до сервера Git, і до сервера Subversion, то маєте спочатку надіслати (dcommit) до Subversion, оскільки ця дія змінює дані комітів.

Отримання нових змін

Якщо ви працюєте з іншими розробниками, то колись хтось з вас надішле зміни, потім хтось інших спробує надіслати зміни, які призводять до конфлікту. Ця зміна буде відхилена, доки ви не зіллєте їхню роботу. З git svn, це виглядає так:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M	README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Щоб розвʼязати цю проблему, ви можете виконати git svn rebase, який отримує будь-які зміни на сервері, яких у вас покищо немає, та перебазовує всю роботу, яка у вас є поверху того, що є на сервері:

$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a b34372b25ccf4945fe5658fa381b075045e7702a M	README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M	README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Тепер, вся ваша робота знаходиться поверху того, що є на сервері Subversion, отже ви можете успішно зробити dcommit:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r85
    M	README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Зверніть увагу, що на відміну від Git, який вимагає від вас зливати роботу з першоджерела, якої у вас немає локально перед надсиланням, git svn вимагає від вас цього лише якщо зміни конфліктують (так само, як працює Subversion). Якщо хтось інший надішле зміну до одного файлу, а потім ви надішлете зміну до іншого файлу, ваш dcommit спрацює без проблем:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	configure.ac
Committed r87
    M	autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M	configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a9439312d80d5d43bb65d4a7d0389ed6d M	autogen.sh
First, rewinding head to replay your work on top of it...

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

Вам також варто виконати наступну команду, щоб отримати зміни зі сервера Subversion, якщо ви не готові створити коміт. Ви можете виконати git svn fetch, щоб взяти нові дані, проте git svn rebase і отримує дані, і оновлює ваші локальні коміти.

$ git svn rebase
    M	autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.

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

Проблеми з галуженням Git

Коли ви звикаєте до процесу роботи Git, ви, вірогідно, створюєте тематичні гілки, працюєте в них, а потім зливаєте їх. Якщо ви надсилаєте до сервера Subversion командою git svn, то можливо ліпше перебазувати вашу роботу поверху однієї гілки замість того, щоб зливати гілки разом. Причина надати перевагу перебазуванню в тому, що Subversion має лінійну історію, та не працює зі зливаннями, як Git, отже git svn слідує лише за першими батьками, коли перетворює відбитки на коміти Subversion.

Припустімо, що ваша історія виглядає наступним чином: ви створили гілку experiment, зробили два коміти, а потім злили їх назад до master. Коли ви зробите dcommit, то побачите щось таке:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	CHANGES.txt
Committed r89
    M	CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M	COPYING.txt
    M	INSTALL.txt
Committed r90
    M	INSTALL.txt
    M	COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Виконання dcommit на гілці зі злитою історією працює успішно, окрім того, що, якщо подивитись на історію проекту Git, то виявиться, що жоден з комітів, створених у гілці experiment, не переписано — натомість, усі ці зміни зʼявляються у версії SVN як єдиний коміт злиття.

Коли хтось зробить клон цієї праці, усе, що вони побачать — коміт зливання з усіма змінами в ньому, ніби ви виконали git merge --squash; вони не побачать дані про окремі коміти - коли вони були створені чи звідки взялися.

Галуження Subversion

Галуження Subversion не таке, як в Git: якщо ви можете уникнути його використання, то, напевно, найкраще це зробити. Втім, ви можете створювати й надсилати коміти до гілок Subversion за допомогою git svn.

Створення нової гілки SVN

Щоб створити нову гілку Subversion, ви можете виконати git svn branch [назва-нової-гілки]:

$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera) cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)

Це рівнозначно команді Subversion svn copy trunk branches/opera та виконується на сервері Subversion. Важливо зазначити, що це не переключає вас до нової гілки; якщо ви зараз створите коміт, цей коміт піде до гілки trunk на сервері, а не до opera.

Переключення активних гілок

Git визначає, до якої гілки dcommit має надсилати ваші зміни наступним чином: знаходить верхівку будь-якої з ваших гілок Subversion в історії — у вас має бути лише одна, та це має бути останній коміт з git-svn-id в історії вашої поточної гілки.

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

$ git branch opera remotes/origin/opera

Тепер, якщо ви бажаєте злити свою гілку opera до trunk (ваша гілка master), то можете це зробити звичайним git merge. Проте, ви маєте зробити повідомлення коміту змістовним (за допомогою -m), інакше злиття просто напише “Merge branch opera” замість чогось корисного.

Памʼятайте: хоча ви використовуєте git merge для цієї операції, і зливання напевно буде набагато легшим, ніж було б у Subversion (адже Git автоматично знайде відповідну базу для злиття), це не звичайний коміт злиття Git. Ви маєте надіслати ці дані назад до серверу Subversion, який не може впоратись з комітом, який має більше одного батька; отже, після того, як ви його надішлете, він буде виглядати, як коміт, який зварив у собі всю роботу з іншої гілки. Після зливання однієї гілки в іншу, ви не зможете легко повернутись назад та продовжити роботу в тій гілці, як зазвичай у Git. Команда dcommit, яку ви виконуєте, стирає будь-яку інформацію про зливання гілки, отже наступні визначення бази для зливання будуть хибними — dcommit призводить до того, що результат вашого git merge виглядає так, ніби ви виконали git merge --squash. На жаль, не існує доладного способу уникнути цієї ситуації — Subversion не може зберігати цю інформацію, отже ви завжди будете окалічені цими обмеженнями, доки використовуєте його як свій сервер. Щоб уникнути проблем, варто вилучити локальну гілку (у даному випадку, opera) після зливання її до trunk.

Команди Subversion

Набір інструментів git svn пропонує чимало команд, щоб полегшити перехід до Git, для чого надає деякий функціонал, схожий на те, що було в Subversion. Ось декілька команд, які надають вам те, що пропонував Subversion.

Історія в стилі SVN

Якщо ви звикли до Subversion та бажаєте бачити свою історію в стилі SVN, то можете виконати git svn log, щоб побачити історію комітів у форматі SVN:

$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines

autogen change

------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines

Merge branch 'experiment'

------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines

updated the changelog

Ви маєте знати дві важливі речі про git svn log. По-перше, вона працює без мережі, на відміну від справжньої команди svn log, яка робить запит до сервера Subversion. По-друге, вона показує вам лише коміти, які були надіслані до сервера Subversion. Локальні коміти Git, для яких ви ще не виконали dcommit, не показано; як і коміти, які інші надіслали до Subversion за цей час. Це більше схоже на останній відомий стан комітів сервера Subversion.

Анотація SVN

Як команда git svn log імітує команду svn log поза мережею, так само ви можете отримати еквівалент svn annotate, якщо виконаєте git svn blame [ФАЙЛ]. Вивід виглядатиме так:

$ git svn blame README.txt
 2   temporal Protocol Buffers - Google's data interchange format
 2   temporal Copyright 2008 Google Inc.
 2   temporal http://code.google.com/apis/protocolbuffers/
 2   temporal
22   temporal C++ Installation - Unix
22   temporal =======================
 2   temporal
79    schacon Committing in git-svn.
78    schacon
 2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol
 2   temporal Buffer compiler (protoc) execute the following:
 2   temporal

Ще раз, тут не показано комітів, які ви зробили локально в Git, або які були надіслані до Subversion за цей час.

Інформація про сервер SVN

Ви також можете отримати інформацію на кшталт тієї, що надає вам svn info, якщо виконаєте git svn info:

$ git svn info
Path: .
URL: https://schacon-test.googlecode.com/svn/trunk
Repository Root: https://schacon-test.googlecode.com/svn
Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029
Revision: 87
Node Kind: directory
Schedule: normal
Last Changed Author: schacon
Last Changed Rev: 87
Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)

Це схоже на blame та log у тому, що виконується поза мережею та відповідає лише часу останнього звʼязку зі сервером Subversion.

Ігнорування того, що ігнорує Subversion

Якщо ви створите клон Subversion, який десь має встановлені властивості svn:ignore, ви напевно забажаєте створити відповідні файли .gitignore, щоб випадково не додати файли, які не треба. git svn має дві команди, щоб допомогти з цим. Перша — це git svn create-ignore, яка автоматично створює відповідні файли .gitignore, отже ваш наступний коміт може включити їх.

Другою командою є git svn show-ignore, яка друкує до stdout рядки, які вам треба помістити до файлу .gitignore, щоб ви могли надіслати вивід до файлу exclude вашого проекту:

$ git svn show-ignore > .git/info/exclude

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

Підсумок по git-svn

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

  • Зберігайте лінійну історію Git, яка не містить комітів злиття, створених git merge. Перебазовуйте будь-яку роботу, що була створена поза головною гілкою поверху неї; не зливайте до неї.

  • Не налаштовуйте співпрацю на окремому сервері Git. Можете мати один, щоб прискорити клонування для нових розробників, проте не надсилайте до нього нічого, що не має git-svn-id. Можливо навіть варто додати гак pre-receive, який перевіряє кожне повідомлення коміту, та відхиляє їх, якщо хтось намагається надіслати якийсь коміт без git-svn-id.

Якщо ви слідуватимете цим порадам, працю зі сервером Subversion буде легше витримати. Втім, якщо є можливість перейти на справжній сервер Git, то це надасть вашій команді набагато більше переваг.

Git і Mercurial

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

Хорошою новиною є те, що якщо ви надаєте перевагу Git, але вам доводиться працювати з проектами, код яких знаходиться в системі Mercurial, існує спосіб використання Git у якості клієнта для роботи з репозиторієм на Mercurial. Оскільки Git працює з серверами через концепцію "віддалених репозиторіїв" (remotes), не дивно, що цей міст реалізовано за допомогою своєрідного "помічника протоколу" (remote helper) для "віддалених репозиторіїв". Проект, який реалізує вищесказане, називається git-remote-hg і розміщений за адресою https://github.com/felipec/git-remote-hg.

git-remote-hg

Для початку, вам потрібно встановити git-remote-hg. Для цього скопіюйте файл до директорії, що є у вашому PATH, наприклад:

$ curl -o ~/bin/git-remote-hg \
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg

…припускаючи, що ~/bin включений у ваш $PATH. Git-remote-hg має ще одну залежність: бібліотеку mercurial для Python. Якщо у вас встановлено Python, просто виконайте:

$ pip install mercurial

(Якщо ж у вас не встановлено Python, перейдіть за посиланням https://www.python.org/ і спочатку встановіть його.)

І останнє, що вам знадобиться, це клієнт Mercurial. Перейдіть за посиланням https://www.mercurial-scm.org/ і встановіть його, якщо ви цього ще не зробили.

Тепер ви готові побачити магію. Усе, що вам необхідно, це Mercurial-репозиторій, у який ви можете надсилати зміни. На щастя, так можна працювати з кожним Mercurial-репозиторієм, тому ми скористаємось репозиторієм "hello world", який використовується для вивчення Mercurial:

$ hg clone http://selenic.com/repo/hello /tmp/hello

Основи

Тепер, коли в нас є "серверний" репозиторій, ми можемо розглянути типові способи роботи з Mercurial. Як ви побачите згодом, ці дві системи дуже схожі, тому все повинно пройти гладко.

Як і при роботі з Git, спершу ми клонуємо репозиторій:

$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Мабуть ви помітили, що для роботи з Mercurial-репозиторієм використовується стандартна команда git clone. Це тому, що git-remote-hg працює на доволі низькому рівні, і використовує механізм, подібний до HTTP/S протоколу в системі Git (помічники протоколу). Оскільки і Git, і Mercurial розраховані на те, що кожен клієнт має повну копію історії репозиторія, вищезгадана команда здійснює повне клонування, включно з усією історією проекту, і робить це досить швидко.

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

$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files

Git-remote-hg намагається нівелювати відмінності між Git та Mercurial, але "під капотом" він керує концептуальними перетвореннями між двома різними системами. У директорії refs/hg знаходяться посилання на об’єкти віддаленого репозиторія. Наприклад, refs/hg/origin/branches/default — це файл-посилання Git, який містить SHA-1, що починається з "ac7955c", який є комітом, на який вказує гілка master. Таким чином, директорія refs/hg — це щось схоже на refs/remotes/origin, але тут окремо зберігаються закладки та гілки.

Файл notes/hg — відправна точка для розуміння того, як git-remote-hg встановлює відповідність між хешами комітів у Git та ідентифікаторами змін у Mercurial. Погляньмо, що там:

$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9

Отже, refs/notes/hg вказує на дерево, яке в базі об’єктів Git містить перелік інших об’єктів та їхніх імен. Команда git ls-tree виводить права доступу, тип, хеш та ім’я файлу для елементів дерева. Коли ми дістанемось першого елемента дерева, ми побачимо, що всередині знаходиться блоб з іменем "ac9117f" (SHA-1 хеш коміту, на який вказує гілка master), який містить "0a04b98" (ідентифікатор останньої зміни гілки default у Mercurial).

Хорошою новиною є те, що нам не потрібно турбуватися про все це. Типовий робочий процес не буде значно відрізнятися від роботи з віддаленим репозиторієм Git.

Є ще одна річ, яку ми повинні враховувати, перш ніж продовжувати: ігноровані файли. І Mercurial, і Git використовують для цього схожий механізм, але зберігати файл .gitignore в Mercurial-репозиторії — не найкраща ідея. На щастя, в Git є можливість ігнорувати файли, що знаходяться в локальній копії репозиторія, а формат списку ігнорованих файлів в Mercurial сумісний з Git, тому вам достатньо скопіювати його:

$ cp .hgignore .git/info/exclude

Файл .git/info/exclude діє подібно до .gitignore, але не включається у коміт.

Робочий процес

Нехай ми виконали певний обсяг роботи і зробили деякі коміти в гілку master, і тепер ви готові надіслати зміни до віддаленого репозиторія. Так виглядає наш репозиторій в цей момент:

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Наша гілка master на два коміти попереду origin/master, але ці два коміти існують лише на нашій локальній машині. Погляньмо, раптом хтось інший зробив важливі зміни:

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87  master     -> origin/master
   ac7955c..df85e87  branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Оскільки ми використали --all ми бачимо посилання "notes", які використовуються всередині git-remote-hg, але ми можемо не звертати уваги на них. Все інше — саме те, що ми очікували; origin/master пішов далі на один коміт і наша історія розійшлася. На відміну від інших систем, з якими ми мали справу в цьому розділі, Mercurial вміє працювати зі злиттям, тому нам не потрібно робити жодних фокусів.

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
*   0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

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

$ git push
To hg::/tmp/hello
   df85e87..0c64627  master -> master

І це все! Якщо ви поглянете на Mercurial-репозиторій, ви переконаєтесь, що відбулось саме те, що ми й очікували:

$ hg log -G --style compact
o    5[tip]:4,2   dc8fa4f932b8   2014-08-14 19:33 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   64f27bcefc35   2014-08-14 19:27 -0700   ben
| |    Update makefile
| |
| o  3:1   4256fc29598f   2014-08-14 19:27 -0700   ben
| |    Goodbye
| |
@ |  2   7db0b4848b3c   2014-08-14 19:30 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Набір змін 2 був здійснений Mercurial’ом, а зміни 3 та 4 — за допомогою git-remote-hg шляхом надсилання комітів, зроблених з Git.

Гілки і закладки

Git має лише один вид гілок: вказівник, який переміщується при комітах. У Mercurial цей вид вказівника називається "закладка", і вона поводить себе подібно до гілки в Git.

Поняття "гілка" в Mercurial більш складне. Гілка, в якій відбувається зміна, записується всередині кожної зміни, таким чином, вона завжди залишається в історії репозиторія. Ось приклад коміту, який зроблено в гілці develop:

$ hg log -l 1
changeset:   6:8f65e5e02793
branch:      develop
tag:         tip
user:        Ben Straub <ben@straub.cc>
date:        Thu Aug 14 20:06:38 2014 -0700
summary:     More documentation

Зверніть увагу на рядок, що починається з "branch". Git не може насправді відтворити це (і не повинен; обидва типи гілок можуть бути представлені як Git-посилання), але git-remote-hg змушений розуміти цю різницю, оскільки це важливо для Mercurial.

Створення закладок у Mercurial настільки ж просте, як створення гілок у Git. У Git ми робимо наступне:

$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
 * [new branch]      featureA -> featureA

Ось і все, що потрібно. А в Mercurial це виглядає так:

$ hg bookmarks
   featureA                  5:bd5ac26f11f9
$ hg log --style compact -G
@  6[tip]   8f65e5e02793   2014-08-14 20:06 -0700   ben
|    More documentation
|
o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| |    update makefile
| |
| o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |    goodbye
| |
o |  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Зверніть увагу на нову мітку [featureA] у п’ятій ревізії. Це працює так само, як гілки в Git, але з одним винятком: ви не можете видаляти закладки в Git (це обмеження помічників протоколу).

Ви можете працювати і з "повноцінними" Mercurial-гілками: просто розмістіть гілку в просторі імен branches:

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch]      branches/permanent -> branches/permanent

Ось як це виглядає в Mercurial:

$ hg branches
permanent                      7:a4529d07aad4
develop                        6:8f65e5e02793
default                        5:bd5ac26f11f9 (inactive)
$ hg log -G
o  changeset:   7:a4529d07aad4
|  branch:      permanent
|  tag:         tip
|  parent:      5:bd5ac26f11f9
|  user:        Ben Straub <ben@straub.cc>
|  date:        Thu Aug 14 20:21:09 2014 -0700
|  summary:     A permanent change
|
| @  changeset:   6:8f65e5e02793
|/   branch:      develop
|    user:        Ben Straub <ben@straub.cc>
|    date:        Thu Aug 14 20:06:38 2014 -0700
|    summary:     More documentation
|
o    changeset:   5:bd5ac26f11f9
|\   bookmark:    featureA
| |  parent:      4:0434aaa6b91f
| |  parent:      2:f098c7f45c4f
| |  user:        Ben Straub <ben@straub.cc>
| |  date:        Thu Aug 14 20:02:21 2014 -0700
| |  summary:     Merge remote-tracking branch 'origin/master'
[...]

Ім’я гілки "permanent" було записано разом зі змінами під номером 7.

З боку Git, робота з обома видами гілок однакова: просто переходите на гілку, робите коміт, отримуєте зміни, робите злиття і надсилаєте зміни (checkout, commit, fetch, merge, pull, і push) як завжди. Ще одна річ, про яку вам потрібно знати: Mercurial не підтримує перезапис історії, лише додавання. Ось як наш Mercurial-репозиторій виглядає після інтерактивної зміни історії та примусового надсилання змін:

$ hg log --style compact -G
o  10[tip]   99611176cbc9   2014-08-14 20:21 -0700   ben
|    A permanent change
|
o  9   f23e12f939c3   2014-08-14 20:01 -0700   ben
|    Add some documentation
|
o  8:1   c16971d33922   2014-08-14 20:00 -0700   ben
|    goodbye
|
| o  7:5   a4529d07aad4   2014-08-14 20:21 -0700   ben
| |    A permanent change
| |
| | @  6   8f65e5e02793   2014-08-14 20:06 -0700   ben
| |/     More documentation
| |
| o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
| |\     Merge remote-tracking branch 'origin/master'
| | |
| | o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| | |    update makefile
| | |
+---o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |      goodbye
| |
| o  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Були створені зміни 8, 9 та 10 і тепер вони належать до гілки permanent, але старі зміни досі там. Це може дуже спантеличити ваших колег, які використовують Mercurial, тому краще уникати цього.

Підсумок по Mercurial

Git і Mercurial достатньо подібні для безболісної роботи один з одним. Якщо ви будете уникати змін вже опублікованої історії (що, загалом, рекомендовано), ви навіть не помітите, що працюєте в Mercurial.

Git and Bazaar

Among the DVCS, another famous one is Bazaar. Bazaar is free and open source, and is part of the GNU Project. It behaves very differently from Git. Sometimes, to do the same thing as with Git, you have to use a different keyword, and some keywords that are common don’t have the same meaning. In particular, the branch management is very different and may cause confusion, especially when someone comes from Git’s universe. Nevertheless, it is possible to work on a Bazaar repository from a Git one.

There are many projects that allow you to use Git as a Bazaar client. Here we’ll use Felipe Contreras' project that you may find at https://github.com/felipec/git-remote-bzr. To install it, you just have to download the file git-remote-bzr in a folder contained in your $PATH:

$ wget https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr -O ~/bin/git-remote-bzr
$ chmod +x ~/bin/git-remote-bzr

You also need to have Bazaar installed. That’s all!

Create a Git repository from a Bazaar repository

It is simple to use. It is enough to clone a Bazaar repository prefixing it by bzr::. Since Git and Bazaar both do full clones to your machine, it’s possible to attach a Git clone to your local Bazaar clone, but it isn’t recommended. It’s much easier to attach your Git clone directly to the same place your Bazaar clone is attached to ‒ the central repository.

Let’s suppose that you worked with a remote repository which is at address bzr+ssh://developer@mybazaarserver:myproject. Then you must clone it in the following way:

$ git clone bzr::bzr+ssh://developer@mybazaarserver:myproject myProject-Git
$ cd myProject-Git

At this point, your Git repository is created but it is not compacted for optimal disk use. That’s why you should also clean and compact your Git repository, especially if it is a big one:

$ git gc --aggressive

Bazaar branches

Bazaar only allows you to clone branches, but a repository may contain several branches, and git-remote-bzr can clone both. For example, to clone a branch:

$ git clone bzr::bzr://bzr.savannah.gnu.org/emacs/trunk emacs-trunk

And to clone the whole repository:

$ git clone bzr::bzr://bzr.savannah.gnu.org/emacs emacs

The second command clones all the branches contained in the emacs repository; nevertheless, it is possible to point out some branches:

$ git config remote-bzr.branches 'trunk, xwindow'

Some remote repositories don’t allow you to list their branches, in which case you have to manually specify them, and even though you could specify the configuration in the cloning command, you may find this easier:

$ git init emacs
$ git remote add origin bzr::bzr://bzr.savannah.gnu.org/emacs
$ git config remote-bzr.branches 'trunk, xwindow'
$ git fetch

Ignore what is ignored with .bzrignore

Since you are working on a project managed with Bazaar, you shouldn’t create a .gitignore file because you may accidentally set it under version control and the other people working with Bazaar would be disturbed. The solution is to create the .git/info/exclude file either as a symbolic link or as a regular file. We’ll see later on how to solve this question.

Bazaar uses the same model as Git to ignore files, but also has two features which don’t have an equivalent into Git. The complete description may be found in the documentation. The two features are:

  1. "!!" allows you to ignore certain file patterns even if they’re specified using a "!" rule.

  2. "RE:" at the beginning of a line allows you to specify a Python regular expression (Git only allows shell globs).

As a consequence, there are two different situations to consider:

  1. If the .bzrignore file does not contain any of these two specific prefixes, then you can simply make a symbolic link to it in the repository: ln -s .bzrignore .git/info/exclude

  2. Otherwise, you must create the .git/info/exclude file and adapt it to ignore exactly the same files in .bzrignore.

Whatever the case is, you will have to remain vigilant against any change of .bzrignore to make sure that the .git/info/exclude file always reflects .bzrignore. Indeed, if the .bzrignore file were to change and contained one or more lines starting with "!!" or "RE:", Git not being able to interpret these lines, you’ll have to adapt your .git/info/exclude file to ignore the same files as the ones ignored with .bzrignore. Moreover, if the .git/info/exclude file was a symbolic link, you’ll have to first delete the symbolic link, copy .bzrignore to .git/info/exclude and then adapt the latter. However, be careful with its creation because with Git it is impossible to re-include a file if a parent directory of that file is excluded.

Fetch the changes of the remote repository

To fetch the changes of the remote, you pull changes as usually, using Git commands. Supposing that your changes are on the master branch, you merge or rebase your work on the origin/master branch:

$ git pull --rebase origin

Push your work on the remote repository

Because Bazaar also has the concept of merge commits, there will be no problem if you push a merge commit. So you can work on a branch, merge the changes into master and push your work. Then, you create your branches, you test and commit your work as usual. You finally push your work to the Bazaar repository:

$ git push origin master

Caveats

Git’s remote-helpers framework has some limitations that apply. In particular, these commands don’t work:

  • git push origin :branch-to-delete (Bazaar can’t accept ref deletions in this way.)

  • git push origin old:new (it will push old)

  • git push --dry-run origin branch (it will push)

Summary

Since Git’s and Bazaar’s models are similar, there isn’t a lot of resistance when working across the boundary. As long as you watch out for the limitations, and are always aware that the remote repository isn’t natively Git, you’ll be fine.

Git і Perforce

Perforce — дуже популярна система контролю версій у корпоративному середовищі. Він з’явився у 1995 році, що робить його найстарішою системою контролю версій з тих, що розглядаються у цьому розділі. Perforce розроблений з обмеженнями тих часів; він передбачає постійне з’єднання з центральним сервером, а локально зберігається лише одна версія файлів. Насправді, його можливості та обмеження добре підходять для вирішення специфічного кола задач, та існує досить багато проектів, які використовують Perforce, але де Git працював би значно краще.

Існують два варіанти сумісного використання Perforce і Git. Перший, який ми розглянемо, — міст “Git Fusion” від розробників Perforce, який дозволить вам виставляти піддерева (subtrees) вашого Perforce-депо (Perforce depot) як Git репозиторії з можливістю читання-запису. Другий — git-p4 — клієнтський міст, який дозволяє вам використовувати Git як клієнт Perforce без необхідності здійснювати будь-яке переналаштування сервера Perforce.

Git Fusion

Perforce забезпечує продукт, який називається Git Fusion (доступний за посиланням http://www.perforce.com/git-fusion), і синхронізує сервер Perforce з репозиторієм Git на стороні сервера.

Налаштування

Для наших прикладів ми використаємо найпростіший метод встановлення Git Fusion, який полягає у завантаженні віртуальної машини, на якій виконується Perforce демон і Git Fusion. Ви можете отримати образ віртуальної машини за посиланням http://www.perforce.com/downloads/Perforce/20-User, і коли завантаження буде завершено, імпортувати його у ваше улюблене програмне забезпечення для віртуалізації (ми використаємо VirtualBox).

Під час першого запуску віртуальної машини вам потрібно налаштувати паролі для трьох Linux-користувачів (root, perforce та git) і ввести ім’я хоста, яке буде відрізняти це встановлення від інших в одній мережі. Коли все буде готово, ви побачите наступне:

Екран віртуальної машини Git Fusion.
Figure 145. Екран віртуальної машини Git Fusion.

Вам потрібно занотувати цю IP адресу, пізніше ми будемо її використовувати. Далі ми створимо користувача Perforce. Виберіть знизу опцію “Login” та натисніть Enter (або скористайтесь SSH), і увійдіть як root. Потім використайте ці команди для створення користувача:

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

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

Наступне, що вам потрібно зробити, це заборонити Git перевіряти SSL сертифікати. Образ Git Fusion розповсюджується з сертифікатом, але він для домену, що не відповідає IP адресі вашої віртуальної машини, тому Git відхилить HTTPS-з’єднання. Якщо це встановлення буде використовуватись на постійній основі, зверніться до документації Git Fusion, щоб встановити інший сертифікат; а для нашої навчальної мети підійде наступне:

$ export GIT_SSL_NO_VERIFY=true

Тепер ми можемо переконатися, що все працює.

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

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

Налаштування Fusion

Після встановлення Git Fusion ви, мабуть, захочете налаштувати його. Насправді, це відносно легко зробити з використанням вашого улюбленого клієнта Perforce; просто відобразіть директорію //.git-fusion на сервері Perforce у ваш робочий простір. Структура файлів повинна бути подібною до:

$ tree
.
├── objects
│   ├── repos
│   │   └── [...]
│   └── trees
│       └── [...]
│
├── p4gf_config
├── repos
│   └── Talkhouse
│       └── p4gf_config
└── users
    └── p4gf_usermap

498 directories, 287 files

Директорія objects використовується Git Fusion для відображення об’єктів Perforce у об’єкти Git і навпаки, вам не варто тут нічого змінювати. Всередині цієї директорії знаходиться глобальний файл p4gf_config, а також по одному для кожного репозиторія - це файли конфігурації, які визначають поведінку Git Fusion. Погляньмо на файл, що знаходиться в корені:

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

Ми не будемо детально розглядати ці опції, але зверніть увагу на те, що це звичайний INI-файл, подібний до файлів конфігурації, які використовує Git. Цей файл задає глобальні опції, які можуть бути перевизначені специфічними для кожного репозиторія файлами конфігурації, такими, як repos/Talkhouse/p4gf_config. Якщо ви відкриєте цей файл, то побачите секцію [@repo] з деякими налаштуваннями, які відрізняються від типових глобальних налаштувань. Ви також побачите секції, які виглядають подібно до цього:

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

Вони задають відповідність між гілками Perforce та гілками Git. Назви таких секцій можуть бути довільними, поки ці назви залишаються унікальними. Команда git-branch-name дозволяє вам конвертувати недоладний для Git шлях всередині депо, щоб у Git мати більш дружелюбне ім’я. Параметр view керує відображенням файлів Perforce у репозиторії Git з використанням стандартного синтаксису відображення видів. Більш ніж одне відображення можна визначити як у прикладі нижче:

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

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

Останній файл, про який ми поговоримо, це users/p4gf_usermap, який відображає користувачів Perforce у користувачів Git, і який, можливо, вам і не знадобиться.

Коли Perforce конвертує набір змін (changeset) у Git коміт, типова поведінка Git Fusion — пошук користувача Perforce і використання адреси його електронної пошти та повного імені для заповнення поля "автор коміту" в Git. При конвертуванні у зворотному напрямку, типова поведінка — пошук користувача Perforce з адресою електронної пошти в полі "автор коміту" у Git, та застосовує набір змін від імені цього користувача (з використанням прав доступу). У більшості випадків така поведінка є нормальною, але розгляньмо наступний файл відповідностей:

john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"

Кожний рядок має формат <user> <email> "<full name>" і створює відповідність для одного користувача. Перші два рядки відображають дві різні адреси електронної пошти для одного користувача Perforce. Це може бути корисним, якщо ви створили коміти у Git з різних адрес електронної пошти (або змінили свою електронну пошту), але хочете їх відобразити на одного й того ж користувача Perforce. При створенні комітів у Git з набору змін Perforce, перший рядок, який відповідає користувачу Perforce, використовується Git як інформація про авторство.

Останні два рядки маскують справжні імена та адреси електронної пошти Боба і Джо у комітах Git при їх створенні. Це корисно, якщо ви хочете вивести ваш внутрішній проект в open-source, але не хочете публікувати свій каталог співробітників для всього світу. Зауважте, що адреси електронної пошти і повні імена повинні бути унікальними, якщо ви не хочете, щоб всі коміти Git належали одному фіктивному автору.

Робочий процес

Perforce Git Fusion — це двосторонній міст між системами контролю версій Perforce і Git. Погляньмо, як виглядає робота з боку Git. Нехай ми налаштували відображення проекту “Jam” з використанням файла конфігурації, як показано вище, який ми можемо клонувати таким чином:

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

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

Як ви бачите, наш репозиторій виглядає як і будь-який інший Git-репозиторій, з яким ви могли працювати. Тут є три гілки і Git доречно створив локальну гілку master, яка відслідковує origin/master. Попрацюймо трохи і створімо кілька нових комітів:

# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Ми маємо два нових коміти. Погляньмо, чи вносив зміни хтось інший:

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15  master     -> origin/master
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Схоже, хтось таки вносив! Ви не дізнаєтесь цього з виводу команди git fetch, але коміт 6afeb15 був створений за допомогою клієнта Perforce. Він виглядає як інший звичайний коміт з точки зору Git, для чого й створений Git Fusion. Погляньмо, як сервер Perforce працює з комітом злиття:

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b  master -> master

З боку Git це працює. Погляньмо на історію файла README з боку Perforce, скориставшись графом ревізій p4v:

Граф ревізій Perforce після надсилання даних з Git.
Figure 146. Граф ревізій Perforce після надсилання даних з Git.

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

Одна річ, яку потрібно зауважити, граф виглядає так само, як і в історії Git. У Perforce не було іменованої гілки для зберігання комітів 1 і 2, тому він створив “анонімну” гілку в директорії .git-fusion для їх збереження. Perforce буде діяти схожим чином і з іменованими гілками Git, які не відповідають іменам гілок Perforce (але ви можете задати для них відповідності у файлі конфігурації Perforce).

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

Підсумок по Git-Fusion

Якщо у вас є (або ви можете отримати) доступ до вашого сервера Perforce, Git Fusion стане чудовим способом налагодження співпраці між Git та Perforce. Звичайно, для цього потрібно налаштувати конфігурацію, але загалом цей процес не є занадто складним. Це одна з небагатьох секцій цього розділу, де не з’являлись попередження про використання всього функціоналу Git. Але це не означає, що Perforce зможе виконати все, що ви захочете - якщо ви спробуєте перезаписати історію, яка вже надіслана на сервер, Git Fusion відхилить такі зміни - але Git Fusion намагається зробити все можливе, щоб ви не відчували незручностей. Ви можете навіть використовувати підмодулі Git (хоча вони й будуть виглядати дивно для користувачів Perforce), і зливати гілки (з боку Perforce це буде виглядати як інтеграція).

Якщо ви не зможете вмовити адміністратора вашого серверу налаштувати Git Fusion, все одно існує спосіб використання цих інструментів разом.

Git-p4

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

Note

Для роботи з git-p4 вам знадобиться виконуваний файл p4, доступний у вашому PATH. На момент написання книги, він вільно доступний за посиланням http://www.perforce.com/downloads/Perforce/20-User.

Налаштування

Для навчальної мети, ми запустимо сервер Perforce з віртуальної машини Git Fusion OVA, як було показано вище, але ми будемо оминати сервер Git Fusion і напряму звертатися до системи контролю версій Perforce.

Для використання клієнта командного рядка p4 (від якого залежить git-p4), вам буде потрібно прописати кілька змінних середовища:

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
Початок роботи

Як і зазвичай при роботі з Git, перша команда — клонування:

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master

Ми створили те, що у термінах Git називається “поверхнева” (shallow) копія; лише найостанніша ревізія Perforce імпортована у Git; пам’ятайте, що Perforce не призначений віддавати кожну ревізію кожному користувачу. Цього достатньо для використання Git як клієнта Perforce, але цього недостатньо для інших задач.

Коли клонування завершиться, ми будемо мати повнофункціональний репозиторій Git:

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from the state at revision #head

Зверніть увагу на наявність віддаленого репозиторія “p4” для сервера Perforce, але все інше виглядає стандартно для клонованого репозиторія. Насправді, це нас трохи вводить в оману; виявляється, там немає ніякого віддаленого репозиторія.

$ git remote -v

Ніяких віддалених репозиторіїв не існує взагалі. Git-p4 створив кілька посилань для представлення стану сервера, і вони схожі на посилання віддаленого репозиторія для git log, але вони не обслуговуються Git і ви не можете надсилати зміни у них.

Порядок роботи

Отже, почнімо працювати. Припустимо, ви виконали певний обсяг роботи по дуже важливій задачі, і ви готові показати свою роботу решті команди.

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

Ми зробити два нових коміти, які вже готові до відправлення на сервер Perforce. Погляньмо, чи хтось інший сьогодні робив зміни:

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)
$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Схоже, що так, і гілки master та p4/master розійшлися. Система галудження Perforce абсолютно не схожа на Git, тому відправлення комітів злиття не має жодного сенсу. Git-p4 рекомендує перебазувати ваші коміти, і навіть надає спеціальну команду для цього:

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Ви, можливо, скажете, що git p4 rebase — це лише скорочення для git p4 sync з наступним git rebase p4/master. Насправді, ця команда трохи розумніша, особливо при роботі з багатьма гілками, але це вірна здогадка.

Тепер наша історія змін знову лінійна, і ми готові надіслати наші зміни до Perforce. Команда git p4 submit буде намагатися створити нову ревізію Perforce для кожного коміту Git між p4/master і master. Її запуск відкриє ваш улюблений редактор, і вміст файлу буде схожим на це:

# A Perforce Change Specification.
#
#  Change:      The change number. 'new' on a new changelist.
#  Date:        The date this specification was last modified.
#  Client:      The client on which the changelist was created.  Read-only.
#  User:        The user who created the changelist.
#  Status:      Either 'pending' or 'submitted'. Read-only.
#  Type:        Either 'public' or 'restricted'. Default is 'public'.
#  Description: Comments about the changelist.  Required.
#  Jobs:        What opened jobs are to be closed by this changelist.
#               You may delete jobs from this list.  (New changelists only.)
#  Files:       What opened files from the default changelist are to be added
#               to this changelist.  You may delete files from this list.
#               (New changelists only.)

Change:  new

Client:  john_bens-mbp_8487

User: john

Status:  new

Description:
   Update link

Files:
   //depot/www/live/index.html   # edit


######## git author ben@straub.cc does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html  2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html   2014-08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

Це практично ті ж самі дані, які б ви побачили, запустивши p4 submit, за винятком кількох рядків у кінці, які були доречно додані git-p4. Git-p4 намагається враховувати ваші індивідуальні налаштування для Git і Perforce, коли потрібно надати ім’я для коміту або набору змін, але у деяких випадках ви захочете змінити їх. Наприклад, якщо коміт Git, який ви імпортуєте, був створений користувачем, який не має облікового запису Perforce, можливо, ви все одно захочете, щоб кінцевий набір змін виглядав так, ніби він був створений тим користувачем (а не вами).

Git-p4 доречно імпортував повідомлення з коміту Git як вміст для набору змін Perforce, тому все, що нам потрібно зробити, це зберегти і вийти, двічі (по одному разу для кожного коміту). У результаті, вивід командного рядка буде виглядати так:

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Результат виглядає так, ніби ми щойно виконали git push, і це дуже близько до того, що насправді відбулося.

Зверніть увагу, що під час цього процесу кожен коміт Git перетворюється у набір змін Perforce; якщо ви хочете зварити їх (squash) в один набір змін, ви можете скористатися інтерактивним перебазуванням перед запуском git p4 submit. Також зазначте, що хеші SHA-1 усіх комітів, перетворених у набори змін, також змінилися; це сталося тому, що git-p4 додає рядок у кінець кожного коміту, який конвертує:

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title

    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

А що станеться, якщо ви спробуєте відправити коміт злиття? Спробуймо. Припустимо, у нас є така ситуація:

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
*   1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Історія змін Git розійшлась з історією Perforce після 775a46f. У Git є два коміти, потім коміт злиття з Perforce, а потім інший коміт. Ми спробуємо надіслати це у набір змін на боці Perforce. Погляньмо, що трапиться, якщо ми спробуємо надіслати зараз:

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

Прапорець -n — це скорочення для --dry-run, який намагається повідомити, що б сталося, якби б відправлення відбулось насправді. У цьому випадку, схоже, ми створили три набори змін Perforce, які відповідають трьом звичайним комітам (крім коміту злиття), які ще не існують на сервері Perforce. Це звучить саме так, як ми й хотіли, погляньмо на вивід:

$ git p4 submit
[…]
$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

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

Галуження

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

//depot
  └── project
      ├── main
      └── dev

І нехай ви маєте гілку dev, яка налаштована наступним чином:

//depot/project/main/... //depot/project/dev/...

Git-p4 може автоматично виявляти такі ситуації і виконувати потрібні дії:

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev

    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev
$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

Зверніть увагу на “@all” в шляху; це говорить git-p4 клонувати не лише останній набір змін для цього піддерева, а й усі набори змін, яких стосуються ці шляхи. Це ближче до концепції клонування Git, але якщо ви працюєте над проектом з довгою історією, це може зайняти деякий час.

Прапорець --detect-branches говорить git-p4 використовувати налаштування гілок Perforce для відображення на посилання Git. Якщо таких відображень на сервері Perforce немає (що цілком коректно для Perforce), ви можете вказати git-p4 ці гілки вручну, і отримаєте такий самий результат:

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

Задавши конфігураційну змінну git-p4.branchList рівною main:dev, ми говоримо git-p4, що і “main” і “dev” є гілками, а друга є нащадком першої.

Якщо ми тепер виконаємо git checkout -b dev p4/project/dev і зробимо кілька комітів, git-p4 достатньо розумний, щоб зрозуміти, у яку гілку надсилати зміни при виконанні команди git p4 submit. На жаль, git-p4 не може використовувати декілька гілок у поверхневих копіях; якщо у вас величезний проект і ви хочете працювати з більш ніж однією гілкою, вам доведеться виконувати git p4 clone для кожної гілки, у яку ви хочете надіслати зміни.

Для створення чи інтеграції гілок ви повинні використовувати клієнт Perforce. Git-p4 може лише синхронізувати і відправляти зміни у гілки, які вже існують, і може це робити лише з одним лінійним набором змін за раз. Якщо ви зіллєте дві гілки у Git та спробуєте надіслати новий набір змін, все, що збережеться — це купа змін у файлах; метадані про гілки, які були змінені при інтеграції, будуть втрачені.

Підсумок по Git і Perforce

Git-p4 робить можливим використання Git з сервером Perforce, і робить це досить добре. Однак, важливо пам’ятати, що Perforce все одно залишається джерелом даних, і ви використовуєте Git лише для локальної роботи. Будьте дуже обережні при публікації комітів Git; якщо ви маєте віддалений репозиторій, який використовують інші люди, не надсилайте жодних комітів, які ще не надіслані на сервер Perforce.

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

Git та TFS

Git популяризується серед Windows розробників і, якщо ви пишете код "під Windows", то є велика ймовірність, що ви також використовуєте Microsoft’s Team Foundation Server (TFS). TFS є набором інструментів для співпраці, що включає в себе облік дефектів, завдань, підтримку Scrum процесу та інших, перегляд коду, та контроль версій. Є трохи плутанини: TFS — це сервер, котрий має підтримку контролю коду, користуючись як Git, так і своєю власною СКВ, що має назву TFVC (Team Foundation Version Control). Підтримка Git є дещо новою особливістю для TFS (починаючи з 2013 версії), тому всі інструменти, що передували цій появі, звертаються до частини версіонування коду як “TFS”, незважаючи на те, що насправді працюють з TFVC.

Якщо ви опинетеся в команді, котра користується TFVC, але краще б надали перевагу Git, як системі контролю версій, то для цього існує готовий проект.

Який інструмент

Насправді, їх є два: git-tf та git-tfs.

Git-tfs (можна знайти за https://github.com/git-tfs/git-tfs) є .NET проектом та (на момент написання цього тексту) сумісний тільки з Windows. Для роботи з Git сховищами він використовує .NET обгортку над libgit2, бібліотеко-орієнтованою реалізацією Git, що є високопродуктивною та дозволяє багато гнучкості з нутрощами Git сховища. Libgit2 не є повною реалізацією Git, тому git-tfs використовує клієнтську командну стрічку Git для того, щоб закрити недостачу, отже, фактично, немає обмежень в тому, що ця утиліта може робити зі сховищами Git. Її підтримка особливостей TFVC є достить зрілою через те, що вона оперує з сервером через бібліотеки Visual Studio. Це значить, що вам потрібен доступ до цих бібліотек, що, в свою чергу означає, що потрібно встановити недавню версію Visual Studio (будь-яку редакцію, починаючи з версії 2010, включно з Express починаючи з 2012), або Visual Studio SDK.

Git-tf (який живе за адресою https://gittf.codeplex.com) є Java проектом і через це може працювати на будь-якому комп’ютері з Java середовищем. Він взаємодіє з Git сховищем через JGit (JVM реалізація Git), тобто, практично не має обмежень щодо функціональності Git. Проте, робота TFVC не є повною, порівняно з git-tfs – немає підтримки гілок, для прикладу.

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

Note

Вам знадобиться доступ до сховища TFVC для того, щоб могти слідувати цим інструкціям. А їх не так просто знайти, як, скажімо, сховища Git чи Subversion, тому, можливо, прийдеться створити самому. Для цієї задачі добре підходять Codeplex (https://www.codeplex.com) чи Visual Studio Online (http://www.visualstudio.com).

Ознайомлення з git-tf

По-перше, як і з будь-яким іншим Git проектом, склонуймо. Ось як це виглядає з git-tf:

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main project_git

Перший аргумент є URL до TFVC колекцій, другий має формат $/проект/гілка, а третій — це шлях до локального Git сховища, що буде створено (цей аргумент не обов’язковий). Git-tf може працювати лише з одною гілкою одночасно; якщо ви хочете зробити чекіни (checkins) до іншої гілки TFVC, то склонуйте заново з потрібної гілки.

Ось як створити повнофункціональний Git репозиторій:

$ cd project_git
$ git log --all --oneline --decorate
512e75a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Checkin message

Це зветься мілким (shallow) клоном, в тому значенні, що завантажено лише найостанніший ченджсет. TFVC не спроектований таким чином, що кожен клієнт має повну копію історії, тому git-tf типово отримає лише останню версію, а це є значно швидшим.

Коли ж у вас вдосталь часу, мабуть, варто клонувати всю історію проекту, за допомогою опції --deep:

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main \
  project_git --deep
Username: domain\user
Password:
Connecting to TFS...
Cloning $/myproject into /tmp/project_git: 100%, done.
Cloned 4 changesets. Cloned last changeset 35190 as d44b17a
$ cd project_git
$ git log --all --oneline --decorate
d44b17a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Goodbye
126aa7b (tag: TFS_C35189)
8f77431 (tag: TFS_C35178) FIRST
0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
        Team Project Creation Wizard

Зверніть увагу на теґи з іменами типу TFS_C35189; за допомогою цієї особливості ви дізнаєтеся який Git коміт та ченджсет TFVC пов’язані між собою. Це є хорошим відображенням, оскільки ви можете за допомогою простої команди log з’ясувати які з ваших комітів також також існують в TFVC. Ці теґи не є обов’язковими (і їх насправді можна вимкнути за допомогою git config git-tf.tag false) – git-tf все одно зберігає реальні зв’язки коміт-ченджсет у файлі .git/git-tf.

Ознайомлення з git-tfs

Клонування в git-tfs відбувається дещо інакше. Погляньте:

PS> git tfs clone --with-branches \
    https://username.visualstudio.com/DefaultCollection \
    $/project/Trunk project_git
Initialized empty Git repository in C:/Users/ben/project_git/.git/
C15 = b75da1aba1ffb359d00e85c52acb261e4586b0c9
C16 = c403405f4989d73a2c3c119e79021cb2104ce44a
Tfs branches found:
- $/tfvc-test/featureA
The name of the local branch will be : featureA
C17 = d202b53f67bde32171d5078968c644e562f1c439
C18 = 44cd729d8df868a8be20438fdeeefb961958b674

Зверніть увагу на прапорець --with-branches. Git-tfs має можливість створювати відповідності між гілками TFVC та Git, а цей прапорець вказує на необхідність створювати локальні Git гілки для кожної гілки TFVC. Його використання є рекомендованим якщо ви збираєтеся робити гілки чи зливати в TFS, проте такий підхід не працюватиме з сервером старішим за TFS 2010 – до цього релізу “гілки” були просто теками, тому git-tfs не міг відрізнити їх від звичайних тек.

Погляньте на результуюче Git сховище:

PS> git log --oneline --graph --decorate --all
* 44cd729 (tfs/featureA, featureA) Goodbye
* d202b53 Branched from $/tfvc-test/Trunk
* c403405 (HEAD, tfs/default, master) Hello
* b75da1a New project
PS> git log -1
commit c403405f4989d73a2c3c119e79021cb2104ce44a
Author: Ben Straub <ben@straub.cc>
Date:   Fri Aug 1 03:41:59 2014 +0000

    Hello

    git-tfs-id: [https://username.visualstudio.com/DefaultCollection]$/myproject/Trunk;C16

Маємо дві локальні гілки, master та featureA, котрі відображають стартову точку клонування (Trunk в іменуванні TFVC) та дочірню гілку (featureA в TFVC). Ви можете бачити також, що “віддалене сховище” tfs має також кілька посилань: default та featureA, які відображають гілки TFVC. Git-tfs робить відповідність між клонованою гілкою та tfs/default, а також інші отримують свої імена.

Ще варто звернути увагу на git-tfs-id: рядки в повідомленнях коміту. Замість теґів, git-tfs використовує ці позначки для зв’язку між TFVC ченджсетами та Git комітами. З цього випливає те, що ваші Git коміти матимуть різні SHA-1 хеші до та після надсилання до TFVC.

Процеси роботи Git-tf[s]

Note

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

$ git config set --local core.ignorecase=true
$ git config set --local core.autocrlf=false

Очевидно, далі ви б хотіли попрацювати над проектом. TFVC та TFS мають кілька особливостей що можуть ускладнити ваш робочий процес:

  1. Тематичні гілки, що не мають відображення в TFVC додають складнощів. Стається це через дуже різний спосіб того, як TFVC та Git тракують гілки.

  2. Пам’ятайте, що TFVC дозволяє користувачам “забирати”(checkout) файли з сервера, замикаючи їх так, що більш ніхто не зможе їх редагувати. Звичайно, це не є перепоною для роботою з цими файлами у вашому локальному сховищі, але може стати такою, коли прийде час надсилання змін до TFVC сервера.

  3. TFS має концепцію “закритих” ??? (“gated”) чекінів, коли цикл побудова-тести має бути успішно завершеним до того, як дозволено робити чекін. Тут використовується функція відкладених комітів “shelve” TFVC, яку ми тут детально не розглядатимемо. Ви можете сфабрикувати це вручну з git-tf та git-tfs котрі мають команду checkintool, що знає про закриті чекіни.

Для стислості, ми розглянемо лише щасливий шлях, що уникає більшість із згаданих нюансів.

Робочий процес git-tf

Скажімо, ви виконали деяку роботу, додали кілька комітів до master, а тепер готові ділитися своїми доробками на TFVC сервері. Ось наше Git сховище:

$ git log --oneline --graph --decorate --all
* 4178a82 (HEAD, master) update code
* 9df2ae3 update readme
* d44b17a (tag: TFS_C35190, origin_tfs/tfs) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Ми хочемо взяти відбиток коміту 4178a82 та надіслати його на TFVC сервер. Насамперед, подивимося чи хтось із колег долучався до проекту з того часу, як ми востаннє з’єднувалися:

$ git tf fetch
Username: domain\user
Password:
Connecting to TFS...
Fetching $/myproject at latest changeset: 100%, done.
Downloaded changeset 35320 as commit 8ef06a8. Updated FETCH_HEAD.
$ git log --oneline --graph --decorate --all
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
| * 4178a82 (HEAD, master) update code
| * 9df2ae3 update readme
|/
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

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

  1. Зробити коміт злиття здається природнім для користувача Git (зрештою, git pull саме це й робить), а git-tf може це виконати за допомогою git tf pull. Однак, зауважте, що TFVC не мислить таким самим чином, і, якщо ви надсилаєте коміти злиття, ваша історія виглядатиме по-різному по обидві сторони, що може спантеличувати. Проте, якщо ви збираєтеся надіслати всі свої зміни як один ченджсет, це, мабуть, найлегший спосіб.

  2. Перебазовування робить історію лінійною, тобто дає нам можливість перетворити кожен Git коміт на ченджсет TFVC. Оскільки даний спосіб залишає нам найбільше можливостей потім, ми рекомендуємо саме його; git-tf підтримує цю дію за допомогою git tf pull --rebase.

Вибір за вами. У цьому прикладі ми перебазовуватимемо:

$ git rebase FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Тепер ми готові до чекіну в TFVC сервер. Git-tf дає можливість вибрати: створити один ченджсет, що представлятиме всі зміни, починаючи з останньої (--shallow, що є типовою поведінкою) чи створювати по ченджсету на кожен коміт (--deep). Тут ми просто створимо один ченджсет:

$ git tf checkin -m 'Updating readme and code'
Username: domain\user
Password:
Connecting to TFS...
Checking in to $/myproject: 100%, done.
Checked commit 5a0e25e in as changeset 35348
$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, tag: TFS_C35348, origin_tfs/tfs, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Створився теґ TFS_C35348, вказуючи що TFVC має такий самий відбиток як відбиток в коміті 5a0e25e. Важливо зауважити, що не кожен Git коміт потребує точного відповідника в TFVC; коміт 6eb3eb5, наприклад, ніде не існує на сервері.

Таким є основний процес роботи. Ось ще кілька міркувань, що варто пам’ятати:

  • Робота з гілками не підтримується. Git-tf в змозі створити Git сховище лише з одної гілки TFVC одночасно.

  • Співпрацюйте за допомогою TFVC або Git, але не за допомогою обидвох. Різні git-tf клони одного й того ж TFVC сховища можуть мати різні SHA-1 хеші, що спричинить нескінченні головні болі.

  • Якщо процес вашої команди включає в себе роботу в Git та періодичні синхронізації до TFVC, з’єднуйте до TFVC лише одне Git сховище.

Робочий процес git-tfs

Пройдімо такий самий сценарій з git-tfs. Маємо нові коміти, зроблені в гілку master нашого Git сховища:

PS> git log --oneline --graph --all --decorate
* c3bd3ae (HEAD, master) update code
* d85e5a2 update readme
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 (tfs/default) Hello
* b75da1a New project

Тепер глянемо чи хтось додав якісь зміни, поки ми тут мудрували:

PS> git tfs fetch
C19 = aea74a0313de0a391940c999e51c5c15c381d91d
PS> git log --all --oneline --graph --decorate
* aea74a0 (tfs/default) update documentation
| * c3bd3ae (HEAD, master) update code
| * d85e5a2 update readme
|/
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Так, виявляється, що хтось із наших колег додав TFVC ченджсет, який ми бачимо як новий коміт aea74a0, а віддалена гілка tfs/default прогресувала.

Так само як і у випадку git-tf, ми маємо дві фундаментальні опції, щоб розв’язати цю розбіжність:

  1. Перебазуватися та зберегти історію лінійною.

  2. Виконати злиття та зберегти що ж насправді трапилося.

В нашому випадку ми робитимемо “глибокий” чекін, тобто кожен Git коміт стає ченджсетом TFVC, тому виберемо варіант з перебазовуванням.

PS> git rebase tfs/default
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
PS> git log --all --oneline --graph --decorate
* 10a75ac (HEAD, master) update code
* 5cec4ab update readme
* aea74a0 (tfs/default) update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Тепер ми готові завершити наш вклад, зачекінивши код до TFVC серверу. Будемо використовувати команду rcheckin для того, щоб створювався TFVC ченджсет на кожен Git коміт від HEAD до першого віддаленого посилання tfs (команда checkin створила б просто один ченджсет, щось типу того як працює зчавлення (squashing) комітів).

PS> git tfs rcheckin
Working with tfs remote: default
Fetching changes from TFS to minimize possibility of late conflict...
Starting checkin of 5cec4ab4 'update readme'
 add README.md
C20 = 71a5ddce274c19f8fdc322b4f165d93d89121017
Done with 5cec4ab4b213c354341f66c80cd650ab98dcf1ed, rebasing tail onto new TFS-commit...
Rebase done successfully.
Starting checkin of b1bf0f99 'update code'
 edit .git\tfs\default\workspace\ConsoleApplication1/ConsoleApplication1/Program.cs
C21 = ff04e7c35dfbe6a8f94e782bf5e0031cee8d103b
Done with b1bf0f9977b2d48bad611ed4a03d3738df05ea5d, rebasing tail onto new TFS-commit...
Rebase done successfully.
No more to rcheckin.
PS> git log --all --oneline --graph --decorate
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Зверніть увагу на те, як після кожного успішного чекіна до TFVC сервера, git-tfs перебазовує залишкові зміни поверх щойно зробленого. Це тому, що додається поле git-tfs-id в кінці повідомлення коміту, і це змінює SHA-1 хеші. Так і було задумано, не потрібно хвилюватися з цього приводу, просто вам потрібно знати, що це відбувається; особливо, якщо ви ділитеся ідентифікаторами Git комітів з іншими.

TFS має багато особливостей для інтеграції зі своєю системою контролю версій, такі як одиниці роботи (work items), переглядальники коду, закриті (gated) чекіни тощо. Опанувати їх всіх з командного рядка може бути досить хвацькою задачею, але, на щастя, git-tfs має досить швидкий доступ і до більш візуальних інструментів чекіну:

PS> git tfs checkintool
PS> git tfs ct

Виглядає це якось так:

Інструмент чекіну git-tfs.
Figure 147. Інструмент чекіну git-tfs.

Це виглядатиме звичним для користувачів TFS, оскільки це той самий діалог, що запускається у Visual Studio.

Git-tfs також дає можливість керувати гілками TFVC з Git сховища. Для прикладу, створимо гілку:

PS> git tfs branch $/tfvc-test/featureBee
The name of the local branch will be : featureBee
C26 = 1d54865c397608c004a2cadce7296f5edc22a7e5
PS> git log --oneline --graph --decorate --all
* 1d54865 (tfs/featureBee) Creation branch $/myproject/featureBee
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Створення гілки в TFVC означає додавання ченджсету до гілки, що вже існує, і це відображено окремим комітом Git. Також, зверніть увагу, що git-tfs створив віддалену гілку tfs/featureBee, але HEAD досі вказує на master. Якщо вам кортить попрацювати з щойно створеною гілкою, потрібно базувати свої нові коміти на коміті 1d54865, можливо, створивши тематичну гілку з цього коміту.

Git та TFS. Підсумок

Обидві Git-tf та Git-tfs є чудовими інструментами для доступу до сервера TFVC та роботи з ним. Вони дають вам локальну могутність Git, уникають постійних мандрів мережею до центрального сервера TFVC, та спрощують ваше розробницьке життя, без необхідності переходу на Git цілою командою. Якщо ви працюєте з Windows (що дуже ймовірно, коли користуєтеся TFS), то вам, мабуть, більше захочеться обрати git-tfs, через більш повний набір особливостей, а у випадку іншої платформи, ви користуватиметеся git-tf, яка є більш обмеженою. Як і з більшістю інструментів цього розділу, вам потрібно обрати одну з систем контролю версій, яка буде основною, а іншу використовувати ніби підлеглу – будь це Git чи TFVC, потрібно обрати один центр для співпраці, не обидва.