вторник, 21 апреля 2009 г.

Плагины bzr: основые сведения

Базар имеет множество команд для выполнения своих основых функций: для просмотра изменений, для фиксирования состояния файлов и каталогов в определенные моменты времени и для работы с историей изменений. И для большей части пользователей этого достаточно. Однако существуют ситуации, когда требуются дополнительные возможности от системы контроля версий, не предоставляемые стандартной поставкой. В этом случае на сцене появляются модули расширения или плагины (plugins) для bzr.

Плагины позволяют расширить возможности bzr за счет новых команд, либо за счет модификации поведения старых. Плагины могут регистрировать специальные обработчики событий (хуки/hooks), которые будут вызываться перед или после некоторых операций, например, commit, pull/push и т.д. Плагины могут добавлять новые алгоритмы обработки данных либо позволять работать с другими форматами репозиториев, например, предоставлять доступ к репозиториям других систем контроля версий. Наиболее знаменитым плагином в этом отношении является bzr-svn, который позволяет напрямую работать с svn.

Список плагинов

Список имеющихся плагинов для bzr приведен на странице BzrPlugins.

Для каждого плагина указано его краткое описание, информация об авторе (авторах) плагина, информация о совместимости с основными операционными системами (Linux/Windows/Mac). Также указан URL на страничку плагина либо bzr-ветку с его кодом.

Что представляют собой плагины

Плагины представляют собой модули или пакеты, написанные на языке программирования Python. Плагины должны писаться на Python, поскольку сама система bzr написана на Python, а плагины взаимодействуют напрямую с внутренним API bzr.

Как подключить плагин

При каждом запуске bzr ищет установленные плагины в следующих каталогах:
  1. Если установлена переменная окружения BZR_PLUGIN_PATH, то проводится поиск в списке каталогов из этой переменной
  2. Иначе проводится поиск плагинов в каталоге настроек приложения для текущего пользователя (на Linux/Mac это ~/.bazaar/plugins; на Windows это C:\Documents and Settings\USERNAME\Application Data\bazaar\2.0\plugins).
  3. Дополнительно проводится поиск в подкаталоге, где установлен bzr:
  • например на Linux это может быть /usr/lib/python2.5/site-packages/bzrlib/plugins
  • на Windows:
    • для случая установки standalone installer это будет C:\Program Files\Bazaar\plugins (если сам bzr установлен в каталоге C:\Program Files\Bazaar)
    • для случая установки как python-программы — аналогично Linux в каталоге site-packages/bzrlib/plugins
Если плагин находится в указанных каталогах, то он будет загружен. Никаких специальных настроек для разрешения или запрещения плагина не требуется. Для того чтобы отключить плагин достаточно удалить плагин из соответствующего каталога.

Установка плагинов

Многие популярные плагины имеют специальные пакеты для установки на Linux. Они могут быть установлены/удалены обычным образом при помощи менеджера пакетов.

Рассмотрим установку плагина из исходного кода.

Большинство плагинов работают прямо из исходного кода и не требуют специальных действий для установки. Достаточно просто поместить каталог (или ветку) с кодом плагина в каталог plugins. Однако все-таки стоит изучить имеющийся файл README или INSTALL на предмет дополнительных инструкций по установке.

Следует также помнить о том, что часто код плагинов размещается на сайте Launchpad.net, который предоставляет бесплатный хостинг для проектов и/или bzr-веток. В этом случае страница bzr-плагина зачастую имеет приставку "bzr-" в имени плагина (чтобы подчеркнуть свою принадлежность).  При установке плагина эту приставку нужно опустить, плюс все дефисы в названии поменять на знак подчеркивания. Так, например, плагин bzr-gtk должен устанавливаться в каталог gtk, а плагин bzr-x-bit в каталог x_bit. Каталог, в котором должен помещаться код плагина, часто жестко предопределен и при его изменении плагин в большинстве случаев оказывается неработоспособным.

Рассмотрим установку на примере плагина merge-into:
  • перейти в каталог plugins:
    cd ~/.bazaar/plugins
  • получить копию ветки с launchpad.net:
    bzr branch lp:bzr-merge-into merge_into
В каталоге merge_into будет создана копия ветки плагина. Таким образом вы сможете легко обновлять свою копию плагина, когда автор плагина опубликует новую функциональность или исправления найденных ошибок.

Чтобы убедиться в том, что плагин установлен корректно, запустите команду bzr plugins. Ваш плагин должен присутствовать в списке, который выдает эта команда:

C:\>bzr plugins
bzrtools 1.13
    Various useful commands for working with bzr.

launchpad
    Launchpad.net integration plugin for Bazaar.

merge_into
    Provide the 'merge-into' command.

Многие плагины содержат набор тестов, которые можно использовать для проверки работоспособности плагина на вашей платформе или для проверки совместимости с используемой версией bzr. Для запуска тестов конкретного плагина используйте команду selftest:

C:\>bzr selftest -s bp.merge_into
testing: C:/Program Files/Bazaar/bzr.EXE
   C:\Program Files\Bazaar\lib\library.zip\bzrlib (1.13.1 python2.5.4)

[5/5 in 4s] bzrlib.plugins.merge_into.test_bb_merge_into.TestMergeInto.test_smoke
----------------------------------------------------------------------
Ran 5 tests in 5.125s

OK
tests passed

Справочная информация о плагине

Для получения справки о плагине посмотрите файл README, поставляемый с кодом плагина, либо используйте команду help:

C:\>bzr help merge_into
Provide the 'merge-into' command.

This command allows you to merge other branches into subdirectories, rather
than always merging into root, and then needing to be moved around.

Некоторые плагины добавляют большое количество команд, не все из которых описаны в скудной справке к плагину. Отыскать все команды нам поможет команда bzr help commands и утилита grep. Например, найдем все команды, предоставляемые популярным плагином bzrtools:

C:\>bzr help commands | grep bzrtools
branch-history     Display the development history of a branch. [bzrtools]
branches           Scan a location for branches [bzrtools]
cbranch            Create a new checkout, associated with a new repository branch. [bzrtools]
cdiff              A color version of bzr's diff [bzrtools]
clean-tree         Remove unwanted files from working tree. [bzrtools]
fetch-ghosts       Attempt to retrieve ghosts from another branch. [bzrtools]
graph-ancestry     Produce ancestry graphs using dot. [bzrtools]
heads              Show all revisions in a repository not having descendants. [bzrtools]
import             Import sources from a directory, tarball or zip file [bzrtools]
link-tree          Hardlink matching files to another tree. [bzrtools]
multi-pull         Pull all the branches under a location, e.g. a repository. [bzrtools]
patch              Apply a named patch to the current tree. [bzrtools]
rspush             Upload this branch to another location using rsync. [bzrtools]
shelf1             Perform various operations on your shelved patches. See also shelve1. [bzrtools]
shell              Begin an interactive shell tailored for bzr. [bzrtools]
shelve1            Temporarily set aside some changes from the current tree. [bzrtools]
trees              Scan a location for trees [bzrtools]
unshelve1          Restore shelved changes. [bzrtools]
zap                Remove a lightweight checkout, if it can be done safely. [bzrtools]

Примечание: пользователям Windows необходимо установить утилиту grep самостоятельно.

Временное отключение плагинов

В некоторых (редких) случаях  может потребоваться запустить команду bzr без плагинов. В этом случае вы можете использовать глобальную опцию командной строки --no-plugins. Глобальные опции должны указываться после имени программы (bzr) и перед собственно командой. Так, например, следующая команда выведет список только встроенных команд:

bzr --no-plugins help commands

Имеется еще одна глобальная опция, воздействующая на плагины: --builtin. При использовании этой опции плагины загружаются как обычно, но при этом плагинам запрещается декорирование команд. Т.е. плагины не смогут зарегистрировать новую команду с именем, которое совпадает с именем встроенной команды. Эта опция полезна прежде всего для целей отладки.

воскресенье, 5 апреля 2009 г.

Автоматический whoami при работе через SSH

В статье рассматривается простой пример использования (документированных) возможностей как sshd, так и bzr, для автоматической установки идентификатора пользователя при работе на удаленной машине.

Сценарий

Имеется общий компьютер, на котором могут работать несколько человек под одной учетной записью. Этот компьютер может использоваться как тестовый полигон, либо как машина для компиляции окончательной версии программы, или для сборки инсталляционных пакетов. Либо же это тестовый/боевой сервер, на котором запущен веб-сайт или другое приложение. Разработчики используют SSH для работы на этом общем компьютере.

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

