вторник, 11 сентября 2012 г.

svn и mercurial

svn

Я лет 5 сижу на работе на svn и тортилле. И все мне в них нравится, кроме того, что информация о мержах нигде наглядно не отображается. 5 лет назад svn вообще не умел хранить такую информацию и я завел привычку в комментарии к мержу указывать, с чем именно был сделан мерж. Примерно так:
[m657-549] Слито с веткой хххх
Это здорово выручает при всяких выяснениях. Проблема только в том, что чтобы получить наглядное представление о слияниях, надо брать карандаш и бумагу и рисовать интересующие ветки и мержи. Что задалбывает.
В svn 1.5 (а может и более позднем) появилось хранение информации о мержах в свойствах. Однако не знаю почему, но тортилла так до сих пор и не научилась показывать эту информацию на графе. Единственное, что она умеет с ней делать - при выборе ревизий для мержа уже смерженные "засериваются". Это приятно, но мало. Еще одна проблема с таким хранением - не знаю уж почему, но иногда эта инфа распространяется из корневой папки в дочерние папки и файлы. При этом при каждом мерже показывается куча объектов, у которых поменялись только свойства, а не содержимое. А так как фильтрануть по этому признаку можно не во всех диалогах, то это весьма надоедает.
Еще я иногда езжу в командировки, а иногда работаю дома (а иногда админы роняют сервер :) ). При этом хотелось бы также иметь доступ к своим репозитариям.

dvcs

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

Базар

Базар вроде как понавороченнее. Тут и легковесные ветки, куча форматов репа, "хитрые" номера ревизий, показывающие, откуда пришел мерж.
Однако его тортилла как-то не фонтан. Стартовое окно не сильно полезное, периодически вылетает, заставить ее использовать tortoise diff/merge я так и не смог. По дефолту во встроенном просмотрщике диффов используется utf-8, в то время как у меня в основном cp1251. Причем даже если поменять руками, то просмотрщик не сохраняет этих изменений. Сравнивать ворд или опенофис в отличие от tortoise svn не умеет. Обновление иконок сильно запаздывает. А когда выяснилось, что в путях с русскими буквами не пашет сравнение, причем это судя по моим изысканиям, бага самого базара, я в нем окончательно разочаровался.

Меркуриал

Меркуриаловская тортилла мне глянулась значительно больше. Падает она редко. По умолчанию открывается страница с журналом. Офисовские файлы сравнивать умеет (как я понял, скрипты для этого они позаимствовали у svn). Тортилловские diff и merge настроились влет. С русскими буквами проблем нет.
Не нравится только ее политика в отношении длинных сообщений - при показе в журнале она их режет до 80 символов (даже если место на экране еще есть), а при просмотре в окне всего сообщения не переносит по границам слов.
Но в принципе ладно, я согласен в случае длинного сообщения делать отдельную "выжимку", а после нее писать само сообщение.
Далее встал вопрос, как хранить центральные репозитарии. На сайте меркуриала обнаружилась масса полезных ссылок, в частности SCM-manager. Последний написан на яве и влет встал на уже имеющийся у меня на работе tomcat. Там же лежит ссылка на RhodeCode, который судя по скринам, понавороченней. Но с ним у меня пока не заладилось - при установке он попытался чего-то скомпилировать, компилятора на сервере нет, и я пока на него забил.
SCM-manager умеет раздавать mercurial и svn репозитарии, а также показывать их в браузере. Также для него можно настроить http-аутентификацию (что я легко сделал) и, судя по доке, относительно легко - ssl (на что я пока забил).

Нажитое

 Остался последний вопрос - что делать с уже имеющейся историей. В свое время при переходе с cvs на svn я истории лишился. Нормально перенести ее у меня не получилось, решил начать с нуля, а если что - смотреть историю в cvs. Однако после переустановки винды cvs-тулы я просто не поставил, а через некоторое время прочно забыл, как ими пользоваться и где вообще лежал мой реп :)
