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

Введение в Mercurial. Часть 3. Начинаем ветвиться и сливаться

Продолжаю писать про систему контроля версий Mercurial. В этой части начну речь про сложные операции с репозиториями, а именно - создание ветвей и работа с ними. Работа с ветвями разработки пользователям Subversion доставляет немало головной боли, поэтому многие из них, когда видят во всех статьях про Mercurial, что им чуть ли не каждый день придется мержить (merge) ветки сильно пугаются, и теряют всякое желаение переходить на него. Однако я постараюсь переубедить всех недоброжелателей и консерваторов, поскольку в Mercurial работа с ветвлениями является не намного более сложной операцией чем коммит.


Перед тем как продолжить разбираться с Mercurial поздравляю всех своих читателей с наступившим новым годом, желаю всяческих успехов в различных аспектах жизни, гармонии и добра во внутреннем мире, а также постоянного интеллектуального и духовного роста! С новым годом, уважаемые!

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

Первым ветвлением, с которым столкнется команда работающаяя с Mercurial - это ветвление при пулле новых изменений. Ситуация возникает когда в вашем локальном репозитории имеются закоммиченные но не запушенные изменения, и один (а то и несколько) ваших соратников закоммитили и запушили в "центральный" репозиторий свои изменения. Попробуем смоделировать ситуацию на имеющихся у нас трех репозиториях, с которыми мы начинали работать во второй части. Напомню читателям, что у нас в обоих репозиториях сохранено по две ревизии, при этом оба репозитория были синхронизированы с "центральным". Попробуем внести в репозитории различные изменения и посмотреть что из этого выйдет.

Для начала создадим файл first.txt в первом репозитории, закоммитим его и запушим:

$ echo "new text to first.txt" > first.txt
$ hg status
? first.txt      
$ hg add first.txt
$ hg commit
$ hg outgoing
comparing with /home/mike/Repositories/newProject
searching for changes
changeset:   2:66c5686e355e
tag:         tip
user:        mike@mike-vbox
date:        Thu Jan 07 22:28:39 2010 +0300
summary:     Коммит файла first.txt в первом репозитории
$ hg push
pushing to /home/mike/Repositories/newProject
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files

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

$ echo "file created in second repository" > second.txt
$ hg status
? second.txt
$ hg add
adding second.txt
$ hg commit
$ hg log
changeset:   2:6872fa960507
tag:         tip
user:        mike@mike-vbox
date:        Sun Jan 10 19:40:45 2010 +0300
summary:     Файл second.txt создан во втором репозитории

changeset:   1:270e49e72f4b
user:        mike@mike-notebook
date:        Fri Nov 27 10:39:35 2009 +0300
summary:     Записан файл other.txt в другом репозитории

changeset:   0:8fae369766e9
user:        mike@mike-notebook
date:        Fri Nov 27 08:58:01 2009 +0300
summary:     Файл readme.txt добавлен в репозиторий

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

$ hg outgoing
comparing with /home/mike/Repositories/newProject
searching for changes
changeset:   2:6872fa960507
tag:         tip
user:        mike@mike-vbox
date:        Sun Jan 10 19:40:45 2010 +0300
summary:     Файл second.txt создан во втором репозитории

$ hg push
pushing to /home/mike/Repositories/newProject
searching for changes
abort: push creates new remote heads!
(did you forget to merge? use push -f to force)

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

$ hg incoming
comparing with /home/mike/Repositories/newProject
searching for changes
changeset:   2:66c5686e355e
tag:         tip
user:        mike@mike-vbox
date:        Thu Jan 07 22:28:39 2010 +0300
summary:     Коммит файла first.txt в первом репозитории

$ hg pull
pulling from /home/mike/Repositories/newProject
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)

Мы вытянули с "центрального" репозитория все имеющиеся изменения, и Mercuial нам сообщает, что в локальном репозитории теперь две "головы" которые требуют слияния (мержа от английского to merge). Можно даже попросить Mercurial показать некоторую картинку (используется дополнение graphlog о котором я ещё не писал, расширение есть в стандартной поставке):

