28,000 コミットあるSVNリポジトリをGit(GitLab)に移行した話
こんにちは、今年は2017年ですね。tanakaです。
このたび、2009年からコミット履歴のあるプロジェクトのSVNリポジトリをようやくGitに移行しました。
その手順やハマリどころについてまとめておきたいと思います。
移行前と移行後の制作フローとか
このSVNリポジトリは、開発用ブランチ trunk
とリリース用ブランチ branches/RB
の2本が常設してあり、
trunk
でリリース可能になったコミットだけをCherry pick で branches/RB
にマージする運用をしており、
マージしないコミットが増えると衝突が発生したり、時間経過で差が増えていき辛い感じになってました。
そこでGit(GitLab)に移行することにしました。
移行にあたり、ブランチ戦略としてGitHub Flow を導入しました。
master
への直接のプッシュは禁止し、すべてマージリクエストでマージするようにしました。
また、機能ごとのレビューをしやすくするため、GitLab CI + Review Apps で、ブランチごとのレビュー環境を自動で作成・消去する仕組みを構築しました。
必要なツールのインストール
CentOS の環境に以下のツールをインストールしました
- Subversion 1.9.5 (wandisco リポジトリより)
- Git 2.13.3 (IUS Community リポジトリより)
- git-svn (IUS Community リポジトリより)
- git導入後に
yum install git2u-svn
で導入します。
- git導入後に
Gitリポジトリへの変換
SVNリポジトリにはユーザー名しか登録されていないので、フルネームとメールアドレスへの変換リストを用意しておきます。 リポジトリから抽出することも出来ますが割愛します。
svn_users.txt
tanaka = Koji Tanaka <tanaka@example.com>
suzuki = Ichiro Suzuki <suzuki@example.com>
...
...
git svn
コマンドで変換します。
$ mkdir svn2git.example.com && cd svn2git.example.com
$ git svn init https://svn.example.com/example_project --branches branches/archives --branches branches --ignore-paths="^example_project/(?:trunk|branches/[-a-zA-Z0-9]+|branches/archives/[-a-zA-Z0-9]+)/(managed_docs|docs)" --ignore-paths="^branches" --no-metadata --stdlayout --prefix=svn/
$ git config svn.authorsfile /path/to/working_dir/svn_users.txt
$ git svn fetch
この時点ではsvnリポジトリに追加されても、あとで再フェッチするので問題ありません。
git svn init
時にいろいろオプションが付いています。以下は、標準的で無い処理をしたくなったので追加しています。
--branches branches/archives --branches branches
… マージ済みブランチをbranches/archives
に退避する運用をしており、引き継ぎたかったので--branches branches/archives
を指定しました。--ignore-paths="^example_project/(?:trunk|branches/[-a-zA-Z0-9]+|branches/archives/[-a-zA-Z0-9]+)/(managed_docs|docs)"
… プロジェクトの初期にドキュメントなどもmanaged_docs
,docs
などのディレクトリにコミットしており、除外しました。
git svn fetch
が中断しても、再度 git svn fetch
すれば、中断されたところから再開されます。
28,000コミットあると、昼間の作業で変換が終わりません。GNU Screen でセッションが切れても処理が継続するようにしました。 また、フェッチし終わって変換されたローカルのGitリポジトリは本移行の時のためバックアップをしておきます。
cp -rp svn2git.example.com svn2git.example.com_fetched_only
変換したら、すべてのブランチをチェックアウトします。
$ git branch -r -d svn/archives
$ for BRANCH_NAME in $(git branch -r | grep -ve 'svn/tags\|svn/trunk\|.*@\d*' | sed -e 's:svn/::'); do
> git checkout -b "$BRANCH_NAME" "svn/$BRANCH_NAME"
> done;
$ git checkout master
GitLab で空のリポジトリを作成し、すべてのブランチを pushしましょう。
$ git remote add origin ssh://git@git.example.com/example_group/example_project.git
$ git push -u origin --all
空のディレクトリを作ります
Gitでは、空ディレクトリのみをコミットできないため、empty
というファイルを登録しておきます。
SVNチェックアウトした作業コピーで空ディレクトリを見つけたら、git作業コピーにディレクトリと empty
というファイルを作成して、コミットします。
$ cd ..
$ svn checkout https://svn.example.com/example_project/trunk example_project_trunk
$ ls
svn2git.example.com example_project_trunk
$ cd example_project_trunk
$ find . -type d -empty -exec mkdir -p ../svn2git.example.com/{} \;
$ find . -type d -empty -exec touch ../svn2git.example.com/{}/empty \;
$ cd ../svn2git.example.com
$ rm -rf .svn
$ git add .
$ git commit -m '空ディレクトリ生成'
$ git push origin master
git svn
には --preserve-empty-dirs
というオプションがあり、svnリポジトリに空ディレクトリがあったら、空ファイルを追加できますが、
28,000コミットあると、10,000コミット前後でフェッチ処理がほとんど進まなくなり、時間が掛かりすぎてしまうため、上記の方法をとりました。
参考: git svn --preserve-empty-dirs - the performace-killer switch
Vagrant + Itamae のスクリプト用リポジトリを統合
c-brains/vagrant-centos-phpdev: Vagrantで作るPHPアプリケーション開発環境のようなGitリポジトリをプロジェクト用に作ってありましたので統合しました。
$ git remote add vagrant ssh://git@git.example.com/example_group/example_project.git
$ git fetch vagrant
$ git merge --allow-unrelated-histories vagrant/master
$ git push origin master
本移行時
本移行時は、上記作業のフェッチ後にsvnリポジトリにコミットされたコードを取り込むためコピーしておいた svn2git.example.com_fetched_only
を svn2git.example.com
に戻して、すべてのブランチをチェックアウト→空のディレクトリの作成…をやりなおすことで、1日程度で移行できます。
参考資料
まとめ
もし、Gitに移行したいけどできてないかたの助けになれば幸いです。