Так что теперь я решил все сделать по человечески. 
Меркуриал вроде как заявляет работу с svn с помощью встроенного расширения convert. Однако реп у меня устроен не по классической схеме - с иерархией в ветках и тегах, с несколькими транками, иногда изменения делались прямо в тегах, проектов в нем штук 15, и лежат они в одном общем транке. Кроме того, я отмечал мержи и не хочу их лишиться.
С помощью convert мне удалось максимум вытащить ветку, причем начиная с момента ее создания (т.е. до начального ее копирования из другой ветки).
Тогда я вспомнил, что базар тоже работает с svn и попробовал конвертацию свн->базар->меркуриал. Эта схема оказалась лучше - мне удалось скопировать ветку целиком (начиная с ревизии 1). Однако это все равно не то, что мне надо.
Тогда я решил написать свой собственный конвертер. Писал я его на питоне. Вроде как все несложно - с svn я работаю через pysvn (скомпилированная версия для винды есть на офсайте), а с меркуриалом - просто через командную строку.
Меркуриал позволяет при коммите указать дату и пользователя. Осталось только заново "проиграть" всю историю.
Однако вожусь я с этим вот уже третью неделю (в свободное от основной работы время, но все же). Вылезла масса всяких нюансов - переименование тегов, положенные в svn бинарные файлы (я  сдуру когда-то запихал туда инсталлированный компилятор :) ), ветки, созданные из тегов, ошибки при коммитах, ....
Так что программа-конвертер разрослась уже за 1000 строк и местами весьма запутанна. Кроме того, прилагается немаленький файл с исключениями :)
Хорошей идеей оказалось прикрутить diff и в ключевых точках (например в тегах) делать автоматическое сравнение рабочих копий hg и svn. Еще одна хорошая идея - с шагом в несколько ревизий делать бэкапы репозиториев hg - чтобы при ошибках начинать не с начала. 
Пока удалось сконвертить чуть больше четверти репа, посмотрим что вылезет дальше



пятница, 8 июля 2011 г.

Перечисления

В последнее время мне полюбился вот такой способ задания всякого рода перечислений:
1 #define SOME_ENUM_PRINT(P) \ 2 P(eNone, 0, "Нет") \ 3 P(eCmd57_Ok, 1, "Команда 57 ОК") \ 4 P(eCmd57_Cancel, 2, "Команда 57 Отмена") \ 5 P(eCmd58, 4, "Команда 58") \ 6 P(eTimer, 8, "Таймер") \ 7 P(ePrintOk, 16, "Печать завершена") \ 8 P(ePrintError, 32, "Ошибка печати") 9 10 enum ESomeEnum { 11 #define P(n, no, s) n = no, 12 SOME_ENUM_PRINT(P) 13 #undef P 14 }; 15 16 // а потом можно вот так: 17 const char * str(ESomeEnum e) 18 { 19 switch(e) 20 { 21 #define P(n, no, s) case n: return s; 22 SOME_ENUM_PRINT(P) 23 #undef P 24 default: return "Unknown"; 25 } 26 }

Понятно, что кроме текста, можно добавить произвольную информацию.  С помощью такого макроса легко строить всякие таблицы, создавать куски кода, работающие с каждым членом перечисления и т.д. Как бонус, если изменить перечисления, то многие связанные с этим вещи обновятся автоматически. Ну и последнее - вся информация в одном месте.
При этом все довольно просто и понятно Улыбка На рсдн была статья, в которой описывалось как создавать “строгие” перечисления и ассоциировать с ними доп инфу. Однако код в ней значительно сложнее Улыбка

среда, 29 июня 2011 г.

Склероз

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

А потом, через полгода-год старая задача снова становится актуальной. Но я уже почти все о ней забыл. Остались какие-то смутные воспоминания и рабочие записи, в которых теперь ничего не понятно. И снова все начинается заново – читаешь доки, наступаешь на те же грабли. Что-то конечно вспоминается, но далеко не все.

Надо делать подробные записи, пока ты в теме. Но пока ты в теме, все и так понятно, не охота тратить время на ненужную писанину…

Видимо надо завести правило – если работа над задачей остановилась больше чем на неделю, то надо потратить день-два и подробно (очень подробно) все записать по горячим следам.

Кстати, рекомендую http://trac.edgewall.org/ – хорошая система учета доки, багов, записей и заодно просмотр коммитов в системе контроля версий. Без проблем ставится под виндоуз, причем в простейшем варианте не требуется веб-сервер.

пятница, 3 июня 2011 г.

Scons tools

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

SCons скрипты часто используют какие-то общие части. Раньше я такие части выносил в отдельные питоновские модули и подключал их через import в билд-скрипт. Однако так как эти модули лежали не рядом со скриптом, и не в PYTHONPATH, возникал ужас вида

 
1 import os
2 import sys
3 from os import path
4
5 sys.path.append(path.normpath(path.join(os.getcwd(), "..", "common", "build")))
6
7 from myModule import SomeFunc

Так вот, тулз – это просто модуль питона, который содержит две функции – generate(env, **kw) и exists(env, **kw).


Первая функция вызывается при подключении тулза к окружению и позволяет добавить к окружению произвольные методы, переменные и т.д.


Теперь все становится очень просто:



1 e = Environment(tools=('MyTool', ), toolpath=('../common/build',))
2 e.SomeFunc(...)


Ну и сам тулз выглядит вот так:



1 def generate(env):
2 env.AddMethod(SomeFunc)
3
4 def exists(env):
5 return 1
6
7 def SomeFunc(env):
8 pass


Полезно читать доку Улыбка

вторник, 15 марта 2011 г.

Я люблю c++

1 void f()
2 {
3 int t;
4 SomeType obj(OtherType(t));
5 obj.method();
6 }

