そうだ Git、移行 (Reposurgeon編)

今年の春に、それまでプロジェクトで長年つかってきたSubversionをGitに移行した。比較的少人数での開発だしリモートで作業している人もいないし、分散SCMであるメリットは実はそれほどないのだけれど。やっぱり技術者として世の中の流れについて行きたいというのもあるし、リリース管理者としてリリースの度に実施するSubversionでのマージ処理が辛くて辛くて(追加/削除がディレクトリ操作になるので、ディレクトリ配下の一部だけをマージしたい時に余計なものまで一緒にマージされてしまって自動化の妨げになる)何とかしたかったのもある。

まぁ、正直なところSubversionもとても良くできた、そして何より実績が多いソフトウェアでなので、別にGitがなくても死にはしない。今Subversionでうまく回っているなら、急いで乗り換える必要は多分、ない。

だけど、これだけGithubがメジャーになって「OSSの管理といえばGit」っていう流れができあがってしまうと、個人的にはSubversionってもう、「移行するかべきか否か」ではなくて「いつ移行するか」を検討するくらいの時期には来ているとも思うわけで。十数年前にCVSとかVSSとかが主流だったSIerも、最近では大半が少なくともSubversionくらいには移行しているわけで、この先10年とかを考えると、SIerだからGitなんかいらないとかいうことはないのではないかと信じている。実際、最近書店にはデザイナーさん向けのGitの書籍が並んでいるくらい、Gitの認知度が上がっている。

そして何より、プロジェクトに新しく若者が入ってきたときに、Subversionという古の構成管理ツールの使い方を教えてあげるとかいうのは、お互い得るものが少なすぎて悲惨である。どうせ同じ時間を使うなら、その場限りのスキルなんかよりはいろいろなところで役に立つスキルを身につけることに時間を使いたい。まぁ、みんながみんなそういう気持ちではないかもしれないのだけど。

移行ツール

まぁ本音は「実は俺がGit使いたいだけ」でそこまで深い意味はなかったかもしれないけど、とにかくGitへの移行方法の話。かつてCVSからSubversionへの移行がスクリプトでサポートされていたように、Gitにも移行ツールがついている。それがGit-SVNというサブコマンドである。

Git-SVN

Git-SVNは事実上Subversionから移行する人のほとんどが一度は通る道だと思う。Git-SVNは移行ツールの一種ではあるのだけど、実際の所は「GitでSubversionのRepositoryにアクセスすることができる」というもので、GitへSubversionから取り込むという流れはもちろんのこと、逆にGit側からSubversionへ変更を送ることもできる。

Git-SVNについては↓こちらで@DQNEOさんが大変きれいにまとめられているので、詳しくはそちらを参照されたい。これは移行の時にじっくり読ませて頂いて、本当に参考になった。Git-SVNを使うかどうかを別としても、事前にSubversionのリポジトリをきれいにしておくとかサイズを小さくするとか、既存のコード資産を移行する人にとっては有益な情報が山ほど詰まっている。

仕事で使ってる巨大SVNレポジトリをGithubに移管するためにやったことまとめ

自分のプロジェクトでも最初はこれでいく想定で進めていた。でも、どうもGit-SVNの変換処理が結構遅いとか、できあがったGit Repositoryでテストしていたら、追加でコマンドをたたかないとSubversionにあったはずのブランチがみえないとか、あと(作業手順が漏れていただけなんだけど)そもそも移行されていないブランチがあったり、移行元が初期はtrunk/branches/tags構造じゃなかったのでそこが移行されずに落ちているとか、いろいろ課題とか問題が見えてきた。

それでほかにもう少しいい移行方法はないものか、と少しまじめに調べてみることにしたのだけど、その時に見つけたのが、ESRことEric S. Raymond大先生のRepository移行ツール、Reposurgeon

Git-SVNは言ってみれば双方向でGitとSubversionをつなぐツール。Subversionから一気にGit移行するのにも使えるけど、一部のメンバーだけ、あるいは一部のプロジェクトだけ、など段階的に移行することもできる。その気になれば移行以外の目的でも使える、案外汎用性の高いツール。

構成管理ツールを変更するときというのは、チームの規模が大きければ大きいほど混乱が伴うもの。だから一部のメンバーだけを段階的にGitに移行でき、しかも移行中もSubversion-Git双方向でお互いの変更が双方向に共有できるというメリットは非常に大きい。

Reposurgeon

対してReposurgeonはRepository編集を行うためのツールである。正規のツールが提供する通常のオペレーションでは絶対に実現できないような変更を履歴に対して加えることもできる。まさにRepositoryに対する外科手術。これもGit-SVNとは別の意味で、移行以外の目的で使えるツールだ。

