20091028

Переименование переменных и слияние изменений в Darcs

Ныне к традиционным холиварам, вроде vi против emacs, прибавился ещё hg (Mercurial) против git. И то, и другое — распределённые системы управления версиями (DVCS). В чём их преимущество перед старыми централизованными системами и как пользоваться новыми давно уже написано. Впрочем, выбор этими двумя системами не ограничивается, отдельные маньяки успешно пользуются и другими системами. А среди альтернативных систем совершенно особняком стоит darcs.

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

Основное отличие darcs от собратьев: он отслеживает не состояние каталога с файлами (и историю его изменений), а хранит сами изменения — патчи. А уж состояние рабочего каталога определяется просто как результат применения всех накопленных изменений-патчей. Всякое такое изменение обратимо, а некоторые можно безболезненно переставлять местами (и это очень облегчает слияния).

В случае обычных DVCS, каждое изменение определяется разницей двух состояний каталога. Чтобы объединить такие изменения, нужен их общий «предок», к которому изменения можно применить. В darcs изменение не обязательно должно определяться разницей между двумя состояниями каталога. Это позвляет создавать разные типы изменений, и автор может определить что именно изменение делает (семантически). В том числе есть и такой вид изменений: замена слов в файле (token replace patch).

Покажу, как это работает, а вы уж сами судите, насколько это круто :-)

Завязка



Итак, создадим вначале исходный репозиторий и поместим в него простую программку. Тут отличия между darcs и hg или git минимальны:
$ mkdir repo-0
$ cd repo-0
repo-0$ darcs init
repo-0$ cat > hello.py
def hello(what):
print "Hello %s" % what

hello("World")
^D

repo-0$ darcs add hello.py
repo-0$ darcs record -m 'initial commit' hello.py
Recording changes in "hello.py":

addfile ./hello.py
Shall I record this change? (1/2) [ynWsfvplxdaqjk], or ? for help: y
hunk ./hello.py 1
+def hello(what):
+ print "Hello %s" % what
+
+hello("World")
Shall I record this change? (2/2) [ynWsfvplxdaqjk], or ? for help: y
Finished recording patch 'initial commit'


А теперь создадим две ветки. В каждой ветке сделаем свои изменения. В одной (A) изменим название переменной what на name, а в другой (B) переименуем и перепишем функцию hello().

Внезапно!



Клонируем исходный репозиторий:
repo-0$ cd ..
$ darcs get repo-0 repo-A
Copying patches, to get lazy repository hit ctrl-C...
Finished getting.

И переименовываем в этой ветке переменную. Только хитрость, мы хотим не просто сделать замену слов в файле, а мы хотим явно указать darcs-у, что это именно замена слов. Поэтому вместо текстового редактора выполняем такую вот команду:
$ cd repo-A
repo-A$ darcs replace what name hello.py

Убеждаемся, что программка изменилась:
repo-A$ cat hello.py
def hello(name):
print "Hello %s" % name

hello("World")

И записываем изменения в репозиторий:
repo-A$ darcs record -m 'renamed: what to name' hello.py
Recording changes in "hello.py":

replace ./hello.py [A-Za-z_0-9] what name
Shall I record this change? (1/1) [ynWsfvplxdaqjk], or ? for help: y
Finished recording patch 'renamed: what to name'


Тем временем...



Параллельно создаём другую ветку и как-нибудь меняем функцию hello:
repo-A$ cd ..
$ darcs get repo-0 repo-B
Copying patches, to get lazy repository hit ctrl-C...
Finished getting.
$ cd repo-B
repo-B$ cat > hello.py
def hello(what):
if len(what) > 6:
print "Hello %s" % what
else:
print "Hi %s" % what

hello("World")
^D

Изменения настолько серьёзны, что старое имя функции уже не подходит. Переименовываем её с помощью darcs replace:
repo-B$ darcs replace hello greet hello.py

И записываем изменения:
repo-B$ darcs record -m 'changed hello and renamed to greet' hello.py
Recording changes in "hello.py":

hunk ./hello.py 2
- print "Hello %s" % what
+ if len(what) > 6:
+ print "Hello %s" % what
+ else:
+ print "Hi %s" % what
Shall I record this change? (1/2) [ynWsfvplxdaqjk], or ? for help: y
replace ./hello.py [A-Za-z_0-9] hello greet
Shall I record this change? (2/2) [ynWsfvplxdaqjk], or ? for help: y
Finished recording patch 'changed hello and renamed to greet'


Кровавый финал



А теперь возвращаемся в исходный репозиторий и объединяем изменения:
repo-B$ cd ../repo-0
repo-0$ darcs pull ../repo-A ../repo-B
Wed Oct 28 17:21:41 CET 2009 me@example.com
* changed hello and renamed to greet
Shall I pull this patch? (1/2) [ynWsfvplxdaqjk], or ? for help: y
Wed Oct 28 17:12:12 CET 2009 me@example.com
* renamed: what to name
Shall I pull this patch? (2/2) [ynWsfvplxdaqjk], or ? for help: y
Finished pulling and applying.

И что же мы видим?
repo-0$ cat hello.py
def greet(name):
if len(name) > 6:
print "Hello %s" % name
else:
print "Hi %s" % name

greet("World")

Изменения объединились правильно. Система управления версиями оказалась достаточно умной, чтобы применить изменения в нужном порядке (вначале переписать функцию, а уж потом переименовать все случаи использования переменной).

Я впечатлён.

20091016

Микросоветы

Всё чаще в твиттер
одной строкой пост целый
пишу на память.

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

Приёмы работы
LaTeX и вёрстка
Программирование
Находки (всякие программки)

