воскресенье, 31 января 2010 г.

Введение в Mercurial. Часть 4. Способы организации ветвей

Progg it

Здравствуйте, уважаемые читатели. Я продолжаю свою серию постов про распределенную систему контроля версий Mercurial. В этой статье мы подробно поговорим об основных приемах организации ветвлений в Mercurial. В предыдущей статье мы рассмотрели "спонтанное" ветвление, возникающее в случае наличия в репозитории разных линий ревизий от разных разработчиков, хотя каждый из них работает в основной ветви разработки, в этой статье мы рассмотрим работу с ветвлениями, вызванную осознанной необходимостью разделения линий разработки. Также в этой статье я буду указывать какие преимущества и недостатки имеются у обсуждаемых способов организации ветвей. Указанные преимущества и недостатки являются моим личным мнением, и вполне могут не совпадать с вашим.

Для начала дадим определения ветви, чтобы начинать с единого понимания процесса. Обратившись к wiki меркуриала я нашел определение, которое мне кажется вполне корректным: ветвь (branch) - это связанная последовательность ревизий (changeset) являющаяся отдельным направлением разработки. Таким образом, ветвь - это в первую очередь логическое понятие, так как в случае с распределенными системами контроля версий она будет содержать значительное число "спонтанных" ветвлений-слияний.

Исходное состояние

Рисунок 1. Исходное состояние репозитория

Анонимная ветвь

Рисунок 2. Новая анонимная ветвь в репозитории

В этой статье продолжим работу над нашим примером. На рисунке показано состояние нашего репозитория после операций с ним в предыдущих частях цикла. И хотя формально в репозитории уже имеется одно ветвление, Mercurial нам говорит что ветвь одна:

$ hg branches
default                        4:6d6c634e2e20

Команда hg branches выводит список всех именованных ветвей в репозитории. Как мы видим основная ветвь разработки называется default. Если быть точным, так называется ветвь в которую происходит первый коммит в репозиторий, так сказать название по умолчанию. На рисунке 1 приведено текущее состояние репозитория и граф ревизий в нем находящихся. Я буду красным кружком отмечать "вершину" (tip) репозитория, как сказано в документации Mecurial, вершина - это самая свежая ревизия в репозитории. Команда hg branches не выводит анонимные ветви, хотя разработчики могут их использовать при необходимости, и создавать самостоятельно.

Анонимное ветвление

На самом деле, создание ветви при работе с Mercurial не является чем-то из ряда вон выходящим, для распределенной модели контроля версий это стандартная операция, и поэтому может быть произведена крайне просто, фактически, простым коммитом. То есть мы должны привести рабочую копию в состояние отличное от "вершины" (tip), внести изменения и выполнить коммит. Ничего больше. Для этого локальную копию исходного кода вернем в состояние ревизии 2:66c5686e355e:

$ hg update -r 66c5
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ ls -l                   
-rw-r--r-- 1 mike mike 22 2010-01-07 22:22 first.txt         
-rw-r--r-- 1 mike mike 61 2009-11-27 11:15 other.txt         
-rw-r--r-- 1 mike mike 57 2009-11-27 00:03 readme.txt    

После этого создадим в локальной копии файл branch.txt, добавим его в репозиторий и выполним коммит:

$ ls -l
-rw-r--r-- 1 mike mike 61 2010-01-31 18:55 branch.txt
-rw-r--r-- 1 mike mike 22 2010-01-07 22:22 first.txt 
-rw-r--r-- 1 mike mike 61 2009-11-27 11:15 other.txt
-rw-r--r-- 1 mike mike 57 2009-11-27 00:03 readme.txt
$ hg add
adding branch.txt
$ hg commit
created new head
$ hg branches
default                        5:ff8ffd5270cb

И Mercurial нам честно сообщает что создал новую "голову". На рисунке 2 показано текущее состояние репозитория. Отмечу два момента: во-первых, ветвь default осталась на своем месте, и теперь заканчивается ревизией 5:ff8ffd5270cb, то есть "вершиной" (tip); а во-вторых все эти ветвления находятся локально в нашем репозитории. Локальность производимых ветвлений - это главное, коренное, отличие от централизованных систем контроля версий, в том числе от Subversion. Никто не увидит вашей ветви до тех пор пока вы не синхронизируете свой репозиторий с удаленным (обычно командой push). С другой стороны выполнение pull приведет к появлению в вашем репозитории всех ветвей, имеющихся в удаленном.

Преимущества

Основным преимуществом этого способа является крайняя простота. Не нужно ничего придумывать, выполнять сложных операций, оповещать других участников команды - просто апдейт и коммит.

Недостатки

