пятница, 25 сентября 2009 г.

Bazaar 2.0 и новый формат репозитория по умолчанию

Сегодня 25 сентября и со дня на день всё прогрессивное человечество ожидает выхода новой версии bzr 2.0. Как видно из названия, эта версия не просто очередная версия bzr, одна из тех, что выходят почти каждый  месяц. Это версия 2.0 (три восклицательных знака).

Одно из ключевых изменений в версии 2.0 — это новый формат репозитория, используемый по умолчанию, который называется 2a. Именно это изменение может стать серьезным камнем преткновения для некоторых пользователей, как оно почти стало для меня.

Проблема

Bazaar имеет заслуженную репутацию системы, в которой существует зоопарк форматов репозиториев. В настоящий момент число форматов превышает 10. История появления каждого формата отмечает этапы развития системы и путь, который bzr прошел ради достижения скорости работы, сравнимой с такими лидерами как Mercurial и Git.

Само по себе такое число форматов ни о чем плохом не говорит: все форматы поддерживаются по сей день, новые версии bzr без проблем умеют работать с репозиториями в старых форматах, хотя в ряде случаев рекомендуют обновить формат на более новый по причине морального старения. Да и новые форматы обычно более компактны и занимают меньше места на диске, плюс bzr работает с ними заметно быстрее. Проблема не в их числе, проблема в том, что существуют две группы форматов, взаимно не заменяемые.

Речь идет о различии между обычными форматами и так называемыми rich-root форматами. По историческим причинам rich-root формат появился для поддержки одной долгожданной функции bzr, которая впрочем до сих пор не реализована. Отличие между простыми форматами и rich-root заключается в наличии дополнительных метаданных о ветках. Форматы rich-root никогда не были рекомендуемыми, и не являлись форматом по умолчанию. Более того, поскольку дополнительная информация rich-root формата не может быть сохранена в простом формате, то вы не можете перейти от rich-root к простому формату. Вообще. Переход от простых форматов к rich-root возможен всегда. Причем, вы не сможете даже сделать pull или merge из rich-root в обычную ветку. Собственно это и составляет проблему: односторонний переход из одной группы форматов в другую.

Ящик пандоры открыл популярный плагин bzr-svn, который первым стал активно использовать формат rich-root при конверсии svn репозитория в bzr. Причины такого решения целиком логичные, однако имели далеко идущие последствия. Каждый новый формат в серии bzr 1.x всегда имел пару реализаций: простую и rich-root. Много раз подымался вопрос об их объединении в единый формат, однако по ряду причин это сделано только в новом формате 2a.

Новый формат bzr 2a поддерживает только rich-root, поэтому новые пользователи будут избавлены от имеющейся дихотомии.

Однако, все существующие репозитории и ветки должны быть либо обновлены до 2a или хотя бы до rich-root, чтобы избежать проблемы несовместимости форматов.

Пример несовместимости

Создадим ветку в формате pack-0.92 (основной формате в серии bzr 1.x, обычный не rich-root).

C:\work\bzr-day\Formats>bzr init 0.92 --format=pack-0.92
Created a standalone tree (format: pack-0.92)

C:\work\bzr-day\Formats\0.92>bzr ci --unchanged -m 1
Committing to: C:/work/bzr-day/Formats/0.92/
Committed revision 1.


Сделаем копию этой ветки и сконвертируем ее в формат 2a.

C:\work\bzr-day\Formats>bzr branch 0.92 2a
Branched 1 revision(s).

C:\work\bzr-day\Formats\2a>bzr upgrade --format=2a
starting upgrade of file:///C:/work/bzr-day/Formats/2a/
making backup of file:///C:/work/bzr-day/Formats/2a/.bzr
  to file:///C:/work/bzr-day/Formats/2a/backup.bzr
starting repository conversion
repository converted
finished

C:\work\bzr-day\Formats\2a>bzr info
Standalone tree (format: 2a)
Location:
  branch root: .

Related branches:
  parent branch: C:/work/bzr-day/Formats/0.92

