Сравнение DVCS - несколько задач — различия между версиями

Материал из YourcmcWiki
Перейти к: навигация, поиск
(Новая страница: «== Работа с SVN (миграция и синхронизация) == '''Mercurial''': отлично! Есть несколько расширений — [h...»)
 
м
 
(не показаны 83 промежуточные версии этого же участника)
Строка 1: Строка 1:
 +
Данная статья является очередным сравнением популярных [[wikipedia:Distributed revision control|DVCS]] — [http://selenic.com/mercurial/ Mercurial], [http://git-scm.com/ Git] и [http://bazaar-vcs.org/ Bazaar], с точки зрения нескольких нетривиальных задач. Ссылка: [http://yourcmc.ru/DVCS_YAC http://yourcmc.ru/DVCS_YAC] («Yet Another Comparison»).
 +
 +
Хотите лаконичный ответ в стиле «ИМХО» на вопрос — кого выбрать из трёх? Ну пожалуйста. Выбирайте Mercurial. А не Git (безумный конгломерат) и не Bazaar (пионерское поделие, унаследованное от Arch’а).
 +
 
== Работа с SVN (миграция и синхронизация) ==
 
== Работа с SVN (миграция и синхронизация) ==
  
'''Mercurial''': отлично! Есть несколько расширений — [http://mercurial.selenic.com/wiki/HgSubversion hgsubversion], [http://pypi.python.org/pypi/hgsvn hgsvn], [http://mercurial.selenic.com/wiki/ConvertExtension convert], позволяющих работать с Subversion тем или иным образом, и не совместимых друг с другом. Самое вменяемое из них — '''hgsubversion''', хотя и заявлено, что оно ещё сырое. Имеет фактически весь необходимый функционал — можно делать и <tt>push</tt>, и <tt>pull</tt> в/из Subversion, можно клонировать SVN-репозиторий с сохранением веток и меток (правда, обязательно стандартное их расположение в корневых поддиректориях <tt>/trunk</tt>, <tt>/branches</tt>, <tt>/tags</tt>), эти два метода совместимы, а граф ревизий сохраняется.
+
Почему-то, говоря о недостатках DVCS, никогда не говорят о следующем не очень удобном при переходе с централизованных систем поведении — DVCS отслеживают версию ''цельного'' репозитория, а не отдельных файлов или директорий. Кроме проблем, которые это создаёт при желании извлечь ''часть'' репозитория для работы, а не обязательно весь, это ведёт к тому, что если две ветки «расходятся», становится обязательным слияние, даже если '''конфликтов в изменениях не было''' и даже если '''вообще менялись разные файлы'''. А централизованным хоть бы хны — <tt>svn up</tt> и живи себе дальше.
  
Остальные два экстенжна «нинужны»: <tt>hgsvn</tt> — нечто более старое, работает сбоку от общего механизма, тоже позволяет делать push и pull, но не клонирует весь репозиторий, а только извлекает (checkout’ит) последнюю версию, чтобы далее можно было использовать Subversion и Mercurial вместе. Ну и конечно, оно не совместимо с <tt>hgsubversion</tt>. <tt>convert</tt> же предназначен для конвертации истории проекта из нескольких различных систем контроля версий в Mercurial, ни черта не совместим ни с <tt>hgsvn</tt, ни с <tt>hgsubversion</tt> и не сохраняет граф ревизий. Зато, правда, поддерживает возможность переименования поддиректорий <tt>trunk/branches/tags</tt>.
+
Что ещё любопытно — с Subversion-репозиториями DVCS работают, как правило, быстрее самого Subversion, и быстрее всех работает '''Bazaar'''. То есть, в принципе, можно вообще жить с Subversion-сервером и DVCS-клиентом (если, конечно мириться с постоянными слияниями и rebase’ами).
  
'''Bazaar''': хорошо. Можно импортировать репозиторий командами <tt>svn-import</tt> или <tt>branch</tt>, можно делать <tt>push</tt> и <tt>pull</tt> в/из Subversion. При импорте можно сохранить все ветки и метки в одном хранилище (если использовать shared repository), для этого также требуются стандартные названия <tt>trunk/branches/tags</tt>. Граф ревизий Subversion, увы, не сохраняется. Также существует несколько других расширений для импорта Subversion в Bazaar, но все они ещё хуже.
+
Во всех трёх DVCS при работе с Subversion есть следующее ограничение: невозможно связывать один и тот же репозиторий с несколькими Subversion-источниками — ни в пределах разных веток, ни в пределах разных каталогов. Mercurial честно говорит «repository unrelated», Bazaar — «branches have no common ancestor», Git пытается что-то родить, но запутывается в одинаковых коммитах, имеющих несколько SVN-родителей. А ведь было бы неплохо иметь такие возможности…
  
Что любопытно — с Subversion-репозиториями DVCS работают, как правило, гораздо быстрее самого Subversion.
+
=== Mercurial ===
 +
 
 +
'''Mercurial''': почти отлично (5-), есть 3 расширения различной степени глюкавости — [http://mercurial.selenic.com/wiki/HgSubversion hgsubversion], [http://pypi.python.org/pypi/hgsvn hgsvn], [http://mercurial.selenic.com/wiki/ConvertExtension convert], из которых позволяющих работать с Subversion тем или иным образом, и не совместимых друг с другом.
 +
 
 +
Самое вменяемое из этих расширений — '''hgsubversion''', и хотя оно ещё сыровато, разработка ведётся активно, в мэйллисте постоянно происходит некая жизнь, а автор отзывчив. Вместе с Mercurial’ом оно не распространяется, нужно ставить отдельно. Тестировал я ревизию 500 из http://bitbucket.org/durin42/hgsubversion.
 +
 
 +
Имеет основной необходимый функционал — можно делать и <tt>push</tt>, и <tt>pull</tt> в/из Subversion, можно клонировать SVN-репозиторий с сохранением веток и меток (правда, обязательно стандартное их расположение в корневых поддиректориях <tt>/trunk</tt>, <tt>/branches</tt>, <tt>/tags</tt>), эти два метода совместимы, <tt>rebase</tt> работает, граф ветвлений сохраняется. Очень крут тот факт, что ветка, которая создавалась неполным копированием <tt>trunk</tt>'а, то есть, копированием некоторых его поддиректорий, успешно подцепилась в нужное место графа ветвлений. Ни Bazaar, ни Git этого не смогли. Поддержки svn:mergeinfo пока что нет, хотя она близится.
 +
 
 +
Минус расширения заключается в сырости — за время тестирования я нашёл уже несколько неприятных багов:
 +
* <tt>push</tt> из Mercurial-клона репозитория без веток и меток (имеющего вид просто одного каталога) в Subversion не работал вообще — [http://groups.google.com/group/hgsubversion/browse_thread/thread/3749eb3cbf007855 обсуждение в группе hgsubversion]. К счастью, меня пропёрло, я потратил час времени и баг этот пофиксил собственноручно. [[hgsubversion-fix-for-singledir-repo.diff|Фикс]] очень простой, на две строчки, и он уже находится в репозитории в виде ревизии r501 — за отзывчивость автору зачёт.
 +
* <s>Не работает клонирование репозиториев по протоколу [http://svnbook.red-bean.com/en/1.0/ch06s03.html svnserve] (<tt>svn://host:port/url</tt>). Очень неприятно, особенно, если вы предпочитаете именно <tt>svnserve</tt> по причине его производительности.</s> Уже давно работает.
 +
* Течёт память, что при клонировании '''толстых''' SVN-репозиториев (например, [http://svn.wikimedia.org/svnroot/mediawiki/ репозитория MediaWiki]) приводит к ошибкам <tt>Abort: out of memory</tt>, то есть нехватки памяти. Обходится методом клонирования репозитория по частям, делая <tt>hg init</tt>, а потом несколько <tt>hg pull</tt> до победного конца. Причина в дырявости [http://www.swig.org/ SWIG] SVN Python-библиотеки. Забавно, что аналогичная [http://www.swig.org/ SWIG] Perl-библиотека такой текучестью, видимо, не страдает, так как Git этот репозиторий глотал (причём по 10 раз за раз) и не давился. ''Perl, говорите, течёт? Ну хи-хи, хи-хи.'' Кстати, Bazaar течёт точно так же, как <tt>hgsubversion</tt>.
 +
* На ревизии 43380 MediaWiki <tt>hgsubversion</tt> свалился с ошибкой <tt>abort: phase3/includes/ConfigurationCache.php@a1335975fba6: not found in manifest!</tt>. [http://groups.google.com/group/hgsubversion/browse_thread/thread/2a2ae0aa680054ec обсуждение в группе hgsubversion]. Причина, вероятно, кроется где-то в районе обработки перемещений каталогов Subversion. Я обошёл данный баг убиением части файла <tt>.hg/svn/rev_map</tt> (можно было и весь убить) и перезапуском <tt>hg pull</tt>. Однако в итоге появилось две ветки вместо одной: <tt>on_wiki-configuration</tt> и <tt>on_wiki-configuration/phase3</tt> (подкаталог ветки…)
 +
 
 +
Итог клонирования MediaWiki-репозитория: успешно с небольшими танцами с бубном — склонировал за несколько перезапусков <tt>pull</tt>'а и с уборкой <tt>rev_map</tt> в середине, все ветки на месте, только одна, перемещённая потом в подкаталог себя, продублировалась: «<tt>on-wiki_configuration</tt>» и «<tt>on-wiki_configuration/phase3</tt>». Репозиторий (то есть директория <tt>.hg</tt>) занимает на диске '''1.63 Гб'''.
 +
 
 +
Остальные два экстенжна «нинужны»: <tt>hgsvn</tt> — нечто более старое, работает сбоку от общего механизма, тоже позволяет делать push и pull, но не клонирует весь репозиторий, а только извлекает (checkout’ит) последнюю версию, чтобы далее можно было использовать Subversion и Mercurial вместе. Ну и конечно, оно не совместимо с <tt>hgsubversion</tt>. <tt>convert</tt> же предназначен для конвертации истории проекта из нескольких различных систем контроля версий в Mercurial, ни черта не совместим ни с <tt>hgsvn</tt>, ни с <tt>hgsubversion</tt> и не сохраняет граф ветвлений. Зато, правда, поддерживает возможность использования других имён поддиректорий <tt>trunk/branches/tags</tt>.
 +
 
 +
=== Git ===
 +
 
 +
'''Git''': отлично (5-)! <tt>[http://git-svn.yhbt.net/ git-svn]</tt> встроен в git и умеет всё, что нужно: клонирование, синхронизация (<tt>fetch</tt>, <tt>pull</tt> или <tt>merge</tt>), фиксация изменений в Subversion-репозиториях (<tt>dcommit</tt>) и <tt>rebase</tt> работают с сохранением веток и меток, причём стандартная схема их именования <tt>trunk/branches/tags</tt> не является обязательной. Ветки SVN импортируются как [http://www.gitready.com/beginner/2009/03/09/remote-tracking-branches.html Remote Tracking Branches]. Можно начинать историю ветки в git’е не с сотворения миров, а с любого момента. Также <tt>git-svn</tt> поддерживает подключаемые внешние репозитории Subversion (т. н. [http://svnbook.red-bean.com/en/1.0/ch07s03.html Externals]). Граф ветвлений сохраняется, хоть и не так круто, как в Mercurial’е. В общем, можно сказать, что функционал полон. <tt>git svn fetch</tt> успешно возобновляет операцию клонирование в случае её падения посредине.
 +
 
 +
А теперь поговорим о неприятном: полное клонирование [http://svn.wikimedia.org/ SVN-репозитория MediaWiki] заняло более 3 дней, хотя должно по логике занимать часов 6-8. Причина в том, что Git время от времени не понимает, откуда ответвилась ветка, лезет в глубокую историю и начинает сканировать по новой уже просканированные ревизии. Таким образом, некоторые (а конкректно — ветвлённые не из самого trunk’а, а из его подпапок) ветки не получаются связанными с историей trunk’а, старые ревизии для них дублируются. Справедливости ради надо отметить, что для двух таких веток старая история объединяется, а физически никаких дубликатов не хранится. Вообще в итоге Git-клон репозитория MediaWiki (директория .git) занял всего '''845 Мб'''. Кроме того, в процессе клонирования память '''не текла вообще''', на всё про всё уходило мегабайт 80 памяти.
 +
 
 +
Кроме того, как всегда с Git’ом, есть различные нетривиальности, что-то бывает нужно настроить (''гитара настроена? нас двое, на! играй, на!''), синтаксис не совмещён с обычными командами Git — то есть например для синхронизации копии с Subversion нужно сказать не <tt>git pull</tt>, а <tt>git svn fetch</tt>.
 +
 
 +
=== Bazaar ===
 +
 
 +
'''Bazaar''': хорошо. Можно импортировать репозиторий командами <tt>svn-import</tt> или <tt>branch</tt>, можно делать <tt>push</tt> и <tt>pull</tt> в/из Subversion. При импорте можно сохранить все ветки и метки в одном хранилище (если использовать [http://bazaar-vcs.org/SharedRepositoryTutorial Shared Repository]), для этого также требуются стандартные названия <tt>trunk/branches/tags</tt>. Граф ветвлений Subversion сохраняется, но также не отражает копирования подпапок. Также существует несколько других расширений для импорта Subversion в Bazaar, но они хуже.
 +
 
 +
Тест на клонирование репозитория MediaWiki прошёл почти без проблем, но почему-то вместо 59589 ревизий копировал 85567 ревизий, то есть откуда-то придумал лишних 25978. Возможно, это были ветки — корректного присоединения их истории к <tt>trunk</tt>'у обнаружено после клонирования '''не было вообще''' (!). Причина, скорее всего, в том, что ветки там в основном не совсем честные (как уже было упомянуто в секции про '''Git''') — они создавались не командой <tt>svn cp trunk branches/ветка</tt>, а создавался каталог ветки и в него копировались каталоги <tt>phase3</tt> и <tt>extensions</tt> из <tt>trunk</tt>’а. В итоге изменения, происходившие «до» создания таких «нечестных» веток, в самих ветках вообще не отражены.
 +
 
 +
Память течёт точно так же, как и Mercurial’е и, очевидно, по той же причине (SWIG), и перезапуски процесса клонирования по причине ухода машины в глубокий своп также были необходимы. После клонирования репозиторий изначально занимал аж 16 Гб, однако при ближайшем рассмотрении стало ясно, что большую часть этого пространства занимали каталоги <tt>obsolete_packs</tt> и <tt>upload</tt> со старыми упакованными данными репозитория и некими временными файлами соответственно. Такая же ситуация осталась и после вызова <tt>bzr pack</tt>, о чём есть целый баг: [https://bugs.launchpad.net/bzr/+bug/304320 Bug 304320 — bzr pack should (optionally?) delete obsolete packs]. Все эти лишние данные, по-видимому, можно смело удалить ручками — и обнаружить, что репозиторий занимает '''925 Мб''', то есть, лишь ненамного больше, чем в случае Git, и примерно в 1.75 раза меньше, чем в Mercurial’е. Правда, по ощущению чтение репозитория происходит медленнее и чем в '''Git''', и чем в Mercurial. При выводе истории изменений по одной и той же ветке '''Git''' не напрягается вообще, '''Mercurial''' напрягается немного, а '''Bazaar''' напрягается ощутимо. При том, что '''Git''' и '''Mercurial''' выводят не только историю изменений самой ветки, а также историю изменений, происходивших ''до создания ветки'' (помним, что ветки нечестные). Правда, я не исключаю, что под виндой ситуация с производительностью может быть совсем другая.
 +
 
 +
А теперь снова о неприятном. Есть у Базаара одна небольшая, зато возникающая в достаточно простом случае, проблемка. То есть пусть бы она и появлялась, но очень редко — это можно терпеть. Но тут ситуация не так чтобы редкая.
 +
 
 +
Так вот. Всё начинается с банального — мы сделали изменение в '''Bazaar'''-клонированном SVN-репозитории, а кто-то тем временем сделал изменение '''в самом SVN-репозитории'''… Причём не важно, в каких файлах: даже если конфликтов в наших изменениях нет, проблема возникает всё равно (помним о том, что DVCS управляют «цельным» репозиторием). Что нам теперь остаётся делать? Ну конечно, мержиться. И merge успешно проходит. Но в случае '''Bazaar''' помните — после этого merge нужно сразу делать rebase и push в Subversion! Иначе, если до этого сделать ещё хотя бы один commit в Bazaar, push не пройдёт (он и не должен), а rebase не поможет или вывалится с исключением.
 +
 
 +
Хотите подробнее? Рассмотрим следующую последовательность действий:
 +
 
 +
# Клонируем svn в ветку bzr1.
 +
# Клонируем bzr1 в bzr2.
 +
# Вносим изменения в bzr2, коммитим.
 +
# Вносим ''другие'' изменения в svn, коммитим. Лучше несколько раз (создаём несколько ревизий).
 +
# Делаем pull из svn в ветку bzr1 — bzr1 снова синхронизирован с svn.
 +
# <strike>Делаем push из bzr2 в bzr1 — облом: ветки «разошлись»</strike>. Хорошо, делаем merge из bzr1 в bzr2. Коммитим (фиксируем).
 +
# (важно) Снова вносим изменения в bzr2, коммитим.
 +
# Теперь хотим протащить изменения из bzr2 в svn. Сначала делаем push из bzr2 в bzr1. Теперь история bzr1 идентична истории bzr2.
 +
#: <font color="red">'''И это не так тривиально, как хотелось бы!'''</font>
 +
#: Потому что теперь импортированные из svn в bzr1 ревизии ''заменяются одной merge-ревизией'', а после неё в истории появляется ревизия с модификацией, импортированная из bzr2. Исходные svn-ревизии «подцепляются» к merge-ревизии. Чтобы увидеть их, нужно сказать не просто <tt>bzr log</tt>, а <tt>bzr log -n0</tt>.
 +
# <strike>Делаем push из bzr1 в svn — облом: в bzr1 есть ревизия, «воткнутая» между уже зафиксированными в репозитории svn-ревизиями</strike>.
 +
 
 +
Две bzr-ветки здесь необязательны — просто полезны для иллюстрации танцев ревизий.
 +
 
 +
И что теперь делать?
 +
<pre>bzr: ERROR: Operation denied because it would change the mainline history.
 +
Set the append_revisions_only setting to False on branch "..." to allow
 +
the mainline to change.</pre>
 +
Bazaar предлагает нам разрешить менять местами ревизии в SVN-репозитории. Но если разрешить — всё равно обламывается (ну кто ж тебе по [[rupedia:WebDAV|WebDAV]] даст ревизии местами переставить, даа) и предлагает использовать rebase. А rebase — и это из-за пункта 7 — нам не поможет, а скажет «no revisions to rebase». А в некоторых комбинациях — вывалится со змеиным исключением.
 +
 
 +
Чтобы исправить эту ситуацию:
 +
 
 +
# Клонируем svn ещё раз в ветку bzrtmp.
 +
# Делаем merge из bzr1 в bzrtmp, коммитим. Теперь в bzrtmp последней ревизией будет merge-ревизия, к которой «подцеплены» ревизии, которые мы так жаждем протащить-таки в SVN.
 +
# Теперь мы можем сделать push из bzrtmp в svn, а потом из svn — pull во все остальные ветки, и они придут к согласованному виду…
 +
 
 +
Хотите автоматический сценарий для воспроизведения ошибки? Пожалуйста: [[bzr-rebase-dont-help.sh]]. В конце видны результаты, которые дадут команды rebase и pull. В частности, в результате последней команды rebase bzr скажет «<tt>ERROR: exceptions.AttributeError: 'NoneType' object has no attribute 'get_known_graph_ancestry'</tt>», уйдёт в Python Traceback и оттуда не вернётся.
 +
 
 +
<div style="height: 150px; overflow: scroll">{{:bzr-rebase-dont-help.sh}}</div>
 +
 
 +
А в '''Mercurial''' в точно такой же ситуации rebase замечательно работает. Пример аналогичного скрипта: [[hg-rebase-better-than-bzr-ithelps.sh]].
 +
 
 +
<div style="height: 150px; overflow: scroll">{{:hg-rebase-better-than-bzr-ithelps.sh}}</div>
 +
 
 +
=== Итоги клонирования SVN MediaWiki ===
 +
 
 +
Место на диске:
 +
* '''Git''' — 845 мб.
 +
* '''Bazaar''' — 925 мб.
 +
* '''Mercurial''' — 1633 мб.
 +
 
 +
«Нечестные» ветки (<tt>svn cp trunk/phase3 trunk/extensions branches/ветка/</tt>):
 +
* '''Mercurial''' — корректно прицепились к trunk’у (!).
 +
* '''Git''' — общая история у всех таких веток, но отдельная от trunk’а.
 +
* '''Bazaar''' — история «ДО» создания нечестных веток потеряна.
 +
 
 +
Проблемы:
 +
* '''Git''' — клонирование SVN идёт очень долго, много данных копируется повторно.
 +
* '''Mercurial''' — течёт память, проблема с <tt>rev_map</tt> в середине клонирования.
 +
* '''Bazaar''' — течёт память, проблемы с <tt>rebase</tt>.
  
 
== Управление патчами ==
 
== Управление патчами ==
  
Первое, что приходит на ум — это, конечно, аналоги [http://savannah.nongnu.org/projects/quilt quilt]'а, работающие поверх DVCS: [http://mercurial.selenic.com/wiki/MqExtension Mercurial Queues] (появился первым), [https://launchpad.net/bzr-loom Bazaar Loom], [http://www.procode.org/stgit/ StGIT]. Все они очень похожи и друг на друга, и на сам '''quilt'''.
+
Рассмотрим задачу управления набором патчей: есть некоторый проект, есть набор сторонних (например, ваших) патчей. Некоторые из них нужно накатывать в определённой последовательности, кроме того, и само ПО, и патчи не стоят на месте и могут меняться, и неплохо бы иметь также их историю. Можно, конечно, просто создать форк, но в этом случае ваша ветка, скорее всего, серьёзно разойдётся с оригинальной и «выделять» из неё отдельные фичи станет очень тяжело. Короче говоря, если кому-то хочется увидеть наглядный пример из разряда «нафига оно надо», выполните команду <tt>apt-get source mc</tt>, то есть, скачайте Debian-пакеты с исходными кодами для [http://www.midnight-commander.org/ Midnight Commander] — патчей там чуть меньше, чем 50.
 +
 
 +
=== quilt, MQ, bzr-loom, StGIT ===
 +
 
 +
Первое, что приходит на ум — это, конечно, аналоги [http://savannah.nongnu.org/projects/quilt quilt]'а, работающие поверх DVCS: [http://mercurial.selenic.com/wiki/MqExtension Mercurial Queues] (MQ), [https://launchpad.net/bzr-loom Bazaar Loom], [http://www.procode.org/stgit/ StGIT]. Все они очень похожи и друг на друга, и на сам '''quilt'''. MQ и StGIT достаточно развиты и стабильны. bzr-loom же пока находится на стадии развития.
 +
 
 +
'''quilt''' — вещь банальная, позволяющая автоматизировать тупое накатывание последовательности большого числа патчей и правку патча, который находится где-то в середине: можно откатить N верхних патчей, внести изменения в файлы и сказать <tt>refresh</tt>, и верхний на данный момент патч (то есть патч откуда-то из середины) обновится, дабы соответствовать внесённым изменениям. '''quilt''' создан на основе скриптов человека, который не использует системы контроля версий — Эндрю Мортона (Andrew Morton) — второго по значимости участника разработки ядра Linux после Линуса Торвальдса.
 +
 
 +
Из реализаций '''quilt''' поверх DVCS первым появился именно '''MQ''' и долгое время, судя по всему, был «изюминкой» Mercurial’а, хотя на самом деле совсем не идеален — идея добавления и удаления истории из/в репозиторий всё-таки странновата, а патчи не хранятся в том же репозитории, что и код — то есть, при клонировании исчезают. Этого недостатка лишены и '''bzr-loom''', и '''StGIT'''.
 +
 
 +
=== Подход «по ветке на патч» ===
 +
 
 +
Крут ли '''quilt''', нет ли — каждый решает сам для себя. Конечно, поддержке Debian-патчей на какой-нибудь Midnight Commander '''quilt''' очень… помогает. Тем не менее, с моей точки зрения, некорректно закладываться на жёстко последовательное применение всех патчей. На самом деле, такие патчи логично организовывать '''в виде графа''' (графа зависимостей). Сразу будет видно, что большинство патчей независимы друг от друга, а реально существующие зависимости окажутся на виду.
 +
 
 +
Теперь вспомним, что мы рассматриваем DVCS и что они умеют очень хорошо управлять ветками, а ревизии как раз организуются в граф — и поймём, что задачу управления патчами удобнее всего решать, заводя ''по отдельной ветке'' на каждый патч. Причём для этого есть даже расширения Mercurial '''[http://arrenbrecht.ch/mercurial/pbranch/ pbranch]''' (от patch branches) и Git '''[http://repo.or.cz/w/topgit.git TopGit]''' (от topic branches). Для Bazaar’а подобного расширения, увы, нет.
 +
 
 +
==== Mercurial pbranch ====
 +
 
 +
'''Mercurial''''овский '''pbranch''': хорошо. Умеет весьма немногое. Самые полезные команды — это <tt>'''pdiff'''</tt>, <tt>'''pgraph'''</tt> и <tt>'''pmerge'''</tt>. <tt>'''pdiff'''</tt> экспортирует текущий патч '''без его родителей''' в стандартном формате. <tt>'''pgraph'''</tt> показывает граф зависимостей патчей, из которого можно, по сути, понять, в какой последовательности их накатывать, если они сами уже экспортированы. Ну и просто вообще граф патчей — это удобно. <tt>'''pmerge'''</tt> объединяет изменения, произошедшие в зависимостях патча, в патч.
 +
 
 +
Другие команды расширения:
 +
 
 +
;pbackout: отменить текущий патч.
 +
;pmessage: посмотреть описание патча.
 +
;peditmessage: изменить описание патча.
 +
;pemail: отправить патч по почте, имхо — бесполезная вещь.
 +
;pnew: создать новый патч из ещё не закоммиченных изменений в текущей ветке.
 +
;pstatus: посмотреть статус патча (какие изменения требуются).
 +
;reapply: применить некую уже закоммиченную ревизию в ветку патча.
 +
 
 +
Чего не хватает (а хочется):
 +
 
 +
* Легко создавать патчи, зависящие от нескольких других (хотя это можно делать и вручную).
 +
* Легко поддерживать ветку, содержащую объединение всех патчей — для развёртывания.
 +
* Удалять зависимости патчей.
 +
 
 +
Первые два пункта в pbranch делаются как-то уж совсем нелогично — нужно '''ручками''' прописать зависимость патча в файл <tt>.hg/pgraph</tt> и вызвать команду <tt>hg pmerge</tt>, которая автоматически объединит все пока что не объединённые зависимости в необходимые ветки. Очень странно, что предписано общаться через <tt>.hg/pgraph</tt> при том, что этот файл вообще-то не является никаким «окончательным хранилищем» — его скорее можно классифицировать, как кэш команды <tt>pgraph</tt> — если его удалить, он будет успешно воссоздан, и при клонировании репозитория он также не копируется.
 +
 
 +
Зато, в отличие от TopGit, '''pbranch''''евый репозиторий без проблем клонируется со всеми ветками и информацией о патчах, и документация на '''pbranch''' [http://arrenbrecht.ch/mercurial/pbranch/index.htm весьма вменяема]…
 +
 
 +
===== Пример использования =====
 +
 
 +
Лично я, для управления патчами MediaWiki, входящими в состав CustIS’овской сборки MediaWiki, и хранящимися под SVN в виде набора diff-файлов, выбрал именно '''pbranch''' и следующую схему работы:
 +
* SVN-репозиторий компании содержит директорию <tt>custisinstall</tt> с конфигурационными файлами и патчами в виде diff’ов и директорию extensions с расширениями, написанными либо сильно модифицированными (по сути «форкнутыми») нами. Без веток, без меток.
 +
* Ветка <tt>default</tt> содержит импортируемый <tt>hgsubversion</tt>'ом SVN-репозиторий.
 +
* Ветка <tt>mediawiki</tt> содержит дистрибутив оригинальной MediaWiki в том виде, в каком он должен присутствовать в DocumentRoot’е, плюс расширения, слабо модифицированные (патчами) или вообще не модифицированные нами, в поддиректории <tt>extensions</tt>.
 +
* На основе ветки <tt>mediawiki</tt> создаются отдельные ветки для независимых друг от друга патчей в код MediaWiki и/или код расширений из ветки mediawiki.
 +
* Для патчей, зависящих от других, создаются ветки на основе веток этих патчей.
 +
* Ветка <tt>all</tt> содержит объединение всех веток патчей.
 +
* Ветка <tt>mergeinstall</tt> содержит объединение веток <tt>all</tt> и <tt>default</tt> и используется для развёртывания.
 +
* Все зависимости между ветками автоматически отслеживаются '''pbranch''''ем.
 +
 
 +
Таким образом, для установки нашей сборки MediaWiki через Mercurial надо только клонировать репозиторий и обновиться до ветки mergeinstall — сразу же будут получены все патчи и расширения. При этом все патчи с помощью команды <tt>hg pdiff</tt> экспортируются в diff-файлы и коммитятся в Subversion, поэтому доступен и старый метод установки — Python-скрипт, выкачивающий из svn.wikimedia.org код MediaWiki, из локального Subversion’а — код нужных расширений и патчи, и накатывающий эти патчи на MediaWiki автоматически.
 +
 
 +
==== TopGit ====
 +
 
 +
'''TopGit''': тоже весьма хорошо, даже чуть получше (версия на момент тестирования была 0.8). А в будущем станет ещё лучше — по ходу просмотра справки по некоторым командам выводится по одному-два TODO — значит, они, наверное, появятся.
 +
 
 +
Забавно, кстати, что '''TopGit''' явно написан в общем духе git-а — смесь скриптов на bash, perl и программ на C. «Головной» скрипт '''<tt>tg</tt>''' (TopGit) написан на шелле, и команды <tt>tg --help, tg -h, tg help</tt> и т. п. сначала вводят в недоумение — «<tt>no git repository here</tt>». Нет, не подумайте, справку TopGit печатать умеет, но для этого сначала надо зайти в git-репозиторий :) типа ''Чего ты спрашиваешь? У тебя интерес реальный? Ты покупать будешь или нет?..''
 +
 
 +
'''TopGit''', как уже сказано, умеет побольше '''pbranch''', в частности, умеет 1-ую и 2-ую вышеупомянутые «хотелки» — команда <tt>tg create</tt> может создать новую ветку-патч на основе множества зависимостей, автоматически объединяя их и предлагая разрешать возникающие конфликты, а с помощью <tt>tg update</tt> можно обновлять изменения в зависимостях в ветки патчей, причём рекурсивно. На самом деле и то, и другое можно сделать и в '''pbranch''', и даже вообще без него, но придётся набрать чуть больше команд.
 +
 
 +
Команды расширения:
 +
 
 +
;create: создать новую ветку-патч на основе одной или нескольких веток.
 +
;delete: удалить ветку-патч.
 +
;depend: редактировать зависимости.
 +
;export: экспортировать историю текущей ветки в виде quilt-серии или новой, «подчищенной», git-ветки.
 +
;import: импортировать коммиты в виде патчей.
 +
;info: аналог <tt>hg pstatus</tt>, выводит состояние ветки — сколько, чего и откуда надо обновить и т. п.
 +
;mail: отправка патчей по почте.
 +
;patch: аналоги <tt>hg diff</tt>, выводит текущий патч в виде diff + комментарий вверху.
 +
;push: отправляет изменения из tg-шной ветки.
 +
;remote: перевести внешние (remote-tracking) ветки под управление tg.
 +
;summary: выводит список патчей со статусами и/или граф зависимостей через [[lib:Graphviz|Graphviz]]. В ASCII-виде этот граф, к сожалению, не рисуется.
 +
;update: см.выше.
 +
 
 +
Чего не хватает (а хочется):
 +
 
 +
* Возможности клонирования репозитория с учётом TopGit’а.
 +
*: С клонированием Git, как известно, выпендривается своими «[http://www.gitready.com/beginner/2009/03/09/remote-tracking-branches.html Remote Tracking Branches]», а TopGit, к сожалению, не полностью основан на стандартных механизмах Git. Поэтому с помощью команд <tt>git clone</tt> или <tt>git pull</tt> скопировать репозиторий с сохранением всей информации TopGit невозможно. Между прочим, весьма серьёзный недостаток.
 +
* Удалять зависимости патчей.
 +
*: То есть по-настоящему удалять зависимости, а не просто отменять изменения. Но это в TODO перечислено и, вероятно, будет реализовано.
 +
 
 +
== Схема управления рабочими копиями ==
 +
 
 +
Что нам предлагают централизованные системы контроля версий? Некий «хардкод»: репозиторий ровно '''один''', рабочих копий много. Что нам предлагают DVCS? Теоретически, полный беспредел, то есть свободу.
 +
 
 +
На практике — не совсем полную.
 +
 
 +
'''Mercurial''' говорит нам: на ровно 1 рабочую копию ровно 1 репозиторий, и иначе быть не может. Это и удобно — сказал <tt>hg up ветка_такая_то</tt>, пара файлов поменялась, и опа — ты уже в другой ветке. Это и неудобно — чтобы положить на диск одновременно две разных ветки, нужно обязательно клонировать репозиторий (поддержки checkout нет).
 +
 
 +
С '''Git''''ом ситуация почти такая же, как и с Mercurial’ом. 1 рабочая копия, 1 репозиторий. Хотя при клонировании данные репозитория можно и не копировать, задавая опцию <tt>--shared</tt>, но это скорее похоже на Bazaar’овские [http://doc.bazaar-vcs.org/latest/en/user-guide/stacked.html Stacked Branches], чем на Lightweight Checkout. Идея checkout’ов, или лёгких рабочих копий (или «идея .gitlink») [http://git.or.cz/gitwiki/SoC2007Ideas высказана для GSoC-2007], однако (пока?) так и не реализована.
 +
 
 +
Управление ветками в '''Bazaar''' вначале было гораздо хуже: на 1 ветку ровно 1 рабочая копия и ровно 1 репозиторий. Однако потом появилось уникальное: <tt>checkout</tt>'ы и [http://bazaar-vcs.org/SharedRepositoryTutorial Shared Repository], имеющий опцию <tt>--no-trees</tt>. Таким образом, стало возможно иметь сколько угодно репозиториев и сколько угодно рабочих копий. В Git и Mercurial этого нет. Переключать легковесную рабочую копию с ветки на ветку Bazaar тоже умеет — командой <tt>switch</tt>, для переключения ветки её надо превратить в легковесную рабочую копию командой <tt>bind</tt>.
 +
 
 +
Тут надо сделать небольшое отступление: Bazaar имеет несколько идеологических моментов. Первый — это идея, гласящая, что '''''директории — не контейнеры веток, а сами ветки'''''. В одной директории, содержащей рабочую копию, не может содержаться нескольких веток. Существуют ещё [http://bazaar-vcs.org/SharedRepositoryTutorial разделяемые репозитории], но они сами по себе не содержат рабочей копии, а содержат директории, которые могут содержать рабочие копии.
 +
 
 +
Идея, с моей точки зрения, далеко не лучшая. Например, из-за неё Bazaar не может, как Git и Mercurial, переключиться на произвольную ревизию и по коммиту автоматически ответвиться в сторону. В Bazaar можно только сделать <tt>revert</tt> («откат») к определённой версии. Рабочие файлы при этом изменятся, а «состояние» рабочей копии — нет. Считается, что рабочая копия ''всегда'' содержит последнюю сохранённую ревизию.
 +
 
 +
Вторая идея — это '''''идея mainline''''', то есть, идея поддержки «центральной» ветки разработки на уровне системы контроля версий. С хвалебной точки зрения про mainline можно почитать в блоге «Базарный день» (части [http://bzr-day.blogspot.com/2009/09/mainline-1.html 1], [http://bzr-day.blogspot.com/2009/09/mainline-2.html 2], и будут ещё). Я же выступлю с критической точки зрения — никакая это не гениальная практика, а просто ещё один хардкод, который можно выразить простыми словами: после синхронизации рабочих копий ''HEAD-ревизия'' (ревизия, не имеющая дочерних — в терминах Mercurial), ''всегда ровно одна''. Все другие ''HEAD’ы'' физически находятся в других директориях (= ветках), ещё не синхронизированных с данной. То есть, Bazaar заставляет пользователя объединять историю при каждой синхронизации с другой веткой.
 +
 
 +
Из ''идеи mainline'' сразу следует определённое неудобство: при синхронизации '''меняется история''' веток (даже твоей, любимой, неприкосновенной) — ревизии, внесённые ранее локально, могут быть перемещены в «под-узлы» ревизии-объединения, которую в свою ветку внесла удалённая сторона. История меняется так, чтобы в конечном счёте история всех веток выглядела одинаково. То есть, «история истории» теряется, а ещё из перестановок ревизий в истории сразу следуют проблемы работы с Subversion.
 +
 
 +
Короче говоря, да, это просто очередной «хардкод». То ли — рассчитанный на то, что если дать людям свободу, они не будут в состоянии достаточно часто объединяться с основной веткой, то ли — просто унаследованный от Arch’а ([[wikipedia:GNU Arch|GNU Arch]] — прародитель Bazaar’а).
  
'''quilt''' — вещь банальная, позволяющая автоматизировать тупое накатывание последовательности большого числа патчей и правку патча, который находится где-то в середине.
+
И если уж зашла речь о недостатках '''Bazaar'''’а, ещё следует отметить, что он не умеет показывать консольные ASCII-псевдографические графы ревизий. '''Git''' и '''Mercurial''' умеют — и это очень удобно. А разработчики Bazaar считают их отсутствие фичей — ревизии, типа, сортируются, зачем графы? ''(и правда, зачем? стройте графы сами, в голове! как не умеете?!!)''
  
 
[[Категория:Разработка]]
 
[[Категория:Разработка]]
 +
[[Категория:Статьи]]

Текущая версия на 18:47, 2 августа 2010

Данная статья является очередным сравнением популярных DVCS — Mercurial, Git и Bazaar, с точки зрения нескольких нетривиальных задач. Ссылка: http://yourcmc.ru/DVCS_YAC («Yet Another Comparison»).

Хотите лаконичный ответ в стиле «ИМХО» на вопрос — кого выбрать из трёх? Ну пожалуйста. Выбирайте Mercurial. А не Git (безумный конгломерат) и не Bazaar (пионерское поделие, унаследованное от Arch’а).

Работа с SVN (миграция и синхронизация)

Почему-то, говоря о недостатках DVCS, никогда не говорят о следующем не очень удобном при переходе с централизованных систем поведении — DVCS отслеживают версию цельного репозитория, а не отдельных файлов или директорий. Кроме проблем, которые это создаёт при желании извлечь часть репозитория для работы, а не обязательно весь, это ведёт к тому, что если две ветки «расходятся», становится обязательным слияние, даже если конфликтов в изменениях не было и даже если вообще менялись разные файлы. А централизованным хоть бы хны — svn up и живи себе дальше.

Что ещё любопытно — с Subversion-репозиториями DVCS работают, как правило, быстрее самого Subversion, и быстрее всех работает Bazaar. То есть, в принципе, можно вообще жить с Subversion-сервером и DVCS-клиентом (если, конечно мириться с постоянными слияниями и rebase’ами).

Во всех трёх DVCS при работе с Subversion есть следующее ограничение: невозможно связывать один и тот же репозиторий с несколькими Subversion-источниками — ни в пределах разных веток, ни в пределах разных каталогов. Mercurial честно говорит «repository unrelated», Bazaar — «branches have no common ancestor», Git пытается что-то родить, но запутывается в одинаковых коммитах, имеющих несколько SVN-родителей. А ведь было бы неплохо иметь такие возможности…

Mercurial

Mercurial: почти отлично (5-), есть 3 расширения различной степени глюкавости — hgsubversion, hgsvn, convert, из которых позволяющих работать с Subversion тем или иным образом, и не совместимых друг с другом.

Самое вменяемое из этих расширений — hgsubversion, и хотя оно ещё сыровато, разработка ведётся активно, в мэйллисте постоянно происходит некая жизнь, а автор отзывчив. Вместе с Mercurial’ом оно не распространяется, нужно ставить отдельно. Тестировал я ревизию 500 из http://bitbucket.org/durin42/hgsubversion.

Имеет основной необходимый функционал — можно делать и push, и pull в/из Subversion, можно клонировать SVN-репозиторий с сохранением веток и меток (правда, обязательно стандартное их расположение в корневых поддиректориях /trunk, /branches, /tags), эти два метода совместимы, rebase работает, граф ветвлений сохраняется. Очень крут тот факт, что ветка, которая создавалась неполным копированием trunk'а, то есть, копированием некоторых его поддиректорий, успешно подцепилась в нужное место графа ветвлений. Ни Bazaar, ни Git этого не смогли. Поддержки svn:mergeinfo пока что нет, хотя она близится.

Минус расширения заключается в сырости — за время тестирования я нашёл уже несколько неприятных багов:

  • push из Mercurial-клона репозитория без веток и меток (имеющего вид просто одного каталога) в Subversion не работал вообще — обсуждение в группе hgsubversion. К счастью, меня пропёрло, я потратил час времени и баг этот пофиксил собственноручно. Фикс очень простой, на две строчки, и он уже находится в репозитории в виде ревизии r501 — за отзывчивость автору зачёт.
  • Не работает клонирование репозиториев по протоколу svnserve (svn://host:port/url). Очень неприятно, особенно, если вы предпочитаете именно svnserve по причине его производительности. Уже давно работает.
  • Течёт память, что при клонировании толстых SVN-репозиториев (например, репозитория MediaWiki) приводит к ошибкам Abort: out of memory, то есть нехватки памяти. Обходится методом клонирования репозитория по частям, делая hg init, а потом несколько hg pull до победного конца. Причина в дырявости SWIG SVN Python-библиотеки. Забавно, что аналогичная SWIG Perl-библиотека такой текучестью, видимо, не страдает, так как Git этот репозиторий глотал (причём по 10 раз за раз) и не давился. Perl, говорите, течёт? Ну хи-хи, хи-хи. Кстати, Bazaar течёт точно так же, как hgsubversion.
  • На ревизии 43380 MediaWiki hgsubversion свалился с ошибкой abort: phase3/includes/ConfigurationCache.php@a1335975fba6: not found in manifest!. обсуждение в группе hgsubversion. Причина, вероятно, кроется где-то в районе обработки перемещений каталогов Subversion. Я обошёл данный баг убиением части файла .hg/svn/rev_map (можно было и весь убить) и перезапуском hg pull. Однако в итоге появилось две ветки вместо одной: on_wiki-configuration и on_wiki-configuration/phase3 (подкаталог ветки…)

Итог клонирования MediaWiki-репозитория: успешно с небольшими танцами с бубном — склонировал за несколько перезапусков pull'а и с уборкой rev_map в середине, все ветки на месте, только одна, перемещённая потом в подкаталог себя, продублировалась: «on-wiki_configuration» и «on-wiki_configuration/phase3». Репозиторий (то есть директория .hg) занимает на диске 1.63 Гб.

Остальные два экстенжна «нинужны»: hgsvn — нечто более старое, работает сбоку от общего механизма, тоже позволяет делать push и pull, но не клонирует весь репозиторий, а только извлекает (checkout’ит) последнюю версию, чтобы далее можно было использовать Subversion и Mercurial вместе. Ну и конечно, оно не совместимо с hgsubversion. convert же предназначен для конвертации истории проекта из нескольких различных систем контроля версий в Mercurial, ни черта не совместим ни с hgsvn, ни с hgsubversion и не сохраняет граф ветвлений. Зато, правда, поддерживает возможность использования других имён поддиректорий trunk/branches/tags.

Git

Git: отлично (5-)! git-svn встроен в git и умеет всё, что нужно: клонирование, синхронизация (fetch, pull или merge), фиксация изменений в Subversion-репозиториях (dcommit) и rebase работают с сохранением веток и меток, причём стандартная схема их именования trunk/branches/tags не является обязательной. Ветки SVN импортируются как Remote Tracking Branches. Можно начинать историю ветки в git’е не с сотворения миров, а с любого момента. Также git-svn поддерживает подключаемые внешние репозитории Subversion (т. н. Externals). Граф ветвлений сохраняется, хоть и не так круто, как в Mercurial’е. В общем, можно сказать, что функционал полон. git svn fetch успешно возобновляет операцию клонирование в случае её падения посредине.

А теперь поговорим о неприятном: полное клонирование SVN-репозитория MediaWiki заняло более 3 дней, хотя должно по логике занимать часов 6-8. Причина в том, что Git время от времени не понимает, откуда ответвилась ветка, лезет в глубокую историю и начинает сканировать по новой уже просканированные ревизии. Таким образом, некоторые (а конкректно — ветвлённые не из самого trunk’а, а из его подпапок) ветки не получаются связанными с историей trunk’а, старые ревизии для них дублируются. Справедливости ради надо отметить, что для двух таких веток старая история объединяется, а физически никаких дубликатов не хранится. Вообще в итоге Git-клон репозитория MediaWiki (директория .git) занял всего 845 Мб. Кроме того, в процессе клонирования память не текла вообще, на всё про всё уходило мегабайт 80 памяти.

Кроме того, как всегда с Git’ом, есть различные нетривиальности, что-то бывает нужно настроить (гитара настроена? нас двое, на! играй, на!), синтаксис не совмещён с обычными командами Git — то есть например для синхронизации копии с Subversion нужно сказать не git pull, а git svn fetch.

Bazaar

Bazaar: хорошо. Можно импортировать репозиторий командами svn-import или branch, можно делать push и pull в/из Subversion. При импорте можно сохранить все ветки и метки в одном хранилище (если использовать Shared Repository), для этого также требуются стандартные названия trunk/branches/tags. Граф ветвлений Subversion сохраняется, но также не отражает копирования подпапок. Также существует несколько других расширений для импорта Subversion в Bazaar, но они хуже.

Тест на клонирование репозитория MediaWiki прошёл почти без проблем, но почему-то вместо 59589 ревизий копировал 85567 ревизий, то есть откуда-то придумал лишних 25978. Возможно, это были ветки — корректного присоединения их истории к trunk'у обнаружено после клонирования не было вообще (!). Причина, скорее всего, в том, что ветки там в основном не совсем честные (как уже было упомянуто в секции про Git) — они создавались не командой svn cp trunk branches/ветка, а создавался каталог ветки и в него копировались каталоги phase3 и extensions из trunk’а. В итоге изменения, происходившие «до» создания таких «нечестных» веток, в самих ветках вообще не отражены.

Память течёт точно так же, как и Mercurial’е и, очевидно, по той же причине (SWIG), и перезапуски процесса клонирования по причине ухода машины в глубокий своп также были необходимы. После клонирования репозиторий изначально занимал аж 16 Гб, однако при ближайшем рассмотрении стало ясно, что большую часть этого пространства занимали каталоги obsolete_packs и upload со старыми упакованными данными репозитория и некими временными файлами соответственно. Такая же ситуация осталась и после вызова bzr pack, о чём есть целый баг: Bug 304320 — bzr pack should (optionally?) delete obsolete packs. Все эти лишние данные, по-видимому, можно смело удалить ручками — и обнаружить, что репозиторий занимает 925 Мб, то есть, лишь ненамного больше, чем в случае Git, и примерно в 1.75 раза меньше, чем в Mercurial’е. Правда, по ощущению чтение репозитория происходит медленнее и чем в Git, и чем в Mercurial. При выводе истории изменений по одной и той же ветке Git не напрягается вообще, Mercurial напрягается немного, а Bazaar напрягается ощутимо. При том, что Git и Mercurial выводят не только историю изменений самой ветки, а также историю изменений, происходивших до создания ветки (помним, что ветки нечестные). Правда, я не исключаю, что под виндой ситуация с производительностью может быть совсем другая.

А теперь снова о неприятном. Есть у Базаара одна небольшая, зато возникающая в достаточно простом случае, проблемка. То есть пусть бы она и появлялась, но очень редко — это можно терпеть. Но тут ситуация не так чтобы редкая.

Так вот. Всё начинается с банального — мы сделали изменение в Bazaar-клонированном SVN-репозитории, а кто-то тем временем сделал изменение в самом SVN-репозитории… Причём не важно, в каких файлах: даже если конфликтов в наших изменениях нет, проблема возникает всё равно (помним о том, что DVCS управляют «цельным» репозиторием). Что нам теперь остаётся делать? Ну конечно, мержиться. И merge успешно проходит. Но в случае Bazaar помните — после этого merge нужно сразу делать rebase и push в Subversion! Иначе, если до этого сделать ещё хотя бы один commit в Bazaar, push не пройдёт (он и не должен), а rebase не поможет или вывалится с исключением.

Хотите подробнее? Рассмотрим следующую последовательность действий:

  1. Клонируем svn в ветку bzr1.
  2. Клонируем bzr1 в bzr2.
  3. Вносим изменения в bzr2, коммитим.
  4. Вносим другие изменения в svn, коммитим. Лучше несколько раз (создаём несколько ревизий).
  5. Делаем pull из svn в ветку bzr1 — bzr1 снова синхронизирован с svn.
  6. Делаем push из bzr2 в bzr1 — облом: ветки «разошлись». Хорошо, делаем merge из bzr1 в bzr2. Коммитим (фиксируем).
  7. (важно) Снова вносим изменения в bzr2, коммитим.
  8. Теперь хотим протащить изменения из bzr2 в svn. Сначала делаем push из bzr2 в bzr1. Теперь история bzr1 идентична истории bzr2.
    И это не так тривиально, как хотелось бы!
    Потому что теперь импортированные из svn в bzr1 ревизии заменяются одной merge-ревизией, а после неё в истории появляется ревизия с модификацией, импортированная из bzr2. Исходные svn-ревизии «подцепляются» к merge-ревизии. Чтобы увидеть их, нужно сказать не просто bzr log, а bzr log -n0.
  9. Делаем push из bzr1 в svn — облом: в bzr1 есть ревизия, «воткнутая» между уже зафиксированными в репозитории svn-ревизиями.

Две bzr-ветки здесь необязательны — просто полезны для иллюстрации танцев ревизий.

И что теперь делать?

bzr: ERROR: Operation denied because it would change the mainline history.
Set the append_revisions_only setting to False on branch "..." to allow
the mainline to change.

Bazaar предлагает нам разрешить менять местами ревизии в SVN-репозитории. Но если разрешить — всё равно обламывается (ну кто ж тебе по WebDAV даст ревизии местами переставить, даа) и предлагает использовать rebase. А rebase — и это из-за пункта 7 — нам не поможет, а скажет «no revisions to rebase». А в некоторых комбинациях — вывалится со змеиным исключением.

Чтобы исправить эту ситуацию:

  1. Клонируем svn ещё раз в ветку bzrtmp.
  2. Делаем merge из bzr1 в bzrtmp, коммитим. Теперь в bzrtmp последней ревизией будет merge-ревизия, к которой «подцеплены» ревизии, которые мы так жаждем протащить-таки в SVN.
  3. Теперь мы можем сделать push из bzrtmp в svn, а потом из svn — pull во все остальные ветки, и они придут к согласованному виду…

Хотите автоматический сценарий для воспроизведения ошибки? Пожалуйста: bzr-rebase-dont-help.sh. В конце видны результаты, которые дадут команды rebase и pull. В частности, в результате последней команды rebase bzr скажет «ERROR: exceptions.AttributeError: 'NoneType' object has no attribute 'get_known_graph_ancestry'», уйдёт в Python Traceback и оттуда не вернётся.

#!/bin/sh
 
svnadmin create test
svn co file://`pwd`/test test-co
cd test-co
echo "helloworld" > a
echo "diepeople" > b
svn add a b
svn ci -m "add a+b to svn"
cd ..
bzr branch `pwd`/test test-bzr
cd test-co
echo "tretiynah" > c
svn add c
svn ci -m "add c to svn"
cd ..
cd test-bzr
echo "rozoviyslonik" > d
bzr add d
bzr ci -m "add d to bzr"
bzr merge
bzr ci -m "merge to bzr"
echo "1nah" > a
bzr ci -m "change a in bzr"
bzr push `pwd`/../test # ФИГВАМ! Operation denied because it would change the mainline history. (логично)
bzr rebase -r 5 # ФИГВАМ! No revisions to rebase.
bzr rebase --onto=2.1.1 # ФИГВАМ! ERROR: exceptions.AttributeError: 'NoneType' object has no attribute 'get_known_graph_ancestry' а дальше идёт Python traceback
bzr rebase -r 2 # Проходит, но изменения, внесённые в bzr, пропадают бесследно и проносить в svn становится нечего.

А в Mercurial в точно такой же ситуации rebase замечательно работает. Пример аналогичного скрипта: hg-rebase-better-than-bzr-ithelps.sh.

#!/bin/sh
 
svnadmin create test
svn co file://`pwd`/test test-co
cd test-co
echo "helloworld" > a
echo "diepeople" > b
svn add a b
svn ci -m "add a+b to svn"
cd ..
hg clone file://`pwd`/test test-hg
cd test-co
echo "tretiynah" > c
svn add c
svn ci -m "add c to svn"
cd ..
cd test-hg
echo "rozoviyslonik" > d
hg add d
hg ci -m "add d to hg"
hg pull
hg merge
hg ci -m "merge to hg"
echo "1nah" > a
hg ci -m "change a in hg"
#hg push file://`pwd`/../test # ФИГВАМ! Sorry, can't find svn parent of a merge revision.
echo "5nah" > e
hg add e
hg ci -m "add e to hg"
hg rebase -d 2
hg push

Итоги клонирования SVN MediaWiki

Место на диске:

  • Git — 845 мб.
  • Bazaar — 925 мб.
  • Mercurial — 1633 мб.

«Нечестные» ветки (svn cp trunk/phase3 trunk/extensions branches/ветка/):

  • Mercurial — корректно прицепились к trunk’у (!).
  • Git — общая история у всех таких веток, но отдельная от trunk’а.
  • Bazaar — история «ДО» создания нечестных веток потеряна.

Проблемы:

  • Git — клонирование SVN идёт очень долго, много данных копируется повторно.
  • Mercurial — течёт память, проблема с rev_map в середине клонирования.
  • Bazaar — течёт память, проблемы с rebase.

Управление патчами

Рассмотрим задачу управления набором патчей: есть некоторый проект, есть набор сторонних (например, ваших) патчей. Некоторые из них нужно накатывать в определённой последовательности, кроме того, и само ПО, и патчи не стоят на месте и могут меняться, и неплохо бы иметь также их историю. Можно, конечно, просто создать форк, но в этом случае ваша ветка, скорее всего, серьёзно разойдётся с оригинальной и «выделять» из неё отдельные фичи станет очень тяжело. Короче говоря, если кому-то хочется увидеть наглядный пример из разряда «нафига оно надо», выполните команду apt-get source mc, то есть, скачайте Debian-пакеты с исходными кодами для Midnight Commander — патчей там чуть меньше, чем 50.

quilt, MQ, bzr-loom, StGIT

Первое, что приходит на ум — это, конечно, аналоги quilt'а, работающие поверх DVCS: Mercurial Queues (MQ), Bazaar Loom, StGIT. Все они очень похожи и друг на друга, и на сам quilt. MQ и StGIT достаточно развиты и стабильны. bzr-loom же пока находится на стадии развития.

quilt — вещь банальная, позволяющая автоматизировать тупое накатывание последовательности большого числа патчей и правку патча, который находится где-то в середине: можно откатить N верхних патчей, внести изменения в файлы и сказать refresh, и верхний на данный момент патч (то есть патч откуда-то из середины) обновится, дабы соответствовать внесённым изменениям. quilt создан на основе скриптов человека, который не использует системы контроля версий — Эндрю Мортона (Andrew Morton) — второго по значимости участника разработки ядра Linux после Линуса Торвальдса.

Из реализаций quilt поверх DVCS первым появился именно MQ и долгое время, судя по всему, был «изюминкой» Mercurial’а, хотя на самом деле совсем не идеален — идея добавления и удаления истории из/в репозиторий всё-таки странновата, а патчи не хранятся в том же репозитории, что и код — то есть, при клонировании исчезают. Этого недостатка лишены и bzr-loom, и StGIT.

Подход «по ветке на патч»

Крут ли quilt, нет ли — каждый решает сам для себя. Конечно, поддержке Debian-патчей на какой-нибудь Midnight Commander quilt очень… помогает. Тем не менее, с моей точки зрения, некорректно закладываться на жёстко последовательное применение всех патчей. На самом деле, такие патчи логично организовывать в виде графа (графа зависимостей). Сразу будет видно, что большинство патчей независимы друг от друга, а реально существующие зависимости окажутся на виду.

Теперь вспомним, что мы рассматриваем DVCS и что они умеют очень хорошо управлять ветками, а ревизии как раз организуются в граф — и поймём, что задачу управления патчами удобнее всего решать, заводя по отдельной ветке на каждый патч. Причём для этого есть даже расширения Mercurial pbranch (от patch branches) и Git TopGit (от topic branches). Для Bazaar’а подобного расширения, увы, нет.

Mercurial pbranch

Mercurial'овский pbranch: хорошо. Умеет весьма немногое. Самые полезные команды — это pdiff, pgraph и pmerge. pdiff экспортирует текущий патч без его родителей в стандартном формате. pgraph показывает граф зависимостей патчей, из которого можно, по сути, понять, в какой последовательности их накатывать, если они сами уже экспортированы. Ну и просто вообще граф патчей — это удобно. pmerge объединяет изменения, произошедшие в зависимостях патча, в патч.

Другие команды расширения:

pbackout
отменить текущий патч.
pmessage
посмотреть описание патча.
peditmessage
изменить описание патча.
pemail
отправить патч по почте, имхо — бесполезная вещь.
pnew
создать новый патч из ещё не закоммиченных изменений в текущей ветке.
pstatus
посмотреть статус патча (какие изменения требуются).
reapply
применить некую уже закоммиченную ревизию в ветку патча.

Чего не хватает (а хочется):

  • Легко создавать патчи, зависящие от нескольких других (хотя это можно делать и вручную).
  • Легко поддерживать ветку, содержащую объединение всех патчей — для развёртывания.
  • Удалять зависимости патчей.

Первые два пункта в pbranch делаются как-то уж совсем нелогично — нужно ручками прописать зависимость патча в файл .hg/pgraph и вызвать команду hg pmerge, которая автоматически объединит все пока что не объединённые зависимости в необходимые ветки. Очень странно, что предписано общаться через .hg/pgraph при том, что этот файл вообще-то не является никаким «окончательным хранилищем» — его скорее можно классифицировать, как кэш команды pgraph — если его удалить, он будет успешно воссоздан, и при клонировании репозитория он также не копируется.

Зато, в отличие от TopGit, pbranch'евый репозиторий без проблем клонируется со всеми ветками и информацией о патчах, и документация на pbranch весьма вменяема

Пример использования

Лично я, для управления патчами MediaWiki, входящими в состав CustIS’овской сборки MediaWiki, и хранящимися под SVN в виде набора diff-файлов, выбрал именно pbranch и следующую схему работы:

  • SVN-репозиторий компании содержит директорию custisinstall с конфигурационными файлами и патчами в виде diff’ов и директорию extensions с расширениями, написанными либо сильно модифицированными (по сути «форкнутыми») нами. Без веток, без меток.
  • Ветка default содержит импортируемый hgsubversion'ом SVN-репозиторий.
  • Ветка mediawiki содержит дистрибутив оригинальной MediaWiki в том виде, в каком он должен присутствовать в DocumentRoot’е, плюс расширения, слабо модифицированные (патчами) или вообще не модифицированные нами, в поддиректории extensions.
  • На основе ветки mediawiki создаются отдельные ветки для независимых друг от друга патчей в код MediaWiki и/или код расширений из ветки mediawiki.
  • Для патчей, зависящих от других, создаются ветки на основе веток этих патчей.
  • Ветка all содержит объединение всех веток патчей.
  • Ветка mergeinstall содержит объединение веток all и default и используется для развёртывания.
  • Все зависимости между ветками автоматически отслеживаются pbranch'ем.

Таким образом, для установки нашей сборки MediaWiki через Mercurial надо только клонировать репозиторий и обновиться до ветки mergeinstall — сразу же будут получены все патчи и расширения. При этом все патчи с помощью команды hg pdiff экспортируются в diff-файлы и коммитятся в Subversion, поэтому доступен и старый метод установки — Python-скрипт, выкачивающий из svn.wikimedia.org код MediaWiki, из локального Subversion’а — код нужных расширений и патчи, и накатывающий эти патчи на MediaWiki автоматически.

TopGit

TopGit: тоже весьма хорошо, даже чуть получше (версия на момент тестирования была 0.8). А в будущем станет ещё лучше — по ходу просмотра справки по некоторым командам выводится по одному-два TODO — значит, они, наверное, появятся.

Забавно, кстати, что TopGit явно написан в общем духе git-а — смесь скриптов на bash, perl и программ на C. «Головной» скрипт tg (TopGit) написан на шелле, и команды tg --help, tg -h, tg help и т. п. сначала вводят в недоумение — «no git repository here». Нет, не подумайте, справку TopGit печатать умеет, но для этого сначала надо зайти в git-репозиторий :) типа Чего ты спрашиваешь? У тебя интерес реальный? Ты покупать будешь или нет?..

TopGit, как уже сказано, умеет побольше pbranch, в частности, умеет 1-ую и 2-ую вышеупомянутые «хотелки» — команда tg create может создать новую ветку-патч на основе множества зависимостей, автоматически объединяя их и предлагая разрешать возникающие конфликты, а с помощью tg update можно обновлять изменения в зависимостях в ветки патчей, причём рекурсивно. На самом деле и то, и другое можно сделать и в pbranch, и даже вообще без него, но придётся набрать чуть больше команд.

Команды расширения:

create
создать новую ветку-патч на основе одной или нескольких веток.
delete
удалить ветку-патч.
depend
редактировать зависимости.
export
экспортировать историю текущей ветки в виде quilt-серии или новой, «подчищенной», git-ветки.
import
импортировать коммиты в виде патчей.
info
аналог hg pstatus, выводит состояние ветки — сколько, чего и откуда надо обновить и т. п.
mail
отправка патчей по почте.
patch
аналоги hg diff, выводит текущий патч в виде diff + комментарий вверху.
push
отправляет изменения из tg-шной ветки.
remote
перевести внешние (remote-tracking) ветки под управление tg.
summary
выводит список патчей со статусами и/или граф зависимостей через Graphviz. В ASCII-виде этот граф, к сожалению, не рисуется.
update
см.выше.

Чего не хватает (а хочется):

  • Возможности клонирования репозитория с учётом TopGit’а.
    С клонированием Git, как известно, выпендривается своими «Remote Tracking Branches», а TopGit, к сожалению, не полностью основан на стандартных механизмах Git. Поэтому с помощью команд git clone или git pull скопировать репозиторий с сохранением всей информации TopGit невозможно. Между прочим, весьма серьёзный недостаток.
  • Удалять зависимости патчей.
    То есть по-настоящему удалять зависимости, а не просто отменять изменения. Но это в TODO перечислено и, вероятно, будет реализовано.

Схема управления рабочими копиями

Что нам предлагают централизованные системы контроля версий? Некий «хардкод»: репозиторий ровно один, рабочих копий много. Что нам предлагают DVCS? Теоретически, полный беспредел, то есть свободу.

На практике — не совсем полную.

Mercurial говорит нам: на ровно 1 рабочую копию ровно 1 репозиторий, и иначе быть не может. Это и удобно — сказал hg up ветка_такая_то, пара файлов поменялась, и опа — ты уже в другой ветке. Это и неудобно — чтобы положить на диск одновременно две разных ветки, нужно обязательно клонировать репозиторий (поддержки checkout нет).

С Git'ом ситуация почти такая же, как и с Mercurial’ом. 1 рабочая копия, 1 репозиторий. Хотя при клонировании данные репозитория можно и не копировать, задавая опцию --shared, но это скорее похоже на Bazaar’овские Stacked Branches, чем на Lightweight Checkout. Идея checkout’ов, или лёгких рабочих копий (или «идея .gitlink») высказана для GSoC-2007, однако (пока?) так и не реализована.

Управление ветками в Bazaar вначале было гораздо хуже: на 1 ветку ровно 1 рабочая копия и ровно 1 репозиторий. Однако потом появилось уникальное: checkout'ы и Shared Repository, имеющий опцию --no-trees. Таким образом, стало возможно иметь сколько угодно репозиториев и сколько угодно рабочих копий. В Git и Mercurial этого нет. Переключать легковесную рабочую копию с ветки на ветку Bazaar тоже умеет — командой switch, для переключения ветки её надо превратить в легковесную рабочую копию командой bind.

Тут надо сделать небольшое отступление: Bazaar имеет несколько идеологических моментов. Первый — это идея, гласящая, что директории — не контейнеры веток, а сами ветки. В одной директории, содержащей рабочую копию, не может содержаться нескольких веток. Существуют ещё разделяемые репозитории, но они сами по себе не содержат рабочей копии, а содержат директории, которые могут содержать рабочие копии.

Идея, с моей точки зрения, далеко не лучшая. Например, из-за неё Bazaar не может, как Git и Mercurial, переключиться на произвольную ревизию и по коммиту автоматически ответвиться в сторону. В Bazaar можно только сделать revert («откат») к определённой версии. Рабочие файлы при этом изменятся, а «состояние» рабочей копии — нет. Считается, что рабочая копия всегда содержит последнюю сохранённую ревизию.

Вторая идея — это идея mainline, то есть, идея поддержки «центральной» ветки разработки на уровне системы контроля версий. С хвалебной точки зрения про mainline можно почитать в блоге «Базарный день» (части 1, 2, и будут ещё). Я же выступлю с критической точки зрения — никакая это не гениальная практика, а просто ещё один хардкод, который можно выразить простыми словами: после синхронизации рабочих копий HEAD-ревизия (ревизия, не имеющая дочерних — в терминах Mercurial), всегда ровно одна. Все другие HEAD’ы физически находятся в других директориях (= ветках), ещё не синхронизированных с данной. То есть, Bazaar заставляет пользователя объединять историю при каждой синхронизации с другой веткой.

Из идеи mainline сразу следует определённое неудобство: при синхронизации меняется история веток (даже твоей, любимой, неприкосновенной) — ревизии, внесённые ранее локально, могут быть перемещены в «под-узлы» ревизии-объединения, которую в свою ветку внесла удалённая сторона. История меняется так, чтобы в конечном счёте история всех веток выглядела одинаково. То есть, «история истории» теряется, а ещё из перестановок ревизий в истории сразу следуют проблемы работы с Subversion.

Короче говоря, да, это просто очередной «хардкод». То ли — рассчитанный на то, что если дать людям свободу, они не будут в состоянии достаточно часто объединяться с основной веткой, то ли — просто унаследованный от Arch’а (GNU Arch — прародитель Bazaar’а).

И если уж зашла речь о недостатках Bazaar’а, ещё следует отметить, что он не умеет показывать консольные ASCII-псевдографические графы ревизий. Git и Mercurial умеют — и это очень удобно. А разработчики Bazaar считают их отсутствие фичей — ревизии, типа, сортируются, зачем графы? (и правда, зачем? стройте графы сами, в голове! как не умеете?!!)