Недостатки вытекают из анонимности ветви. Фактически, понять, что у вас есть ветвь разработки, исходя из вывода стандартных операций Mercurial, затруднительно. Особенно после 2-3 месяцев активной работы с репозиторием, когда количество таких мелких ветвлений приближается к сотне. Соответственно вам придется писать информативные подписи к коммитам, если анонимные ветви предполагается использовать в дальнейшем. Для переключения между ветвями разработки вам придется использовать номер ревизии, который, как известно, простой хэш - очень удобен для машины, но крайне неудобен для человека.

Таким образом, анонимные ветви - это отличный механизм для внесения быстрых (по количеству ревизий) исправлений, логически сильно связанных с направлением основной ветви разработки, то есть для организации ветвления на 2-3 ревизии с последующим слиянием с основной ветвью. Для организации больших ветвей, логически необходимых для разделения направлений разработки следует использовать именованные ветви.

Именованное ветвление

Именованная ветвь

Рисунок 3. Именованные ветви в репозитории

Вполне естественно, что в Mercurial предусмотрен способ создания ветвей разработки с некоторыми именами, задаваемыми пользователем. Для организации подобных ветвлений предназначена команда hg branch. С помощью этой команды версия, находящаяся в локальной копии помечается ветвью с новым именем, при этом сама ветвь будет создана только после того, как вы выполните коммит. Попробуем сейчас сделать именованную ветвь, родительской ревизией для которой будет ff8f:

$ hg branch new_feature
marked working directory as branch new_feature
$ hg commit
$ hg branches
new_feature                    6:4d530267d302
default                        5:ff8ffd5270cb

Итак мы видим, что Mercurial уже знает про две именованные ветви, "вершинами" для которых являются ревизии ff8f и 4d53, хотя на графе ревизий это одна ветвь. На рисунке 3 я показал, что именно понимается под именованной ветвью в Mercurial, при этом, фактически, для каждой ветви есть своя "вершина" (tip), хотя hg log это не показывает (я приведу только смысловой отрывок):

$ hg log
changeset:   6:4d530267d302
branch:      new_feature
tag:         tip
user:        mike@mike-vbox
date:        Sun Jan 31 21:31:07 2010 +0300
summary:     Создаие именованной ветви в репозитории

changeset:   5:ff8ffd5270cb
parent:      2:66c5686e355e
user:        mike@mike-vbox
date:        Sun Jan 31 18:56:23 2010 +0300
summary:     Создание анонимной ветви

changeset:   4:6d6c634e2e20
parent:      3:6872fa960507
parent:      2:66c5686e355e
user:        mike@mike-vbox
date:        Sun Jan 10 20:34:21 2010 +0300
summary:     Выполнен мерж двух веток

changeset:   3:6872fa960507
parent:      1:270e49e72f4b
user:        mike@mike-vbox
date:        Sun Jan 10 19:40:45 2010 +0300
summary:     Файл second.txt создан во втором репозитории

Убедиться в том, что "вершины" все таки существуют можно с помощью hg update, то есть переключившись на другую ветвь:

$ hg update default
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg ident
ff8ffd5270cb

А затем переключится обратно:

$ hg update new_feature
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg ident
4d530267d302 (new_feature) tip

При этом в нашем репозитории сложилась интересная ситуация. tip ветви default не совпадает с "головой" (head) этой же ветви. В этом легко убедиться попросив Mercurial сказать какие же "головы" в нашем репозитории:

$ hg heads
changeset:   6:4d530267d302
branch:      new_feature
tag:         tip
user:        mike@mike-vbox
date:        Sun Jan 31 21:31:07 2010 +0300
summary:     Создаие именованной ветви в репозитории

changeset:   4:6d6c634e2e20
parent:      3:6872fa960507
parent:      2:66c5686e355e
user:        mike@mike-vbox
date:        Sun Jan 10 20:34:21 2010 +0300
summary:     Выполнен мерж двух веток

Отмечу лишь один очень важный факт - ветвление произведено в вашем локальном репозитории, и вы можете работать с ним так, как вам угодно, при этом вы не боитесь поломать чужой код своим коммитом, или вызвать у тимлида приступ головной боли своими ветвлениями. Вот именно так концепция распределенной системы контроля версий позволяет решить стандартные болячки централизованных систем.

Преимущества

Итак именованное ветвление лишь немногим сложнее анонимного, однако позволяет организовать работу над сложным проектом с несколькими направлениями разработки наиболее эффективным образом. Имя ветви является метаданными каждой ревизии, что позволяет корректно отслеживать изменения, произошедшие в проекте. Хотя принципиальных отличий от анонимных ветвей, на самом деле, нет.

Недостатки

