Переезд проекта с SVN на Git

:

image
Много лет подряд в качестве системы контроля версий для большого количества проектов использовали только 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 завершена.

Каждый разработчик клонирует себе на машину проект и начинает с ним работать.

Я бы посоветовал разработчикам ознакомиться с этими статьями: