воскресенье, 22 ноября 2009 г.

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

Это заключительная статья с рассказом о концепции mainline в Bazaar (предыдущие части первая и вторая). Я должен признать, что несмотря на то, что сама концепция mainline достаточно проста, но рассказать про нее просто и понятно у меня получается не так хорошо, как хотелось бы. Поэтому в начале этой части я снова повторю некоторые ключевые особенности концепции mainline и затем расскажу как она влияет на работу с Bazaar.

Концепция mainline

Концепция mainline разделяет ревизии на те, которые были зафиксированы непосредственно в конкретной ветке, и на все остальные, которые были присоединены из других веток (при помощи команды merge).

Благодаря тому, что в Bazaar используется модель "одна ветка в одном каталоге", то концепция mainline становится возможной и в некотором смысле логичной.

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

Наиболее полно этот принцип используется самими разработчиками Bazaar. Если посмотреть на журнал ревизий ветки с кодом bzr, то мы увидим историю в виде списка улучшений и добавлений:
C:\work\Bazaar\bzr-2a\bzr.dev>bzr log -l10 --short
 4819 Canonical.com Patch Queue Manager 2009-11-20 [merge]
      (jam) Fix bug #485771,
        only change slashes on arguments that are being globbed.

 4818 Canonical.com Patch Queue Manager 2009-11-20 [merge]
      (jam) Add a developer doc describing dependencies for win32 builds

 4817 Canonical.com Patch Queue Manager 2009-11-20 [merge]
      (igc) Trivial formatting fix to merge help

 4816 Canonical.com Patch Queue Manager 2009-11-20 [merge]
      (igc) Explain that .bzrignore is implicitly added (Patrick Regan,
        #59608)

 4815 Canonical.com Patch Queue Manager 2009-11-19 [merge]
      (jam) Fix CommitBuilder.inv_sha1 when using record_iter_changes.

 4814 Canonical.com Patch Queue Manager 2009-11-19 [merge]
      (jam) Release the gil during some of the core groupcompress code
        paths.

 4813 Canonical.com Patch Queue Manager 2009-11-19 [merge]
      (jam) Remove a @needs_read_lock decorator from something that doesn't
        really need it.

 4812 Canonical.com Patch Queue Manager 2009-11-19 [merge]
      (Alexander Sack) Add --commit-time option to 'bzr commit'. (#459276)

 4811 Canonical.com Patch Queue Manager 2009-11-19 [merge]
      (Andrew Bennetts) Add 'Bazaar Contribution in Five Minutes'
        introduction to developer docs.

 4810 Canonical.com Patch Queue Manager 2009-11-18 [merge]
      (jam) Last few tweaks to get the win32 test suite to pass.

Use --include-merges or -n0 to see merged revisions.

Разделение ревизий на 2 группы

Как отмечено выше концепция mainline делит все ревизии в ветке на две неравные группы:
  • ревизии, непосредственно зафиксированные в конкретной ветке
  • ревизии, присоединенные из других веток командой merge
Последовательность ревизий из 1й группы образует "основную" историю ветки, или mainline. Иногда еще эту последовательность называют "left-hand history", поскольку при выводе журнала ревизий основная группа отображается начиная с крайней левой колонки, в то время как присоединенные ревизии выводятся в журнале с отступом.

Второе неравенство между группами ревизий заключается в том, что для "основной" истории ревизии нумеруются целыми числами, начиная с 1 для первой ревизии. Присоединенные ревизии нумеруются по сложной "точечной" схеме M.B.N, где
  • M — это основная ревизия, от которой отпочковалась ветка,
  • B — это условный порядковый номер ветки (чтобы различать несколько веток отпочковавшихся из одной и той же основной ревизии)
  • N — это номер ревизии в ветке, после отпочкования

Собственно номера ревизий — это одно из наиболее заметных проявлений концепции mainline. Рассмотрим теперь как mainline влияет на различные команды bzr.

Журнал ревизий

Как уже отмечалось, журнал ревизий выводит основные и присоединенные ревизии немного по-разному. Прежде всего присоединенные ревизии выводятся с отступом. А в последних версиях bzr присоединенные ревизии по умолчанию не отображаются (это сделано из соображений производительности). Для того, чтобы увидеть присоединенные ревизии необходимо использовать опцию командной строки --include-merges или -n0:
C:\work\Bazaar\bzr-2a\bzr.dev>bzr log -r-1 --short -n0
 4819 Canonical.com Patch Queue Manager 2009-11-20 [merge]
      (jam) Fix bug #485771,
        only change slashes on arguments that are being globbed.

       4818.1.1 John Arbash Meinel      2009-11-20
                Fix bug #485771. Only change '/' to '/' when expanding globs.

                The code we had would replace '/' even if it was in a quoted section,
                or if it was part of a simple argument that didn't have a glob.

При просмотре ревизий в GUI окне команды qlog присоединенные ревизии скрыты и помечены значком +:

qlog-test-collapsed

Щелчком мышки по значку + (либо нажатие стрелки вправо при использовании клавиатуры) раскрывает присоединенные ревизии:

qlog-test-expanded

Объединение двух веток

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

Рассмотрим несколько типовых случаев. Приведенные ниже примеры и рекомендации имеют смысл только если ваша команда разработчиков договорилась использовать парадигму mainline.

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

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

При длительной работе над новой функцией в отдельной (функциональной) ветке зачастую приходится делать merge новых изменений из других веток (либо из главной ветки). Это может требоваться для того, чтобы задействовать новую функциональность, необходимую для вашей работы. Либо для того, что решить возможные конфликты из-за радикальных изменений (например рефакторинга) в основной ветке.

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

Объединение главной ветки и нового кода из функциональной ветки

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

Для выполнения объединения-приземления удобно использовать локальную копию главной ветки. Перед объединением вы обновляете локальную копию главной ветки (командой pull). Затем из каталога с копией главной ветки запускаете команду merge:
bzr merge ../my-work
После объединения вы проверяете результат, решаете конфликты если таковые имеются и фиксируете результат в копии главной ветки. Затем делаете push в главную ветку (на сервере).

Такой порядок действия хорошо работает в небольших командах, где каждый может делать push в главную ветку. В тех командах, где присоединением новых веток в главную занимается специальный человек (gatekeeper) или объединение производится специальной программой (так например в проекте Bazaar объединением с основной веткой занимается PQM — программа-работ, получающая инструкции через электронную почту), в этом случае целесообразно сделать merge из главной ветки в свою функциональную ветку перед подачей заявки на объединение с главной веткой. Это merge позволит вам исправить все возможные конфликты и следовательно упростит процедуру включения ваших изменений.

Когда объединение делать необязательно?

Продолжая тему объединения новой функциональности с главной веткой рассмотрим вопрос: всегда ли нужно использовать merge для этого?

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

При отсутствии автоматических тестов у вас будет выбор. Но опять же — в общем случае делать прямой push не следует, особенно если в вашей ветке больше одной ревизии.

Если все ваши изменения уместились в одну ревизию, то в этом случае выбор за вами: делать merge или push. Операция merge может оказаться полезной тем, что вы сможете написать другой комментарий к вашим изменениям, более уместный в контексте главной ветки.

Push/pull и основная история

Следует знать и всегда помнить о том, что операции push и pull могут поменять основную историю ветки. В некотором смысле даже без вашего желания. Это происходит в тех случаях, когда одна ветка была объединена с другой.

Pull

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

Push

Наиболее частая ошибка при объединении функциональной ветки с главной веткой, это когда merge делается в функциональную ветку (вместо копии главной ветки). В этом случае push из функциональной ветки в главную ветку становится возможным. Но делать такой push — очень плохая идея, потому что при этом изменяется нумерация ревизий в главной ветке: push при этом меняет mainline главной ветки на mainline функциональной ветки. Так делать не следует. Как было описано выше правильно делать merge в локальную копию главной ветки и делать push оттуда.

Хотя подобный push не является фатальным сам по себе, его следует избегать. Потому что если кто-то другой зафиксирует новую ревизию после того, как основная история была изменена, это превратится в проблему.

append_revisions_only

В bzr существует способ запретить изменение основной истории ветки.

Если при создании ветки указать опцию --append-revisions-only, то для такой ветки устанавливается флаг, запрещающий изменение основной истории за исключением добавления новых ревизий. Т.е. запрещаются операции uncommit и pull/push, если в результате pull/push существующие mainline ревизии могут быть заменены другими с одинаковыми номерами.

Флаг append_revisions_only можно установить и позднее, после того как ветка создана. Для этого необходимо добавить следующую строку в файл конфигурации branch.conf (он находится в .bzr/branch/branch.conf):
append_revisions_only = True

Установка такого флага возможна и для веток на Launchpad.net, хотя и не совсем тривиальным способом: вам понадобится использовать утилиту hitchhicker.

Заключение

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

Еще раз хочу отметить, что идея mainline уникальна для bzr и отсутствует в других распределенных системах контроля версий (git, hg). У идеи mainline есть свои достоинства и недостатки, и правильное использование mainline требует определенной внимательности и соглашений в команде разработчиков.

2 комментария:

  1. Получается что если следовать концепции Mainline, то разработчику для коммита в главную ветку надо иметь актуальную копию главной ветки, чтобы объединить в копию функциональную ветку, а уже затем сделать push из копии в главную ветку. Не слишком ли затратно это, особенно на больших проектах?

    ОтветитьУдалить
  2. Что значит "затратно"? Вас смущает необходимость иметь две рабочие копии? Можно использовать git-стиль и только одну рабочую копию. Для объединения главной ветки и функциональной ветки вам нужна только рабочая копия в главной ветке.

    ОтветитьУдалить