1. Приёмы работы:

  • Чтобы не закрывать Firefox, когда закрывается последняя вкладка по Ctrl-W: идём в about:config, находим browser.tabs.closeWindowWithLastTab, ставим false. Проверено на FF 3.5.
  • OpenOffice: чтобы запретить разрыв слова (т.е. запретить перенос), вставляем нечитаемый символ U+2060 (Zero-width WORD JOINER). Символ можно найти, запустив gucharmap. Надо в .XCompose добавить...
  • Чтобы использовать новый, сжимающий раза в два лучше, видео-кодировщик Theora 1.1, нужно взять саму новую библиотечку (уже есть в Debian unstable), и, главное, ffmpeg2theora версии не ниже 0.25. На сайте разработчиков есть и бинарные сборки.
  • Принудительное отключение подсветки ЖК-дисплея: xset dpms force off. Отсюда.
  • Банальность. Удаление пустых строк sed-ом: sed '/^\s*$/d'.
  • Редактируя диаграммы Graphviz в Vim, быстрый просмотр по :make можно сделать так: :set makeprg=dot\ -Tpng\ %\ \\\|display\ png:- errorformat='' autowrite. Подставить название используемой программы (dot, neato, fdp, ...).
  • Создание паролей (если нет KeePassX): cat /dev/urandom | tr -d -c 'a-zA-Z0-9' | fold -w 8 | head -1
  • Поиск и удаление дубликатов файлов: fdupes в командной строке, fslint — утилита с графическим интерфейсом.
  • В Debian можно заменить файл пакета, не пересобирая пакет. Поможет dpkg-divert.
  • sudo -i имитирует логин под рутом (даёт #). Бывает полезно (раньше sudo su - иногда пользовался).
  • Как создавать картинки предварительного просмотра видеофайлов:
    ffmpeg -itsoffset -1 -i видеофайл.avi -vcodec mjpeg -vframes 1 -an -y -f rawvideo -s 320x240 картинка.jpg ; done
    Как создавать картинки из PDF:
    convert -thumbnail 300x300 документ.pdf[0] -gravity center -extent 300x300 картинка.png


2. LaTeX и вёрстка:

  • Рекомендуемая минимальная ширина полей, чтобы документ можно было печатать и на A4, и на Letter — А4, слева и справа 20 мм, сверху и снизу 33 мм. RFC 2346.
  • Чтобы избежать разрыва страницы в LaTeX, можно поместить фрагмент текста в окружение samepage. Это частый вопрос.
  • Отступы элементов списка в LaTeX можно настроить, если использовать окружение list вместо itemize. Пример.
  • Чтобы добавить межабзацный пробел, \setlength{\parskip}{10pt plus 1pt minus 1pt}. Особенно полезно в наборе без абзацного отступа. Отсюда.
  • Чтобы выравнять картинку и текст справа от неё по вертикали, по середине, повозившись, сделал себе макрос \sidebyside{}{}:
    \newsavebox{\leftbox}\newlength{\leftboxheight}\newcommand{\sidebyside}[2]{\sbox{\leftbox}{#1}\settoheight{\leftboxheight}{\usebox{\leftbox}}\usebox{\leftbox}\raisebox{0.5\leftboxheight}{#2}}
    Смотрите пример использования.
  • Чтобы автоматически закрывать окружения LaTeX, пользователи Vim могут поставить плагин tex_autoclose. Использование: в режиме вставки Ctrl+\, затем c.
  • Разрезать на страницы и «склеивать» PDF-документы можно с помощью pdftk. Объединить два файла в один:
    pdftk первый.pdf второй.pdf cat output новый.pdf


3. Программирование:

  • В Python, примитивное транспонирование списка пар в пару списков:
    unzip = lambda pairs: zip(*pairs)
    @vlasovskikh подсказал, что для больших списков izip будет быстрее (проверили, так).
  • Занятное и доходчивое объяснение «что такое продолжения» на 11-й минуте видеопрезентации Swarm-dpl.
  • Быстро создавать графический интерфейс для научных программок позволяет библиотечка TraitsUI (Python). Пока не пробовал, но прочитал урок по TraitsUI.
  • Говорят, Intel готовит Concurrent Collections и для Хаскеля.
  • Я же пока проснулся и прочитал про со-процедуры на Си и устройство Даффа. Впечатлился.
  • Хотите полюбоваться, как можно добавлять побочные вычисления «наследованием» типов? Вот, пожалуйста, в этом примере (на Хаскеле). Хотя это, конечно не Java.
  • Учился использвать монадные трансформеры (бррр!) — оказалось несложно. В результате получился такой пример использования StateT поверх IO. Может кому пригодится.
  • Мелкое копирование словарей в Python — грабли.


4. Находки (всякие программки):

  • Atrack — анонимный открытый битторент трекер для Google App Engine. Всего 246 строк кода.
  • Sweet Home 3D — программа для планирования интерьера. Можно рисовать планы комнат, расставлять мебель, крутить по всякому. Сделана красиво.
  • fuse-zip — файловая система FUSE для монтирования zip-архивов. Быстрая, легко собирается по make, умеет писать в архив. Использование:
    fuse-zip архив.zip /точка/монтирования
    Есть также avfs, которая монтирует любые архивы, но не пишет и не такая удобная. Её использовать так:
    mountavfs ; ls ~/.avfs/полный/путь/к/архиву.zip#/файл/в/архиве
    В Debian нужно предварительно добавить пользователя в группу fuse.
  • Python(x,y) — дистрибутив Python для научных работников, для Windows и Ubuntu. Все инструменты и библиотечки «из коробки».
  • В дополнение к своему однострочнику antiodt нашёл ещё хороший конвертер ODT в Markdown odt2txt.py.
  • Дружественный к Гному вариант Xmonad — Bluetile. Раз попробовал, и две недели им пользовался.
  • Попробовал gitit. Самая простая вики для совместной работы над математическими текстами (вместе с jsMath из коробки). Хранилище — git или darcs.
  • TxtSushi — утилитки, позволяющие выполнять SQL-запросы по простому текстовому (CSV, TSV) файлу.


Ух-ты, а немало получилось.