Много лет подряд в качестве системы контроля версий для большого количества проектов использовали только SVN. Но наступил момент, когда количество разработчиков на одном из проектов заметно увеличилось, проект уже запущен в работу, и нужно как активно разрабатывать параллельно несколько фич, так и фиксить уже имеющиеся баги в оперативном режиме. Единый trunk в SVN не позволяет этого делать, а организация бранчей в нем же превращает жизнь разработчиков в ад. Поэтому было принято решение о переезде этого проекта с SVN на Git.
1. Сервер для центрального репозитория
Несмотря на то, что Git — система распределенная, это не отменяет необходимости наличия центрального репозитория, с которым в конечном итоге будут сихронизироваться все разработчики, а также с которого будут разворачиваться как тестовые сборки, так и будет производиться деплой на production. Поэтому нам необходим в первую очередь сервер. Поскольку проект коммерческий, то хранить исходники на чужих серверах как-то не очень хотелось, значит надо поднять свой сервер для хранения git-репозиториев. Сам сервер работает на Gentoo, поэтому нам нужно поставить на него весь необходимый софт.
Здесь выбор особо не велик — gitosis либо gitolite. Поскольку gitosis уже не разрабатывается несколько лет как, то выбор пал на gitolite.
1.1. Установка gitolite
Ставим gitolite 3.03 на сервер:
$ emerge gitolite
При этом создается пользователь git, который и будет владеть всеми будущими репозиториями.
1.2. Первичная настройка
Теперь нам нужно сгенерировать rsa-ключ для доступа к админскому репозиторию (gitolite хранит все настройки в git-репозитории) и сохранить публичный ключ в общедоступном месте:
$ ssh-keygen -t rsa
$ cp ~/.ssh/id_rsa.pub /tmp/admin.pub
После этого можно собственно инициализировать gitolite:
$ su git
$ cd
$ mkdir -p bin
$ gitolite/install -ln
$ gitolite setup -pk /tmp/admin.pub
1.3. Создание репозитория для проекта
Сервер установлен, возвращаемся в своего пользователя и клонируем себе репозиторий с конфигами:
$ cd
$ git clone git@server:gitolite-admin.git
Здесь и далее server — это hostname сервера, на котором установлен gitolite и хранятся репозитории.
Открываем появившийся файл gitolite-admin/conf/gitolite.conf и добавляем в конец описание репозитория для нашего проекта (пока только с одним пользователем):
repo project
RW+ = javer
После этого сохраняем наши изменения. Находясь в gitolite-admin, выполняем:
$ git add .
$ git commit -am "Repository for project added"
$ git push origin master
gitolite автоматически проинициализирует все репозитории, которые описаны в конфиге и еще не существуют.
Все, gitolite установлен и первично настроен, репозиторий для нашего проекта создан, можно двигаться дальше.
2. Импорт проекта из SVN
Непосредственно преобразование SVN-репозитория в Git осуществляется с помощью команды
$ git svn clone
Для этого git должен быть собран с поддержкой perl.
2.1. Определение стартовой ревизии
Наш проект Project находится в SVN репозитории наряду с множеством других проектов. Поскольку номера ревизий сквозные на весь репозиторий, то находим в svn log первый коммит, который касается именно нашего проекта, и запоминаем. Это нужно для ускорения импорта, чтобы не сканировались все ревизии, начиная с первой. В нашем случае это ревизия 19815, поэтому к команде выше добавляется опция:
-r19815:HEAD
2.2. Соответствие SVN-пользователей с Git-пользователями
Далее нам необходимо составить соответствие SVN-пользователей с будущими Git-пользователями, чтобы они при импорте успешно заменились. Для этого где-нибудь создаем файл authors примерно такого содержания:
javer = javer <javer@domain.tld>
developer1 = developer1 <developer1@domain.tld>
...
Где user@domain.tld — это e-mail git-пользователя (в git-е каждый пользователь идентифицируется именем и электропочтой).
Соответственно, к команде импорта добавляется опция:
--authors-file=/path/to/authors
2.3. Исключение ненужных файлов
Едем дальше. В нашем проекте были случайные коммиты больших бинарных файлов, которые в новом репозитории нам не нужны. При импорте их можно исключить опцией:
--ignore-paths="\.(avi|mov)$"
2.4. Дополнительные опции
Также нам нужен пользователь в SVN, от имени которого будет производиться доступ к репозиторию:
--username javer
Добавляем еще опцию --no-metadata, которая нужна для того, чтобы в логе коммитов в каждом комментарии не было добавлений вида:
git-svn-id: svn://svn.domain.tld/repo/project/trunk@19815 e13dc095-444b-fa4e-8f24-06838a8318a5
В SVN-репозитории нашего проекта полезная информация хранилась только в trunk, немногочисленные бранчи содержали временный код, который в свое время путем невероятных усилий все-таки был смержен с trunk-ом, поэтому более они нам не нужны.
2.5. Клонирование проекта из SVN-репозитория
Собираем все вместе и запускаем:
$ cd
$ mkdir project && cd project
$ git svn clone -r19815:HEAD --authors-file=/path/to/authors --ignore-paths="\.(avi|mov)$" --username javer --no-metadata svn://svn.domain.tld/repo/project/trunk .
Где svn://svn.domain.tld/repo/project/trunk — адрес trunk-а нашего проекта в SVN-репозитории.
Начинается процесс клонирования, длительность которого зависит от количества коммитов и их объема. В нашем случае было около 4.5 тыс. коммитов, и на их клонирование понадобилось около двух часов.
2.6. Исключение более ненужных файлов и каталогов
По завершении клонирования в нашем каталоге project мы получаем полный клон проекта со всей историей коммитов. После клонирования может внезапно обнаружиться, что мы склонировали также какой-то каталог, который в новом репозитории нам не нужен, например, потому что мы выделим его в отдельный репозиторий. Удалить каталог и все упоминания о нем в истории можно так:
$ git filter-branch --tree-filter 'rm -rf unneeded_directory' -f HEAD
Этот процесс также достаточно длительный, поскольку пересматривается каждый коммит в отдельности, и в нашем случае это занимало около 1 секунды на каждый коммит.
2.7. Удаление пустых коммитов
В результате всех предыдущих действий мы получили наш склонированный проект со всей историей коммитов, среди которых теперь имеются пустые коммиты, то есть коммиты без единого измененного файла. Они появились в результате исключения некоторых файлов через опцию ignore-paths, либо же из-за последующей фильтрации через tree-filter. Для удаления таких пустых коммитов делаем:
$ git filter-branch --commit-filter 'git_commit_non_empty_tree "$@"' HEAD
Эта операция занимает примерно столько же времени, как и tree-filter.
2.8. Пустые каталоги и svn:ignore
Далее, нам необходимо сконвертировать бывшие svn:ignore в новые .gitignore. Это делается так:
$ git svn create-ignore
Не забываем, что git не хранит информацию о каталогах, только о файлах, поэтому во всех пустых каталогах нужно создать пустой файл .gitignore, после чего закоммитить все эти файлы:
$ git add .
$ git commit -am "Added .gitignore"
2.9. Удаление упоминания об SVN
Поскольку наш проект переезжает с SVN на Git окончательно, то удаляем всяческие упоминания об SVN:
$ git branch -rd git-svn
$ git config --remove-section svn-remote.svn
$ git config --remove-section svn
$ rm -rf .git/svn
2.10. svn:externals
В нашем проекте некоторые symfony-плагины, как кастомные, так и публичные, были подключены через svn:externals. Поскольку в git такой механизм отсутствует, будем использовать submodules для этого. С публичными плагинами проще:
$ git submodule add git://github.com/propelorm/sfPropelORMPlugin.git plugins/sfPropelORMPlugin
$ git submodule add git://github.com/n1k0/npAssetsOptimizerPlugin.git plugins/npAssetsOptimizerPlugin
Со своими плагинами чуть сложнее — для них нужно создать отдельные репозитории аналогично описанному выше, после чего точно также подключить к нашему проекту:
$ git submodule add git@server:customPlugin.git plugins/customPlugin
После подключения submodules их необходимо склонировать в каталог проекта:
$ git submodule update --init --recursive
$ git commit -am "Added submodules: sfPropelORMPlugin, npAssetsOptimizerPlugin, customPlugin"
2.11. Отправка локальной копии проекта на сервер
Перед отправкой нашего проекта на сервер для ускорения этой операции оптимизируем его:
$ git gc
Подключаем к проекту наш новый репозиторий:
$ git remote add origin git@server:project.git
И, наконец, заливаем локальную копию проекта на сервер:
$ git push origin master
2.12. Обновление submodules в будущем
Поскольку в отличие от svn:externals каждый submodule указывает на конкретный коммит, то при простом обновлении локальной копии через
$ git pull
содержимое submodules обновляться не будет. Их обновление производится следующим образом:
$ git submodule update
В случае, если были изменения:
$ git submodule foreach git pull
$ git commit -am "Updated submodules"
3. Настройка прав доступа к репозиторию
3.1. Пользовательские ключи
Поскольку доступ к удаленному git-репозиторию осуществляется через ssh, то теперь каждый разработчик должен сгенерировать на своей машине rsa-ключ.
3.1.1. Linux/Unix
В случае Linux/Unix или Git bash под Windows это делается так:
$ ssh-keygen -t rsa
После чего полученный публичный ключ ~/.ssh/id_rsa.pub передается админу репозитория.
3.1.2. Windows
В случае Windows также можно воспользоваться puttygen, который можно скачать здесь: puttygen.
Запускаем puttygen, нажимаем Generate, возим мышкой по окну, пока ключ не будет сгенерирован, потом в поле комментария указываем к чему этот ключ, вводим пароль доступа к ключу при необходимости. После этого копируем содержимое поля Public key, сохраняем в файл user.pub и передаем админу репозитория.
Потом нажимаем Save private key и сохраняем этот ключ в укромном месте для дальнейшего в использования, например, в TortoiseGit.
Также в меню Conversions выбираем пункт Export OpenSSH key и сохраняем его в файл под названием C:\Users\USERNAME\.ssh\id_rsa, где USERNAME — имя вашего пользователя в системе. Этот ключ нам будет нужен при использовании git из командной строки.
3.2. Настройка прав доступа
Полученные на предыдущем шаге публичные ключи пользователей помещаем в админский репозиторий в каталог ~/gitolite-admin/keydir/ в файлы с названиями USERNAME.pub, где USERNAME — имя пользователя.
Поскольку gitolite имеет достаточно широкие возможности по настройке, используем их для настройки прав доступа к репозиторию нашего проекта. Для этого редактируем файл ~/gitolite-admin/conf/gitolite.conf и приводим его к виду:
@owners = javer
@project_developers = user1 user2 user3
@deploy = root@production
repo project
- master$ = @project_developers
- refs/tags = @project_developers
RW+ = @project_developers @owners
R = @deploy
Этим мы даем полный доступ для группы пользователей owners. Для группы project_developers — также полный доступ с возможностью создания своих веток, за исключением записи в ветку master и создания тегов. Для группы deploy, которая используется для деплоя на продакшн, разрешаем доступ только для чтения.
В конце не забываем сохранить все изменения:
$ git add .
$ git commit -am "New users for project: user1, user2, user3..."
$ git push origin master
4. Установка и настройка на машинах разработчиков
Серверная часть полностью готова, теперь остается установить и настроить git-клиент на машинах разработчиков.
4.1. Linux/Unix
Тут все просто — устанавливаем git с помощью своего любимого менеджера пакетов.
После установки не забываем указать свое имя и e-mail, такие же, которые использовались при импорте из SVN:
$ git config --global user.name "javer"
$ git config --global user.email "user@domain.tld"
4.2. Windows
Здесь существует несколько различных клиентов, я пока остановился на TortoiseGit.
Перед его установкой сначала нужно установить msysgit, желательно самой последней версии, несмотря на надпись Beta. Во время установки на вопрос об интеграции в систему я советую выбирать пункт Git Bash and Command prompt, чтобы можно было запускать git как из Git bash, так и с командной строки.
После этого устаналиваем сам TortoiseGit. Я советую устанавливать последнюю стабильную версию, но не nightly-build.
Теперь заходим в настройки TortoiseGit (правой клик по любому каталогу и TortoiseGit->Settings), находим там раздел Git и справа в блоке User Info вписываем свои имя и e-mail.
5. Переезд завершен. Приступаем к работе
Все, на этом процедура переезда с SVN на Git завершена.
Каждый разработчик клонирует себе на машину проект и начинает с ним работать.
Я бы посоветовал разработчикам ознакомиться с этими статьями: