| コメント(0) | トラックバック(0)

Piece FrameworkのプロダクトのひとつPiece_UnitySubversionリポジトリには、Piece_Unity本体に加えて、Piece_Unity_Component_Authentication, Piece_Unity_Component_Flexyといったプロダクト(ここではモジュールと呼びます)が多数含まれており、これらは本体とは別のリリース単位を持っています。以前の記事「リポジトリをGitへ移行する」で取り扱ったのは単一のモジュールのみを含むSubversionリポジトリでしたが、複数のモジュールを含むリポジトリはどのように移行すればいいのでしょうか?

KUBO Atsuhiro

ゴール

SubversionリポジトリをGitへ移行する」と同じく、今回のゴールはPiece_UnitySubversionリポジトリをgitの共用リポジトリに移行することです。移行元のリポジトリレイアウトを示します。

+--branches
|  |
|  +--branch-1.0
|  |  ...
|
+--releases
|  |
|  +--Piece_Unity_Component_Authentication-1.0.0
|  |  ...
|  +--release-1.6.2x
|
+--tags
|
+--trunk
|  |
|  +--components
|  |  |
|  |  +--Authentication
|  |  |  ...

複数のモジュールは個別のリポジトリへ

Linus Torvalds氏は2007年の講演(YouTube - Tech Talk: Linus Torvalds on git)で、複数のモジュールをどのように扱うべきかについて下記のように語っています。

If you have multiple components, do them as separate repositories, you can actually have what we call superproject that contains pointers to other projects, the user interfaces there are somewhat lacking, but you keep separate projects separate. Then you avoid the problem of "you have to get it all". Because with git you do have to get it all.

また、マニュアルには下記の記述があります。

集中型のリビジョン管理システムでは、1つのリポジトリ内に各モジュールを含むことによってこれを実現します。開発者は全てのモジュールあるいは必要なモジュールだけをチェックアウトすることができます。API や翻訳の移動や更新といった幾つかのモジュールにまたがったファイルを1回のコミットで変更することができます。

Gitは部分的なチェックアウトを許可していない為、Git の複製アプローチでは開発者は興味のないモジュールのコピーまで取得しなくてはなりません。 Git は各ディレクトリの変更をスキャンしなくてはならず、莫大なコミットをチェックアウトすることで、想像以上に Git は遅くなります。

これらを指針とし、今回は下記のような戦略を採ることにしました。

  • 複数のモジュールを含むSubversionリポジトリは個別のgitリポジトリに分割する
  • 各モジュールへのポインタ(git-submoduleによるサブモジュール)を含むgitリポジトリ(スーパープロジェクト)をひとつ用意する

これで移行後の構成がみえてきました。

piece-unityリポジトリ(スーパープロジェクト)
+--ブランチ
|  |
|  +--master
|  |  |
|  |  +--components
|  |  |  |
|  |  |  +--piece-unity-component-authentication (サブモジュール)
|  |  |  |  ...
|  |
|  +--branch-1.0
|
+--タグ
|  |
|  +--release-0.1.0
|  |  ...
|  +--release-1.6.2
piece-unity-component-authenticationリポジトリ
+--ブランチ
|  |
|  +--master
|
+--タグ
|  |
|  +--release-0.13.0
|  |  ...
|  +--release-1.1.1

サブモジュールとなるすべてのモジュールを移行する

スーパープロジェクトを移行する前に、サブモジュールとなるすべてのモジュール(以下、単にサブモジュール)を移行します。前回と同じく移行には svn2git を使うことができます。しかし、前回とは異なり今回の移行には多少込み入った作業が必要になります。Piece_Unity_Component_Flexyパッケージの移行を例にサブモジュールの移行について見ていきましょう。

最初にサブモジュールの移行方針を確認しておきます。

  • trunk/components/Flexyをtrunkとする
  • releases/Piece_Unity_Component_Flexy-xxxについてはgitへの移行後に手作業でタグを作成する

複数のサブモジュールを移行することになるため、今回はあらかじめリポジトリのダンプファイルを作成しておきます。サブモジュール毎にこのダンプファイルから svndumpfilter include により必要な部分のみからなるダンプファイルを作成することになります。

svndumpfilter include \
    --drop-empty-revs \
    --renumber-revs \
    --skip-missing-merge-sources \
    --quiet \
    /trunk/components/Flexy \
    /trunk/components/Piece_Unity_Component_Flexy \
    /trunk/Piece/Unity/Plugin/Renderer/Flexy.php \
    /trunk/Piece/Unity/Service/FlexyElement.php \
    /trunk/components/Piece_Unity_Component_PieceORM/package.php \
    /trunk/tests/Piece/Unity/Plugin/Renderer/FlexyTestCase \
    /trunk/tests/Piece/Unity/Service/FlexyElementTestCase.php \
    /trunk/components/Piece_Unity_Component_PieceORM/tests/prepare.php \
    < /path/to/piece-unity.dump > /path/to/piece-unity-component-flexy.dump

含めるパスが多くなっていますが、これらはあらかじめ調べておく必要はありません。指定したパスで整合性が保てない場合、 svndumpfilter include は下記のようにエラーメッセージを出力して終了します。

$ svndumpfilter include \
    --drop-empty-revs \
    --renumber-revs \
    --skip-missing-merge-sources \
    --quiet \
    /trunk/components/Flexy \
    < piece-unity.dump > piece-unity-component-flexy.dump
svndumpfilter: コピー元のパス '/trunk/components/Piece_Unity_Component_Flexy' が不正です。

このエラーメッセージに含まれるパスを引数に加えて再度コマンドを実行します。これをエラーがなくなるまで繰り返します。

次にSubversionリポジトリを作成し、ダンプファイルをロードします。

$ svnadmin create /path/to/repos/piece-unity-component-flexy
$ svnadmin load /path/to/repos/piece-unity-component-flexy < /path/to/piece-unity-component-flexy.dump
<<< オリジナルのリビジョン 1 に基づき、新しいトランザクションを開始しました
svnadmin: File not found: transaction '0-0', path 'trunk/Piece/Unity/Plugin/Renderer/Flexy.php'
     * パスを追加しています : trunk/Piece/Unity/Plugin/Renderer/Flexy.php ...

svndumpfilter で作成されたダンプファイルには必要なディレクトリが含まれていないため、上記のようなエラーが発生しますが、ロードの前に必要なディレクトリ構造をインポートすることで対処できます。svndumpfilter の場合と同じように、svnadmin load の出力に含まれるパスを事前に作成してください。例えば上記の場合、追加されるパスは trunk/Piece/Unity/Plugin/Renderer/Flexy.php なので、必要なディレクトリは trunk/Piece/Unity/Plugin/Renderer となります。一連の流れは下記のようになります。

rm -rf /path/to/initial-structure
mkdir -p /path/to/initial-structure/trunk/Piece/Unity/Plugin/Renderer
mkdir -p /path/to/initial-structure/trunk/tests/Piece/Unity/Plugin/Renderer
mkdir -p /path/to/initial-structure/trunk/Piece/Unity/Service
mkdir -p /path/to/initial-structure/trunk/tests/Piece/Unity/Service
mkdir -p /path/to/initial-structure/trunk/components/Piece_Unity_Component_PieceORM
mkdir -p /path/to/initial-structure/trunk/components/Piece_Unity_Component_PieceORM/tests
rm -rf /path/to/repos/piece-unity-component-flexy
svnadmin create /path/to/repos/piece-unity-component-flexy
svn import /path/to/initial-structure file:///absolute-path/to/repos/piece-unity-component-flexy -m '- Initial import.'
svnadmin load /path/to/repos/piece-unity-component-flexy < /path/to/piece-unity-component-flexy.dump

これで必要な部分のみからなるSubversionリポジトリが完成しました。svn2git を実行しましょう。(筆者が「SubversionリポジトリをGitへ移行する」に掲載したパッチを適用したバージョンはこちらからダウンロードすることができます。)

rm -rf /path/to/git-tmp-repos
mkdir -p /path/to/git-tmp-repos
cd /path/to/git-tmp-repos
/var/lib/gems/1.8/gems/svn2git-1.1.0/bin/svn2git \
    file:///absolute-path/to/repos/piece-unity-component-flexy \
    trunk=trunk/components/Flexy \
    authors=/path/to/authors.txt 2>&1 | tee svn2git.log

無事に変換が完了したら、Subversionのログとgitのログを確認しながら適切にタグを作成します。

$ git tag release-1.0.0 3fe0ad1c7e15bbd9f5cf811f70fb9909e06d3fb1
$ git tag release-1.1.0 0b587c935d4e42ad17ac53258b6405ebcafaf4e2
$ git tag release-1.2.0 68bf2ce743369cf19fc5ba9720f067bba2cb0a87
$ git tag release-1.3.0 661a66371cbde14e2865cded92c54e3df22d271f
$ git tag
release-1.0.0
release-1.1.0
release-1.2.0
release-1.3.0

準備が整ったらローカルリポジトリの内容を共用リポジトリへ反映します。

git remote add origin git@github.com:piece/piece-unity-component-flexy.git
git push origin master
git push --tags

最後に改めて git clone を実行します。

$ cd /path/to/GITREPOS
$ git clone git@github.com:piece/piece-unity-component-flexy.git
$ cd piece-unity-component-flexy/
$ git branch -a
* master
  origin/HEAD
  origin/master
$ git tag
release-1.0.0
release-1.1.0
release-1.2.0
release-1.3.0

ひとつのサブモジュールの移行はこれで完了です。残りすべてのサブモジュールに対してこの作業を行います。

スーパープロジェクトとなるモジュールを移行する

いよいよスーパープロジェクトとなるモジュールの移行に入ります。先に移行方針を確認しておきます。

  • trunk/componentsを除外する
  • releases/release-xxxについてはgitへの移行後に手作業でタグを作成する
  • branches/xxxについてはgitへの移行後に手作業でブランチを作成する

サブモジュールの場合と同じく、ダンプファイルから svndumpfilter include により必要な部分のみからなるダンプファイルを作成します。続けてSubversionリポジトリを作成し、ダンプファイルをロードします。

svndumpfilter exclude \
    --drop-empty-revs \
    --renumber-revs \
    --skip-missing-merge-sources \
    --quiet \
    /branches \
    /releases \
    /tags \
    /trunk/components \
    < /path/to/piece-unity.dump > /path/to/piece-unity-base.dump
rm -rf /path/to/initial-structure
rm -rf /path/to/repos/piece-unity-base
svnadmin create /path/to/repos/piece-unity-base
svnadmin load /path/to/repos/piece-unity-base < /path/to/piece-unity-base.dump
rm -rf /path/to/git-tmp-repos
mkdir -p /path/to/git-tmp-repos
cd /path/to/git-tmp-repos
/var/lib/gems/1.8/gems/svn2git-1.1.0/bin/svn2git \
    file:///absolute-path/to/repos/piece-unity-component-flexy \
    trunk=trunk/components/Flexy \
    authors=/path/to/authors.txt 2>&1 | tee svn2git.log

無事に変換が完了したら、Subversionのログとgitのログを確認しながら適切にタグとブランチを作成します。

git branch branch-1.0 a21c41c421a99e65850b0d84e1eab4f4ce07f1fe
git tag release-0.1.0 d94a24a6c75b9b60b811e15a8d3d46f2f34ac62d
...
git tag release-1.6.2 2df3510e462c273227306ef697c6e995d4fbee2d
$ git branch
  branch-1.0
* master
$ git tag
release-0.1.0
...
release-1.6.2

ローカルリポジトリの内容を共用リポジトリへ反映し、改めて git clone を実行します。

$ git remote add origin git@github.com:piece/piece-unity.git
$ git push --all
$ git push --tags
$ cd /path/to/GITREPOS
$ git clone git@github.com:piece/piece-unity-component-flexy.git
$ cd piece-unity-component-flexy/
$ git branch -a
$ git branch -a
* master
  origin/HEAD
  origin/branch-1.0
  origin/master
$ git tag
release-0.1.0
...
release-1.6.2

スーパープロジェクトにサブモジュールを追加する

さて、最後のステップです。スーパープロジェクト piece-unity にサブモジュールを追加します。GitHubの場合、リポジトリにはPublic Clone URLを指定するといいでしょう。

$ git submodule add git://github.com/piece/piece-unity-component-authentication.git components/piece-unity-component-authentication
...
$ git submodule add git://github.com/piece/piece-unity-component-smarty.git components/piece-unity-component-smarty
$ git submodule
-238a3d7a1d3331c83cda2a89102b53cbf2ff044c components/piece-unity-component-authentication
...
-a0461e6c09df0305a17d07947ce8d61e080a211d components/piece-unity-component-smarty

最後にスーパープロジェクトをコミットします。これで移行は完了です。

Piece_Unityの例では、gitへの移行によって本来無理があった単一リポジトリ内での複数モジュールの同居が露呈し、結果的にモジュール設計の改善を行うことができました。ソフトウェアの設計において単一のソフトウェアを適切なモジュールに分割することは重要なプラクティスです。この例のようにgitを使うことをきっかけにして、よりよいモジュール設計についての先人の知見がより多くのソフトウェアに適用されるとしたら、それはとても素晴らしいことです。

参考文献

トラックバック(0)
  • このブログ記事のトラックバックURL:
コメント