RSS2.0

svn リポジトリをローカルにコピーしてから git リポジトリに変換する

今年になって、個人的なコードを書き溜めていた svn リポジトリを徐々に git に移行しています。

私はフリープランでもプライベートリポジトリを無制限に作れることから、数年前から assembla を使っていました。しかし最近(??)になってフリープランで作れるリポジトリ数に上限が設定されたようで、新しいリポジトリが作れなくなっていました。有料プランへ乗り換えられればよかったのですが、リポジトリ数をふまえて開発予算(お小遣い)と相談した結果、やむなくお世話になった assembla から bitbucket に移住することにしました。念のため強調しておきますが、これまでながらく愛用していたことからもわかるとおり、assembla 自体に不満があるわけではありません。あくまでやんごとなき事情(お小遣い)のため、泣く泣くの別れとなります(涙)。

移住先として選んだ bitbucket はリポジトリ数の上限こそないものの、コミッターの総数が 5 人までに制限されています。私は他人に見せられるコードは別途 github に置いているので、プライベートリポジトリについてはこの制限はまったく考慮しなくてよいものでした。

 assembla の svn リポジトリに git svn clone するとエラーで落ちる

assembla から git への引越し作業にはすこし面倒でした。
ネットの各所に資料があるとおり、svn リポジトリを git リポジトリに変換するには git svn clone を使えばいいのですが、assembla 上の svn リポジトリにこのコマンドを使うと以下のようなエラーが発生し、変換処理が異常終了してしまいました。
...
r372 = 4b5404a5643c5d3refref081c06c3acaa2e8eeb (refs/remotes/trunk)
Checked out HEAD:
  https://subversion.assembla.com/svn/momokan/myrepo01/trunk r372
error closing pipe: 不正なファイル記述子です at /usr/libexec/git-core/git-svn line 0.
error closing pipe: 不正なファイル記述子です at /usr/libexec/git-core/git-svn line 0.
詳細がよくわかないのですが、時間を置いて再度実行してもここで落ちてしまうため、別の手段で移行を進める必要がありました。

 svnsync でリモートの svn リポジトリをローカルマシンにコピーする

直接 assembla の svn リポジトリに git svn clone をたたくことはできないので、一度 assembla の svn リポジトリをローカルマシンにコピーし、コピーしたローカルマシン上の svn リポジトリに対して git scn clone することにしました。
とはいっても、svn リポジトリのメタデータ等含めたリポジトリの本体は assembla サーバー上にあるので、もちろんリモートから触れることはできません。そこで svnsync コマンドを使って、ローカルマシン上にリモートの svn リポジトリをコピーすることにしました。

svnsync はもともと subversion のリモートリポジトリのミラーリングツールです。実行にはリモートリポジトリへのユーザーとしての読み込み権限だけあればよく、リモートリポジトリの各リビジョンを 1 つずつ読み込んでミラーリング先リポジトリに再現・再構築することができます。

準備

git リポジトリへの変換作業で必要となる git svn をインストールしておきます。
# yum install -y git-svn
git, svn はすでにインストールされているものとして、私の環境ではこのコマンドは入っていなかったので入れておきます。

また、万が一の自体に備えて専用の作業ディレクトリを用意しておきます。
この後の変換作業は /home/momokan/work 内で行っているものとして読んでください。
$ mkdir work
$ cd work

svn から git に引越しをする

正確な引越し順序は、リモート svn リポジトリ → ローカル svn リポジトリ → ローカル git リポジトリ → リモート git リポジトリという流れになります。
通常の git リポジトリへの変換手順と異なるのは svnsync でリモート svn リポジトリをローカルマシン上にコピーする部分になります。

svnsync でリモート svn リポジトリをとってくる

コピー先の svn リポジトリとして、ローカルディレクトリ上に svnrepo を作成します。
$ svnadmin create svnrepo
svnadmin は svn サーバー上にリポジトリを作るためのツールです。svn サーバーを構築したことのある人にはお馴染みのあれです。