C:\work\bzr-day\Formats\2a>bzr ci --unchanged -m 2a
Committing to: C:/work/bzr-day/Formats/2a/
Committed revision 2.

Зафиксируем еще одну ревизию в первой ветке:

C:\work\bzr-day\Formats\0.92>bzr commit --unchanged -m 2-0.92
Committing to: C:/work/bzr-day/Formats/0.92/
Committed revision 2.

И попробуем сделать объединение. Объединение из обычного формата в 2a работает без проблем:

C:\work\bzr-day\Formats\2a>bzr merge ../0.92
All changes applied successfully.

А вот в обратную сторону не работает вовсе:

C:\work\bzr-day\Formats\0.92>bzr merge ../2a
bzr: ERROR: KnitPackRepository('file:///C:/work/bzr-day/Formats/0.92/.bzr/repository/')
is not compatible with
CHKInventoryRepository('file:///C:/work/bzr-day/Formats/2a/.bzr/repository/')
different rich-root support


В последней строке явно виден корень проблемы: different rich-root support.

Проблема усугубляется тем, что конвертация из простого формата в rich-root может произойти неявно и без вашего ведома. Например, когда вы делаете копию не-rich-root ветки в разделяемый репозиторий (shared repository) в rich-root формате:

C:\work\bzr-day\Formats>bzr init-repo --2a shared-repo
Shared repository with trees (format: 2a)
Location:
  shared repository: shared-repo

C:\work\bzr-day\Formats\shared-repo>bzr branch ../0.92 trunk
Branched 2 revision(s).

C:\work\bzr-day\Formats\0.92>bzr merge ../shared-repo/trunk
bzr: ERROR: KnitPackRepository('file:///C:/work/bzr-day/Formats/0.92/.bzr/repository/')
is not compatible with
CHKInventoryRepository('file:///C:/work/bzr-day/Formats/shared-repo/.bzr/repository/')
different rich-root support

Как узнать текущий формат ветки/репозитория

Команда bzr info -v отображает различную информацию о ветке/репозитории и в том числе формат репозитория.

C:\work\bzr-day\Formats\0.92>bzr info -v
Standalone tree (format: pack-0.92)
Location:
  branch root: .

Related branches:
  submit branch: C:/work/bzr-day/Formats/2a

Format:
       control: Meta directory format 1
  working tree: Working tree format 4
        branch: Branch format 6
    repository: Packs containing knits without subtree support
...

В строке repository описан детальный формат. Если там написано without subtree support — это обычный не-rich-root формат.

C:\work\bzr-day\Formats\2a>bzr info -v
Standalone tree (format: 2a)
Location:
  branch root: .

Related branches:
  parent branch: C:/work/bzr-day/Formats/0.92
  submit branch: C:/work/bzr-day/Formats/0.92

Format:
       control: Meta directory format 1
  working tree: Working tree format 6
        branch: Branch format 7
    repository: Repository format 2a - rich roots, group compression and chk inventories
...


Заметьте, что для 2a в описании формата присутствует упоминание rich roots.

Что делать

С первым вопросом русской интеллигенции мы разобрались выше, теперь перейдем ко второму вопросу. Ответ на него имеет несколько вариантов в зависимости от конкретной ситуации.

1. Конвертировать все свои ветки и репозитории в формат 2a

В долгосрочной перспективе наиболее правильное решение — это конвертация всех ваших репозиториев в формат, поддерживающий rich-root. В первую очередь в формат 2a.
Формат 2a поддерживается в bzr, начиная с версии 1.16. Поэтому если на всех компьютерах в вашей организации установлена достаточная свежая версия bzr вы можете пойти этим путём.

Рекомендуется сделать тестовое обновление на локальной машине. Перед обновлением целесообразно запустить команду bzr reconcile для исправления возможных нестыковок внутри репозитория. Затем кто-то один из вашей команды должен сделать обновление веток на центральном сервере, а затем остальные сделают новую копию главной ветки на свои компьютеры, или обновят все свои ветки в формат 2a.
Подробная инструкция по обновлению.
2. Конвертировать свои ветки в формат rich-root