Проблема

При такой совместной работе остро стоит вопрос адекватной идентификации разработчиков, которые фиксировали свои изменения. По умолчанию bzr использует автоматический идентификатор, состоящий из имени учетной записи и имени компьютера, например: root@server. А поскольку под одной учетной записью могут работать несколько человек, то нельзя установить глобальный идентификатор через команду bzr whoami.

Способы идентификации

Базар позволяет идентифицировать автора ревизии несколькими способами: глобальная идентификация, идентификация на уровне ветки, или через использование переменных окружения.

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

Остается вариант с использованием переменных окружения. Базар использует значение переменной окружения BZR_EMAIL или EMAIL в качестве идентификатора пользователя. Если одна из этих переменных определена, то она имеет приоритет как над глобальным идентификатором, так и над идентификацией на уровне ветки.

Вариант с использованием переменных окружения тоже имеет свой недостаток: в начале каждой новой SSH-сессии разработчик должен установить переменную окружения BZR_EMAIL. О необходимости установки этой переменной легко забыть. К счастью имеется возможность автоматизировать этот скучный процесс при использовании аутентификации пользователей при помощи SSH-ключей.

Использование файла authorized_keys

При аутентификации при помощи SSH-ключей используется файл ~/.ssh/authorized_keys (или ~/.ssh/authorized_keys2). В этом файле сохраняется список публичных SSH-ключей, которым разрешена аутентификация. Однако кроме самих ключей, дополнительно могут быть указаны различные параметры соединения, либо ограничения доступа, либо директивы запуска некоторой программы при аутентификации конкретным ключом. Либо директива установки переменной окружения при аутентификации конкретным ключем. Именно эта директива нам и нужна.

Директива установки переменной окружения при логине указывается в виде:
environment="ИМЯ=значение"
Подробнее о формате и параметрах в файле authorized_keys читайте в man sshd.

Таким образом для настройки автоматической идентификации пользователей необходимо применить вход на общий компьютер через SSH-ключи, и для каждого пользователя (перед его публичным ключем) указать свою директиву environment, например:

--------------------Файл: authorized_keys-------------------------------------
environment="BZR_EMAIL=Vasya Pupkin <pupkin@mail.ru>" ssh-dss AAAAB3NzaC1kc3MAAACAcdSwa...
------------------------------------------------------------------------------

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

пятница, 3 апреля 2009 г.

Работа с ветками: push, pull, merge (Часть 3)

Рассмотрим еще один пример работы с ветками: совместная работа над проектом в небольшом коллективе. Для определенности будем рассматривать коллектив из 2х человек, поскольку приводимый сценарий легко масштабируется и на большее количество участников.

Сценарий №2

Над созданием программы под кодовым названием MegaApp работают Иван и Сергей. У них есть общее хранилище для веток проекта на центральном сервере. Там находится главная ветка проекта под названием trunk. Каждый из участников проекта, Иван и Сергей, имеет локальную копию главной ветки на своем компьютере и периодически синхронизирует ее с оригиналом на сервере.

Для реализации новой функции Иван создает новую копию ветки trunk для локальной работы. Иван пишет новую функциональность, фиксирует изменения, тестирует свой код в локальной рабочей ветке. Когда работа над новой функцией завершена Иван объединяет свои изменения с копией главной ветки, фиксирует результат объединения и отправляет новые ревизии в главную ветку на сервере.

Для исправления найденного дефекта в программе Сергей создает новую копию ветки trunk для локальной работы. Сергей находит причину дефекта и исправляет его, фиксирует изменения, тестирует свой код в локальной рабочей ветке. Когда работа над исправлением дефекта завершена Сергей объединяет свои изменения с копией главной ветки, фиксирует результат объединения и отправляет новые ревизии в главную ветку на сервере.

Главная ветка проекта

С технической точки зрения для Базара все ветки равны. Однако в любом проекте должна быть главная ветка, которая используется всеми участниками проекта для синхронизации, в которую попадают новые изменения, багфиксы, из которой в конечном счете формируются окончательные версии (релизы) вашего продукта. Такая ветка является центральным связующим звеном для проекта.