Что такое obj? Казалось бы – переменная типа SomeType. Однако на самом деле – это объявление функции SomeType obj(OtherType t);


Причем на строку 5 MSVC даст вот такую ошибку:


error C2228: left of '.method' must have class/struct/union


Уже не первый раз напарываюсь, но в ступор все равно вгоняет на пару минут…


Чтобы работало, надо заключить OtherType в скобки:


SomeType obj( (OtherType) (t));


Получается так потому, что:


1. Разрешены локальные объявления функций – слабо понятно зачем такое вообще может кому-нить понадобиться.


2. int (t) – это вполне корректное определение переменной t типа int – опять же слабо понятно почему разрешен такой синтаксис.


 

понедельник, 14 марта 2011 г.

shared_ptr vs auto_ptr

Был такой код:

struct IInterf
{
virtual void func() = 0;
};
boost::shared_ptr
<IInterf> PInterf;

PInter createObj();

Через некоторое время стало ясно, что у объекта будет конкретный владелец и я заменил shared_ptr на auto_ptr.


И словил access violation при завершении программы.


Минут через 5 сообразил, что деструктор не виртуальный; shared_ptr динамически создает “удалятель”, зная истинный тип IInterf, а auto_ptr просто зовет delete p.


Когда я писал IInterf, то подумал что он будет всегда обернут в shared_ptr и решил не писать виртуальный деструктор.


Мораль сей басни – не экономьте на спичках :)

пятница, 11 марта 2011 г.

boost::exception

Недавно потребовалось как-то упорядочить исключения в программе. Посмотрел в сторону boost::exception, благо boost  в проекте уже используется.

Библиотечка позволяет решить две проблемы:

  1. Добавлять к исключению любого типа (производного от boost::exception) произвольную информацию, причем не только в месте выброса, но и на более высоких промежуточных уровнях обработки
  2. Клонировать текущее исключение, сохранять его и выкидывать в другом месте, а также “вкладывать” в другое исключение.

Пока остановимся на первом пункте, о втором я расскажу в очередном посте.

Сохранение в исключении произвольной информации

Выглядит примерно так:

1 typedef boost::error_info<struct errinfo_str_, std::string> errinfo_str; // 1
2 typedef boost::error_info<struct errinfo_file_, Handle> errinfo_file;
3
4 ...
5 try
6 {
7 try
8 {
9 throw MyException() << errinfo_file(hfile); // 2
10 }
11 catch(MyException &e)
12 {
13 e << errinfo_str("bla-bla"); // 3
14 throw;
15 }
16 }
17 catch(const boost::esception &e)
18 {
19 std::cout << diagnostic_information(e); // 4
20 if(std::string const * inf=get_error_info<errinfo_str>(e) ) //5
21 std::cout << "Info: " << *inf << "\n";
22 }
23
24

Основная идея – мы можем поэтапно добавлять к исключению информацию. В момент выкидывания исключения часть информации может быть недоступна; она может быть добавлена на верхних уровнях и т.д.


В строке //1 определяется описание пользовательской информации, которую можно сохранить в исключении. Первый аргумент error_info – это уникальный тип, второй – тип данных, который должен храниться в исключении (кстати, вы знали, что можно объявить структуру внутри шаблона? Я – нет).


В строке //2 выкидывается исключение, в котором сохраняется информация о файле.


В строке //3 к перехваченному исключению добавляется новая информация.


В //4 используется функция boost::diagnostic_information, которая формирует строку с описанием исключения и всей присоединенной к нему информации. Строка не особо человеко-читаема и пользователю ее лучше не видеть. Может быть полезно например для сохранения информации в лог.


В //5 происходит проверка на наличии в исключении данных errinfo_str и если они есть, выдается соответствующая строка.


Иерархия исключений


С учетом такой техники классы исключений становятся простыми "маркерами", не содержащими данных или методов. Каждый пользовательский класс исключений наследуется от boost::exception и возможно от std::exception. Чтобы пользовательские исключения могли использовать множественное наследование друг от друга, авторы библиотеки рекомендуют виртуальное наследование.


Удобно также завести свой собственный базовый класс исключений.


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


1 struct MyException : virtual boost::exception, virtual std::exception
2 {
3 const char* what()const throw() { return boost::diagnostic_information_what(*this); }
4 };
5
6 struct MyFileException : virtual MyException {};
7 struct MyNetException : virtual MyException {};
8 struct MyComplexException : virtual MyFileException, virtual MyNetException {};

Использование


В библиотеке определен макрос BOOST_THROW_EXCEPTION, который выкидывает указанное исключение, добавляя к нему имя функции, имя и строку файла. На самом деле он не выкидывает исключение, а вызывает boost::throw_exception. Поведение последней настраивается опциями компиляции, по умолчанию она оборачивает исключение для возможности клонирования.


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


Впечатления


Библиотечка удобна в использовании, относительно невелика и понятно написана.


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


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