Если по ряду причин в вашем ведении находятся компьютеры со старой версией bzr, либо вы используете сторонние продукты, которые зависят от старых версий bzr, то вы можете рассмотреть вариант обновления до формата rich-root-pack, который совместим с 2a. Рекомендации по последовательности обновления те же самые.

3. Не использовать bzr 2.0 и выше

Если по ряду причин вы не можете обновить часть компьютеров и не считаете целесообразным обновлять все ветки и репозитории в rich-root формат, то, возможно, вам стоит принять волевое решение не обновлять ни на одном подведомственном вам компьютере bzr до версии 2.0. Последняя стабильная версия из серии bzr 1.x — это bzr 1.18.

Однако рано или поздно перейти придётся. Хотя бы потому, что в новых версиях чинят старые баги.

4. Использовать специальный плагин для установки старого формата по умолчанию

Я написал маленький плагин format1, который устанавливает старый не-rich-root формат pack-0.92 в качестве формата по умолчанию для bzr. После установки этого плагина при каждом запуске bzr форматом по умолчанию будет устанавливаться pack-0.92. Поэтому все создаваемые с нуля новые ветки и разделяемые репозитории (что самое важное!) будут иметь формат pack-0.92. При этом пользователь может принудительно выбрать другой формат через опции командной строки.

Ветка плагина располагается на Launchpad: https://code.launchpad.net/~bialix/+junk/format1

Установка плагина: как обычно, поместите копию ветки в ваш каталог plugins.

ПРЕДУПРЕЖДЕНИЕ ОБ ОТКАЗЕ ОТ ОТВЕТСТВЕННОСТИ: написанный мною плагин должен работать корректно, однако 100% гарантию я давать не буду. Поэтому используйте его на свой страх и риск, либо не используйте вовсе, а рассмотрите предыдущие озвученные варианты решения проблемы.

Выводы

Переход на использование нового bzr 2.0 влечёт за собой и переход на новый формат 2a. Будьте внимательны и донесите до сведения каждого участника вашей команды все последствия такого перехода и скоординируйте обновление всех ваших веток.

понедельник, 14 сентября 2009 г.

Mainline: главная линия разработки и номера ревизий (Часть 2)

Продолжаем разговор об одной специфичной особенности bzr, которую вы не найдете ни в одной другой распределенной системе контроля версий. Мы рассматриваем понятие mainline (главная линия разработки), его связь с номерами ревизий, и каким образом mainline влияет на работу с несколькими ветками.

Откуда есмь пошло понятие mainline

В английском языке mainline означает основную дорогу, магистраль, основную колею участка железной дороги.
Понятие mainline пришло в Bazaar из его предшественника Arch. В Arch это понятие использовалось, чтобы разделить ревизии, зафиксированные в этой конкретной ветке, от ревизий, присоединенных (merged) из других веток. В таком же контексте mainline используется и в Bazaar.

Синонимом понятия mainline (главная линия или главная ветка) в распределенных системах можно считать ствол (trunk) в централизованных системах (svn). Однако при этом промежуточные стадии разработки производятся в отдельных ветках, а в главную ветку (trunk) попадает уже готовый отлаженный результат работы. В этом случае trunk теоретически всегда находится в работоспособном состоянии: программа заведомо компилируется и работает. Подробное изложение такого метода разработки можно найти в документе Ultimate Quality Development System (UQDS).

Реально, mainline в Bazaar — это фактически закрепленная на уровне системы контроля версий модель разработки с основной (центральной) веткой, в которая содержит законченные результаты работы, и множества  рабочих веток (features branches — ветки для разработки новых функций), которые собственно используются разработчиками.

Достоинством встроенной поддержки парадигмы mainline является то, что при правильном использовании вы получаете красивую историю вашего проекта, в которой каждая ревизия в главной ветке представляет собой мини-аннотацию для одной или несколько новых функций, присоединенных из соответствующей ветки разработки.