ローカル svn リポジトリには、svnsync する際に必要となる svn hook を作っておきます。
$ echo '#!/bin/sh' > svnrepo/hooks/pre-revprop-change
$ chmod +x svnrepo/hooks/pre-revprop-change

次に、リモート svn リポジトリをローカル svn リポジトリにミラーリングします。
$ svnsync init file:///home/momokan/work/svnrepo https://subversion.assembla.com/svn/myrepo01/
$ svnsync sync file:///home/momokan/work/svnrepo
svnsync init はローカル svn リポジトリにミラーリングするリモート svn リポジトリを登録し、ミラーリング状態を初期化するコマンドです。
そして実際にミラーリングを行うのが svnsync sync コマンドです。svnsync sync は実行毎にまだミラーリングしていないリモート svn リポジトリのリビジョンをローカル svn リポジトリに反映してくれます。

svn - git リポジトリ間のユーザーマッピングを作成する

ここからは通常の git リポジトリへの変換作業になります。
公式ドキュメントにあるとおり、まずは svn と git のコミットユーザーのマッピングを作成しておきます。
コミットユーザーのマッピングは、以下のような書式でテキストファイル users.txt に保存しておきます。
svn ユーザー1 = git ユーザー1 <git ユーザー1メールアドレス>
svn ユーザー2 = git ユーザー2 <git ユーザー2メールアドレス>
svn ユーザー3 = git ユーザー3 <git ユーザー3メールアドレス>
...

とはいえ、svn リポジトリの過去のすべてのリビジョンを調べ直すのは面倒なので、以下のコマンドでコミットユーザーを洗い出しておきます。
svn log コマンドの引数に指定する svn リポジトリは先ほどコピーしたローカル svn リポジトリになるので、file プロトコルの URL として file://<ローカル svn リポジトリの絶対パス> を渡します。
$ svn log --xml file:///home/momokan/work/svnrepo | grep -P "^<author" | sort -u | perl -pe 's/<author>(.*?)<\/author>/$1 = /' > users.txt
これで svn リポジトリ側のコミットユーザーはすべて users.txt に書き出されるので、これに各 svn ユーザーに対応する git ユーザーを追記しておます。

svn リポジトリを git リポジトリに変換する

それでは git svn clone コマンドで git リポジトリへの変換を行います。
-T オプション、-t オプション、-b オプションにはそれぞれ svn リポジトリ上の trunk、tags、branches ディレクトリを指定します。必要ないディレクトリは指定しなくて大丈夫です。
また、変換するリモート svn リポジトリの構成がルート直下に trunk、tags、branches を持つ標準的な svn レイアウトなら、これらのオプションの代わりに -s オプションのみを指定することができます。
$ git svn clone --authors-file=users.txt --localtime file:///home/momokan/work/svnrepo -T trunk/myproject01 -t tags gitrepo
--localtime オプションは、コミットログの時刻をローカルのタイムゾーンで変換するオプションです。デフォルトでは UTC で変換されるので指定しておくと幸せになれます。
公式ドキュメントにある --no-metadata オプションは変換した git リポジトリに svn リポジトリのメタデータを残さないオプションです。どうやら既に推奨されなくなっているようで、これを指定すると .gitignore 変換用の git svn create-ignore が使えなくなるため、ここでは指定はしませんでした。
実行すると、gitrepo ディレクトリ内に変換された git リポジトリが作られます。

実際に git リポジトリ内で git log を実行すると、svn のコミットが反映されていることがわかります。
$ cd gitrepo
$ git log -1
commit d99a755b279de9c3cdrefreff5c1da5940d5ea54
Author: momokan <momokan@example.com>
Date:   Wed Apr 23 19:10:24 2014 +0900

    桃缶をいちはやく開けるために缶切りの場所を部屋の間取り上に表示する

    git-svn-id: file:///home/momokan/work/svnrepo@72 26df1703-5a2b-4a2d-8e80-refref0cfaa6
...

git リポジトリのユーザー情報を設定する

