レポジトリ分割の作業記録

とりあえず新規レポジトリを作るまでもないけど作業履歴をとっておきたい,という場合,わたしは Sandbox というレポジトリに全部つっこんでいます。

  • /Sandbox
    • /project1
    • /project2
    • /foobar

各サブプロジェクトごとに trunk 等を切ることもありますし,そうしないこともあります。こんな感じで Sandbox で作業してて熟してくると,単独プロジェクトとして分割管理したくなります。

  • /foobar
    • /trunk
    • /tags
    • /branches

できれば,いままでの作業履歴も移行したい!


svnadmin dump」や「svndumpfilter」というキーワードでググると色々でてきますが,今回は下記のサイトを参考にしました。

では,実際の作業記録です。

まず svnadmin dump の用法をみてみます。

% svnadmin dump --help
dump: usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER]] [--incremental]

Dump the contents of filesystem to stdout in a 'dumpfile'
portable format, sending feedback to stderr.  Dump revisions
LOWER rev through UPPER rev.  If no revisions are given, dump all
revision trees.  If only LOWER is given, dump that one revision tree.
If --incremental is passed, then the first revision dumped will be
a diff against the previous revision, instead of the usual fulltext.

Valid options:
  -r [--revision] arg      : specify revision number ARG (or X:Y range)
  --incremental            : dump incrementally
  --deltas                 : use deltas in dump output
  -q [--quiet]             : no progress (only errors) to stderr

--incremental とか --deltas とか -r とか魅惑のオプションがいろいろあるのですが,普通にすべて dump するのなら特に指定する必要はなさそうです。

ではリモートから dump してみます。

% svnadmin dump http://server/repos/Sandbox > sandbox.dump
svnadmin: 'http://server/repos/Sandbox' is an URL when it should be a path

(ローカル)パスじゃないとだめだよ,と怒られてしまいました。

しかたがないので,レポジトリのおいてあるサーバにログオンして dump することにします。

$ svnadmin dump /repos/Sandbox > sandbox.dump
* Dumped revision 0.
* Dumped revision 1.
* Dumped revision 2.
...... snip ......
* Dumped revision 66.
* Dumped revision 67.
* Dumped revision 68.

無事 dump できました。


このままだと Sandbox レポジトリすべての dump になってしまっているので,今回扱いたい foobar プロジェクトだけ抜き出します。

まずは svndumpfilter の用法を確認。

$ svndumpfilter help
general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]
Type 'svndumpfilter help <subcommand>' for help on a specific subcommand.
Type 'svndumpfilter --version' to see the program version number.

Available subcommands:
   exclude
   include
   help (?, h)

$ svndumpfilter include --help
include: Filter out nodes without given prefixes from dumpstream.
usage: svndumpfilter include PATH_PREFIX...

Valid options:
  --drop-empty-revs        : Remove revisions emptied by filtering.
  --renumber-revs          : Renumber revisions left after filtering.
  --preserve-revprops      : Don't filter revision properties.
  --quiet                  : Do not display filtering statistics.

includeexclude というコマンドで抽出対象パスを限定できるようです。今回の場合,レポジトリの新規移行がメインなので --drop-empty-revs が有用ぽい。

ということで,foobar に限定した dump を抽出します。

$ cat sandbox.dump | \
      svndumpfilter --drop-empty-revs --renumber-revs include /foobar > foobar1.dump

Including (and dropping empty revisions for) prefixes:
   '/foobar'

Revision 0 committed as 0.
Revision 1 skipped.
Revision 2 skipped.
...... snip ......
Revision 55 skipped.
Revision 56 skipped.
Revision 57 committed as 1.
Revision 58 committed as 2.
...... snip ......
Revision 67 committed as 11.
Revision 68 committed as 12.

Dropped 56 revision(s).

Revisions renumbered as follows:
   68 => 12
   67 => 11
...... snip ......
   58 => 2
   57 => 1
   56 => (dropped)
   55 => (dropped)
...... snip ......
   2 => (dropped)
   1 => (dropped)
   0 => 0

Dropped 121 node(s):
   '/hogehoge'
   '/hogehoge/fugafuga'
...... snip ......

