Mercurial керівництво по створенню гілок - tech notes

Останнім часом я багато сидів на irc каналах #mercurial і #bitbucket на freenode і зауважив, що часто виникає питання «чому створення гілок в Mercurial відрізняється від git?»







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

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

Для початку давайте подивимося на приклад сховища, який я підготував:

Mercurial керівництво по створенню гілок - tech notes

Репозиторій розташовується в директорії

/ Src / test-project. У ньому міститься 3 changeset'а: 0, 1 і 2.

Примітка для користувачів git: кожен changeset в Mercurial має ідентифікатор у вигляді хешу, як і в git. Крім цього, в Mercurial все changeset'и для зручності мають ще й номери. Ця нумерація не є глобальною для всіх клонів сховища, а визначена в кожному конкретному локальному репозиторії в залежності від послідовності зроблених раніше pull / push.

За замовчуванням в Mercurial є всього один branch під назвою «default», але ми ще повернемося до цього моменту пізніше. Я згадую branch «default» зараз, тому що на діаграмі вище є відповідний маркер.

Пунктирна межа на діаграмах означає, що насправді для такого маркера в репозиторії не існує ніякого об'єкта. Це ім'я може використовуватися для ідентифікації changeset'а замість довгих хешів або номерів - Mercurial вирахує номер ревізії нальоту.

Поки проіґноруймо маркер «default». Я спеціально виділив його сірим кольором, тому що для поточного обговорення він не має значення.

Розгалуження за допомогою клонування

Найповільніший, але надійний спосіб зробити нову гілку в Mercurial - це зробити клон:
$ cd

/ src
$ Hg clone test-project test-project-feature-branch

Тепер у нас є 2 копії сховища. Спробуємо внести зміни в кожну:

Mercurial керівництво по створенню гілок - tech notes

Отже, ми отримали дві копії сховища, кожна з яких містить changeset'и на момент клонування. Крім того, ми можемо робити commit в кожну з копій незалежно. Потім, якщо, наприклад, зробити push з test-project в test-project-feature-branch, то changeset «Fix a critical bug» потрапить в праву копію.

Примітка для користувачів git: пам'ятайте я говорив, що нумерація changeset'ов в Mercurial має локальний характер? На цьому прикладі наочно видно: два різних changeset'а в двох репозиторіях мають номер 3. Саме тому з номерами краще працювати тільки в рамках одного сховища, а для синхронізації pull / push і при спілкуванні з іншими людьми слід використовувати хеш-кодування - вони унікальні.

переваги

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

Видалити гілку, якщо вона більше не потрібна, при такому підході дуже легко: rm -rf test-project-feature-branch. Немає ніякої необхідності возитися з репозиторієм, видаляючи щось з історії.

недоліки

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

Проте, розгалуження через клонування може помітно сповільнити хід розробки в команді. Якщо ви публікуєте дві гілки як два окремих сховища (наприклад, stable і version-2), це означає, що іншим учасникам доведеться завантажити собі обидва сховища окремо, якщо вони хочуть мати у себе обидві гілки. Це може зайняти багато часу при великих репозиторіях і вузькому каналі зв'язку.

Але є один спосіб як уникнути зайвого трафіку, він був описаний Guido Ostkamp і timeless_mbp на каналі #mercurial. Ідея полягає в тому, щоб клонувати тільки одну гілку з сервера, а потім зробити pull всіх інших в уже наявну. Після цього необхідно розділити всі скачані гілки з одного локального сховища за кількома локальним гілках-клонам. Таким чином однакові changeset'и не доведеться викачувати по кілька разів.

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

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

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

Особисто я не люблю цей метод. Однак деякі користуються, зокрема, при розробці самого Mercurial використовується модель гілок-клонів.

Порівняння з git

У git такий підхід теж застосуємо. Технічно, це те ж саме, що і створити fork на GitHub. Однак коли ми говоримо «fork» і «гілка», ми маємо на увазі все ж дещо різні концепції.

Розгалуження за допомогою Bookmarks

Наступний шлях - це використовувати bookmark. наприклад:

/ Src / test-project
$ Hg bookmark main
$ Hg bookmark feature







Тепер у вас два bookmark'а для двох майбутніх гілок в поточному changeset'е.

Для того щоб перейти на одну з гілок, можна використовувати hg update feature - оновлює робочу директорії до tip changeset'а в гілці feature і позначає цю гілку як робочу. Коли ви зробите commit, Mercurial пересуне поточний bookmark (feature) на новостворений changeset.

Більш докладно деталі роботи з bookmark'амі можна почитати на відповідній wiki сторінці.

Ось як буде виглядати репозиторій в разі використання bookmark'ов:

Mercurial керівництво по створенню гілок - tech notes

Діаграма гранично проста: поділ гілок було зроблено на changeset'е номер 2 і далі ми додали по одному новому changeset'у до кожній гілці.

Тепер зверніть увагу на маркери. «Default» є на цій схемі, але ми продовжуємо ігнорувати його.

Два додаткових ярлика є bookmark'і. Звернули увагу, що вони зображені не пунктирною лінією? Кожен bookmark'і є реальним об'єктом на диску, а не віртуальної посиланням!

Їх імена можна використовувати як покажчики на номери конкретних ревізій при роботі з Mercurial.

переваги

Bookmark'і дають швидкий і легкий спосіб давати гілках імена.

Ви можете видалити їх, якщо вони більше не потрібні. Для прикладу, якщо робота над новою фичей завершена і ми злили зміни з основною гілкою, нам вже не потрібно зберігати bookmark «feature».

недоліки

До версії Mercurial 1.6 bookmark'і не синхронізовані між репозиторіями (pull / push). Їх доводилося передавати у вигляді файлів вручну.

Порівняння з git

Розгалуження за допомогою bookmark'ов дуже схоже на звичайні гілки в git. Bookmark'і в Mercurial як git refs: іменований покажчик на changeset, який рухається разом c кожним новим commit'ом.

Єдиним великим відмінністю до недавнього часу (до Mercurial 1.6) було те, що git refs синхронізувалися між репозиторіями через pull / push, а bookmark'і в Mercurial немає.

Розгалуження за допомогою Named Branches

Третім способом створення гілок в Mercurial є використання так званих named branches (далі по тексту словом «гілка» я називаю саму концепцію поділу коду в процесі програмування, а словом branch - конкретну технологію в Mercurial. - прим. Перекладача).

Щоб створити named branch:
$ cd

/ Src / test-project
$ Hg branch feature

Кожен новий changeset буде належати тому ж named branch, що і його предок. Щоб поміняти named branch для наступних changeset'ов, необхідно знову використовувати команду hg branch

Ось як буде виглядати наш репозиторій при використанні named branches:

Mercurial керівництво по створенню гілок - tech notes

Важлива відмінність цього методу в тому, що ім'я branch'а записується безпосередньо в метадані changeset'a (це видно у changeset 4 на діаграмі).

За замовчуванням існує named branch під назвою «default», однак його ім'я ховається при виведенні логів, якщо тільки не запросити його явно в тій чи іншій команді опцією -v (-verbose).

Настав час пояснити магію цих ярликів з пунктирною кордоном на діаграмі. Використовуючи ім'я branch'а, ми вказуємо на так званий tip changeset в даному named branch. В даному прикладі:

  • Виконавши hg update default. ми оновимо робочу директорію до changeset 3, який є tip в «default» branch.
  • Виконавши hg update feature. ми оновимо робочу директорію до changeset 4, який є tip в «feature» branch.

Жоден з цих ярликів зображених на діаграмі пунктиром фізично не існує на диску (на відміну від bookmark, розглянутих раніше). При зверненні на ім'я named branch Mercurial обчислює номер ревізії нальоту.

переваги

Найбільше гідність використання named branches це те, що кожен changeset зберігає в своїх метаданих інформацію про named branch, а це дуже зручно, особливо при використанні graphlog.

недоліки

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

У минулому у Mercurial була проблема з тим, що гілки не можна було «закривати». Це означає, що з часом список named branch міг помітно зрости. Починаючи з версії Mercurial 1.2 з'явилася опція -close-branch для команди hg commit.

Порівняння з git

Наскільки мені відомо, в git немає еквівалента named branches від Mercurial. Інформація про гілках ніколи не зберігається в метаданих changeset'а.

Анонімні гілки

Останній метод створення гілок в Mercurial, який ми розглянемо, найшвидший і простий: update на будь-яку ревізію і commit. Вам не треба думати про імена або будь-яких додаткових діях, просто update і commit!

Механіка така: коли ви робите update на певну ревізію, ця ревізія позначається як батьківська для робочої директорії. Коли ви робите commit, то створюваний в цей момент changeset стає нащадком тієї самої ревізії, що є батьківської для робочої директорії.

Зробимо commit "Fix critical bag", потім повернемося (update) до ревізії 2 і ще один commit "Add new feature":

Mercurial керівництво по створенню гілок - tech notes

Як перемикатися між такими гілками? Для цього в якості покажчика гілки необхідно використовувати номер останньої ревізії в гілці: hg update -check (-check можна скоротити до -c).

Примітка: опція -check з'явилася в Mercurial 1.3, але вона не працювала. Реально вона заробила з версії 1.3.1. Якщо у вас стоїть більш рання версія Mercurial, рекомендую оновитися.

Команди типу hg log і hg graphlog покажуть все changeset'и в репозиторії, таким чином, не варто боятися втратити номер анонімної гілки. Крім того, існує команда hg heads, яка показує всі голови (по суті це і є анонімні гілки).

переваги

Це найлегший спосіб створення гілок. Можете більше не турбуватися про імена гілок або їх закриття / видаленні.

Цей метод ідеальний для швидких фіксів, які складаються з двох-трьох changeset'ов в гілці.

недоліки

Другий наслідок анонімності: до гілок доводиться звертатися за номерами ревізій або хешу, які попередньо необхідно знайти за допомогою hg log або hg graphlog (або hg heads - прим. Перекладача). Якщо вам часто потрібно переключатися між гілками, це може скласти труднощі.

Порівняння з git

У git подібний спосіб не працює. Звичайно, ви можете зробити update і commit в будь-якому місці, але якщо ви не створите в цей момент (named) ref і потім перейдіть на іншу гілку, буде складно повернутися назад на «анонімну». Єдиний спосіб знайти її - це використовувати grep на результатах балки.

Погодьтеся, іноді немає бажання придумувати ім'я для гілки, якщо це буде коротка гілка для одномоментного баг-фікса. Але в git вам доведеться дати ім'я новій гілці. У Mercurial це не обов'язково.

Ще одна відмінність між Mercurial і git

Існує ще одне велике відмінність між Mercurial і git в питанні гілок:

Mercurial робить push / pull всіх гілок за замовчуванням. Git, навпаки, робить push / pull тільки поточної гілки.

Це дуже важливо, якщо ви користувач git і захотіли попрацювати з Mercurial. Якщо ви хочете синхронізувати (pull / push) тільки одну гілку в Mercurial, необхідно використовувати опцію -rev (скорочено -r) і вказати tip ревізію (або посилання на tip ревізію у вигляді named branch або bookmark):

$ Hg push -rev branchname
$ Hg push -rev bookmarkname
$ Hg push -rev 4

Якщо ви вказуєте ревізію, Mercurial зробить pull / push цієї ревізії і всіх її предків, яких немає в репозиторії призначення.

Уточнення: якщо використовувати модель «Галуження за допомогою клонування», то pull / push не зможе відправити всі гілки, бо гілки-клони є ізольованими репозиторіями і знаходяться в різних директоріях.

висновок

Я сподіваюся, це керівництво було корисним. Якщо ви помітите щось, що я упустив, особливо в описі поведінки git (я сам не використовую git, тільки якщо доводиться) або у вас є якісь питання - пишіть!

У розділі «Анонімні гілки» виправте "Fix critical bag" на "Fix a critical bug" щоб збігалося з картинкою.

Також трохи не в тему, але в Git немає особливих проблем отримати доступ до «анонімної гілці» оскільки в звичайному репозиторії Git ведеться т.зв. «Reflog» - глобальний лог змін, в якому можна побачити все коммітов, включаючи коммітов, зроблені в стані «detached head» (аналог анонімної гілки в Hg). Крім того, після зміни гілки tip лінії історії, з якого був здійснений перехід, доступний під ім'ям ORIG_HEAD.

Дякую за статтю: я Hg не використовую, але цікавлюся питанням, і пост багато прояснив.