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

Материал из YourcmcWiki
Перейти к: навигация, поиск
(Работа с SVN (миграция и синхронизация))
Строка 4: Строка 4:
  
 
Что любопытно — с Subversion-репозиториями DVCS работают, как правило, гораздо быстрее самого Subversion, и быстрее всех работает '''Bazaar'''. То есть, в принципе, можно вообще жить с Subversion-сервером и Bazaar-клиентом.
 
Что любопытно — с Subversion-репозиториями DVCS работают, как правило, гораздо быстрее самого Subversion, и быстрее всех работает '''Bazaar'''. То есть, в принципе, можно вообще жить с Subversion-сервером и Bazaar-клиентом.
 +
 +
Кстати, почему-то, говоря о недостатках DVCS, никогда не говорят о следующем не очень удобном при переходе с централизованных систем поведении — DVCS отслеживают версию цельного репозитория, а не отдельных файлов или директорий. Кроме проблем, которые это создаёт при желании извлечь часть репозитория для работы, а не обязательно весь, это ведёт к тому, что если две ветки «расходятся», становится обязателен merge, даже если '''конфликтов в изменениях не было''' и даже если '''вообще менялись разные файлы'''.
  
 
'''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’ом. Имеет фактически весь необходимый функционал — можно делать и <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 этого не смогли.
 
'''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’ом. Имеет фактически весь необходимый функционал — можно делать и <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 этого не смогли.
Строка 45: Строка 47:
 
# Теперь мы можем сделать push из bzrtmp в svn, а потом из svn — pull во все остальные ветки, и они придут к согласованному виду…
 
# Теперь мы можем сделать push из bzrtmp в svn, а потом из svn — pull во все остальные ветки, и они придут к согласованному виду…
  
Хотите автоматический сценарий для воспроизведения ошибки? Пожалуйста: [[bzr-rebase-dont-help.sh]]. В результате последней команды bzr скажет «<tt>ERROR: exceptions.AttributeError: 'NoneType' object has no attribute 'get_known_graph_ancestry'</tt>», уйдёт в Python Traceback и оттуда не вернётся.
+
Хотите автоматический сценарий для воспроизведения ошибки? Пожалуйста: [[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>
 
<div style="height: 150px; overflow: scroll">{{:bzr-rebase-dont-help.sh}}</div>

Версия 14:52, 19 ноября 2009

Данная статья является очередным сравнением популярных DVCS — Mercurial, Git и Bazaar, с точки зрения нескольких нетривиальных задач.

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

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

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

Mercurial: отлично! Есть несколько расширений — hgsubversion, hgsvn, convert, позволяющих работать с Subversion тем или иным образом, и не совместимых друг с другом. Самое вменяемое из них — hgsubversion, хотя и заявлено, что оно ещё сырое, и, к сожалению, не распространяется вместе с Mercurial’ом. Имеет фактически весь необходимый функционал — можно делать и push, и pull в/из Subversion, можно клонировать SVN-репозиторий с сохранением веток и меток (правда, обязательно стандартное их расположение в корневых поддиректориях /trunk, /branches, /tags), эти два метода совместимы, rebase также работает, а граф ветвлений сохраняется. Очень крут тот факт, что ветка, которая создавалась неполным копированием trunk'а, то есть, копированием некоторых его поддиректорий, успешно подцепилась в нужное место графа ветвлений. Ни Bazaar, ни Git этого не смогли.

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

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

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

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

Так вот. Всё начинается с обычной ситуации — мы сделали изменение в Bazaar-клонированном SVN-репозитории, а кто-то тем временем сделал изменение в самом SVN-репозитории… Причём не важно, в каких файлах: даже если конфликтов в наших изменениях нет, ситуация возникает всё равно. Что нам теперь остаётся делать? Ну конечно, мержиться. И 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

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

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

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

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

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

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

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

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

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

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

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

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