Системы контроля версий

Это будет небольшая страничка с инфой о системах контроля версий (таких как SVN, mercurial и git). В последнее время я особенно заинтересовался этой темой, и в процессе изучения задавал сам себе некоторые вопросы в моментах, которые были неочевидны, ну и приобретал некоторый опыт. Самые с моей точки зрения интересные выводы я буду писать здесь в формате вопрос-ответ.

Q : Почему все утверждают, что ветвления и слияния в SVN неудобны по сравнению с тем, как они реализованы в Mercurial или Git ?
A : Ветвления неудобны потому, что
а) засоряют пространство имён в центральном репозитории (в отличие от hg/git, где всё локально)
б) если создал ветку – назад дороги нет, центральный репозиторий уже изменён (опять же, в отличие от hg/git)
в) при создании ветки SVN не запоминает, из какого места было произведено ответвление
г) при создании ветки SVN выполняет сетевые операции, что есть медленно
Слияния неудобны потому, что
а) механизм слияния SVN это по сути даже не слияние, а получение диффа между двумя коммитами одной ветки (например между коммитами 120 и 125) и применение этого патча к текущей ветке (diff + apply patch). Таким образом, если у нас 5 коммитов, SVN “схлопывает” (collapse) изменения в один патч и пытается наложить его на текущую ветку. Все остальные неприятности свновского мерджа – следствие этой особенности. Среди них :
б) невозможность получить инфу о том, откуда пришли изменения (поэтому приходится ручками записывать в сообщение коммита – какие ревизии и из какой ветки мы сливаем)
в) возможные адские конфликты при попытке повторно смерджить один и тот же набор изменений (как следствие предыдущего)
г) неудобство просмотра истории (особенно доставляет боли необходимость посмотреть, что же было изменено в другой ветке перед тем как было слито в текущую – ведь у нас есть инфа только патча целиком, а история изменений потеряна – надо переключаться в исходную ветку и отматывать историю)
В SVN история всегда представлена в виде прямой линии. А ветки идут будто бы параллельно транку. А в hg/git история веток представлена с помощью ориентированного ациклического графа (DAG) – поскольку эти системы запоминают, где разошлись ветки и какие изменения куда были перенесены, что избавляет нас от вышеперечисленных проблем. Hg и git никогда не будут применять к одной ветке один и тот же набор изменений из другой, поскольку они отслеживают выполненные слияния. А SVN может это попытаться сделать, если вы ненароком укажете набор коммитов, который уже сливали когда-то. На самом деле не совсем понятно, почему до сих пор SVN не улучшил свои механизмы представления истории и слияния ветвей, ведь можно было сделать аналогичным образом, и тогда одним недостатком было бы меньше. Скорее всего, это просто исторический фактор, и сейчас уже сложно что-то менять в этом.

Q: Мы хотим перейти с SVN на mercurial. Можно ли заюзать расширение hgsubversion ?
A: Да, можете использовать hgsubversion, но на самом деле это довольно бесполезный инструмент, поскольку он не даст вам долго работать с ветками. Перед любым пушем обратно в svn он будет вынуждать вас делать rebase так, чтобы outgoing-ревизии не содержали merge-коммитов. И еще – с этим расширением можно работать только одному. Если вы хотите скажем перейти вдвоем на hg так, что один из вас будет пулить и пушить в svn, а другой – работать исключительно с вашим hg-репозиторием, то у вас ничего не выйдет, потому что при пуше в svn расширение hgsubversion делает это странным образом так, что выполняет еще 1 rebase, и переписывает историю. И если ваш коллега запушил вам изменения, чтобы вы их запушили в svn, то при следующем пулле с вашего hg-репозитория он получит дубликаты своих коммитов. Спасибо hgsubversion’у за это. Поэтому вывод: hgsubversion – инструмент только для 1 девелопера. Лучше всего просто один раз сконвертить репозиторий в hg и работать уже с ним.

Q: Что означает фраза “git tracks content not files”.
А: Это говорит Линус во время своего известного доклада на Google TechTalks. Относится она к тому, как git хранит и отслеживает изменения в содержимом репозитория. Если другие системы контроля версий базируются на том, что контент репозитория состоит из файлов, то git считает, что контент репозитория состоит из набора данных, некоторым из которых должны соответствовать файлы. Отличие проявляется, если рассмотреть случай, когда мы берем файл и переименовываем его. Обычные файл-ориентированные системы затрекают это изменение репозитория как удаление одного файла и добавление другого. Более продвинутые файл-ориентированные системы (как меркуриал) позволяют отметить эту модификацию как rename и обработают особым образом. А git сам определит, что содержимое файла как было в репозитории, так и осталось, но имя соответствующего ему файла поменялось. То есть git отвязывается от файловой системы и “мыслит” более абстрактно, что позволяет более качественно трекать изменения (Линус приводит в пример, что git может отследить вырезанную из одного файла и вставленную в другой функцию и показать её историю до перемещения). В деталях я пока эту тему не изучил, но первое приближение примерно такое. Кстати, интересный ответ был на stackoverflow по поводу того, почему hg и git не позволяют закоммитить пустые директории, оказывается, причина схожая, но разная:

  1. git track content of files and since no content is added, it only mentions the top directory has “having no content added”
  2. mercurial tracks files (not directories), hence the comprehensive list (of files).

И еще 1 ответ со stackoverflow : http://stackoverflow.com/a/995799