Поскольку в распределенных системах все ветки равноправны, то участники проекта должны договориться какую ветку использовать в качестве главной. Имя такой ветки может быть произвольным, но наиболее часто используются такие имена: trunk, main, dev.
Имя trunk пришло из svn. По английски "trunk" означает "ствол" (ствол дерева), как противоположность просто веткам (branch), которые ответвляются от ствола.

Центральное хранилище

Для хранения эталонной главной ветки, с которой все участники проекта будут периодически синхронизироваться, нужно какое-либо центральное хранилище, или центральный сервер. Базар поддерживает различные типы серверов для хранения веток: SFTP, FTP, специализированный bzr-сервер, можно даже задействовать какой-то общий каталог на одной из машин (в сети Windows) или на SAMBA-сервере. Выбор того или иного варианта зависит от конкретной ситуации.

Далее в примерах будет использоваться локальный bzr-сервер, запущенный командой:
bzr serve --allow-writes --directory C:/work/bzr-day/server
где опция --directory указывает каталог, в котором располагаются ветки. Используйте любой другой удобный для вас каталог.
Будьте внимательны: bzr-сервер не осуществляет никакую авторизацию пользователей, и с опцией --allow-writes разрешает любому клиенту операции как чтения так и записи. bzr-сервер использует порт 4155 для работы.

Создание главной ветки на сервере

Иван использует команду init (или push), чтобы создать ветку trunk на сервере:

C:\work\bzr-day\ivan>bzr init bzr://localhost/trunk
Created a standalone branch (format: unnamed)

Создание локальной копии главной ветки

Для работы над проектом Ивану необходима локальная копия главной ветки. Для получения главной ветки с сервера Иван использует команду branch:

C:\work\bzr-day\ivan>bzr branch bzr://localhost/trunk
Branched 0 revision(s).

Ветка совсем пустая (0 revisions), поэтому Иван решает сделать первую фиксацию прямо в ней и обновить ветку на сервере:

C:\work\bzr-day\ivan\trunk>bzr commit -m initial --unchanged
Committing to: C:/work/bzr-day/ivan/trunk/
Committed revision 1.

C:\work\bzr-day\ivan\trunk>bzr push bzr://localhost/trunk
Pushed up to revision 1.

Рекомендации по работе над проектом

Участники проекта могут изменять файлы и фиксировать новые ревизии прямо в своей копии ветки trunk, однако целесообразнее использовать отдельные ветки (так называемые feature branches или функциональные ветки). Для чего это нужно?

При работе над сложным проектом редко когда новую функцию удается реализовать быстро, гораздо чаще требуется длительный промежуток времени (больше чем 5-10 минут). Работа над сложными функциями может состоять из реализации нескольких последовательных этапов работы. Каждый такой логический этап можно (и нужно) фиксировать командой commit. При этом что-то может быть недоделано или программа вообще откажется работать. Фиксировать неработоспособный код прямо в trunk — это очень плохая идея. Ваш коллега по работе может взять новый неработоспособный код, не подозревая об этом, и получить в нагрузку кучу неприятной головной боли. Всегда старайтесь держать в trunk только работоспособный (комплирующийся, протестированный) код.

Еще одним плюсом функциональных веток является логическая группировка ревизий. Гораздо удобнее анализировать историю проекта, когда ревизии, относящиеся к реализации конкретной функции, идут в журнале вместе. Если же каждый разработчик фиксирует свои изменения прямо в trunk и часто делает push в главную ветку, то в результате история изменений становится запутанной, как куча спагетти. Хотите ли вы этого?

Это не все причины в пользу использования функциональных веток, но две наиболее серьезные.
Правило использования функциональных веток — это не догма, но полезная рекомендация.

Иван работает над новой функцией

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

C:\work\bzr-day\ivan\trunk>bzr pull
Using saved parent location: bzr://localhost/trunk/
No revisions to pull.

C:\work\bzr-day\ivan\trunk>cd ..

C:\work\bzr-day\ivan>bzr branch trunk feature
Branched 1 revision(s).

C:\work\bzr-day\ivan>cd feature

Иван пишет новый код, фиксирует каждый логически законченный этап, тестирует его...

C:\work\bzr-day\ivan\feature>bzr st
...
C:\work\bzr-day\ivan\feature>bzr diff
...
C:\work\bzr-day\ivan\feature>bzr commit ...
...
C:\work\bzr-day\ivan\feature>bzr log
...

Наконец, работа над новой функцией закончена и Иван объединяет главную ветку со своей работой:

C:\work\bzr-day\ivan\feature>cd ../trunk
C:\work\bzr-day\ivan\trunk>

C:\work\bzr-day\ivan\trunk>bzr merge ../feature
+N  foo.c
All changes applied successfully.

C:\work\bzr-day\ivan\trunk>bzr status
added:
  foo.c
pending merge tips: (use -v to see all merge revisions)
  Базарный день 2009-03-31 Реализована и протестирована новая функция foo

C:\work\bzr-day\ivan\trunk>bzr status -v
added:
  foo.c
pending merges:
  Базарный день 2009-03-31 Реализована и протестирована новая функция foo
    Базарный день 2009-03-31 Исправлена ошибка в функции foo
    Базарный день 2009-03-31 Реализован базовый алгоритм функции foo

C:\work\bzr-day\ivan\trunk>bzr commit -m "Реализована функция foo"
Committing to: C:/work/bzr-day/ivan/trunk/
added foo.c
Committed revision 2.

После чего можно отправлять новые ревизии на сервер командой push:

C:\work\bzr-day\ivan\trunk>bzr push bzr://localhost/trunk
Pushed up to revision 2.

Подобным образом работают все участники проекта.

Сергей работает над исправлением найденного дефекта

Работа над исправлением дефекта для Сергея мало отличается от написания новой функции программы. Сергей точно также использует отдельную ветку для работы и в конце объединяет главную ветку со своими изменениями:
  • обновить локальную копию главной ветки (bzr pull)
  • создать отдельную ветку для работы (bzr branch trunk bugfix)
  • проделать работу в отдельной ветке (status, diff, commit, log)
  • объединить локальную копию главной ветки со своими изменениями (bzr merge ../bugfix)
  • передать новые ревизии в главную ветку на центральном сервере (bzr push)

Регулярная синхронизация с главной веткой

При длительной уединенной работе в отдельной ветке следует помнить о том, что ваши коллеги по проекту не спят и тоже пишут новый код. Поэтому чем дольше вы работаете над новыми функциями или багфиксами, тем выше вероятность, что кто-то из коллег сделает изменения, которые будут влиять на вашу работу. Это может быть и простое исправление опечатки в коде или комментариях, массивный рефакторинг, либо просто частичное изменение API используемых вами модулей. В первых двух случаях у вас возникнут конфликты при объединении вашей законченной работы и trunk. В случае изменения API вы получите неработоспособную программу. Поэтому необходима регулярно обновлять свою копию ветки trunk и синхронизировать свою работу с ней.
Рассмотрим следующий пример. Иван работает над новой сложной функцией программы не первый день. В это время Сергей исправляет найденный дефект в коде одного из модулей, и свои исправления публикует в ветке trunk. Исправления Сергея затрагивают код, над которым работает Иван. Иван время от времени обновляет копию главной ветки и просматривает новые ревизии. Когда он замечает, что появились изменения, влияющие на его работу, то Иван объединяет новые ревизии из trunk со своей веткой, исправляет возможные несоответствия в своем коде и продолжает работу в своей ветке.

Вот как этот пример выглядит в командах bzr:

Иван работает в своей ветке bigfeature над новой сложной функцией:

bzr branch trunk bigfeature
cd bigfeature
hack-hack-hack
bzr diff
bzr commit

Иван периодически обновляет свою копию trunk, просматривает новые ревизии и обнаруживает появление багфикса от Сергея:

cd ../trunk
bzr pull
bzr log

Для того, чтобы увидеть только новые ревизии в главной ветке, которые отсутствуют в ветке bigfeature Иван может воспользоваться командой missing:

cd ../bigfeature
bzr missing --theirs-only ../trunk

Флаг --theirs-only указывает команде missing, что пользователя интересует только информация о новых ревизиях в ветке trunk.

Иван решает, что ему нужно объединить новые правки из trunk с его собственной работой и делает merge:

cd ../bigfeature
bzr merge ../trunk
bzr diff
bzr commit -m "merge trunk"

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

Резюме

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