Недостатком встроенной поддержки парадигмы mainline является то, что отключить эту поддержку в Bazaar нет возможности, поэтому для нормальной работы с системой пользователь должен так или иначе следовать этой парадигме. В противном случае журнал ревизий будет не так легко анализировать.

Как mainline влияет на вывод журнала ревизий

Дополнительное (негативное) влияние парадигма mainline косвенно оказывает на быстродействие некоторых операций, в которых участвуют составные "точечные" номера ревизий (dotted revno), например, ревизия 2.1.1. Для однозначного вычисления "точечного" номера ревизии Bazaar должен проанализировать полный граф ревизий на достаточную глубину, чтобы найти ревизию, после которой ветки разошлись.

В связи с этим разработчики Bazaar, начиная с версии bzr 1.14, пошли на небольшую хитрость: по умолчанию "точечные" ревизии в журнале не отображаются, что позволяет избежать затрат на вычисление их номеров. При этом у пользователя остается возможность принудительно включить их вывод при помощи соответствующей опции командной строки.

Чтобы было понятнее, рассмотрим основные форматы вывода журнала ревизий, которые нам предлагает Bazaar.
  • bzr log --long — формат вывода журнала по умолчанию; отображается детальная информация о ревизии в несколько строк: условный номер ревизии, теги, автор(ы), короткое имя ветки (branch nick), дата, полный текст комментария к ревизии. Пример (из знакомой нам ветки Test):
C:\work\bzr-day\Basic-commands\Test>bzr log -r-1
------------------------------------------------------------
revno: 4 [merge]
committer: Базарный день <ru_bzr@googlegroups.com>
branch nick: Test
timestamp: Tue 2009-09-08 23:50:01 +0300
message:
  Объединение с веткой Experimental
------------------------------------------------------------
Use --include-merges or -n0 to see merged revisions.

  • bzr log --short — "краткий" формат вывода: в одну строку выводится условный номер ревизии, автор, дата; ниже выводится полный комментарий к ревизии. Пример:
C:\work\bzr-day\Basic-commands\Test>bzr log -r-1 --short
    4 Базарный день     2009-09-08 [merge]
      Объединение с веткой Experimental

Use --include-merges or -n0 to see merged revisions.
  • bzr log --line — наиболее компактный формат вывода: в одну строку выводится условный номер ревизии, автор, дата и начало комментария к ревизии. Пример:
C:\work\bzr-day\Basic-commands\Test>bzr log -r-1 --line
4: Базарный день 2009-09-08 [merge] Объединение с веткой Experimental

До версии bzr 1.14 log --long всегда отображал присоединенные ревизии, а log --short и log --line не умели этого. Теперь все форматы умеют отображать присоединенные ревизии при запуске команды log с опцией -n0 или --include-merges. Например:

C:\work\bzr-day\Basic-commands\Test>bzr log --line -n0
4: Базарный день 2009-09-08 [merge] Объединение с веткой Experimental
  2.1.1: Базарный день 2009-09-08 Скорректирован файл goodbye.txt в ветке Experimental
3: Базарный день 2009-09-08 Скорректирован файл foo.txt в ветке Test
2: Базарный день 2009-09-08 Внесены изменения для иллюстрации команд status и diff
1: Базарный день 2009-09-08 Начальное состояние файлов

Итак, мы можем видеть, что сегодня журнал по умолчанию отображает только ревизии, соответствующие mainline. Поэтому, если ваш проект не следует этой парадигме, вы всегда должны запускать команду log с включенной опцией отображения присоединенных ревизий. (Этого можно достичь при помощи aliases). Аналогичная картина и с GUI командой qlog (из плагина QBzr): после запуска этой команды пользователь видит только mainline-ревизии, а присоединенные ревизии свернуты. Для разворачивания/отображения этих ревизий пользователь должен щелкнуть мышкой по значку + в круглом узле на графе ревизий (либо использовать стрелки влево-вправо на клавиатуре), см. снимок с экрана ниже.