Reposurgeonでの移行はRepositoryを一気に移行してしまう。そのため、Git-SVNのように同時並行稼働で双方向にいったりきたりすることはできず、Subversionから段階的にGitに移行する、という計画では基本的に使うことができない。ここがツール選択のポイントになるケースも多いはず。

その代わりに、より移行に適した形で、より高速に移行を済ませてくれる(と思う)。全員が一度に移行するなら個人的にはこちらのが良いのではないかと思う。ツール自体が難しく情報量もGit-SVNと比較するとかなり限られるので、自分で調べて問題を解決できる人向きではあるけど。

何れにせよ、絶対的にどちらのツールが優れているとかそういう類のものではない。リポジトリの分割方針とか利用者の数やスキルを考慮して適切なツールを選びたいところ。

Reposurgeonのメリット

  • 移行機能は基本的に一括移行
  • 追加手順なく、最初から意図した形の移行結果になりやすい
  • 通常のツールではできない、履歴に対する各種変換処理が可能

Reposurgeonのデメリット

  • Unix系プラットフォームのみサポート
  • Subversionとの相互運用不可
  • 情報が少ない、コマンドや作業フローがわかりづらい

ReposurgeonはUnix系のツールなので、Windowsでは動かない。ここは注意が必要。でも、WindowsでホストしているSubversionのリポジトリをMac OS XにもってきてGitに移行し、完成したGitリポジトリをWindowsに戻してWindowsのGitで運用する、といったことは問題なく行えるので、実際にはそれほど大きな制約ともいえない。実際、自分はOSXで移行したリポジトリをWindowsのGitで動かしてもうそろそろ1年だけど、何も問題になっていない。

Reposurgeonの導入

自分はOSXでしか試していないんだけど、きっと大抵のパッケージマネージャでサポートされていると思う。Homebrewがはいっていれば

brew install reposurgeon

これだけ。

conversion.mkによる変換

Reposurgeonのコマンドは最初は取っつきづらいと思う。そのため(かどうかは知らないけど)、SubversionからGitに移すだけ、を簡単に実現してくれるmakeファイルが配られている。

http://www.catb.org/~esr/reposurgeon/conversion.mk

makeファイルをダウンロードしたらエディタで開いて以下の2行を変更する。PROJECTは最終的に作られるGit Repositoryの名前になるけど、まぁ多分このMakefileで作成したリポジトリをそのまま使うことはないんじゃないかと思うので、あまり気にしなくていいと思う。SVN_URLはデータを取ってくる場所になるのでちゃんと書かないとダメ。

PROJECT = code-repo
SVN_URL = http://192.168.1.100/svn/code-repo

これでmake -f conversion.mkすれば、勝手に変換が行われてcode-repo-gitというディレクトリにgitのRepositoryが完成。

ちなみにGitに移行したときにリビジョンが数字からSHA1ハッシュに変わるのだけど、Subversionのリビジョン番号に依存する他のツールと連携しているような場合、Subversion側でのリビジョン番号を記録として残すことができる。この場合makefileを少し編集する必要がある。

44: # Build the second-stage fast-import stream from the first-stage stream dump
45: $(PROJECT).fi: $(PROJECT).$(SOURCE_VCS) $(PROJECT).lift $(PROJECT).map $(EXTRAS)
46:    $(REPOSURGEON) $(VERBOSITY) "read <$(PROJECT).$(SOURCE_VCS)" "authors read <$(PROJECT).map" "prefer git" "script $(PROJECT).lift" "fossils write >$(PROJECT).fo" "write >$(PROJECT).fi"

これを、

44: # Build the second-stage fast-import stream from the first-stage stream dump
45: $(PROJECT).fi: $(PROJECT).$(SOURCE_VCS) $(PROJECT).lift $(PROJECT).map $(EXTRAS)
46:     $(REPOSURGEON) $(VERBOSITY) "read <$(PROJECT).$(SOURCE_VCS)" "authors read <$(PROJECT).map" "prefer git" "script $(PROJECT).lift" "fossils write >$(PROJECT).fo" "write --fossilize >$(PROJECT).fi"

こんな感じにする。最後のwriteコマンドに–fossilizeオプションをつけるのがポイントだ。RedmineのようなIssue管理システムとRevision番号で連携を取っているような場合は、Gitのリビジョン情報で過去の関連づけを上書きできると理想的だけど、その材料となる大事な情報となる。

インタラクティブなコマンド実行による変換

conversion.mkによる変換はなにもわからない状態でも2、3行編集するだけでさくっとSubversionからGitに変換できてしまう手軽さがポイントで、できあがったGitリポジトリを見ていると結構ちゃんとしていて感激する。

だけど、変換処理について細かいカスタマイズをしようと思うと、結局最後はmake fileの中で呼び出されているコマンドを調べることになる。Makefileを読むのって結構面倒くさいのだけど、このツールについて言えば、説明を読むよりもまずはこれを見てみるのが効果的だと思う。いきなりオフィシャルの説明を読んだところで、普通の人は動かせないと思うので。

一連の流れは次のセクションを見てもらうとして、Reposurgeonを使うには基本形を知っておく必要がある。

  1. read <[ファイル]
    Subversionのダンプや、Reposurgeonや各種Version Control System (VCS)からwriteで書き出したfast-import形式のファイルを読み込む。ここで読み込んだファイルに対して、各種コマンドを実行してリポジトリの内容を編集していく。基本的にはReposurgeonを起動して一番最初に呼ぶコマンドになるはず。
  2. write >[ファイル]
    編集した内容を、exportする。自分は編集作業後のバックアップとしてしか使っていないけど、編集したリポジトリを他のfast-importできるツールにインポートする場合はその元ネタになる。
  3. rebuild [リポジトリ]
    編集後のリポジトリを実際にVCSで使えるリポジトリとして再構築する。

基本形はこの、read→編集→rebuildもしくはwriteという流れで、readからrebuildの間には好きなだけ編集コマンドを挟めばいい。例えば正規表現にマッチするファイルを含むリビジョンを探して、該当ファイルだけ削除してしまうとか、不要なタグを消し去る(削除コミットができるのではなく、履歴のどこにも登場しなくなる)とか。

コマンド実行する場合には、Reposurgeonを起動してreposurgeon>プロンプトにする。SubversionからGitに移行するには、以下のような流れでコマンドをたたいていけばよい。

  1. readコマンドを使って、Subversionのdumpファイル(code-repo.dmp)からリポジトリ情報を読み込む。dumpというのはもちろんsvnadmin dumpで作成したもの。ちなみにdumpと一言でいってもSubversionのダンプにはバージョンがあり、バージョンが高すぎるとreposurgeonが読み込めないときがある。–deltaとか特殊なオプションをつけるとバージョン指定が厳しくなったりするので、Reposurgeonに食わせる場合はオプションをつけずに出力するのが良いと思う。
     reposurgeon> read <code-repo.dmp

    ちなみにreposurgeonのリダイレクトはシェルのリダイレクトとは異なり、記号とファイル名との間にスペースをいれると動かない。必ず<とか>とファイル名を空白で区切らずに続けて書く。

  2. author map ファイルを読み込む (subversionのuser idをgit user name + emailにマッピングするファイル、git-svnで使うのと同じものがそのまま使える)
    reposurgeon> authors read <users.map
  3. 移行先VCSをgitに指定。
    reposurgeon> prefer git
  4. Subversionリビジョンと日付/コミットした人の一覧を出力(optional)
    reposurgeon> fossils write >dev.fo
  5. Repository名のリネーム(これもoptionalかな?実はどこに効いているのかよくわかっていない)
    reposurgeon> rename code-repo2
  6. Subversionからgitに間違ってtagとして認識されてしまうゴミタグの削除
    reposurgeon> =T & /(emptycommit-.*|tipdelete-.*|.*-root$)/n delete
  7. ここまで編集した内容でgit-fast-import用のファイル(dev.fi)を書き出す
    reposurgeon> write --fossilize >code-repo.fi
  8. とりあえずここまでで準備完了。あとのRepository作成はこんな感じ。
    $ reposurgeon
    reposurgeon> read <code-repo.fi
    reposurgeon> rebuild code-repo2
  9. ここまでの手順で作業コピー付きのGitリポジトリが作成される最後にbareリポジトリ作ってpush
    $ cd code-repo2
    $ git remote add origin ../code-repo2.git
    $ git push --all origin
    $ git push --tags origin
    $ git init --bare code-repo2.git
  10.  git gcでとどめの圧縮をしておく。
    $ git gc --aggressive
    Counting objects: 104054, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (91278/91278), done.
    Writing objects: 100% (104054/104054), done.
    Total 104054 (delta 60931), reused 32576 (delta 0)

自分が作業しているとき、あまりReposurgeonの記事見かけなかったので、今後移行する人の参考になればということで記録を残しておく。自分もそれほど深く理解できていないので説明なんか書くのもどうかと思うけど、何もないよりいいだろう、と。