$ hg glog
o  changeset:   3:66c5686e355e
|  tag:         tip
|  parent:      1:270e49e72f4b
|  user:        mike@mike-vbox
|  date:        Thu Jan 07 22:28:39 2010 +0300
|  summary:     Коммит файла first.txt в первом репозитории
|
| @  changeset:   2:6872fa960507
|/   user:        mike@mike-vbox
|    date:        Sun Jan 10 19:40:45 2010 +0300
|    summary:     Файл second.txt создан во втором репозитории
|
o  changeset:   1:270e49e72f4b
|  user:        mike@mike-notebook
|  date:        Fri Nov 27 10:39:35 2009 +0300
|  summary:     Записан файл other.txt в другом репозитории
|
o  changeset:   0:8fae369766e9
   user:        mike@mike-notebook
   date:        Fri Nov 27 08:58:01 2009 +0300
   summary:     Файл readme.txt добавлен в репозиторий

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

$ hg merge
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ ls
first.txt  other.txt  readme.txt  second.txt

Итак, Mercurial, после нашей команды "hg merge" смержил рабочую копию и репозиторий, и напоминает нам, что эти изменения следовало бы закоммитить. Так сделаем это:

$ hg commit
$ hg glog
@    changeset:   4:6d6c634e2e20
|\   tag:         tip
| |  parent:      2:6872fa960507
| |  parent:      3:66c5686e355e
| |  user:        mike@mike-vbox
| |  date:        Sun Jan 10 20:34:21 2010 +0300
| |  summary:     Выполнен мерж двух веток
| |
| o  changeset:   3:66c5686e355e
| |  parent:      1:270e49e72f4b
| |  user:        mike@mike-vbox
| |  date:        Thu Jan 07 22:28:39 2010 +0300
| |  summary:     Коммит файла first.txt в первом репозитории
| |
o |  changeset:   2:6872fa960507
|/   user:        mike@mike-vbox
|    date:        Sun Jan 10 19:40:45 2010 +0300
|    summary:     Файл second.txt создан во втором репозитории
|
o  changeset:   1:270e49e72f4b
|  user:        mike@mike-notebook
|  date:        Fri Nov 27 10:39:35 2009 +0300
|  summary:     Записан файл other.txt в другом репозитории
|
o  changeset:   0:8fae369766e9
   user:        mike@mike-notebook
   date:        Fri Nov 27 08:58:01 2009 +0300
   summary:     Файл readme.txt добавлен в репозиторий

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

$ hg push
pushing to /home/mike/Repositories/newProject
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 1 changes to 1 files

Теперь переместимся в каталог первого разработчика, и получим изменения и из центрального репозитория:

$ hg pull
pulling from /home/mike/Repositories/newProject
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 1 changes to 2 files
(run 'hg update' to get a working copy)
$ hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg glog
@    changeset:   4:6d6c634e2e20
|\   tag:         tip
| |  parent:      3:6872fa960507
| |  parent:      2:66c5686e355e
| |  user:        mike@mike-vbox
| |  date:        Sun Jan 10 20:34:21 2010 +0300
| |  summary:     Выполнен мерж двух веток
| |
| o  changeset:   3:6872fa960507
| |  parent:      1:270e49e72f4b
| |  user:        mike@mike-vbox
| |  date:        Sun Jan 10 19:40:45 2010 +0300
| |  summary:     Файл second.txt создан во втором репозитории
| |
o |  changeset:   2:66c5686e355e
|/   user:        mike@mike-vbox
|    date:        Thu Jan 07 22:28:39 2010 +0300
|    summary:     Коммит файла first.txt в первом репозитории
|
o  changeset:   1:270e49e72f4b
|  user:        mike@mike-notebook
|  date:        Fri Nov 27 10:39:35 2009 +0300
|  summary:     Записан файл other.txt в другом репозитории
|
o  changeset:   0:8fae369766e9
   user:        mike@mike-notebook
   date:        Fri Nov 27 08:58:01 2009 +0300
   summary:     Файл readme.txt добавлен в репозиторий

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

