Git и поддержка проектов

Опубликовано 01.10.2008

Начав заниматься разработкой порталов, я стал задавать себе очень много вопросов. Одним из интересных вопросов, на который мне пришлось ответить - это вопрос каким образом мы будем поддерживать проекты? В те времена, когда я работал в студии, мы так и не нашли для себя ответа на этот вопрос - опыта не хватило для понимания. Так и жили без обновлений. В этот раз вопрос потребовалось решить на берегу - проекты гораздо серьезнее, да и нервы надо беречь.

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

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

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

Когда мы работали с Subversion, с обновлением кода была масса проблем. Постоянно возникали какие-то косяки с применением патчей. Чтобы решить эту проблему, нам приходилось держать все проекты тупо в виде веток одного репозитория. Это крайне неудобно в плане администрирования и повседневного использования. Работа по такой схеме вызывала натуральную зубную боль - настолько было неудобно.

С переходом на Git проблема поддержки как таковая перестала существовать - обновление скелетного кода перестало отличаться от применения любых других изменений.

Практика

Давайте по шагам рассмотрим, каким образом можно организовать обновление проектов, унаследованных от единой первоначальной версии. Допустим у нас есть исходное приложение mega-cms, размещенное в репозитории git@myhost.ru:mega-cms.git, на базе которого мы создаем проекты.

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

mkdir ~/mega-project
cd ~/mega-project
git init

Создан девственно чистый репозиторий. Следующий шаг - заливка кода из базового приложения:

git remote add mega-cms git@myhost.ru:mega-cms.git
git pull mega-cms master

Первой командой мы создаем ссылку на удаленный репозиторий, а второй сливаем из него все изменения начиная с самых первых. Фактически, мы теперь имеем в проекте mega-project точную копию проекта mega-cms.

Теперь давайте зальем эту копию в удаленный репозиторий, чтобы использовать ее для развертывания на сервере:

git remote add origin git@myhost.ru:mega-project.git
git push origin master

Отлично. Теперь давайте внесем какие-нибудь изменения в исходный код mega-project. Например, добавим файл с описанием проекта:

echo "This is readme file for mega-project" > README
git add .
git commit -a -m "Adding README file"

А теперь внесем изменения в проект mega-cms - добавим список задач:

cd ~/mega-cms
echo "Create tasks to automate project update" > TODO
git add .
git commit -a -m "Adding TODO list"
git push

Последняя команда выгружает изменения в проекте в удаленный репозиторий. Теперь самое интересное - нам нужно обновить код в mega-project, чтобы созданный список появился и там:

cd ~/mega-project
git pull mega-cms master

Вуаля! Git автоматически определяет, какие изменения в mega-cms были внесены после копирования и автоматически применяет их к mega-project. Если возникают какие-либо конфликты (например, мы исправляем один и тот же абзац README в mega-project и в mega-cms), то Git попросит нас ручками разрешить спорную ситуацию.

И последний шаг - заливаем примененные изменения в удаленный репозиторий:

git push

Наслаждайтесь :) Я сливаю изменения из проекта в проект по нескольку раз на дню. Даже думать не хочется о том, что это можно делать как-то иначе.

Subversion под Windows - запуск сервиса

Опубликовано 02.07.2007

Вот у меня возникла такая задача: я хочу запустить Subversion под Windows в качестве сервиса (службы), чтобы доступ к локальному репозиторию был постоянно, без запуска сервера вручную. Давайте попробуем это сделать.

В первую очередь, нам нужно поставить свежий дистрибутив Subversion для Windows. Устанавливаем его, например в папку C:\Program Files\Subversion.

После установки Subversion нам необходимо создать папку, в которой будут храниться наши локальные репозитории. Например, это будет папка D:\Repository.

Далее, нам потребуется утилита instsrv.exe. Ее можно скачать вместе с Windows Resource Kit (его искать на хомяке у дяди Гейтса, где конкретно - не скажу) или просто взять отсюда (спасибо WinAll за малый объем архива). Файл надо распаковать и содержимое скопировать в папку C:\Windows\system32

Получив эту замечательную утилиту, мы уже можем установить сервис. Для этого мы запускаем вот такую сказочную команду:

instsrv.exe “Subversion Server” “c:\Program Files\Subversion\bin\svnserve.exe”

После выполнения этой команды мы в списке служб можем увидеть новую службу под названием Subversion Server. Запускать ее пока рано, нужно сделать еще немножко телодвижений. Нужно открыть в редакторе реестра (regedit) ветку HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Subversion Server и поправить в ней параметр ImagePath, указав в нем следующее значение:

c:\Program Files\Subversion\bin\svnserve.exe --service -r d:\repository

Ключ --service означает, что программа должна запускаться как сервис, а параметр -r d:\repository дает программе понять, что все запрашиваемые адреса надо обрабатывать относительно директории d:\repository.

Сохраняем значение параметра и запускаем сервис. Если все сделано правильно, сервис должен успешно запуститься.

Далее, заходим в папку d:\repository и создаем репозиторий:

svnadmin create myfirstrepo

Теперь мы можем начинать работу с этим репозиторием используя адрес svn://127.0.0.1/myfirstrepo/

Удачного коммита!

UPD: В комментах MiRacle рассказал как можно создать сервис в Windows XP без загрузки дополнительных утилит. За это спасибо ему большое.

Внешние ресурсы для приложений

Опубликовано 03.06.2007

После перехода перехода с PHP на Ruby on Rails и отказа от изобретения собственных велосипедов, у меня появилась новая страсть, которую необходимо как-то контролировать - встраивание чужих велосипедов в свою систему. При создании Rails-приложений довольно сложно обойтись без использования сторонних библиотек. Сложно не потому, что эти библиотеки обязательны, а потому, что грех не использовать такие замечательные вещи, как acts_as_authenticated, better_nested_set, acts_as_dropdown, rspec и многие другие.

Создатели языка Ruby и фреймворка Rails на славу потрудились над общей схемой использования чужого кода в проектах. В большинстве случаев достаточно просто установить gem на сервере или скопировать необходимый плагин в папку vendor/plugins. Однако, даже при такой упрощенной схеме есть тонкие моменты, которые видны не сразу и могут проявиться в самый неподходящий момент.

В одном из наших проектов используется около десятка сторонних библиотек, включая Edge Rails, подключенных через svn:externals. Для тех кто слабо знаком с тонкостями работы Subversion, я поясню: такая схема подключения означает, что в локальном репозитории хранится только код приложения, а сторонние библиотеки упоминаются исключительно в виде ссылок на сторонние репозитории. При скачивании последней актуальной версии кода приложения SVN-клиент автоматически подключается к внешним репозиториям библиотек и скачивает оттуда последнюю актуальную версию.

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

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

Немножко порывшись в книжках и статьях по Subversion, я откопал замечательный способ - отдельный локальный репозиторий под внешние библиотеки. Делается это так:

  1. Создаем репозиторий, например с именем vendor, на том же сервере, где вы храните основной репозиторий приложения.
  2. В репозитории создаем папки под каждую из сторонних библиотек:

    -+-vendor
     |-acts_as_dropdown
     |-netter_nested_set
     |-rails
    
  3. Выкачиваем необходимые версии библиотек на свой локальный компьютер через svn checkout

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

    -+-vendor
     |-acts_as_dropdown
     |  +-12
     |-better_nested_set
     |  +-127
     |-rails
        +-8557
    
  5. Импортируем код из локальных папок в репозиторий в соответствующие папки с номерами версий

  6. Делаем ветки от папок с номерами версий в папку current для каждой библиотеки:

    -+-vendor
     |-acts_as_dropdown
     |  +-12
     |  +-current
     |-better_nested_set
     |  +-127
     |  +-current
     |-rails
        +-8557
        +-current
    

Собственно, это все. С этого момента уже можно начинать использовать локальный репозиторий в своих целях. Для этого нужно в проектах поменять ссылки svn:externals с внешних репозиториев на соответствующие папки current локального репозитория. При следующем обновлении библиотеки будут тянуться уже из локального репозитория.

Если вам потребуется обновить какую-то библиотеку до последней актуальной версии, то достаточно обновить локальные папки с библиотеками командой svn update (или заново выкачать эти библиотеки через svn checkout) и повторить пункты 4-6:

    -+-vendor
     |-acts_as_dropdown
     |  +-12
     |  +-15
     |  +-current
     |-better_nested_set
     |  +-127
     |  +-131
     |  +-current
     |-rails
        +-8557
        +-8560
        +-current

На мой взгляд, схема более чем удобная. Как итог мы получаем сразу ворох выгод:

  1. Держим все библиотеки в своем кармане и не зависим от сторонних источников
  2. Можем обновлять версии библиотек одновременно во всех проектах используя папки current в качестве основных источников
  3. Можем использовать строго определенную версию библиотеки, ссылаясь на папку, соответствующую нужному номеру версии
  4. Можем модернизировать исходный код библиотек и использовать его независимо от того, принимает автор патчи к своим библиотекам или нет

Удачного чекаута!

Полезные ссылки: