28,000 コミットあるSVNリポジトリをGit(GitLab)に移行した話

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 の環境に以下のツールをインストールしました

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_onlysvn2git.example.com に戻して、すべてのブランチをチェックアウト→空のディレクトリの作成…をやりなおすことで、1日程度で移行できます。

参考資料

まとめ

もし、Gitに移行したいけどできてないかたの助けになれば幸いです。

  • このエントリーをはてなブックマークに追加

この記事を読んだ人にオススメ