А теперь думаю самое главное - зачем же все это надо, ведь в SVN нет подобных проблем, да и мержить там не так часто... Ответ здесь очень простой - подобная модель взаимодействия с репозиторием провоцирует пользователя на частые коммиты, он не боится сломать код или репозиторий неудачным коммитом, не боится помешать другим пользователям и т.д. Коммит в Mercurial локален - пока вы не захотите отправить его в другой репозиторий, вся ветка коммитов останется у вас. К тому же слияние ревизий в Mercurial сделано намного проще и логичнее слияния ревизий в Subversion. Да, в поздних версиях и SVN научился более-менее нормально мержить, однако до Mercurial ему по прежнему далековато. И именно возможность частых локальных коммитов, в том числе когда у вас отстутствует подключение к интернету (а со мной такое случается нередко), меня так привлекла в Mercurial. Все остальные аспекты были на втором плане.

В заключении сделаю одно важное замечание. В случае командной разработки, у Mercurial есть одна особенность (назвать недостатком как-то язык не поворачивается): целочисленная нумерация ревизий может быть различна в различных репозиториях, поэтому не стоит использовать целочисленные номера для идентификации ревизий при общении внутри команды. Поскольку идентифиратором ревизии в Mercurial является SHA1-хеш, вероятность совпадения которого для различных ревизий крайне мала, то стоит использовать именно его. Причем как показывает практика различных пользователей Mercurial - вполне достаточно первых 4х символов этого самого хеша (когда вам перестанет хватать 4х символов - используйте 5 ;) ).

Progg it

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

  1. Спасибо, за статью доставило ) переходим с svn!

    ОтветитьУдалить
  2. Давно пора. После того как год попользуешься Mercurial понимаешь насколько же неудобен был Subversion.

    ОтветитьУдалить
  3. Не могу прийти к ясному пониманию идеологии mercurial в части локальных репозиториев. Зачем они вообще нужны? Репозиторий - это хранилище, в которое могут писать несколько человек и которое позволяет объединять изменения нескольких человек. Локальным же репозиторием никто кроме владельца локальной машины пользоваться как правило не будет (если не рассматривать какие-то извращенные схемы работы). Поэтому вообще не понял про написанное выше о том, как здорово, что можно делать частые локальные коммиты... А кому они вообще нужны? Их лучше вообще не делать, локальные коммиты. Локальный коммит - это сохранение файла в рабочей папке. Есть конечно еще одно применение репозитория - версионность. Т.е. даже работая одному, имея локальный репозиторий, я имею возможность откатиться на любые свои изменения, если делаю коммиты в репозиторий. Но ребята, я например работаю с NetBeans и там система абсолютно прозрачно для меня делает локальные версии изменяемых файлов. И я в любое время могу их посмотреть. Здесь по-моему всё должно работать точно также. Сохранил файл - тут же произошел его коммит в локальный репозиторий. Иначе это какой-то анонизм. Сохранил файл - закоммитил, сохранил - закоммитил... Или нужно постоянно думать: коммитить мне сделанные изменения, или не коммитить... ??? Получается, нужен какой-то плагин, который будет отслеживать изменения в рабочей папки и автоматом коммитить в локальный репозиторий? Или я чего-то недопонимаю?

    ОтветитьУдалить
  4. Мне кажется вы чего-то недопонимаете. Коммит - это не просто "сохранить". Это значит, что вы фиксируете логически законченную часть изменений, к которой можно сделать комментарий, и которая действительно должна быть сохранена.

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

    ОтветитьУдалить
  5. Тот кто не понимает что такое и зачем нужны локальные коммиты - не программист или говнокодер. При работе над проектами некоторые таски, а особенно юзер-стори могут делаться несколько дней а то и пару недель. Зафигачить недоделанную юзер стори в центральное хранилище - это нонсенс. Внутри работы над юзер стори нужно делать локальные комиты, например по таскам. Если бы программисты писали с первого раза 100% рабочий код - тогда можно было поставить под сомнение бранчи и локальные коммиты. Спасибо автору за статьи и хотелось бы увидеть что-то подобное про UI расширения для работы а также узнать что изменилось за последнее время

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