К недостаткам, в некоторой степени, можно отнести синдром разрастания ветвей. То есть, если вы будете использовать именованные ветви при каждой необходимости отпочковаться от основной, вывод команды hg branches будет просто гиганским через некоторое время. Хотя ветви можно закрывать при коммитах (опция --close-branch), не стоит делать именованные ветви там где они не нужны.

Ветвление в клонах репозитория

Последний способ организации ветвления о котором я расскажу - ветвление в клонах. На самом деле способ очевиден, и вытекает из третьей части этого цикла. Ничто не мешает пользователю лично создать клон репозитория и в нем вести отдельную ветвь разработки. Таким образом, для каждой ветви потребуется отдельный репозиторий. Безусловно модель распределенной системы контроля версий Mercurial позволяет подобную трактовку ветвления разработки, однако рассмотрим аспекты подобного подхода:

Преимущества

Немного более безопаснее чем при других способах. На самом деле в данном случае способ выстрелить себе в ногу только один - запушить что нибудь этакое в удаленный репозиторий, тогда как при локальном ветвлении способов выстрелить себе в ногу немного больше.

Недостатки

На мой взгляд недостатки перевешивают все преимущества. Самый главный недостаток - при каждом клонировании вам придется вытягивать весь репозиторий, то есть, если вам захотелось получить доступ к некоторой ветви вам придется вытянуть весь репозиторий относящийся к этой ветви, и так для каждой. Второй недостаток - бекапить вам придется не один репозиторий, а несколько. Что не логично.

На этом я заканчиваю рассматривать ветвления в Mercurial. Думаю следующая статья будет посвящена способам слияния ветвей, а также сложным моментам при слиянии, при упоминании которых у пользователей Subversion резко падает давление и начинают трястись руки. :)

Уважаемые читатели. Просьба комментировать посты. Возможно я упустил какие-то моменты, требующие разъяснения, о которых стоило бы написать.

