solovyov.net

Mercurial: расширения

10 min read · hg, vcs

Продолжение, см. также: Mercurial: введение в распределённые системы контроля версий, Mercurial: основы.

Несмотря на то, что Меркуриалом можно полноценно пользоваться, используя только встроенную функциональность, существует достаточно большое количество различных расширений, которые значительно увеличивают круг возможностей и решаемых задач. Немалая их часть входит в комплект поставки hg — все описанные, кроме TortoiseHG и QCT, так что начало их использования требует всего лишь включения в .hgrc (или Mercurial.ini в случае Windows) в таком виде:

[extensions]
hgext.<имя-расширения>=

alias

Особенно описывать тут нечего — позволяет делать псевдонимы для команд. Я вот пользуюсь пятью, для сокращения вывода:

[alias]
slog = log --template '{date|shortdate} [{rev}:{node|short}] {author|person}: {desc|firstline}\n' -l 10
sin = incoming --template '{date|shortdate} [{rev}:{node|short}] {author|person}: {desc|firstline}\n'
sout = outgoing --template '{date|shortdate} [{rev}:{node|short}] {author|person}: {desc|firstline}\n'
sheads = heads --template '{date|shortdate} [{rev}:{node|short}] {author|person}: {desc|firstline}\n'
sglog = glog --template '[{rev}:{node|short}] by {author|person} \n{desc|fill68}\n\n'

О шаблонах подробнее можно прочесть в предыдущей статье.

bisect

Перевод слова bisect — разрезать, делить пополам. Именно этим расширение и занимается — и, кстати, после версии 1.0 это больше не расширение, а встроенная команда — уж очень популярна. :-)

Использование крайне просто — мы обнаруживаем в текущей версии кода какую-то ошибку, которой раньше не наблюдалось (регрессию). И хорошо помнится, что вот 100 ревизий назад её точно не было. Помечаем ревизии как плохую и хорошую соответственно:

hg bisect -b
hg bisect -g -100

После этого bisect обновляет рабочую копию до состояния ровно посредине между наличием ошибки и её отсутствием. Мы проверяем, в каком состоянии находится код в данный момент, и вызываем hg bisect соотстветственно с флагами -b (bad) или -g (good). За несколько прыжков мы точно получаем ревизию, в которой была внесена ошибка.

patchbomb

Расширение, которое автоматизирует отправку патчей по email’у. Спрашивает описание, которое шлёт в первом письме серии (помеченном как [PATCH 0 of N], где N — кол-во патчей), и потом каждый патч — отдельным письмом (помеченное как [PATCH M of N], где M — номер патча). Может также вместо текстовых диффов слать бандлы — в моём предыдущем проекте вообще используется изменённая версия, которая одновременно шлёт бандл — для точного применения, и текстовый патч — для просмотра. Несмотря на своё название, расширение добавляет команду email.

graphlog и view

Два расширения, занимающихся практически одним и тем же — демонстрацией списка ревизий. От hg log они отличаются тем, что показывают ещё и граф изменений (со всеми ветками). hg glog — это консольная команда, hg view — это вызов внешней программы hgk, распространяющейся вместе с меркуриалом. Программа написана на Tcl/Tk и является прямым потомком gitk, аналогичной программы для git’а. Из возможностей кроме демонстрации графа ревизий и изменений в каждой из них может показывать изменения, произошедшие между двумя любыми ревизиями (и сгенерировать соответствующий патч).

record

Аналог commit, позволяющий зафиксировать отдельные кусочки изменений — полезно, когда есть большой файл с изменениями, которые неплохо бы разнести в две различных ревизии. При запуске hg record задаётся вопрос по каждому файлу — внести его в ревизию полностью, частично или пропустить. При выборе второго варианта будет задан о включении в ревизию по каждому кусочку (chunk) изменений в файле.

transplant

Другое название операции, которую выполняет transplant — cherry-pick: перенос изменения из другой ветки, из другой именованной ветки (named branch), из другого репозитория (в том числе репозитории могут быть не связанными друг с другом историей). Используется в тех случаях, когда какое-то исправление из нестабильной ветки должно быть применено в стабильной.

rebase

Тут я немного схитрил — на самом деле это расширение появится лишь в версии 1.1.0 (есть надежды, что появится в ближайшие несколько недель), а сейчас доступно лишь в ветке разработки, которая станет новой версией. rebase — результат Google Summer of Code. Суть команды в том, что при разветвлении репозитория она позволяет «переместить» ветку на голову другой, т.е. позволяет избежать ревизий, где происходит слияние двух веток. На деле полезность этого каждый определяет сам для себя — к примеру, многие пользователи git’а часто используют эту команду вместо слияния, и я в принципе ждал её реализации (честно говоря, я сам подумывал ей заняться одно время ;), но вот она вышла и оказалось, что я этой командой не пользуюсь вообще.

С другой стороны, в git’е я ею пользуюсь, но исключительно потому, что там нет встроенного аналога mq (mercurial queues — о них будет ниже). Использование довольно простое:

piranha@gtv ~/test>hg sglog
@  [3:01be576a255c] by Alexander Solovyov
|  upstream changes
|
| o  [2:ef01a8e462c3] by Alexander Solovyov
|/   local changes
|
o  [1:458e57460db8] by Alexander Solovyov
|  next
|
o  [0:15cb0004cf51] by Alexander Solovyov
   initial

piranha@gtv ~/test>hg rebase -s ef01 -d 01be
saving bundle to /home/piranha/test/.hg/strip-backup/ef01a8e462c3-temp
adding branch
adding changesets
adding manifests
adding file changes
added 2 changesets with 3 changes to 3 files
rebase completed

piranha@gtv ~/test>hg sglog
@  [3:bc22405c957a] by Alexander Solovyov
|  local changes
|
o  [2:01be576a255c] by Alexander Solovyov
|  upstream changes
|
o  [1:458e57460db8] by Alexander Solovyov
|  next
|
o  [0:15cb0004cf51] by Alexander Solovyov
   initial`</pre>

convert

Название говорит само за себя — расширение, которое занимается конвертированием репозиториев. В настоящее время поддерживает несколько входящих форматов — Mercurial, CVS, Darcs, git, Subversion, Monotone, Arch, и два исходящих — Mercurial и Subversion. Позволяет указывать маски файлов, которые необходимо включить или исключить из результирующего репозитория, потому иногда им пользуются для конвертирования из hg в hg с исключением каких-то файлов из истории. :)

Web

Основной интерфейс меркуриала к вебу — hgweb. Он достаточно небольшой — 100 килобайт (это вместе с языком шаблонов и прочей обвязкой) кода на питоне, юзабельный — можно его использовать и как cgi, и как wsgi-приложение (а значит, и FastCGI), имеет нормальные человекопонятные адреса (вида /file/tip/README), и предоставляет множество удобных вещей. Например, скачивание архива любой ревизии или отрисованный граф (аналог glog и view) прямо в вебе.

В то же время есть отличный плагин к trac’у — TracMercurial, насколько я знаю, на текущий момент лучший плагин для VCS, кроме родного траку Subversion. Для Trac 0.10 он достаточно простенький (т.е. основная функциональность работает, но на то она и основная), для trac 0.11 он уже может больше — именованные ветки, теги в адресе, поддержка annotate и возможно что-то ещё.

GUI

Для Mercurial существует графический клиент — TortoiseHg. В основном он ориентирован на Windows, но есть кое-какая интеграция с GNOME (точнее с Nautilus’ом). Умеет клонировать, мержить, показывать лог ревизий и т.п. — я им не пользуюсь, потому подробно описать не могу.

Ещё есть qct (Qt Commit Tool) — умеет не только hg, но и другие VCS (git, monotone, bazaar, subversion, cvs). Довольно приятный интерфейс, когда надо выбрать файлики для коммита (я им иногда пользуюсь ;), в том числе может выбирать только часть изменений из файла (т.е. делать то же самое, что делает hg record).

mq

И на десерт я оставил самое сладкое, самое большое расширение меркуриала — mercurial queues. Я здесь не буду раскрывать эту тему до мельчайших подробностей — всё-таки она достаточно обширна, заняла целых две главы в hgbook (раз и два), но об основах расскажу.

Основной сценарий работы с mq: существует какая-то программа, которую хочется улучшить. При работе, например, с svn — просто исправляются файлы в рабочей копии, а потом отправляется авторам по почте diff рабочей копии и последней ревизии. Но такой механизм имеет кучу неудобств, первым из которых является невозможность нормально разделять два (и больше) исправления. В меркуриале, конечно, можно просто закоммитить изменения локально, и отправить их автору, но всегда есть вероятность, что какой-то из патчей исправят, а какой-то, возможно, не примут. И тогда получится, что в репозитории сидят по соседству две почти идентичных ревизии, либо новая ветка, где одна из ревизий пропущена.

И тут на помощь приходит mq. :) Он помечает ревизии (не сам помечает, конечно, ему нужно указывать ;) особым образом и может их «выдёргивать» из репозитория (сами патчи хранятся в .hg/patches/). Работает он только с сериями патчей (последовательно расположенными ревизиями), при этом на патч нельзя поместить обычную ревизию (иначе получается не совсем понятно, что же делать с ревизией, когда патч выдёргивают из репозитория). Все эти патчи легко изменяются, но вместо объяснения я лучше продемонстрирую, как это выглядит.

У нас есть репозиторий с несколькими ревизиями:

piranha@gtv ~/test>hg slog
2008-10-02 [1:458e57460db8] Alexander Solovyov: next
2008-10-02 [0:15cb0004cf51] Alexander Solovyov: initial

Мы делаем какие-то изменения и создаём новый патч:

piranha@gtv ~/test>echo "some changes" > c
piranha@gtv ~/test>hg add c
piranha@gtv ~/test>hg qnew -f -m "our patch" our.patch

Команда qnew создаёт новый патч с названием our.patch, описанием “our patch” и заносит туда все изменения рабочей копии (-f):

piranha@gtv ~/test>hg log -r tip
changeset:   2:fef5d05adfa0
tag:         qtip
tag:         our.patch
tag:         tip
tag:         qbase
user:        Alexander Solovyov
date:        Thu Oct 02 12:54:18 2008 +0300
summary:     our patch
piranha@gtv ~/test>ls .hg/patches
our.patch  series  status

Как видно, mq дополнительно устанавливает несколько тегов на свои ревизии, где qbase — первая из применённых ревизий, qtip — последняя из применённых ревизий.

Кстати, о способе хранения патчей. В директории .hg/patches/ видно ещё два файла. Файл series содержит список (по порядку) патчей, которые у нас есть, а status — список патчей, применённых к репозиторию. При этом порядок патчей можно поменять простым редактированием файла series (желательно, чтоб в этот момент они не были применены к репозиторию ;).

Предположим, в апстриме произошли какие-то изменения, и нам надо адаптировать патч под них. Для этого мы выдёргиваем патч, до или после вытягивания изменений (это не важно):

piranha@gtv ~/test>hg qpop
Patch queue now empty
piranha@gtv ~/test>hg pull -q -u ../upstream
piranha@gtv ~/test>hg qpush
applying our.patch
Now at: our.patch
piranha@gtv ~/test>hg slog
2008-10-02 [3:4b1960ecfa1a] Alexander Solovyov: our patch
2008-10-02 [2:3189f2f2bdb8] Alexander Solovyov: third revision
2008-10-02 [1:458e57460db8] Alexander Solovyov: next
2008-10-02 [0:15cb0004cf51] Alexander Solovyov: initial

В чём же суть? В том, что мы в любой момент можем изменить патч — этим он и отличается от обычной ревизии. Изменили что-нибудь? Обновим патч:

piranha@gtv ~/test>hg diff
diff --git a/c b/c
--- a/c
+++ b/c
@@ -1,1 +1,1 @@
-some changes
+some change - yeah
piranha@gtv ~/test>hg qref
piranha@gtv ~/test>hg slog -r our.patch -p
2008-10-02 [3:9c80951d796e] Alexander Solovyov: our patch
diff --git a/c b/c
new file mode 100644
--- /dev/null
+++ b/c
@@ -0,0 +1,1 @@
+some change - yeah

qref — это сокращение от qrefresh, команда, которая обновляет патч. Изменять она может всё — пользователя (-u), дату (-d), описание (-m — из команды, -e — интерактивно, -l — из файла), включать/исключать файлы (либо по именам, либо с помощью -I и -X).

Можно добавить ещё один патч:

piranha@gtv ~/test>echo "something new" > s
piranha@gtv ~/test>hg add s
piranha@gtv ~/test>hg qnew -fm "second patch" second.patch
piranha@gtv ~/test>hg log -r qtip:qbase
changeset:   4:e82cd0bf259d
tag:         qtip
tag:         second.patch
tag:         tip
user:        Alexander Solovyov <<a href="mailto:piranha@piranha.org.ua">piranha@piranha.org.ua</a>>

date:        Thu Oct 02 14:21:07 2008 +0300
summary:     second patch

changeset:   3:9c80951d796e
tag:         our.patch
tag:         qbase
user:        Alexander Solovyov <<a href="mailto:piranha@piranha.org.ua">piranha@piranha.org.ua</a>>
date:        Thu Oct 02 14:16:26 2008 +0300
summary:     our patch

А потом, если вдруг окажется, что два патча — это излишество, их можно слить в один:

piranha@gtv ~/test>hg qpop
Now at: our.patch
piranha@gtv ~/test>hg qfold -m "merged patch" second.patch
piranha@gtv ~/test>hg slog -r qtip
2008-10-02 [3:9412b2fed86f] Alexander Solovyov: merged patch

Ну и, в конце концов, отправить патчи на рассмотрение:

piranha@gtv ~/test>hg email -r qbase:qtip -t author@project.com

Естественно, на этом возможности mq не заканчиваются, стоит лишь посмотреть на hg help mq — самые заметные, про которые я не упоминал, это стражи (guards, возможность автоматически применять или не применять патч в зависимости от условий) и версионирование патчей (тут всё просто, .hg/patches/ становится обычным репозиторием). Но про них лучше читать в hgbook’е, благо там формат позволяет куда более подробные описания.

If you like what you read — subscribe to my Twitter, I always post links to new posts there. Or, in case you're an old school person longing for an ancient technology, put a link to my RSS feed in your feed reader (it's actually Atom feed, but who cares).

Other recent posts

Server-Sent Events (SSE), but with POST
ngrok for the wicked, or expose your ports comfortably
PostgreSQL collation
History Snapshotting in TwinSpark
Code streaming: hundred ounces of nuances