手作業修正 -> load 完了
svnadmin dump した結果をスクリプトと手作業で編集して svnadmin load するのに成功した。まぁ成功して当たり前なんだけど、一応メモ。使ったのは svn 1.4.2 と Ruby 1.8.5 とエディタ。
svndumpfilter はそんなに使えない
svnadmin dump の結果に対して path で単純なフィルタリングを行う svndumpfilter というものがあるが、これを適用しても特に以下のようなリポジトリレイアウトの場合、あまり役に立たない。
branches/
各モジュール/
...
tags/
...
trunk/
...
このツールは以下のようなレイアウトになっていて、
モジュール/
branches/
tags/
trunk/
モジュール/
branches/
...
...
ここで言うモジュール単位に分割する際には使える。つまり、リポジトリそのものの操作がのちのち楽なのは後者のレイアウト。
新規にリポジトリを作成する場合はともかく、cvs2svn で既存のリポジトリを変換する場合は何も考えないと前者のレイアウトになってしまうので注意が必要。
※ その他にも cvs2svn は svn:eol-style を native にしてしまうのでクライアントのプラットフォームが混ざっている場合に思わぬ不具合を引き起こす可能性がある。
dump したテキストファイルは mbox のような感じ
なので、mbox っぽいデータを自分でパースしたことのある人ならなんとかなります。XML ではないのでデータの終わりは明示されず、始まりしか明示されないので、始まりの区切りをそこまでのデータの終わりとして機能させれば ok.
ただし改行コードは混ざっているので注意。パースする際に改行コードを無造作に chop しちゃダメ。
タグを打った操作の追跡時に注意が必要
svn copy は特定のパスだけを copy する作業がなかなかやりにくい。これが cvs2svn を使ってリポジトリを変換した際、タグを打つ操作に対応する revision を見るとなるほどと思う。丸ごといちばん上の階層から copy して、不要なパスを delete している。このとき、この delete するパスが存在していないと load の際に失敗する。
つまり、
branches/
..
trunk/
path1/
path2/
tags/
..
というリポジトリがあり、
branches/
path1/
trunk/
path1/
のように path1 だけを取り出したいという場合、最初の方の revision で path2 を add する作業をカットしてあると、copy -> delete のコンボ revision の中で path2 を delete するアクションがあってはいけない。
でもまぁ手作業対応もそれなりに可能
今回はまだリポジトリの full dump が 124MB、取り出したい部分の dump ファイルが 26MB で収まっていたので、なんとかエディタで開いてスクリプトで対処しきれなかった余計な部分のカット、逆にスクリプトで取り除きすぎてしまった部分を full dump から戻す作業を行えた。(重かったけど。)ブランチの数もせいぜい数十。
ただこのレベルを越えるとなるともっと精度の高いフィルタが必要だなとは思った。
使ったスクリプト
本当はできるだけ手作業を減らそうと、revision ごとに dump ファイルを分割する機能をつけて、保全した revision の差分を取ってゴニョゴニョ(まだあまり考えていない)する機能をつけようと思っていたのですが、面倒くさくなったので放置です。
実に中途半端な状態ですが、とりあえずバックアップ目的で貼っておきます。なんか使えそうならご自由に。
#! /usr/bin/env ruby
require 'pathname'
require 'logger'
=begin
Subversion の dump ファイルから特定の条件を満たす node および revision
だけの dump ファイルを生成する。
Usage: ruby SCRIPT srcdump > destdump
Revision について
=================
Prop-content-length: は当然ヘッダ部以降 PROPS-END までのサイズ。
Content-length: は Prop-content-length と同じ。この数字は Node の
content-length に影響されない。つまり、Revision の途中で Node がバッサ
リ落ちても影響ない
ただし Node をただ単独で落とすと cvs2svn が作る、丸ごと copy してから要
らないものを削除するという revision に対応できないことがある。
Node-copy* という文字列を見つけたら Revision 丸ごと保存したい。
Node について
=============
Prop-content-length: はヘッダ部以降 PROPS-END までのデータのサイズ
Text-content-length: は PROPS-END 以降次のヘッダまでのデータのサイズ
Content-length: は上の content-length の合計
=end
#
# svnadmin dump した結果を処理するツール
#
module Svndump
#
# パースした node および revision に対する共通メソッド
#
class Common
def initialize( loglevel = nil )
_init_logger( loglevel )
@curr_rev = nil
@curr_node = nil
@buf = []
@revs = []
end
#
# Logger の初期化
#
def _init_logger( level = nil )
if ( level.nil? )
return false
end
@logger = Logger.new( STDERR )
case level
when 'debug'
@logger.level = Logger::DEBUG
when 'info'
@logger.level = Logger::INFO
when 'warn'
@logger.level = Logger::WARN
when 'error'
@logger.level = Logger::ERROR
else # include 'fatal'
@logger.level = Logger::FATAL
end
return true
end
private :_init_logger
#
# 指定ファイルまたは標準入力から dump ファイルを読み込みあれこれ
#
# 行ごとに分割して特徴的なヘッダ行をもとに pre_hook, post_hook を定義
# chomp() しない。改行コードが混ざっている可能性があるので。
#
def read
while ( line = gets )
case line
when /\ARevision-number: ([0-9]+)/
revision_pre_hook()
@curr_rev = $1.to_i
@curr_node = nil
revision_post_hook()
when /\ANode-path: (.*)/
node_pre_hook()
@curr_node = $1
node_post_hook()
when /\ANode-copy/
nodecopy_pre_hook()
end
@buf.push( line )
end
end # of read()
def revision_pre_hook
end
def revision_post_hook
end
def node_pre_hook
end
def node_post_hook
end
def nodecopy_pre_hook
end
def clear_rev_buf
@rev_buf = []
@keep_rev = false
end
private :clear_rev_buf
def clear_buf
@buf = []
@storable = true
end
private :clear_buf
end # of class Common
#
# dump ファイルを各 revision ごとに分割する
#
class Splitter < Common
end
#
# 小さくする
#
class Compact < Common
#
# コンストラクタ
#
def initialize( loglevel = nil )
super( loglevel )
@dump_header = true
@keep_rev = false
@rev_buf = []
@storable = true
end
#
# 主処理
#
def run
read()
after_all()
end
#
# Revision-number の切り替わりのタイミングで呼ばれるメソッド
#
def revision_pre_hook
if ( !@logger.nil? )
@logger.debug( "in revision_pre_hook() rev #{@curr_rev} node #{@curr_node}" )
end
store2rev()
output_rev()
if ( @curr_rev == 0 )
@dump_header = nil
end
end
#
# Revision-number の行を読んだ瞬間に働く hook
#
def revision_post_hook
end
#
# Node-path の切り替わりのタイミングで呼ばれるメソッド
#
def node_pre_hook
if ( !@logger.nil? )
@logger.debug( "in node_pre_hook() rev #{@curr_rev} node #{@curr_node}" )
end
store2rev()
end
#
# Node-path の行を読んだ瞬間に働く hook
#
def node_post_hook
end
#
# Node-copy* 行が現れるタイミングで呼ばれるメソッド
#
def nodecopy_pre_hook
@keep_rev = true
end
#
# 全部読み終わった時点で呼ばれるメソッド
#
def after_all
if ( !@logger.nil? )
@logger.debug( "in after_all() rev #{@curr_rev} node #{@curr_node}" )
end
store2rev()
output_rev()
end
#
# 現在の @buf の内容を保存するかどうか
#
def store?
return ( @keep_rev or @storable )
end
private :store?
#
# dump header および revision 0 かどうか
#
def dump_header?
return @dump_header
end
private :dump_header?
#
# @buf_rev の内容を出力する
#
# 出力後、クリアもする
#
def output_rev
if ( !@logger.nil? )
p @curr_rev
pp @rev_buf
p "dump_header #{dump_header?}"
p @rev_buf.size
end
if ( dump_header? or @rev_buf.size > 1 )
@rev_buf.each { |buf|
puts buf.join()
}
@revs.push( @curr_rev )
end
clear_rev_buf()
end
#
# @buf の内容を @buf_rev に store
#
def store2rev
if ( !@logger.nil? )
@logger.debug( "in store2rev() store? #{store?}" )
end
if ( store? and @buf.size > 0 )
@rev_buf.push( @buf )
end
clear_buf()
end
end # of class Compact
end # of Svndump
#
# テストじゃなかったら実行する
#
if ( Pathname( __FILE__ ).realpath == Pathname( $0 ).realpath )
tool = Svndump::Compact.new( )
def tool.node_post_hook
if (
# ここの書き方で調整
)
@storable = true
else
@storable = false
end
end
tool.run
end
cf.