蛇足になりますが、この後変換した git リポジトリでもろもろの調整作業をするので、忘れないうちにユーザー情報を git リポジトリ内で設定しておきます。
変換した git リポジトリを push する際に Your Name さんにならないために。
$ git config user.name "momokan"
$ git config user.email "momokan@example.com"

git リポジトリのコミットログから git-svn-id を取り除く

さて、変換したコミットログを見ていて気になるのが git-svn-id です。これは変換元の svn リポジトリのコミットとの紐付けを示すもので、公式ドキュメントによれば、git svn clone 時に --no-metadata オプションを指定した場合には破棄される情報の 1 つとなります。
今回は git svn create-ignore を使うために --no-metadata オプションは指定しませんでしたが、コミットログにもはや svn リポジトリの git-svn-id を残しておく必要はありません。そこで git filter-branch コマンドで、変換した git リポジトリのコミットログから一括で git-svn-id を削除します。
$ git filter-branch -f --msg-filter 'sed -e "/git-svn-id:/d"'
Rewrite 3dc1433043efd460fbarefref3b2f663e0460cad (372/372)
Ref 'refs/heads/master' was rewritten
git filter-branch を使うと、git リポジトリの各コミットに対して任意のコマンドを実行することができます。イメージとしては、シェルの xargs コマンドに近いです。
今回はコミットメッセージを編集したいので --msg-filter オプションを指定した上で、sed コマンドによる文字列置換を行っています。
git filter-branch はすごい便利です。さすが公式で最強とうたわれているだけあります。

svn の svn:ignore 情報を .gitignore に変換する

svn リポジトリでバージョン管理の対象外となるファイルの設定を git リポジトリに引き継ぐことができます。
git svn create-ignore コマンドを変換した git リポジトリ内で実行し、作成された .gitignore ファイルをリポジトリに追加します。
$ git svn create-ignore

$ git add .gitignore
$ git commit -m "svn:ignore を .gitignore に変換"

git のリモートリポジトリに push する

変換したローカル git リポジトリを引越し先リモート git リポジトリに反映させます。
先に引越し先となる bitbucket にリポジトリを作っておき、そこをリモートリポジトリとして登録してから push します。
$ git remote add origin https://momokan@bitbucket.org/myrepo01/myproject01.git
$ git push origin master

svn のタグを git のタグに移行する

変換した git リポジトリ内で git branch -va するとわかりますが、svn のタグは svn 的にはただのブランチに過ぎません。そのため git リポジトリに変換した後もこれらは git 的なタグではなく、ただのブランチとして変換されます。これを git のタグにするには、git tag コマンドでひとつずつタグとして打ち直してやる必要があります。
$ git tag v0.1 remotes/tags/myproject01_ver0.1 -m "version 0.1"
この辺は git svn clone が賢くやってくれるとうれしいのですが、現時点ではひとつひとつ手動で対応していくしかなさそうです。シェルスクリプトなどでうまく対応したいですね。

ローカルリポジトリ内でタグの打ち直しが終わったら、タグをまとめて push します。
$ git push --tags
ローカルリポジトリ内にあるタグは打ち直した移行対象のタグだけなので、すべてまとめて push してしまいます。

同じように、ブランチについても必要なものは push します。
svn のタグから移行された git ブランチは既に git タグとして push しているので、ブランチとして push する必要はありません。他に開発ブランチとして移行したいブランチがあれば、それらを個別に push していきます。
リモートブランチの push は通常の git 操作と同じようにやれば OK です。
$ git chechkout -b remotes/branches/develop01
$ git push origin develop01

さて、これにて git への引越し作業は完了です。無事に作業が終わったら引越しそばを食べましょう。
  Linux  コメント (0)  2014/05/13 22:15:33


公開範囲:
プロフィール HN: ももかん
ゲーム作ったり雑談書いたり・・・していた時期が私にもありました。
カレンダー
<<2019, 11>>
272829303112
3456789
10111213141516
17181920212223
24252627282930