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

Материал из YourcmcWiki
Перейти к: навигация, поиск
м (Mercurial)
м
 
(не показано 10 промежуточных версий этого же участника)
Строка 1: Строка 1:
Данная статья является очередным сравнением популярных [[wikipedia:Distributed revision control|DVCS]] — [http://selenic.com/mercurial/ Mercurial], [http://git-scm.com/ Git] и [http://bazaar-vcs.org/ Bazaar], с точки зрения нескольких нетривиальных задач.
+
Данная статья является очередным сравнением популярных [[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 (миграция и синхронизация) ==
Строка 11: Строка 13:
 
=== Mercurial ===
 
=== Mercurial ===
  
'''Mercurial''': было бы отлично, но есть баги… Есть несколько расширений — [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.
+
'''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>, и <tt>pull</tt> в/из Subversion, можно клонировать SVN-репозиторий с сохранением веток и меток (правда, обязательно стандартное их расположение в корневых поддиректориях <tt>/trunk</tt>, <tt>/branches</tt>, <tt>/tags</tt>), эти два метода совместимы, <tt>rebase</tt> работает, граф ветвлений сохраняется. Очень крут тот факт, что ветка, которая создавалась неполным копированием <tt>trunk</tt>'а, то есть, копированием некоторых его поддиректорий, успешно подцепилась в нужное место графа ветвлений. Ни Bazaar, ни Git этого не смогли. Поддержки svn:mergeinfo пока что нет, хотя она близится.
Строка 17: Строка 21:
 
Минус расширения заключается в сырости — за время тестирования я нашёл уже несколько неприятных багов:
 
Минус расширения заключается в сырости — за время тестирования я нашёл уже несколько неприятных багов:
 
* <tt>push</tt> из Mercurial-клона репозитория без веток и меток (имеющего вид просто одного каталога) в Subversion не работал вообще — [http://groups.google.com/group/hgsubversion/browse_thread/thread/3749eb3cbf007855 обсуждение в группе hgsubversion]. К счастью, меня пропёрло, я потратил час времени и баг этот пофиксил собственноручно. [[hgsubversion-fix-for-singledir-repo.diff|Фикс]] очень простой, на две строчки, и он уже находится в репозитории в виде ревизии r501 — за отзывчивость автору зачёт.
 
* <tt>push</tt> из Mercurial-клона репозитория без веток и меток (имеющего вид просто одного каталога) в Subversion не работал вообще — [http://groups.google.com/group/hgsubversion/browse_thread/thread/3749eb3cbf007855 обсуждение в группе hgsubversion]. К счастью, меня пропёрло, я потратил час времени и баг этот пофиксил собственноручно. [[hgsubversion-fix-for-singledir-repo.diff|Фикс]] очень простой, на две строчки, и он уже находится в репозитории в виде ревизии r501 — за отзывчивость автору зачёт.
* Не работает клонирование репозиториев по протоколу [http://svnbook.red-bean.com/en/1.0/ch06s03.html svnserve] (<tt>svn://host:port/url</tt>). Очень неприятно, особенно, если вы предпочитаете именно <tt>svnserve</tt> по причине его производительности.
+
* <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>.
 
* Течёт память, что при клонировании '''толстых''' 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> (подкаталог ветки…)
 
* На ревизии 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 Гб'''.
+
Итог клонирования 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>.
 
Остальные два экстенжна «нинужны»: <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>.
Строка 84: Строка 88:
  
 
Место на диске:
 
Место на диске:
# '''Git''' — 845 мб.
+
* '''Git''' — 845 мб.
# '''Bazaar''' — 925 мб.
+
* '''Bazaar''' — 925 мб.
# '''Mercurial''' — 1633 мб.
+
* '''Mercurial''' — 1633 мб.
  
 
«Нечестные» ветки (<tt>svn cp trunk/phase3 trunk/extensions branches/ветка/</tt>):
 
«Нечестные» ветки (<tt>svn cp trunk/phase3 trunk/extensions branches/ветка/</tt>):
# '''Mercurial''' — корректно прицепились к trunk’у (!).
+
* '''Mercurial''' — корректно прицепились к trunk’у (!).
# '''Git''' — общая история у всех таких веток, но отдельная от trunk’а.
+
* '''Git''' — общая история у всех таких веток, но отдельная от trunk’а.
# '''Bazaar''' — история «ДО» создания нечестных веток потеряна.
+
* '''Bazaar''' — история «ДО» создания нечестных веток потеряна.
  
 
Проблемы:
 
Проблемы:
Строка 138: Строка 142:
 
Первые два пункта в pbranch делаются как-то уж совсем нелогично — нужно '''ручками''' прописать зависимость патча в файл <tt>.hg/pgraph</tt> и вызвать команду <tt>hg pmerge</tt>, которая автоматически объединит все пока что не объединённые зависимости в необходимые ветки. Очень странно, что предписано общаться через <tt>.hg/pgraph</tt> при том, что этот файл вообще-то не является никаким «окончательным хранилищем» — его скорее можно классифицировать, как кэш команды <tt>pgraph</tt> — если его удалить, он будет успешно воссоздан, и при клонировании репозитория он также не копируется.
 
Первые два пункта в 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 хорошая online-документация]…
+
Зато, в отличие от 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 ====
Строка 178: Строка 196:
 
'''Mercurial''' говорит нам: на ровно 1 рабочую копию ровно 1 репозиторий, и иначе быть не может. Это и удобно — сказал <tt>hg up ветка_такая_то</tt>, пара файлов поменялась, и опа — ты уже в другой ветке. Это и неудобно — чтобы положить на диск одновременно две разных ветки, нужно обязательно клонировать репозиторий (поддержки checkout нет).
 
'''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], однако пока так и не реализована.
+
С '''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''' вначале было гораздо хуже: на 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>.
Строка 195: Строка 213:
  
 
[[Категория:Разработка]]
 
[[Категория:Разработка]]
 +
[[Категория:Статьи]]

Текущая версия на 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 считают их отсутствие фичей — ревизии, типа, сортируются, зачем графы? (и правда, зачем? стройте графы сами, в голове! как не умеете?!!)