qlog-test-collapsed

Рисунок 1. Отображение графа ревизий после запуска: только mainline ревизии

qlog-test-expanded

Рисунок 2. Развернутый узел с присоединенными ревизиями

В следующей части этой статьи мы рассмотрим как mainline влияет на команды merge, commit и push. Также мы попытаемся сформулировать ряд советов по правильному использованию парадигмы главной линии разработки (mainline).

вторник, 8 сентября 2009 г.

Mainline: главная линия разработки и номера ревизий (Часть 1)

Сегодня речь пойдет об одной специфичной особенности bzr, которую вы не найдете ни в одной другой распределенной системе контроля версий. Мы рассмотрим понятие mainline (главная линия разработки), его связь с номерами ревизий, и каким образом mainline влияет на работу с несколькими ветками.

Номера ревизий в распределенной системе

Прежде всего необходимо четко понимать и помнить, что в распределенных системах контроля версий для однозначной идентификации любой ревизии необходимо применять некие уникальные идентификаторы. Простая нумерация ревизий 1,2,3,… для этого не подходит, поскольку идентификаторы должны быть уникальны в глобальном смысле, а простые номера уникальны только в пределах одной физической копии репозитория (что вполне подходит для централизованных систем типа svn).

Поэтому все современные распределенные системы применяют уникальные идентификаторы. Так Monotone, Git и Mercurial используют в качестве уникального идентификатора ревизии SHA-1 хэш от данных самой ревизии. Bazaar тоже использует уникальные идентификаторы для ревизий, однако эти идентификаторы представляют произвольную строку и нет требования, чтобы идентификатор был основан на данных самой ревизии.

Использование длинных и/или абстрактных идентификаторов в большинстве случаев не является оптимальным способом адресации ревизии для простых смертных. Поэтому разработчики Bazaar решили, что внутри системы будут использоваться уникальные идентификаторы, а пользователи системы будут большую часть времени оперировать с условными номерами ревизий. При этом в Bazaar остается возможность использовать собственно уникальные идентификаторы.

Номера ревизий в Bazaar имеют условный характер и имеют смысл только в контексте конкретной ветки, с которой производятся какие-то действия и в большинстве случаев эти номера стабильны и не меняются.

Принципы формирования номеров ревизий в Bazaar

Каждой ревизии, которую вы фиксируете в своей ветке присваивается свой порядковый номер (revno), начиная с 1. Эти номера неизменны для конкретной ветки.

Когда вы объединяете свою ветку с другой, то должны зафиксировать результат объединения в виде новой ревизии. Эта ревизия получит свой порядковый номер как и раньше. А вот ревизии, которые были добавлены в вашу ветку в результате объединения, получат специальные новые "номера", состоящие из 3х чисел, разделенных точками (так называемые dotted revno).

В качестве примера вернемся к нашей прошлой статье Начинаем работу с bzr: базовый набор команд (Часть 2). Я приведу кусочек вывода команды bzr log для ветки после объединения:
C:\work\bzr-day\Basic-commands\Test>bzr log -n0 
------------------------------------------------------------ 
revno: 4 [merge] 
committer: Базарный день <ru_bzr@googlegroups.com>
branch nick: Test 
timestamp: Tue 2009-09-08 23:50:01 +0300 
message: 
  Объединение с веткой Experimental     
------------------------------------------------------------ 
    revno: 2.1.1
    committer: Базарный день <ru_bzr@googlegroups.com>
    branch nick: Experimental 
    timestamp: Tue 2009-09-08 23:48:42 +0300 
    message: 
      Скорректирован файл goodbye.txt в ветке Experimental 
------------------------------------------------------------ 
revno: 3 
committer: Базарный день <ru_bzr@googlegroups.com>
branch nick: Test 
timestamp: Tue 2009-09-08 23:49:13 +0300 
message: 
  Скорректирован файл foo.txt в ветке Test 
