Brain IT!
О программировании и около
суббота, 31 декабря 2011 г.
Новогодний привет
пятница, 22 апреля 2011 г.
Управление сессиями NHibernate в приложениях ASP.NET MVC
Здравствуйте, уважаемые читатели!
В этой статье я хочу ответить на типичный вопрос разработчика, начинающего использовать NHibernate в web-приложениях, разрабатываемых на основе ASP.NET MVC - как управлять сессиями и конфигурацией NHibernate в рамках веб-приложения. Это первая проблема, которая встречается разработчику, и для того, чтобы не потерять производительность, и не получить странных трудновоспроизводимых ошибок необходимо корретно реализовать этот механизм. В сети я находил несколько разных версий, и в этой статье я приведу ту, которая показалась мне наиболее удобной.
Итак, сначала немного теории. Как говорит вся документация на NHibernate - создавать конфигурацию и фабрику сессий затратная по времени операция, в то время как создавать сессию операция относительно быстрая. Таким образом, необходимо, чтобы в нашем приложении, конфигурация создавалась как можно реже, и была одна фабрика сессий, а сессии создавались для каждого HTTP запроса. Создавать больше одной сессии для HTTP запроса не имеет особого смысла.
Таким образом, самое подходящее место для конфигурирования и создания фабрики сессий - это обработчик Application_Start. Я использую DI-контейнер LinFu, но он может быть с легкостью заменен любым другим. Я думаю семантика выполняемых действий будет ясна из приведенного кода.
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); var serviceContainer = new ServiceContainer(); serviceContainer.AddService(CreateNhSessionFactory()); ServiceContainerProvider.Init(serviceContainer); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } protected ISessionFactory CreateNhSessionFactory() { var sessionFactory = Fluently.Configure() .Database( MsSqlConfiguration.MsSql2008.ConnectionString( x => x.FromConnectionStringWithKey("ApplicationServices")) ) .Mappings(x => x.FluentMappings.AddFromAssemblyOf<Issue>()) .BuildSessionFactory(); return sessionFactory; }
Думаю из кода видно, что в Application_Start конфигурируется NHibernate, создается фабрика сессий и помещается в DI контейнер. По умолчанию LinFu использует поведение типа Singleton (единственный объект на все приложение), если при регистрации сервиса передается конкретный объект. Итак, я добился того, что у меня будет одна фабрика сессий для всего ASP.NET MVC приложения. Замечу, что приложение ASP.NET - это отдельная тема для обсуждения, но как минимум следует знать, что в одном приложении могут обрабатываться тысячи запросов, создает и уничтожает приложение IIS в соответствии с настройками. Теперь нужно сделать так, чтобы у нас на один запрос была только одна сессия, которая будет использоваться всеми классами слоя доступа к данным.
На самом деле нет ничего проще. Достаточно вспомнить, что есть структура данных привязанная к конктретному запросу - HttpContext. Ей и воспользуемся, для хранения сессии в рамках обработки одного HTTP запроса:
protected const string NH_SESSION_KEY = "NH_REQUEST_SESSION"; protected ISession GetSession() { if (HttpContext.Current.Items.Contains(NH_SESSION_KEY)) return HttpContext.Current.Items[NH_SESSION_KEY] as ISession; var session = ServiceContainerProvider.Container .GetService<ISessionFactory>() .OpenSession(); HttpContext.Current.Items.Add(NH_SESSION_KEY, session); return session; } protected void CloseSession() { if (!HttpContext.Current.Items.Contains(NH_SESSION_KEY)) return; var session = HttpContext.Current.Items[NH_SESSION_KEY] as ISession; session.Flush(); session.Close(); session.Dispose(); }
Итак, мы реализовали метод, который возвращает мне объект сессии привязанный к текущему потоку обработки HTTP запроса. При этом создание сессии ленивое, она не будет создаваться и открываться когда этого не требуется. Осталось только зарегистрировать способ получения сессии в DI-контейнере. Нет ничего проще:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterServices(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } protected void RegisterServices() { var serviceContainer = new ServiceContainer(); serviceContainer.AddService(CreateNhSessionFactory()); serviceContainer.AddService(x => GetSession(), LifecycleType.OncePerRequest); ServiceContainerProvider.Init(serviceContainer); }
Параметр определяющий время жизни объекта в данном случае не имеет особого значения, так как требуемый нам способ контроля за временем жизни реализован в методе GetSession(). Остался последний штрих, добавить закрытие сессии после окончания обработки HTTP-запроса:
protected void Application_EndRequest() { CloseSession(); }
Все. Приведенные фрагменты легко адаптируются для другого DI контейнера, для ASP.NET WebForms приложения.
Возможно в посте содержатся неточности и даже ошибки, в связи с чем я буду благодарен, если вы мне на них укажете.
четверг, 10 февраля 2011 г.
Рассуждения о стоимости заказной разработки программного обеспечения
В последнее время к нам в фирму стали обращаться клиенты с довольно странным суждением о разработке программного обеспечения. Можно даже сказать накипело. Поэтому хочу поделиться выкладками и своим пониманием формирования стоимости разработки программного обеспечения на заказ. Возможно я в чем то ошибаюсь, возможно у меня неправильные представления, но лично я не вижу в чем ошибочность моих представлений.
Я работаю в фирме которая занимается разработкой программного обеспечения, в том числе и разработкой на заказ. Я не буду раскрывать всех тонкостей нашего внутреннего формирования стоимости, но опишу главную идею и алгоритм прикидки цены, который мы используем каждый раз, когда к нам приходит заказчик и у нас начинаются с ним переговоры. Сначала расскажу пару небольших историй, которые произошли совсем недавно. Итак, история первая:
Деньгами не обижу, даю 20 тысяч!
Не так давно обратился к нам заказчик, который сказал что хочет "маленькую программку" отображающую данные поступающие с микроконтроллера. Пришли к нему в офис, начали выяснять, чего же он хочет на самом деле, про деньги сначала не говорили. Оказалось, что фирма уже сделала и сдает систему контроля уровня нефти в резервуарах для нефтехранилища. Есть как минимум два резервуара, в каждом из которых стоит штук по десять датчиков, микроконтроллер сибирает с них информацию и передает на пункт управления, предполагается что резервуаров может быть и больше.
Так вот, нам предлагалось разработать программу, которая бы собирала эти данные, сохраняла их в виде логов, и контролировала аварийные режимы работы системы. В программе предполагалось два рабочих места - одно место для рабочего на конкретном резервуаре, которое бы отображало текущее состояние в графическом виде, контролировала бы динамику процесса и аварийные режимы, ну и собственно сигнализировала бы об аварийных режимах. Второе рабочее место - это место главного инженера, на котором собирается информация со всех "одиночных" рабочих мест, и соответственно отображается информация и выполняются те же функции контроля. Нам показали другие программы, которые делают примерно тоже самое. Ах, да, еще важным требованием было, чтобы программа была "красивая".
Когда речь зашла о бюджете, нам сказали что проект срочный, что сделать надо за 2 недели (как раз с этого дня), и поэтому с деньгами нас не обидят. Правда из документации, требующейся программисту, у них на всю систему была только тонюсенькая методичка по протоколу взаимодейтсвия, и ничего больше у них не было. Заказчик упорно пытался увильнуть от ответа, сколько же он денег предполагает потратить, но все таки раскололся, и огласил гигантскую сумму в 20 тысяч рублей! Я чуть не рассмеялся прямо там же, но совладал с собой
Когда прошла первая волна эмоций, мы попробовали объяснить, что такие проекты нереально сделать за 20 тысяч, и также нереально сделать за 2 недели. Заказчик говорил, что вот, посмотрите, мы вам программы показывали! Они вообще бесплатные! Правда он забыл упомянуть что они идут в комплекте к серийной системе сбора информации, которая сама стоит приличных денег. В целом закончилось все хорошо - посмеялись и разошлись :)
Система лицензирования и оплаты за 6000!
Буквально сегодня зашел на weblancer.net и увидел, что некто хочет получить систему лицензирования и оплаты по СМС и пластиковым картам за 6000 рублей и в срок 1 неделя. Мне очень интересно было бы поговорить с заказчиком, что именно он хочет получить? Готов ли он доверить свои деньги и свой софт (который он по всей видимости собрался защищать) разработке неизвестного студента который будет это делать за пожрат. Может проще не защищать? Ведь это по всей видимости дешевле будет! А если уж защищать, то почему не заплатить $400-$600 за более-менее нормальную систему, которая конечно ничего не гаратирует, но от ручонок школьников вполне может помочь.
То же самое с оплатой по смс и картам, ведь это же деньги. Как можно доверять системе за 6000 которая будет это делать? Человек уверен что он сможет верифицировать код на наличие дырок, которые переводят деньги тому самому фрилансеру? Кто потом будет поддерживать код, если потребуется его куда-то перенести? Как вообще это можно сделать за одну неделю? Да, оплата через пластик это несложно, достаточно заключить договор с агрегатором и написать небольшой кусок для обертки его API, и да, какую-то версию можно сделать за неделю. Но стоит ли неделя разработки 6000? А офис? А интернет? А налоги?
Даже не глядя на результат, который заказчик возможно получит, я могу сказать, что это будет говнокод в лучшем случае, который дешевле выкинуть и написать заново. У меня такое представление, что наши "бизнес-владельцы" не умеют считать свои собственные деньги. Ведь в данном случае следовало бы один раз вложить деньги, и получить нормальный результат, который принесет ощутимую экономию в будущем. А если реальная экономия порядка 6000 рублей, то может не стоит инвестировать в проект?
Откуда берется стоимость часа работы
Я бы хотел немного рассказать свое понимание формирования стоимости разработки. Ведь цена часа разработчика берется не с потолка, а, по идее, должна быть экономически обоснована. Можно конечно работать "за пожрат", но только вопрос, почему квалифицированный человек, разработчик программы должен делать работу и получать за нее столько, что ему хватает только поесть? Ведь, если по хорошему, он должен еще и лицензионным софтом пользоваться, и работать на хорошей машине, и еще много чего.
Итак начнем. Из чего же складывается стоимость разработки:
- Зарплата. Примем зарплату разработчика равную 25000 рублей в месяц. На руки.
- Начисления на зарплату и налоги. Если это организация на упрощенной схеме налогообложения, то это примерно 50% от чистой зарплаты (подоходный, пенсионные, ФСС)
- Отпускные. Нужно же отдыхать? Пусть будет 10% от зарплатных затрат.
- Непроизводительные расходы. Сюда входит ожидание ответов от заказчиков, выяснение требований, время на сдачу проекта, разборки с глюками сторонних библиотек и интерфейсов. Это примерно 10% от затрат. И это очень низкая оценка.
- Офис, интернет, мебель, железо и софт. По реальным оценкам - это где то 5000 в месяц. Опять же, очень низкая оценка, но жить, в принципе, можно.
- Риски. Всегда есть шанс недооценить проект, просчитаться по срокам, или вляпаться в неадекватного заказчика, у которого "хотелки" будут составлять столько же, сколько основной проект. Ну пусть тоже 10%.
- Прибыль. Нужно же еще и развиваться, что то делать для себя, покупать компоненты и т.д. ну пусть будет 10%. Так сказать по европейски скромно (сейчас это процент по рублевому банковскому депозиту).
Давайте просто арифметически прикинем во что это выливается. Я прикинул в экселе, и получается что необходимо иметь входящих 55500 рублей в месяц. Чтобы все получилось так, как я описывал выше. То есть от чистой зарплаты это больше чем в 2 раза! И это я взял еще маленькую зарплату, за которую в провинции можно найти человека, с более-менее адекватной квалификацией. Но в городах с развитым IT сектором на такую зарплату к вам пойдут только умственные инвалиды. Если посчитать стоимость часа (в среднем в месяц рабочих часов 164) то получается примерно 340 рублей. То есть уже больше $10. И это я оценил по минимальной планке!
Именно поэтому во всем цивилизованном мире уже известно, что лучше заплатить за готовый, серийный продукт чем написать свой такой же. Разница в стоимости будет минимум в десятки раз! Думаю теперь становится понятно, почему нельзя сделать систему защиты за $200, и систему контроля нефтехранилища за $700. Стоит ли говорить сколько подобных неадекватных заказчиков мы уже повстречали? Я уже не знаю как объяснить людям, что работа, котороая требует привлечения высококвалифицированных сотрудников не может стоить столько же сколько работа уборщицы или грузчика. Что странно доверять людям, которые просят за свою работу существенно меньше чем она того стоит. Что нужно учиться считать деньги, и считать их. Когда у вас болит зуб, и вам его выдергивают, и берут 700-1000 рублей фактически за 30 минут, вас это не смущает!
суббота, 27 ноября 2010 г.
Используем reCaptcha в приложениях django
Сегодня мы поговорим об использовании reCaptcha в django-приложениях. Не буду говорить о достоинствах reCaptcha, думаю, что они и так понятны. Итак, наша задача состоит в том, чтобы как можно меньшими усилиями прикрутить reCaptcha к нашему приложению. Для этого нам потребуется выполнить следующее:
- Зарегистрироваться здесь и получить наши ключи
- Сделать приложение django, которое будет отвечать за взаимодействие с серверами reCaptcha, и позволит использовать ее в наших формах
- Включить ее в одну из форм приложения, где она требуется
Шаг 1. Регистрация
Думаю, что как регистрироваться может разобраться любой из тех, кто понял что ему нужна reCaptcha в разрабатываемом приложении. Главное получить и сохранить ключи для вашего приложения. В зависимости от вашего желания вы можете получить ключи на конкретный адрес, или на все. Отмечу сразу, что любая пара валидных ключей корректно работает с адреса 127.0.0.1, что позволяет писать и отлаживать приложение на локальной машине, и не огребать при этом лишних проблем. Полученные ключи пропишем в settings.py примерно таким образом:
...
#reCAPTCHA keys
RECAPTCHA_PUBLIC_KEY = < ПУБЛИЧНЫЙ КЛЮЧ >
RECAPTCHA_PRIVATE_KEY = < СЕКРЕТНЫЙ КЛЮЧ >
...
На этом подготовительный этап закончен.
Шаг 2. Приложение django
Создадим приложение django, которое будет предоставлять функциональность другим приложениям и скрывать детали взаимодействия с сервисом reCaptcha. Скажу честно, что я взял приложение marcofucci, и только слегка его модифицировал.
Для начала необходимо скачать recaptcha-client. При этом нам потребуется только captcha.py из клиента. Берем этот файлик и кладем в наше приложение. Он обеспечит нам базовое взаимодействие с сервисом. Теперь нам необходимо сделать виджет и поле формы, для того, чтобы можно было легко использовать reCaptcha в нашем приложении. Приведу сразу код, который взят у marcofucci:
widgets.py
1: # -*- coding: utf-8 -*-
2: from django import forms
3: from django.utils.safestring import mark_safe
4: from django.conf import settings
5: from recaptcha import captcha
6: class ReCaptcha(forms.widgets.Widget):
7: recaptcha_challenge_name = 'recaptcha_challenge_field'
8: recaptcha_response_name = 'recaptcha_response_field'
9: def render(self, name, value, attrs=None):
10: return mark_safe(u'%s' % captcha.displayhtml(settings.RECAPTCHA_PUBLIC_KEY))
11: def value_from_datadict(self, data, files, name):
12: return [data.get(self.recaptcha_challenge_name, None),
13: data.get(self.recaptcha_response_name, None)]
forms.py
1: # -*- coding: utf-8 -*-
2: from django.conf import settings
3: from django import forms
4: from django.utils.encoding import smart_unicode
5: from django.utils.translation import ugettext_lazy as _
6: from recaptcha.widgets import ReCaptcha
7: from recaptcha import captcha
8: class ReCaptchaField(forms.CharField):
9: default_error_messages = {
10: 'captcha_invalid': _(u'Invalid captcha')
11: }
12: def __init__(self, *args, **kwargs):
13: self.widget = ReCaptcha
14: self.required = True
15: super(ReCaptchaField, self).__init__(*args, **kwargs)
16: def clean(self, values):
17: super(ReCaptchaField, self).clean(values[1])
18: recaptcha_challenge_value = smart_unicode(values[0])
19: recaptcha_response_value = smart_unicode(values[1])
20: check_captcha = captcha.submit(recaptcha_challenge_value,
21: recaptcha_response_value, settings.RECAPTCHA_PRIVATE_KEY, {})
22: if not check_captcha.is_valid:
23: raise forms.util.ValidationError(self.error_messages['captcha_invalid'])
24: return values[0]
Теперь мы готовы к использованию reCaptcha в нашем приложении.
Шаг 3. Использование
Использование сводится к созданию в форме соответствующего поля, и вполне обычной обработки формы в стиле django. Напиример так:
1: class RegistrationForm(ModelForm):
2: recaptcha = ReCaptchaField(error_messages = {
3: 'required': u'Это поле должно быть заполнено',
4: 'invalid' : u'Указанное значение было неверно'
5: })
6: class Meta:
7: model = UserData
8: fields = (
9: 'email',
10: 'fio',
11: 'phone',
12: 'company',
13: 'address'
14: )
В данном случае у меня имеется форма по модели, к которой добавлено поле с reCaptcha. Использование формы в функции вида приводить не буду, так как оно ничем не отличается от стандартного, достаточно заглянуть в документацию
суббота, 20 марта 2010 г.
Джоел и Subversion, Mercurial, Git. Действительно антибиотик?
вторник, 9 марта 2010 г.
Известные баги System.Net.Mail.SmtpClient в .NET 3.5
воскресенье, 31 января 2010 г.
Введение в Mercurial. Часть 4. Способы организации ветвей
Здравствуйте, уважаемые читатели. Я продолжаю свою серию постов про распределенную систему контроля версий Mercurial. В этой статье мы подробно поговорим об основных приемах организации ветвлений в Mercurial. В предыдущей статье мы рассмотрели "спонтанное" ветвление, возникающее в случае наличия в репозитории разных линий ревизий от разных разработчиков, хотя каждый из них работает в основной ветви разработки, в этой статье мы рассмотрим работу с ветвлениями, вызванную осознанной необходимостью разделения линий разработки. Также в этой статье я буду указывать какие преимущества и недостатки имеются у обсуждаемых способов организации ветвей. Указанные преимущества и недостатки являются моим личным мнением, и вполне могут не совпадать с вашим.
Для начала дадим определения ветви, чтобы начинать с единого понимания процесса. Обратившись к wiki меркуриала я нашел определение, которое мне кажется вполне корректным: ветвь (branch) - это связанная последовательность ревизий (changeset) являющаяся отдельным направлением разработки. Таким образом, ветвь - это в первую очередь логическое понятие, так как в случае с распределенными системами контроля версий она будет содержать значительное число "спонтанных" ветвлений-слияний.
Рисунок 1. Исходное состояние репозитория
Рисунок 2. Новая анонимная ветвь в репозитории
В этой статье продолжим работу над нашим примером. На рисунке показано состояние нашего репозитория после операций с ним в предыдущих частях цикла. И хотя формально в репозитории уже имеется одно ветвление, Mercurial нам говорит что ветвь одна:
$ hg branches default 4:6d6c634e2e20
Команда hg branches выводит список всех именованных ветвей в репозитории. Как мы видим основная ветвь разработки называется default. Если быть точным, так называется ветвь в которую происходит первый коммит в репозиторий, так сказать название по умолчанию. На рисунке 1 приведено текущее состояние репозитория и граф ревизий в нем находящихся. Я буду красным кружком отмечать "вершину" (tip) репозитория, как сказано в документации Mecurial, вершина - это самая свежая ревизия в репозитории. Команда hg branches не выводит анонимные ветви, хотя разработчики могут их использовать при необходимости, и создавать самостоятельно.
Анонимное ветвление
На самом деле, создание ветви при работе с Mercurial не является чем-то из ряда вон выходящим, для распределенной модели контроля версий это стандартная операция, и поэтому может быть произведена крайне просто, фактически, простым коммитом. То есть мы должны привести рабочую копию в состояние отличное от "вершины" (tip), внести изменения и выполнить коммит. Ничего больше. Для этого локальную копию исходного кода вернем в состояние ревизии 2:66c5686e355e:
$ hg update -r 66c5 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ ls -l -rw-r--r-- 1 mike mike 22 2010-01-07 22:22 first.txt -rw-r--r-- 1 mike mike 61 2009-11-27 11:15 other.txt -rw-r--r-- 1 mike mike 57 2009-11-27 00:03 readme.txt
После этого создадим в локальной копии файл branch.txt, добавим его в репозиторий и выполним коммит:
$ ls -l -rw-r--r-- 1 mike mike 61 2010-01-31 18:55 branch.txt -rw-r--r-- 1 mike mike 22 2010-01-07 22:22 first.txt -rw-r--r-- 1 mike mike 61 2009-11-27 11:15 other.txt -rw-r--r-- 1 mike mike 57 2009-11-27 00:03 readme.txt $ hg add adding branch.txt $ hg commit created new head $ hg branches default 5:ff8ffd5270cb
И Mercurial нам честно сообщает что создал новую "голову". На рисунке 2 показано текущее состояние репозитория. Отмечу два момента: во-первых, ветвь default осталась на своем месте, и теперь заканчивается ревизией 5:ff8ffd5270cb, то есть "вершиной" (tip); а во-вторых все эти ветвления находятся локально в нашем репозитории. Локальность производимых ветвлений - это главное, коренное, отличие от централизованных систем контроля версий, в том числе от Subversion. Никто не увидит вашей ветви до тех пор пока вы не синхронизируете свой репозиторий с удаленным (обычно командой push). С другой стороны выполнение pull приведет к появлению в вашем репозитории всех ветвей, имеющихся в удаленном.
Преимущества
Основным преимуществом этого способа является крайняя простота. Не нужно ничего придумывать, выполнять сложных операций, оповещать других участников команды - просто апдейт и коммит.
Недостатки
Недостатки вытекают из анонимности ветви. Фактически, понять, что у вас есть ветвь разработки, исходя из вывода стандартных операций Mercurial, затруднительно. Особенно после 2-3 месяцев активной работы с репозиторием, когда количество таких мелких ветвлений приближается к сотне. Соответственно вам придется писать информативные подписи к коммитам, если анонимные ветви предполагается использовать в дальнейшем. Для переключения между ветвями разработки вам придется использовать номер ревизии, который, как известно, простой хэш - очень удобен для машины, но крайне неудобен для человека.
Таким образом, анонимные ветви - это отличный механизм для внесения быстрых (по количеству ревизий) исправлений, логически сильно связанных с направлением основной ветви разработки, то есть для организации ветвления на 2-3 ревизии с последующим слиянием с основной ветвью. Для организации больших ветвей, логически необходимых для разделения направлений разработки следует использовать именованные ветви.
Именованное ветвление
Рисунок 3. Именованные ветви в репозитории
Вполне естественно, что в Mercurial предусмотрен способ создания ветвей разработки с некоторыми именами, задаваемыми пользователем. Для организации подобных ветвлений предназначена команда hg branch. С помощью этой команды версия, находящаяся в локальной копии помечается ветвью с новым именем, при этом сама ветвь будет создана только после того, как вы выполните коммит. Попробуем сейчас сделать именованную ветвь, родительской ревизией для которой будет ff8f:
$ hg branch new_feature marked working directory as branch new_feature $ hg commit $ hg branches new_feature 6:4d530267d302 default 5:ff8ffd5270cb
Итак мы видим, что Mercurial уже знает про две именованные ветви, "вершинами" для которых являются ревизии ff8f и 4d53, хотя на графе ревизий это одна ветвь. На рисунке 3 я показал, что именно понимается под именованной ветвью в Mercurial, при этом, фактически, для каждой ветви есть своя "вершина" (tip), хотя hg log это не показывает (я приведу только смысловой отрывок):
$ hg log changeset: 6:4d530267d302 branch: new_feature tag: tip user: mike@mike-vbox date: Sun Jan 31 21:31:07 2010 +0300 summary: Создаие именованной ветви в репозитории changeset: 5:ff8ffd5270cb parent: 2:66c5686e355e user: mike@mike-vbox date: Sun Jan 31 18:56:23 2010 +0300 summary: Создание анонимной ветви changeset: 4:6d6c634e2e20 parent: 3:6872fa960507 parent: 2:66c5686e355e user: mike@mike-vbox date: Sun Jan 10 20:34:21 2010 +0300 summary: Выполнен мерж двух веток changeset: 3:6872fa960507 parent: 1:270e49e72f4b user: mike@mike-vbox date: Sun Jan 10 19:40:45 2010 +0300 summary: Файл second.txt создан во втором репозитории
Убедиться в том, что "вершины" все таки существуют можно с помощью hg update, то есть переключившись на другую ветвь:
$ hg update default 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg ident ff8ffd5270cb
А затем переключится обратно:
$ hg update new_feature 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg ident 4d530267d302 (new_feature) tip
При этом в нашем репозитории сложилась интересная ситуация. tip ветви default не совпадает с "головой" (head) этой же ветви. В этом легко убедиться попросив Mercurial сказать какие же "головы" в нашем репозитории:
$ hg heads changeset: 6:4d530267d302 branch: new_feature tag: tip user: mike@mike-vbox date: Sun Jan 31 21:31:07 2010 +0300 summary: Создаие именованной ветви в репозитории changeset: 4:6d6c634e2e20 parent: 3:6872fa960507 parent: 2:66c5686e355e user: mike@mike-vbox date: Sun Jan 10 20:34:21 2010 +0300 summary: Выполнен мерж двух веток
Отмечу лишь один очень важный факт - ветвление произведено в вашем локальном репозитории, и вы можете работать с ним так, как вам угодно, при этом вы не боитесь поломать чужой код своим коммитом, или вызвать у тимлида приступ головной боли своими ветвлениями. Вот именно так концепция распределенной системы контроля версий позволяет решить стандартные болячки централизованных систем.
Преимущества
Итак именованное ветвление лишь немногим сложнее анонимного, однако позволяет организовать работу над сложным проектом с несколькими направлениями разработки наиболее эффективным образом. Имя ветви является метаданными каждой ревизии, что позволяет корректно отслеживать изменения, произошедшие в проекте. Хотя принципиальных отличий от анонимных ветвей, на самом деле, нет.
Недостатки
К недостаткам, в некоторой степени, можно отнести синдром разрастания ветвей. То есть, если вы будете использовать именованные ветви при каждой необходимости отпочковаться от основной, вывод команды hg branches будет просто гиганским через некоторое время. Хотя ветви можно закрывать при коммитах (опция --close-branch), не стоит делать именованные ветви там где они не нужны.
Ветвление в клонах репозитория
Последний способ организации ветвления о котором я расскажу - ветвление в клонах. На самом деле способ очевиден, и вытекает из третьей части этого цикла. Ничто не мешает пользователю лично создать клон репозитория и в нем вести отдельную ветвь разработки. Таким образом, для каждой ветви потребуется отдельный репозиторий. Безусловно модель распределенной системы контроля версий Mercurial позволяет подобную трактовку ветвления разработки, однако рассмотрим аспекты подобного подхода:
Преимущества
Немного более безопаснее чем при других способах. На самом деле в данном случае способ выстрелить себе в ногу только один - запушить что нибудь этакое в удаленный репозиторий, тогда как при локальном ветвлении способов выстрелить себе в ногу немного больше.
Недостатки
На мой взгляд недостатки перевешивают все преимущества. Самый главный недостаток - при каждом клонировании вам придется вытягивать весь репозиторий, то есть, если вам захотелось получить доступ к некоторой ветви вам придется вытянуть весь репозиторий относящийся к этой ветви, и так для каждой. Второй недостаток - бекапить вам придется не один репозиторий, а несколько. Что не логично.
На этом я заканчиваю рассматривать ветвления в Mercurial. Думаю следующая статья будет посвящена способам слияния ветвей, а также сложным моментам при слиянии, при упоминании которых у пользователей Subversion резко падает давление и начинают трястись руки. :)
Уважаемые читатели. Просьба комментировать посты. Возможно я упустил какие-то моменты, требующие разъяснения, о которых стоило бы написать.
воскресенье, 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 ;) ).
среда, 30 декабря 2009 г.
ProFTPD: Показать скрытые файлы
воскресенье, 27 декабря 2009 г.
Введение в Mercurial. Часть внеочередная. Конвертируемся из Subversion
Давненько я ничего не писал, но для того была достойная причина - 22 декабря я все таки защитил кандидатскую, что собственно и забирало уйму времени последние месяцы. Теперь я полноправный к.т.н.
Сегодня маленький пост о том как сконвертировать имеющийся репозиторий Subversion в репозиторий Mercurial. Думаю что в необходимости перехода на Mercurial я постепенно смогу убедить своих читателей. Итак исходная позиция:
1. Имеется репозиторий svn лежащий на диске, пусть здесь: /[svn_repos_path]/svnrepo;
2. Хочется заиметь репозиторий Mercurial /[hg_repos_path/hgrepo со всей историей накопленной в Subversion, фактически импортировать все ревизии из svn в Mercurial.
Нет ничего проще.
1. Создаем новый репозиторий Mercurial:
cd /[hg_repos_path/hgrepo
hg init
2. Разрешаем расширение convert. В Debian это делается так: в файл /[hg_repos_path/hgrepo/.hg/hgrc добавляем строчки:
[extensions]
convert=
3. А теперь выполняем собственно конвертирование:
cd /[hg_repos_path/hgrepo
hg convert file:///[svn_repos_path]/svnrepo .
Все. Mercurial выдает последовательность ревизий, и загоняет все что было в SVN репозитории в новый репозиторий Mercurial. Можно пользоваться ;)