Sandbox 自体は 68 回コミットしたことになっていますが,foobar についてはわずか 12 回分のリビジョンで済みました。

このままの dump file だと新レポジトリの中に foobar というディレクトリを掘って,その下にファイルが置かれてしまいます。レポジトリの各操作対象パスは Node-path: *** という書式で書かれているのでそれを置換することにします。上記 yoshiki@Clouder さんの作業を参考にして perlワンライナーで置換しました。

$ perl -pe 's{^Node-path: foobar}{Node-path: trunk}' foobar1.dump > foobar2.dump

以上で foobar 用の dump ファイルが出来上がったので,実際に foobar レポジトリを作成します。また,あらかじめ trunk, tags, branches ディレクトリを作成しておきます。

$ svnadmin create /repos/foobar

% svn mkdir http://server/repos/foobar/trunk \
            http://server/repos/foobar/tags  \
            http://server/repos/foobar/branches

リビジョン 1 をコミットしました。


いよいよ dump ファイルのインポートです。svnadmin load コマンドを使います。

$ svnadmin load --help
load: usage: svnadmin load REPOS_PATH

Read a 'dumpfile'-formatted stream from stdin, committing
new revisions into the repository's filesystem.  If the repository
was previously empty, its UUID will, by default, be changed to the
one specified in the stream.  Progress feedback is sent to stdout.

Valid options:
  -q [--quiet]             : no progress (only errors) to stderr
  --ignore-uuid            : ignore any repos UUID found in the stream
  --force-uuid             : set repos UUID to that found in stream, if any
  --use-pre-commit-hook    : call pre-commit hook before committing revisions
  --use-post-commit-hook   : call post-commit hook after committing revisions
  --parent-dir arg         : load at specified directory in repository

色々オプションがあるようですが,今回の場合何も指定しなくてよさそうです。

いざインポート!

$ svnadmin load foobar < foobar2.dump
<<< Started new transaction, based on original revision 1
svnadmin: File already exists:
  filesystem '/www/repos/foobar/db', transaction '1-1', path 'trunk'
     * adding path : trunk ...

すでに trunk というパスがレポジトリにあるよ,と怒られてしまいました。dump ファイルの中には trunk フォルダを生成するというログも入っているので当たり前です。

ここでレポジトリの作成からやり直す手もあるのですが,今回は dump ファイルを手直しして,trunk ディレクトリを作成している部分を削除することにしました。

$ less foobar2.dump

...... snip ......
PROPS-END

Revision-number: 1
Prop-content-length: 121
Content-length: 121

K 7
svn:log
V 17
foobar の作成

K 10
svn:author
V 9
dayflower
K 8
svn:date
V 27
2007-11-21T01:08:46.472700Z
PROPS-END

Node-path: trunk
Node-action: add
Node-kind: dir
Prop-content-length: 10
Content-length: 10

PROPS-END


Revision-number: 2
...... snip ......

$ vi foobar2.dump

Revision-number: 1 では trunk というディレクトリを追加しているだけです。ですので vi で「Revision-number: 1」のエリアをごっそり削除してみました。

さぁ,これで今度こそインポートできるでしょうか。

$ svnadmin load /repos/foobar < foobar2.dump
<<< Started new transaction, based on original revision 2
     * editing path : trunk ... done.
     * adding path : trunk/Makefile ... done.
     * adding path : trunk/foobar.c ... done.

------- Committed revision 2 >>>

<<< Started new transaction, based on original revision 3
     * editing path : trunk/foobar.c ... done.

...... snip ......

------- Committed revision 12 >>>

無事できました*1


今回の場合,svndumpfilter の出力をほぼそのまま使うことができましたが,レポジトリの環境によっては苦労も多いみたいです。

dump ファイルをあれこれ操作したおすなら,SVN::Dump という cpan モジュールを使うといいのかもしれません。自称 alpha release とのこともあり,詳しく調べてはいません。

また,svk を使うともっと簡単なステップで分割することもできるようです(⇒koshigoewiki:開発環境:svk [KoshigoeWiki]の「リポジトリ分割」)。今回の環境では svk がインストールされていなかったので svn を使いました。

*1:ただ,実はこの手順だと revision 1 のほうが 2〜12 より日付が新しくなってしまうんですよね。ちょこっと不安。