Mercurial-3: head всему голова

Это продолжение части 1 и части 2.

С момента написания предыдущей части прошло две недели, но за это время произошло немало событий. Зарелизились Mercurial 1.5 и TortoiseHg 1.0, а Джоэл Спольски написал туториал по mercurial. Что характерно, повествование своё, Джоэл разбавил логами виндовой консоли, что позволяет мне в тайне надеяться, что перед этим он читал мой блог :)

Впрочем, даже столь серьёзная конкуренция не отобьёт у меня желания продолжить свой убогий обзор.

Сегодня тема будет небольшая и, на первый взгляд, простая. Однако и здесь можно запутаться. Итак — головы (heads) в Mercurial.

Голова, это ревизия, не имеющая потомков. При линейной разработке (без ветвей), всё вообще кристально ясно — последняя ревизия является текущей рабочей ревизией и головой всей разработки. При наличии ветвей, всё немного усложняется.

Создадим для тестов новый рабочий каталог и репозиторий.

c:\>mkdir c:\hg\heads

c:\>cd c:\hg\heads

C:\hg\heads>hg init

Доведём дело до первой ревизии.

C:\hg\heads>echo "1" > 1.txt

C:\hg\heads>hg add
adding 1.txt

C:\hg\heads>hg commit --message="1"

Теперь просмотрим список голов с помощью команды hg heads:

C:\hg\heads>hg heads

changeset:   0:6d02b12a67d2
tag:         tip
user:        go <>
date:        Wed Mar 10 17:07:12 2010 +0300
summary:     1

Как видно, головой является наша первая и пока единственная ревизия.

Следующий шаг:

C:\hg\heads>echo "2" > 2.txt

C:\hg\heads>hg add
adding 2.txt

C:\hg\heads>hg commit --message="2"

C:\hg\heads>hg heads

changeset:   1:4e0e603cfacf
tag:         tip
user:        go <>
date:        Wed Mar 10 17:16:12 2010 +0300
summary:     2


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

Анонимные ветки

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

Анонимную ветвь создать проще простого (настолько просто, что можно создать случайно, когда она и не нужна). Откатимся на предыдущую ревизию и продолжим разработку параллельно уже сделанному.

C:\hg\heads>hg update 0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved

C:\hg\heads>echo "3" > 3.txt

C:\hg\heads>hg add
adding 3.txt

C:\hg\heads>hg commit --message="3"
created new head


Обратите внимание: «created new head». Теперь у 0-й ревизии сразу две ревизии-потомка, а у них самих потомков нет — они обе головы:

C:\hg\heads>hg heads
changeset:   2:e0858832eb0d
tag:         tip
parent:      0:6d02b12a67d2
user:        go <>
date:        Wed Mar 10 17:29:20 2010 +0300
summary:     3

changeset:   1:4e0e603cfacf
user:        go <>
date:        Wed Mar 10 17:16:12 2010 +0300
summary:     2

У нас образовалось две ветки разработки (при том, что branch по-прежнему один — default). В этих ветках можно продолжать параллельную разработку (голова каждой ветки будет переходить к следующим ревизиям), можно плодить ещё ветки, ну и самое приятное: ветки можно сливать вместе.

C:\hg\heads>hg merge
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

C:\hg\heads>hg commit --message="merge"

C:\hg\heads>hg heads
changeset:   3:e057a6b01c4a
tag:         tip
parent:      1:4e0e603cfacf
parent:      2:e0858832eb0d
user:        go <>
date:        Wed Mar 10 17:47:05 2010 +0300
summary:     merge

Мы слили две ветки (объединили две головы в одну). Теперь обе предыдущие головы являются предками новой ревизии, которая и стала нашей текущей единственной головой.

Заметьте, что у ревизии может быть не более двух предков, то есть слить одновременно можно только две головы. Так как у нас только две головы и было, то нам хватило вызова hg merge без параметров. Если же голов было бы больше, пришлось бы точно указывать какую из них следует слить с текущей.

Именованные ветки (branches)

Теперь посмотрим на branch’и новым взглядом.

Создадим для тестов новый репозиторий:

c:\>mkdir c:\hg\branches

c:\>cd c:\hg\branches

C:\hg\branches>hg init

C:\hg\branches>

Сделаем пару комитов в default-ветвь:

C:\hg\branches>echo "1" > 1.txt

C:\hg\branches>hg add
adding 1.txt

C:\hg\branches>hg commit -m "1"

C:\hg\branches>echo "2" > 2.txt

C:\hg\branches>hg add
adding 2.txt

C:\hg\branches>hg commit -m "2"

А теперь ответвим ветвь «one» и туда ещё парочку.

C:\hg\branches>hg branch one
marked working directory as branch one

C:\hg\branches>echo "3" > 3.txt

C:\hg\branches>hg add
adding 3.txt

C:\hg\branches>hg commit -m "3"

C:\hg\branches>echo "4" > 4.txt

C:\hg\branches>hg add
adding 4.txt

C:\hg\branches>hg commit -m "4"

Проверим, что у нас с ветвями и головами:

C:\hg\branches>hg branches

one                            3:5d8341509529
default                        1:7da81129f01e (inactive)

C:\hg\branches>hg heads

changeset:   3:5d8341509529
branch:      one
tag:         tip
user:        go <>
date:        Wed Mar 10 18:11:42 2010 +0300
summary:     4

Вот незадача, ветви у нас теперь две, а голова всего одна.

Ревизия #1, которая является последней в ветви default и, на первый взгляд, должна быть её головой, на самом деле ей быть не может. Просто потому, что она имеет потомка (ревизию #2, пусть она и из другой ветви). Таким образом, ветвь default вообще осталась без головы и помечена в списке, как (inactive).

Так приделаем ей голову! Сначала вернёмся в default-ветку:

C:\hg\branches>hg update default
0 files updated, 0 files merged, 2 files removed, 0 files unresolved

Обратите внимание на то, почему эти ветви называются «именованными». «Имя» default здесь просто является синонимом последней ревизии из соответствующей ветки. То есть, мы могли ровно с тем же успехом написать hg update 1.

C:\hg\branches>echo "5" > 5.txt

C:\hg\branches>hg add
adding 5.txt

C:\hg\branches>hg commit -m "5"
created new head

Мы создали голову в ветке default (created new head).

Проверим, что вышло

C:\hg\branches>hg branches

default                        4:6ded3e912b46
one                            3:5d8341509529

C:\hg\branches>hg heads

changeset:   4:6ded3e912b46
tag:         tip
parent:      1:7da81129f01e
user:        go <>
date:        Wed Mar 10 18:29:25 2010 +0300
summary:     5

changeset:   3:5d8341509529
branch:      one
user:        go <>
date:        Wed Mar 10 18:11:42 2010 +0300
summary:     4

Теперь имеется две головы, а default стала полноценной веткой.

Слив!

C:\hg\branches>hg merge 3

2 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

C:\hg\branches>hg commit -m "merge"

C:\hg\branches>

Вместо hg merge one я выпендрился и написал hg merge 3 с тем, чтобы опять указать на то, что one, это всего лишь «именование» ревизии #3 (последней в именованной ветке one).

Результаты:

C:\hg\branches>hg branches

default                        5:2657a30296ee
one                            3:5d8341509529 (inactive)

C:\hg\branches>hg heads

changeset:   5:2657a30296ee
tag:         tip
parent:      4:6ded3e912b46
parent:      3:5d8341509529
user:        go <>
date:        Wed Mar 10 18:39:03 2010 +0300
summary:     merge

Головы слились в одну. Так как мы «вливали» ветку one в ветку default, голова эта принадлежит default. Ветка one, таким образом, осталась вообще без головы и стала inactive.

Ветви с подветвями

Для чуть лучшего понимания (или окончательного запутывания), рассмотрим следующее извращение:

Мало того, что здесь у нас две именованные ветки, так ещё и в каждой из них, по две анонимные. То есть в итоге, у нас 4 головы (#3, #4, #5 и #6).

Итак, вопрос, на что указывают имена default и test? Ответ: они указывают на последние ревизии в своих ветках (соответственно, #5 и #6). Это видно в списке ветвей:

c:\hg\ch>hg branches

test                           6:3948f137a081
default                        5:b195b529cb3f

В какую ревизию будут вливаться изменения при merge? В ту, в которой находимся. Перейдём в #4 и смержимся с test.

c:\hg\ch>hg update 4

0 files updated, 0 files merged, 0 files removed, 0 files unresolved

c:\hg\ch>hg merge test

2 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

c:\hg\ch>hg commit -m "merge"

И что у нас получилось? А получилось вот что:

c:\hg\ch>hg branches

default                        7:753bb1ce4da0
test                           6:3948f137a081

c:\hg\ch>hg heads

changeset:   7:753bb1ce4da0
tag:         tip
parent:      4:75522fbdb615
parent:      6:3948f137a081
user:        go <>
date:        Wed Mar 10 19:15:19 2010 +0300
summary:     merge

changeset:   5:b195b529cb3f
parent:      1:ed719fdb06b3
user:        go <>
date:        Wed Mar 10 18:53:23 2010 +0300
summary:     5

changeset:   3:f4d1c7f1f1a0
branch:      test
user:        go <>
date:        Wed Mar 10 18:52:41 2010 +0300
summary:     3

Ушла только одна голова (#6) и одна голова сменила ревизию (#4 → #7). Все остальные остались, в том числе и одна из голов ветви test, за счёт чего, та продолжает быть активной.

Можно сделать ещё много подобных бессмысленных вещей…

Так то, ребятки.

18 комментариев »

  • Спасибо за статью. очень полезно.

    alexey_baranov, 11.03.2010, 9:58

  • Много букв, как-нибудь потом осилю

    adw0rd, 11.03.2010, 14:48

  • Зато много чёрно-белых картинок для расскрашивания

    vasa_c, 11.03.2010, 17:55

  • Только половина — это и не картинки вовсе =)

    inst, 12.03.2010, 0:56

  • а есть что-то по автоматическому деплою через меркуриал? И когда на удаленном сервере нет ssh а есть только ftp

    Абырвалг, 27.05.2010, 0:30

  • Круто. Прочитал все три части. Мне понравилось. Не узнав до конца SVN, познать философию веток в Mercurial — это действительно радость жизни!

    kostyl, 4.06.2010, 0:04

  • Спасибо за отличную статью! Теперь надеюсь буду уверенно создавать ветки;)

    loder, 22.06.2010, 16:05

  • Супер. Большое спасибо, а то после bzr слегка не въезжал и на heads, branches запутался.
    После статьи решил все свои проблемы :) И даже нашел парочку там где думал что проблем нет. :)

    roman, 9.07.2010, 8:15

  • Отличнейшая статья, в корне отличается от остальных. Во первых есть иллюстрации, во вторых все объяснено с примерами. Спасибо!

    Rastler, 2.10.2010, 0:23

  • Статьи хорошие. Всё детально и по полочкам. Спасибо.

    afgm, 24.11.2010, 21:20

  • Не еби мозга людям, читайте оригиналы:
    http://stevelosh.com/blog/2009/08/a-guide-to-branching-in-mercurial/

    pusher, 2.02.2011, 13:05

  • Наконец то на наше гнилое болото пришли знатоки.
    Поаплодируем — pusher!

    vasa_c, 2.02.2011, 13:35

  • Ура! Ура! Ура!

    kostyl, 2.02.2011, 14:16

  • Очень понятно описанно в картинках.
    Мне помогло разобратся с мержем.

    vl, 17.02.2011, 20:49

  • > а есть что-то по автоматическому деплою через меркуриал? И когда на удаленном сервере нет ssh а есть только ftp

    Смотри тут: http://lug.nsk.ru/lugnskru/2012/01/zagruzka-dannyih-po-ftp-s-ispolzovaniem-mercurial.html

    Green, 3.01.2012, 11:03

  • Материал Джоэла Спольски не охватывает бранчи. С помощью ваших статей смог нормально разобраться, до этого прочитал много всего. Спасибо большое, хорошее дело делаете.

    Игорь, 5.07.2012, 17:53

  • Игорь, спасибо на добром слове :)

    vasa_c, 5.07.2012, 18:50

  • У меня, почему-то, после hg merge, головы у веток не пропадают, а также послушно выводятся командой hg heads. Это кажется довольно логичным, ведь коммит после слияния веток попадает в ветку первого родителя, а в ветке мы можем продолжить разработку, т.к. там осталась голова. Бранч, при этом, помечается как inactive.

    stan, 20.06.2013, 8:21

Leave a comment