Системы управления версиями для программистов и не только. Часть 3

May 1, 2008

Я продолжаю рассказ о системах управления версиями файлов (далее СУВ). Сегодня я завершу рассказ об основных понятиях СУВ: мы поработаем с ветвями и тегами. Попробуем создать репозиторий доступный для коллективной работы и настроить политику безопасности.

Кто такие “ветки” и “теги” в терминологии SVN? Рассмотрим небольшой сценарий: вы завершили разработку некоторой программы, отдали ее заказчику, затем решили начать разработку версии 2, в которой архитектура проекта сильно изменилась. Однако, “уйти от прошлого нельзя” и спустя какое-то время заказчик просит вас внести изменения. Нет проблем, ведь вы сохранили копию старого проекта, поэтому легко вносите в нее правки и отдаете клиенту. Проблема лишь в том, что одним разом это не ограничится, и вам приходится заниматься регулярными правками старой версии (и даже не одной). Одновременно с этим вы занимаетесь разработкой новой версии. Момент в том, что часть правок, которые вы вносите в “старые” версии было бы неплохо внести и в “новые” версии. Равно как и обратный процесс: правки могут переходить из “новой” в “старую” версию, или исправления в одной “старой” версии переносятся в другую “старую” версию. В любом случае этот процесс нужно автоматизировать. Итак, в терминологии СУВ “ветка” – это направление разработки, которое существующее отдельно от другого направления разработки, однако имеющее с ним общую историю (как ветви дерева, которые прорастают из единого корня). Ветки создаются также и в случае, если вы работаете не один и хотите внести значительные изменения в проект, но боитесь, что нарушите работу других участников. В этом случае имеет смысл создать ветку-копию и, после завершения ее развития, выполнить слияние с основной ветвью разработки. Репозиторий SVN может хранить произвольное количество копий проекта (веток) и это не сильно влияет на его размер: также же, как и при создании очередной ревизии проекта создавалась разностная копия, так и при создании ветки, создаются ссылки на файлы, хранящиеся в основной ветви разработки. Репозиторий растет в размере только тогда, когда меняется, собственно, содержимое файлов проекта. Метки (tags) похожи на фотографические снимки, которые фиксируют текущее состояние какой-либо ветки и не могут изменяться (нельзя создавать новые ревизии для этой псевдо-ветки). Фокус в том, что на самом деле svn и теги, и ветки реализует как каталоги и не делает никаких различий между ними на низком уровне. Хотя формально метка – статический снимок, но вы можете делать с ней все что угодно.

Рассмотрев идею веток и тегов, перейдем к структуре типового хранилища. Показанное далее разделение имен является наиболее привычным для многих разработчиков (хотя svn не накладывает никаких ограничений на имена каталогов). Предположим, что мы создали репозиторий с именем “test/project”, тогда в корне проекта должны быть созданы папки: trunk (ствол дерева), затем папка branches(ветки дерева) и tags (метки). Все предшествующие статьи предполагали, что в каталоге “test/project” находятся файлы проекта. Теперь же нам нужно создать еще несколько каталогов и выполнить перемещение в них файлов из корня проекта. Используем для этого команду mkdir (предварительно я зашел в локальный каталог проекта):
 svn mkdir branches
Возможен и такой способ создания каталогов на сервере, когда указываются не имена каталогов относительно текущего, а полный URL
 svn mkdir "file:///h:/docs_xp/my temps/SVN2/images" -m"new images dir"
Теперь необходимо выполнить перемещение файлов и каталогов из корня в папку trunk. Используем для этого команду move. Важно, что в ходе перемещения вся история файлов (ревизии) не теряется.
 svn move about.txt trunk/
Последний шаг – “закрепить” правки: только после выполнения команды commit начнется реальное изменение файловой структуры хранилища.

svn commit -m"finish layout"

Теперь попрактикуемся в создании ветвей и меток. Для этого мы используем команду copy (одна команда и для веток и для меток – ведь фактически различий между ними нет). В качестве параметра для copy указываются адреса “откуда копировать” и “куда копировать”. Поведение утилиты зависит от значений этих адресов: в простейшем случае вы указываете пути в локальном хранилище, например, так (важно, что фактические изменения выполняются только при commit-е):
  svn copy project/trunk project/branches/fixed
  A         project\branches\fixed
  svn commit project  -m"new branches"
  Adding         project\branches\fixed
  Committed revision 29.
Возможен и способ, когда в репозиторий копируется информация из другого репозитория, например:
 svn copy file:///h:/docs_xp/SVN/flash/cabinet project/tags/image001
 svn commit project -m"x"
Попробуем и более сложный пример с копированием между репозиториями напрямую (а здесь копирование выполняется тут же, еще обратите внимание на заданный комментарий к снимку “-m”):
 svn copy file:///h:/docs_xp/SVN/flash/game2 file:///h:/docs_xp/SVN/test/project/tags/image002 -m"new test"
Отдельный момент в том, что вы можете захотеть создать “снимок” не с последнего номера ревизии (его называют HEAD), но с произвольного номера в прошлом (используем параметр “-r”):
 svn commit -m"copy 19 rev"
 svn commit project -m"copy 19 rev"
Предположим, что вы захотели создать копию не с основного “ствола” разработки (trunk), а с произвольной ветки или тега – нет проблем, ведь “все на свете” каталоги:
 svn copy project/branches/super project/tags/clone
Теперь самый главный вопрос: а как работать с этими копиями? Т.е. если открыть корень каталога проекта (там где находятся tags, trunk, branches) и выполнить команду обновления (update), то к вам на компьютер будет скопировано все содержимое репозитория. И … самое страшное … никаких оптимальных алгоритмов в виде разностных копий (как на сервере). У вас на компьютере все копии будут полными, и размер репозитория выйдет за рамки разумного. Решение тривиально: не нужно хранить у себя все копии файлов – нужно хранить только одну версию (ту, над которой вы работаете). Эта копия может быть и “основным” стволом и копией – не важно. Главное, что нам нужен механизм переключения между этими копиями, для этого мы используем команду switch. В качестве ее параметров указывается путь, куда переключаемся и, опционально, номер ревизии (в примере предполагается, что я нахожусь в каталоге project):
 svn switch file:///h:/docs_xp/SVN/test/project/trunk
Операция переключения не является изменяющей содержимое репозитория, поэтому ее не нужно подтверждать commit-ом, и сразу после запуска файлы на вашем локальном компьютере будут скопированы или удалены, чтобы отобразить текущую версию проекта. А так можно работать с конкретным номером ревизии:
 svn switch file:///h:/docs_xp/SVN/test/project/trunk -r 32
После того как мы переключили текущий каталог проекта на “ствол” trunk, показанный ранее способ создания копии проекта в виде ветки или тега уже не работает (уже нет таких путей как project/trunk, есть просто project отображающийся на project/trunk):
 svn copy project/trunk project/branches/fixed
Больших проблем, однако, это не составляет т.к. мы теперь будем использовать для создания веток синтаксис, оперирующий полными путями к репозиторию, а не относительными.
 svn copy -r 35 project file:///h:/docs_xp/SVN/test/project/branches/newbranch -m"35"
Есть и такая разновидность switch, когда было выполнено перемещение репозитория с одного сервера на другой (не копирование самого репозитория, этим должен заниматься администратор, а только ссылки на него). Собственно, если подобная ситуация происходит часто (ломаются сервера, или вы переходите из одной сети в другую), то лучше посмотреть в сторону распределенных СУВ или купить SVN-хостинг в internet (http://cvsdude.com/, http://code.google.com/hosting/, http://www.assembla.com/).

После переключения на новое местоположение проекта (с помощью switch) вы работаете как обычно. Но здесь у начинающих возникает странная “непонятка”: как нумеруются версии? Например, у вас текущая версия проекта 100, вы переключились на другую ветку (версия 50), затем делаете правки и отправляете файлы в репозиторий. Вопрос: что происходит с версиями файлов? Да ничего не происходит: новая ревизия получает номер 101 (при условии, что никто не успел отправить в репозиторий изменения до вас), номера остальных ревизий никак не меняются. Фактически, я никогда не смотрю на номер ревизии как на способ отличать версии проекта между собой. Вместо этого, как только были сделаны существенные изменения (не важно заняли они пол дня или неделю, одну ревизию или сто) я делаю копию проекта в форме tag-а.

Теперь самое главное: после того как мы понаделали меток, веток и внесли в них множество правок, мы хотим выполнить слияние между файлами. Здесь мы используем команду merge в тесной связке с рассмотренной в предыдущей статье командной diff. Diff мы использовали как команду определяющую различия, которые существуют между двумя ревизиями проекта (отдельных его файлов). Тогда мы еще не знали о существовании веток и тегов, пора исправить недоработку и далее я покажу сравнение HEAD версий файла about.txt в “стволе” trunk и ветке “branch001”:
 svn diff file:///h:/docs_xp/SVN/test/project/trunk/about.txt file:///h:/docs_xp/SVN/test/project/branches/br001/about.txt
В случае если я хочу сравнить не HEAD версии, а ревизии под заданным номером, то нужно после имени файла указать значок “@” и номер ревизии, например, так:
 svn diff file:///h:/docs_xp/SVN/test/project/trunk/about.txt@30 file:///h:/docs_xp/SVN/test/project/branches/br001/about.txt@37
Также как в прошлый раз я могу не указать при сравнении имена файлов, тогда будут получены все сведения об измененных между ветками (и ревизиями) файлах:
 svn diff file:///h:/docs_xp/SVN/test/project/trunk@30 file:///h:/docs_xp/SVN/test/project/branches/br001
Полезной в ходе анализа является и команда blame. Ее назначение вывести список тех примечаний, которые были сделаны в ходе некоторого файла, каждым из участников (сначала номер ревизии, затем имя программиста, затем правки файла):
 svn blame about.txt
 17 Programmer my project
 23 Programmer Правка 1
 22 Programmer Правка 2
Теперь “прикинув” чем отличаются между собой файлы в двух версиях пора приступить к, собственно, слиянию:

svn merge file:///h:/docs_xp/SVN/test/project/trunk/about.txt@30 file:///h:/docs_xp/SVN/test/project/branches/br001/about.txt@37

Я хочу взять два версии файла about.txt одна в основном стволе проекта (ревизия 30), второй – в ветке br001 (ревизия 37) и слить внутрь текущей (рабочей) версии файла. В результате я получил три новых файла: about.txt.merge-left.r30, about.txt.merge-right.r37, about.txt.working. Также содержимое файла about.txt изменилось и стало содержать в diff-виде три набора данных (вспомните, в прошлой статье я приводил примеры, как выглядит diff-файл и как его “читать”). Итак, три различные версии файла (r30, r37, working) необходимо объединить в один файл about.txt. Делаем это (не забываем удалить конфликтные маркеры). Затем мы должны сообщить, что операция слияния была выполнена успешно (используем команду resolved):
 svn resolved about.txt
Большей частью команды svn мы разобрали. Осталось только сделать качественный шажок, от работы с репозиторием размещенным в локальной файловой системе к репозиторию доступному по сети. Здесь все снова очень просто: есть два способы получения по сети доступа к svn и ни один из них не требует специальных “администраторских” знаний. Первый основан на доступе через “особенный” svn-протокол к запущенному на выделенной машине сервису. Второй предполагает, что svn интегрирован в веб-сервер и доступ к хранилищу выполняется через протокол DAV. Начнем с первого способа: во всех уроках мы использовали утилиту svn.exe, рядом с ней находится еще одна программка: svnserve.exe. Это и есть сервер svn, можно просто набрать в командной строке:
 svnserve.exe -d -r H:\docs_xp\SVN
После чего сервис будет запущен (за это отвечает опция “-d”), опция же “–r” указывает на каталог, где находится SVN-репозиторий. В практике гораздо удобнее, чтобы сервис svn запускался автоматически, например, при запуске windows (как служба). К счастью в составе windows предусмотрен легкий способ зарегистрировать произвольное приложение как автоматически запускаемое (sc.exe – это служебная утилита windows и к svn, в общем случае, никакого отношения не имеет):
 sc create svn binpath= "\"E:\Program Files\SSH\Subversion\bin\svnserve.exe\" 
 --service -r H:\docs_xp\SVN" displayname= "SVN Server" depend= TCPIP start= auto
За регистрацию новой службы отвечает команда “create”. Затем мы указываем имя этой службы “svn” и список конфигурационных переменных. Самая главная среди них: binpath – содержит командную строку запуска svnsever.exe (в режиме службы вместо параметра “–d” указываем, именно, “-service”), не забываем о пути к репозиторию. Остальные параметры очевидны: “красивое имя службы” и сведения о режиме запуска (auto-автоматически при запуске windows). Параметр depend говорит о том, что запуск svn должен выполняться после успешного запуска служб сетевого доступа через tcp/ip. В случае если стандартный порт, на котором работает svn (3690), для вас запрещен, то можно указать для svnserve еще и параметр “--listen-port”. В особо сложных случаях, возможен вариант, когда прямой

доступ к svn будет закрыт, а работать вы будете через ssh-туннель.

Если операция регистрации службы выполнится удачно, то вам сообщат: “ [SC] CreateService SUCCESS”. На всякий случай, если вы ошибетесь в параметрах командной строки, например, выполните регистрацию сервиса, но неверно укажите путь к репозиторию, то можно удалить службу и зарегистрировать ее заново. Удаление делаем так (имя svn совпадает с указанным чуть выше именем службы при регистрации):
 sc.exe delete svn
Как только к svn был разрешен совместный доступ возникает вопрос контроля за этим доступом. Ни одна из ранее приведенных svn-команд не содержала указания на имя и пароль, пора это исправить. У каждого репозитория есть специальный каталог conf, где находится файл управляющий внешним доступом (svnserve.conf). Файл содержит комментарии и на основании их легко можно настроить доступ для группы пользователей. Итак, пользователь квалифицируется именем и паролем. Возможен и анонимный доступ. В любом случае мы можем указать доступный для такого клиента режим работы (чтение или запись, или доступа нет вообще). Ниже пример показывающий такие режимы работы для анонимов:
 anon-access = read или write или none
После изменения файла svnserve.conf вовсе не обязательно перезапускать сервис: настройки “подхватываются” налету. Если пользователь указывает имя и пароль, то есть две методики разрешить ему что-то: глобальные разрешения и локальные. В первом случае нужно в файл svnserve.conf добавить строки:
 auth-access = write
 password-db = passwd
Вторая строка содержит имя файла с перечислением имен и паролей (пример такого файла находится в том же каталоге conf). В случае если права пользователей неравнозначны и вы хотите разграничить для них доступ к различным репозиториям и даже отдельным его каталогам (помните, я рассказывал как часто доступ к основному trunk-у для начинающих программистов ограничивают). Тогда вам нужно вернуться в файл svnserve.conf, закомментировать строку, начинающуюся на “auth-access = … ”, и убрать комментарий для еще одной строки:
 authz-db = authz
Следующие действия тривиальны: открываем файл authz и начинаем его править:
 [groups]
 harry_and_sally = harry,sally
 [/test/project]
 harry = rw
 * = r
 [/test/bazza]
 @harry_and_sally = rw
 * = 
Первая строка определяет список групп, на которые разделены пользователи (harry и sally входят в состав группы harry_and_sally). Затем мы в квадратных скобках задаем путь к репозиторию [/test/project]. Последующие записи в секции определяют права людей: harry – разрешен доступ на чтение и запись. Всем остальным пользователя доступ запрещен. Для первого репозитория [/test/project] разрешено чтение для всех, а harry разрешена и запись в хранилище. Права можно давать и группам пользователей, в этом случае имя группы предваряется знаком “@”.

После того как был поднят svn-сервер и созданы политики безопасности попробуем что-либо сделать с нашим репозиторием. Казалось бы, когда если я закрыл всем доступ к svn, и выполнил несколько команд update, commit, то мне должны сообщить об ошибке доступа? Увы, нет: все операции выполнялись как прежде. Дело в том, что созданный проект ссылается на файловое хранилище, значит нужно заменить “файловый” протокол на svn-протокол. Для этого нам снова пригодится команда switch, заметьте, что я указываю не только откуда и куда переключиться, но и учетные данные для этого, также указывать пути нужно к корню репозитория (здесь center – имя моего компьютера):
 svn switch --relocate file:///h:/docs_xp/SVN svn://center/ --username jim --password secret
Теперь пару слов о доступе к SVN посредством DAV. DAV расшифровывается как Distributed Authoring and Versioning. Его назначение сделать веб-сервер чем-то похожим на обычный файловый сервер: вы можете работать с файлами, размещенными на DAV-сервере, используя обычные средства О.С. (тот же проводник), также поддержка DAV встроена во множество программных продуктов (ms office). Несмотря на наличие слова Versioning, DAV не является чем-то похожим на SVN. В составе SVN идут модули mod_authz_svn, mod_dav_svn. Их необходимо подключить к apache, написать пару строк в конфигурационном файле, чтобы все заработало. Честно говоря, я никогда занимался серьезным исследованием этой методики, т.к. пару раз возникали странные проблемы, я и переключился на более родной и более быстрый способ работы через собственный протокол svn, а не http.

На этом я завершаю статью и считаю, что рассказал о 99% команд нужных для работы с SVN. Рассказ же о графических средствах работы с SVN будет совмещен с кратким описанием еще одной известной СУВ – perforce (большинство моих знакомых, как и я, склоняются к мысли, что perforce за счет развитого интерфейса и системы плагинов более удобен для непрограммистов). Но об этом в следующий раз.