Mercurial: руководство по созданию веток

Перевод статьи Steve Losh: A Guide to Branching in Mercurial


Последнее время я много сидел на irc каналах #mercurial и #bitbucket на freenode и заметил, что часто всплывает вопрос «чем создание веток в Mercurial отличается от git

Какое-то время назад в твиттере я обсуждал с Ником (Nick Quaranto) модель ветвления в Mercurial и git’е, что в итоге вылилось в небольшую заметку об основных отличиях. Я показывал эту заметку в том числе и пользователям git и, похоже, им понравилось. Я решил осветить вопрос более подробно.

Примечание: этот пост не претендует на руководство пользователя по командам в Mercurial. Это руководство описывает лишь концепцию, которая лежит в Mercurial за использованием веток. Если вы ищете описание конкретных команд, почитайте отличное руководство под названием hg book (и, если понравится, вы даже можете купить бумажную версию и увидеть величайший фейл в истории печати).

Пролог

Для начала давайте посмотрим на пример репозитория, который я подготовил:
image

Репозиторий располагается в директории ~/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 копии репозитория. Попробуем внести изменения в каждую:
image

Итак, мы получили две копии репозитория, каждая из которых содержит 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), это значит, что другим участникам придётся скачать себе оба репозитория по отдельности, если они хотят иметь у себя обе ветки. Это может занять много времени при больших репозиториях и узком канале связи.

Такой подход особенно расточителен, если, например, у вас было 10000 changeset’ов до ветвления и 100 новых changeset’ов в каждой ветке-клоне после. Вместо того чтобы скачать 10200 changeset’ов, вашему коллеге придётся скачать 20200 changeset’ов. При трёх аналогичных ветках скачивать придётся уже 30300 changeset’ов вместо 10300.

Но есть один способ как избежать лишнего трафика, он был описан Guido Ostkamp и timeless_mbp на канале #mercurial. Идея состоит в том, чтобы клонировать только одну ветку с сервера, а затем сделать pull всех остальных в уже имеющуюся. После этого необходимо разделить все скаченные ветки из одного локального репозитория по нескольким локальным веткам-клонам. Таким образом одинаковые changeset’ы не придётся выкачивать по нескольку раз.

Пример:
$ hg clone http://server/project-main project
$ cd project
$ hg pull http://server/project-branch1
$ hg pull http://server/project-branch2
$ cd ..
$ hg clone project project-main --rev [head of mainline branch]
$ hg clone project project-branch1 --rev [head of branch1]
$ hg clone project project-branch2 --rev [head of branch2]
$ rm -rf project
$ cd project-main
$ [edit .hg/hgrc file to make the default path http://server/project-main%5D
$ cd ../project-branch1
$ [edit .hg/hgrc to make the default path http://server/project-branch1%5D
$ cd ../project-branch2
$ [edit .hg/hgrc to make the default path http://server/project-branch2%5D

Этот пример предполагает, что вы знаете номера ревизий (или хеши) вершин всех веток. Скорее всего, это не так, поэтому их придётся предварительно искать в логе.

Второй факт, который предполагает этот пример: все скачиваемые ветки имеют по одной голове. Если в какой либо ветке несколько голов, то все их ревизии необходимо будет указать в команде clone при разнесении по веток по отдельным директориям.

В некоторых проектах бывает проблема с путями к определённым файлам. Если вы создали ветку клонированием, придётся переименовывать директории каждый раз, когда вы захотите переключиться поработать над другой веткой. Обычно, жесткая привязка к путям в коде встречается не часто, но и такое бывает.

Лично я не люблю этот метод. Однако некоторые пользуются, в частности, при разработке самого Mercurial используется модель веток-клонов.

Сравнение с git

В git такой подход тоже применим. Технически, это то же самое, что и создать fork на GitHub. Однако когда мы говорим «fork» и «ветка», мы подразумеваем всё же несколько разные концепции.

Ветвление с помощью Bookmarks

Следующий путь – это использовать bookmark. Например:

$ cd ~/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’ов:
image

Диаграмма предельно проста: разделение веток было сделано на changeset’е номер 2 и далее мы добавили по одному новому changeset’у к каждой ветке.

Теперь обратите внимание на маркеры. «default» есть на этой схеме, но мы продолжаем игнорировать его.

Два дополнительных ярлыка представляют собой bookmark’и. Обратили внимание, что они изображены не пунктирной линией? Каждый bookmark’и является реальным объектом на диске, а не виртуальной ссылкой!

Их имена можно использовать как указатели на номера конкретных ревизий при работе с Mercurial.

Достоинства

Bookmark’и дают вам быстрый и лёгкий способ давать веткам имена.

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

Недостатки

Лёгкость этого способа одновременно является и его недостатком. Если вы удалите bookmark и взгляните на свой код годом позже, скорее всего вы уже не вспомните, зачем были нужны все эти ветвления и слияния. Впрочем, если писать хорошие комментарии к каждому changeset’у, то такой проблемы не возникнет.

До версии 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:
image

Важное отличие этого метода в том, что имя 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”:
image

Как переключаться между такими ветками? Для этого в качестве указателя ветки необходимо использовать номер последней ревизии в ветке: hg update –check (—check можно сократить до –c).

Примечание: опция –check появилась в Mercurial 1.3, но она не работала. Реально она заработала с версии 1.3.1. Если у вас стоит более ранняя версия Mercurial, рекомендую обновиться.

Команды типа hg log и hg graphlog покажут все changeset’ы в репозитории, таким образом, не стоит бояться потерять номер анонимной ветки. Кроме того, существует команда hg heads, которая показывает все головы (по сути это и есть анонимные ветки).

Достоинства

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

Этот метод идеален для быстрых фиксов, которые состоят из двух-трёх changeset’ов в ветке.

Недостатки

Использование анонимных веток естественным образом полагает, что они не имеют имени для описания. Поэтому нужно не забывать писать подробные комментарии во время commit.

Второе следствие анонимности: к веткам приходится обращаться по номерам ревизий или хешу, которые предварительно необходимо найти с помощью 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, только если приходится) или у вас есть какие-то вопросы – пишите!

Advertisements

9 thoughts on “Mercurial: руководство по созданию веток

  1. В разделе «Анонимные ветки» исправьте “Fix critical bag” на “Fix a critical bug” чтобы совпадало с картинкой.

    Также немного не в тему, но в Git нет особых проблем получить доступ к «анонимной ветке» поскольку в обычном репозитории Git ведётся т.н. «reflog» — глобальный лог изменений, в котором можно увидеть все коммиты, включая коммиты, сделанные в состоянии «detached head» (аналог анонимной ветки в Hg). Кроме того, после смены ветки tip линии истории, с которого был совершён переход, доступен под именем ORIG_HEAD.

    Спасибо за статью: я Hg не использую, но интересуюсь вопросом, и пост многое прояснил.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s