|
Персональные инструменты |
![]() |
||
|
![]() |
![]() Сравнение DVCS - несколько задачМатериал из CustisWiki(перенаправлено с «DVCS YAC»)
Короткая ссылка: DVCS YAC Данная статья является очередным сравнением популярных DVCS — Mercurial, Git и Bazaar, с точки зрения нескольких нетривиальных задач. Хотите лаконичный ответ в стиле «ИМХО» на вопрос — кого выбрать из трёх? Ну пожалуйста. Выбирайте 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-родителей. А ведь было бы неплохо иметь такие возможности… MercurialMercurial: почти отлично (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 пока что нет, хотя она близится. Минус расширения заключается в сырости — за время тестирования я нашёл уже несколько неприятных багов:
Итог клонирования 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. GitGit: отлично (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. BazaarBazaar: хорошо. Можно импортировать репозиторий командами 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 не поможет или вывалится с исключением. Хотите подробнее? Рассмотрим следующую последовательность действий:
Две 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». А в некоторых комбинациях — вывалится со змеиным исключением. Чтобы исправить эту ситуацию:
Хотите автоматический сценарий для воспроизведения ошибки? Пожалуйста: 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Место на диске:
«Нечестные» ветки (svn cp trunk/phase3 trunk/extensions branches/ветка/):
Проблемы:
Управление патчамиРассмотрим задачу управления набором патчей: есть некоторый проект, есть набор сторонних (например, ваших) патчей. Некоторые из них нужно накатывать в определённой последовательности, кроме того, и само ПО, и патчи не стоят на месте и могут меняться, и неплохо бы иметь также их историю. Можно, конечно, просто создать форк, но в этом случае ваша ветка, скорее всего, серьёзно разойдётся с оригинальной и «выделять» из неё отдельные фичи станет очень тяжело. Короче говоря, если кому-то хочется увидеть наглядный пример из разряда «нафига оно надо», выполните команду 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 pbranchMercurial'овский pbranch: хорошо. Умеет весьма немногое. Самые полезные команды — это pdiff, pgraph и pmerge. pdiff экспортирует текущий патч без его родителей в стандартном формате. pgraph показывает граф зависимостей патчей, из которого можно, по сути, понять, в какой последовательности их накатывать, если они сами уже экспортированы. Ну и просто вообще граф патчей — это удобно. pmerge объединяет изменения, произошедшие в зависимостях патча, в патч. Другие команды расширения:
Чего не хватает (а хочется):
Первые два пункта в pbranch делаются как-то уж совсем нелогично — нужно ручками прописать зависимость патча в файл .hg/pgraph и вызвать команду hg pmerge, которая автоматически объединит все пока что не объединённые зависимости в необходимые ветки. Очень странно, что предписано общаться через .hg/pgraph при том, что этот файл вообще-то не является никаким «окончательным хранилищем» — его скорее можно классифицировать, как кэш команды pgraph — если его удалить, он будет успешно воссоздан, и при клонировании репозитория он также не копируется. Зато, в отличие от TopGit, pbranch'евый репозиторий без проблем клонируется со всеми ветками и информацией о патчах, и документация на pbranch весьма вменяема… Пример использованияЛично я, для управления патчами MediaWiki, входящими в состав CustIS’овской сборки MediaWiki, и хранящимися под SVN в виде набора diff-файлов, выбрал именно pbranch и следующую схему работы:
Таким образом, для установки нашей сборки MediaWiki через Mercurial надо только клонировать репозиторий и обновиться до ветки mergeinstall — сразу же будут получены все патчи и расширения. При этом все патчи с помощью команды hg pdiff экспортируются в diff-файлы и коммитятся в Subversion, поэтому доступен и старый метод установки — Python-скрипт, выкачивающий из svn.wikimedia.org код MediaWiki, из локального Subversion’а — код нужных расширений и патчи, и накатывающий эти патчи на MediaWiki автоматически. TopGitTopGit: тоже весьма хорошо, даже чуть получше (версия на момент тестирования была 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, и даже вообще без него, но придётся набрать чуть больше команд. Команды расширения:
Чего не хватает (а хочется):
Схема управления рабочими копиямиЧто нам предлагают централизованные системы контроля версий? Некий «хардкод»: репозиторий ровно один, рабочих копий много. Что нам предлагают 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 считают их отсутствие фичей — ревизии, типа, сортируются, зачем графы? (и правда, зачем? стройте графы сами, в голове! как не умеете?!!)
Внимание! Эта статья была создана путем автоматического реплицирования из внутренней базы знаний компании Заказные Информ Системы. Любые правки этой статьи могут быть перезаписаны при следующем сеансе репликации. Если у вас есть серьезное замечание по тексту статьи, запишите его в раздел «discussion». |
![]() |
![]() |
![]() |