12 комментариев:

  1. Поясните пожалуйста, как быть в случае, если в локальном репозитории образовалась вторая (третья, четвертая) "голова" и есть желание при выполнении push "затолкать" в удаленный репозиторий только ветку с какой-то конкретной "головой".

    Например, после клонирования удаленного репозитория в локальном образовалась последовательность ревизий 1-2-3-4.
    Четвертая чем-то не понравилась, сделали hg up -r
    После фиксации появляется вторая "голова" пятой ревизии, её надо отправить в удаленный репозиторий не отправляя при этом ревизию 4

    Как это можно сделать, если работать только в одном каталоге ?

    ОтветитьУдалить
  2. Есть команда hg push -r REVISION, которая отправляет в удаленный репозиторий куст с ревизиями до REVISION. Сейчас только что проверил - получилось. Отправил только минимально необходимое количество ревизий, т.е. только те, которые фиксируются как предки (поиском в графе ревизий).
    Но вообще говоря, это неправильный подход. Вообще говоря, исходя из концепции неизменности истории, в которой все системы контроля версий и работают - пусть эта повисшая ветка остается, что в ней такого то. Есть конечно обходные пути - можно либо смержить полученные ветви, так чтобы из 4й ревизии ничего не попало, либо закрыть ветвь c помощью --close-branch, либо многоходовая комбинация с hg clone -r.
    Но нужно ли это в действительности? Вот в чем вопрос

    ОтветитьУдалить
  3. >>> Есть команда hg push -r REVISION, которая отправляет в удаленный репозиторий куст с ревизиями до REVISION.
    Как-то даже и не догадался посмотреть в help push. Спасибо.

    >>> Но вообще говоря, это неправильный подход. Вообще говоря, исходя из концепции неизменности истории, в которой все системы контроля версий и работают - пусть эта повисшая ветка остается, что в ней такого то.
    Тут дело в том что пусть история будет в локальном репозитории, а в публичном - зачем светить исправлением синтаксических ошибок или чем-то экспериментальным явно зешедшим в тупик.

    ОтветитьУдалить
  4. >Самый главный недостаток - при каждом клонировании вам придется вытягивать весь репозиторий, то есть, если вам захотелось получить доступ к некоторой ветви вам придется вытянуть весь репозиторий относящийся к этой ветви, и так для каждой.

    А в чём, собственно, заключается недостаток? Локальное клонирование (в рамках одного раздела) катастрофически дешёвое, так как использует хардлинки и copy-on-write.

    ОтветитьУдалить
  5. Я около года работал с Hg, после SVN конечно сказка. Есть просто недостатки при работе с ветками. Нельзя их удаленно создавать и работать с ними, так как это сделано в GIT. Сейчас работаю на гите.

    ОтветитьУдалить
  6. Анонимное ветвление это какой то странный способ ветвления. Мы возвращаемся к предыдущей ревизии, просто для того чтобы создать еще одну ветку? А как же текущая ревизия, ведь в ней много что изменено, и в предыдущей из которой сделали новую ветку, изменений нет. Т.е. тогда надо чтобы создать новое направление разработки, снова внести изменения в новую ветку, чтобы ее вершина содержала такие же изменения, как и вершина в ветке default.
    Не проще ли для нового направления создать клон ветки? Будет меньше путаницы по крайней мере.

    ОтветитьУдалить
  7. Если нужно ответвиться от текущей головы - то ничего не мешает этого сделать. Просто будет выглядеть так, как будто у вас линейный код - был бы нехороший пример для демонстрации. Сделав клон репозитория вы получите примерно то же самое что и при анонимном ветвлении, фактически будет лишь одна дополнительная локальная копия, и ее тоже придется синхронизировать.
    На самом деле анонимное ветвление - это очень частая операция, когда вы работаете в группе. очень часто индивидуальные коммиты разработчиков попадают в анонимные ветви, а потом сливаются. Это происходит много раз в течении дня.
    Поэтому ничего странного не вижу. Работаете над некоторым куском, который пока нет возможности сливать с основной веткой, но коммитить нужно, чтобы достичь некоторой гранулярности. Тогда мы просто коммитим в локальный репозиторий и выстраиваем свою цепочку. Если другие разработчики в это время пушили в общедоступный репозиторий, то, когда вы спуллите, вы увидите свою цепочку в виде анонимной ветки. Что тут странного? В контексте DVCS это нормально.

    ОтветитьУдалить
  8. Спасибо за статьи по Mercurial!

    Просили замечания? Получайте! ;)

    >Анонимное ветвление

    Я бы в первых предложениях дал определения термину анонимное ветвление.

    >Для этого локальную копию исходного кода вернем в состояние ревизии 2:66c5686e355e:

    Не понятно, зачем?

    >Убедиться в том, что "вершины" все таки существуют можно с помощью hg update, то есть переключившись на другую ветвь:

    Из последующих листингов я не понял как же мы убедились в том что вершины все-таки существуют.

    И еще, о каких ужасаных проблемах при мержах в SVN вы говорите? Можно подробнее?

    Если решите более подробно расписать по моим вопросам, то лучше подправьте текст статьи, а не отвечайте в комментарии, так будет удобнее для читателей.

    ОтветитьУдалить
  9. Хорошая статья. Спасибо.

    ОтветитьУдалить
  10. Здравствуйте. У меня такой вопрос.

    Я люблю делать частые коммиты в клонированном репозитории по незначительным поводам, но не хочу, чтобы история этих мелких изменений светилась в основном репозитории.

    Единственный способ, который я нашёл, это:
    - создать ветку, предназначенную для сливания изменений из неё в основной репозиторий, (скажем, назвать её trunk) и сделать коммит (нулевая ревизия);
    - переключиться в ветку default или создать любую другую ветку, и вести разработку в ней, при необходимости ветвиться и сливать всё обратно в неё;
    - когда будут готовы значительные изменения, которые нужно залить в основной репозиторий, скопировать все файлы, находящие в рабочей копии ветки разработки куда-нибудь в другой каталог, не находящийся под управлением Mercurial, затем переключиться в ветку trunk и скопировать эти файлы в неё, затерев предыдущее содержимое;
    - сделать hg addremove, hg commit и hg push -r .

    Таким образом ненужная история мелких локальных коммитов и ветвлений-слияний не попадёт в основной репозиторий.

    Вопрос: можно ли избезать такого варварского, на мой взгляд, способа и вместе с тем отправлять в центральный репозиторий только минимально необходимое количество изменений? Спасибо.

    ОтветитьУдалить
  11. Подозреваю что вы хотите сделать это http://mercurial.selenic.com/wiki/ConcatenatingChangesets

    Но мне непонятно чего плохого в том, что в репозиторий попадают маленькие коммиты.

    ОтветитьУдалить
  12. Проблема маленьких коммитов, описанная в комментарии от 1 октября 2011 г. 10:44 решена сокращением количества ревизий в основной ветке разработки путём объединения определённого количества changeset'ов командой hg collapse -r ПЕРВАЯ:ПОСЛЕДНЯЯ, обеспечиваемой расширением Collapse. Расширение Collapse переписывает историю, сокращая количество ревизий в локальном репозитории. После этого можно делать hg push в центральный репозиторий.

    P.S. Маленькие коммиты не хочется отправлять в центральный репозиорий, чтобы не засорять вывод команды hg log центрального репозитория большим количеством никому кроме меня не нужных changeset'ов.

    ОтветитьУдалить