------------------------------------------------------------
Обратите внимание на строки, начинающиеся с "revno:" — они показывают номер ревизии.

Можно видеть, что после ревизии номер 3 было произведено объединение, результат объединения зафиксирован в ревизии под номером 4. В результате объединения появилась ревизия из другой ветки, ей был присвоен номер 2.1.1.

Ревизии с целыми номерами (без точек) образуют главную линию разработки вашей ветки (mainline). Ревизии, присоединенные из других веток (merged revisions) являются частью полной истории ветки, но не входят в главную линию и имеют составной номер ревизии, разделенный точками (X.Y.Z).

Как узнать уникальные идентификаторы ревизий

Команда bzr log способна отображать настоящие глобально-уникальные идентификаторы ревизий при запуске с дополнительной опцией --show-ids:
C:\work\bzr-day\Basic-commands\Test>bzr log -n0 --show-ids -l3 
------------------------------------------------------------ 
revno: 4 [merge] 
revision-id: ru_bzr@googlegroups.com-20090908205001-h9q4wxqx76bi0j6u
parent: ru_bzr@googlegroups.com-20090908204913-43cyv2j60fva7w8g
parent: ru_bzr@googlegroups.com-20090908204842-5c0hseh1g4zyrzwx
committer: Базарный день <ru_bzr@googlegroups.com>
branch nick: Test 
timestamp: Tue 2009-09-08 23:50:01 +0300 
message: 
  Объединение с веткой Experimental     
------------------------------------------------------------ 
    revno: 2.1.1 
    revision-id: ru_bzr@googlegroups.com-20090908204842-5c0hseh1g4zyrzwx
    parent: ru_bzr@googlegroups.com-20090908204210-qegq6pfotpffqda6
    committer: Базарный день <ru_bzr@googlegroups.com>
    branch nick: Experimental 
    timestamp: Tue 2009-09-08 23:48:42 +0300 
    message: 
      Скорректирован файл goodbye.txt в ветке Experimental 
------------------------------------------------------------ 
revno: 3 
revision-id: ru_bzr@googlegroups.com-20090908204913-43cyv2j60fva7w8g
parent: ru_bzr@googlegroups.com-20090908204210-qegq6pfotpffqda6
committer: Базарный день <ru_bzr@googlegroups.com>
branch nick: Test 
timestamp: Tue 2009-09-08 23:49:13 +0300 
message: 
  Скорректирован файл foo.txt в ветке Test 
------------------------------------------------------------
Строки, начинающиеся на "revision-id:" собственно отображают идентификаторы ревизий. Так, ревизии номер 4 в этой ветке соответствует уникальный идентификатор "ru_bzr@googlegroups.com-20090908205001-h9q4wxqx76bi0j6u".

Можно заметить, что Bazaar использует данные об авторе ревизии и дате фиксации плюс некоторые случайные данные для формирования уникального идентификатора. Однако, как я упоминал ранее, формат и содержание идентификатора — произвольные.

Что означают цифры в номере присоединенной ревизии

Правило образования номеров для присоединенных ревизий следующее:
  • первая цифра означает номер ревизии в главной ветке, от которой отпочковалась побочная ветка
  • вторая цифра означает порядковый номер побочной ветки (начиная с 1)
  • третья цифра означает порядковый номер ревизии в побочной ветке, после того, как история разошлась.
Взглянем на граф ревизий, визуализированный при помощи команды qlog из плагина QBzr:

граф ревизий
Ревизия 2.1.1 отпочковалась от ревизии 2 в основной ветке, имеем всего одну побочную ветку (Experimental), и одну ревизию в побочной ветке. Если бы в побочной ветке Experimental были бы еще ревизии, то они были бы пронумерованы как 2.1.2, 2.1.3 и т.д.

В следующей части статьи мы рассмотрим, каким образом концепция главной линии разработки (mainline) влияет на работу с Bazaar.

Примеры к статье можно найти на Launchpad.net: ветка Test, ветка Experimental.