いまさらだけどSubversionからGitへ移行する（その1: リポジトリー変換）

## 目的

[Trac](https://trac.edgewall.org)+[Subversion](https://subversion.apache.org), [Redmine](https://www.redmine.org)+Subversion/Git, [GitLab CE](https://gitlab.com/) などを時代に合わせてオンプレで運用してきたが、サーバーの管理・維持などの関係もあり、Trac+Subversion は廃止し、GitLab あるいは Redmine+Git に移行したくなってきた。

ところで、10年以上前に、CVS で運用していたリポジトリーを [cvs2svn](https://web.archive.org/web/20200701230059/http://cvs2svn.tigris.org/)[^1] により SVN に移行したことがあるが、この記事を書くのに[GitHubに移行されたもの](https://github.com/mhagger/cvs2svn)を見ると、SVN だけでなく Git/Mercurial/Bazaar にも移行できるっぽい。VSS=[Microsoft Visual SourceSafe](https://ja.wikipedia.org/wiki/Microsoft_Visual_SourceSafe)で運用していたリポジトリーは闇に葬った…。（余談）

[^1]: 2020-07-01に閉鎖につきWebArchiveのリンク

## 参考

- Pro Git book - [9.2 Gitとその他のシステムの連携 - Git へ移行する](https://git-scm.com/book/ja/v2/Git%E3%81%A8%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%AE%E9%80%A3%E6%90%BA-Git-%E3%81%B8%E7%A7%BB%E8%A1%8C%E3%81%99%E3%82%8B)
- [git-svnでSVN→Gitへの移行をやってみたログ](https://qiita.com/hidekuro/items/4727715fbda8f10b6b11)

基本的に Pro Git book の手順や上記 Qiita 記事やブログ記事などで見つかるもので良いと思うのだが…

実際の移行とハマったところなどを記載していく。

## SVNディレクトリー構成

いわゆる`trunk`,`branches`,`tags`のスタンダードレイアウト。基本的にこれで運用していたので良かった。もちろんプロジェクト毎に別のSVNリポジトリーとして分離していた。

たとえば、 [Apache Software Foundation](http://svn.apache.org/repos/asf/
)の様に全社のプロジェクトが同一SVNリポジトリーにフラットにぶら下がっている様なものや、`trunk`,`branches`,`tags`を使わない独自形式だと、`git svn`のオプション指定も異なってくるはず。

## 環境

あんまり関係なさそうだけど一応。

- 移行元: Subversion
  - CentOS 7 + Subversion 1.7.14 (r1542130) (標準 RPM package)
- 移行作業: git-svn
  - macOS 10.15.5 + git-svn 2.27.0 (svn 1.10.4) (brew版 git)
- 移行先: Git
  - CentOS7 + GitLab CE 13.1.4 (Omnibus package版)

## 移行手順

### 生SVNリポジトリーの入手

外部のSVNホスティングサービス利用ならまだしも、自分がSVNサーバー管理者だっり、他のSVN管理者でもgit-svnで移行するということならその管理者の協力が得られるはずなので、[生のSVNリポジトリー](http://discypus.jp/svnbook/svnbook_ja_html/ch05.html)をローカルにコピーして作業するのが良いと思われる。

Pro Git Bookや各種記事には次のようなコマンド例があり（時代的に2016に終了した、Google Code[^2]のSVNからの移行でしょうか？）

[^2]: [Bidding farewell to Google Code](https://opensource.googleblog.com/2015/03/farewell-to-google-code.html)

```sh
$ git svn clone http://my-project.googlecode.com/svn/ \
      --authors-file=users.txt --no-metadata -s my_project
```

この、http(WebDAV)のリモートからの取得で失敗することが多いので、`git svn init` してから `git svn fetch` し、失敗した場合そのリビジョンから `git svn fetch -r1234:HEAD` にて再開するという記事が多いですが、生のリポジトリーが入手できるなら、ローカルで完結させたほうが絶対楽です。いまだにcommitされ生きているSVNリポジトリーならこの限りではないですが…結局どこかのタイミングで運用停止して変換するでしょうし…

以下、`/var/lib/svn/myproj` にSVNプロジェクトがあるものとして話をすすめます。

### authors 一覧ファイルを作る

ここは基本的に Pro Git Book の通りです。

SVN は http などの認証ユーザー名が Author になるが、Git の場合は、 `user.name` と `user.email` が Author になるのでその変換テーブルが必要。

いや、`cvs2svn` なんかで変換した年代物のリポジトリーなんて、遠い昔にそんなヤツいた？ってのをみんなに聞いたり、誰かを辿るのにすごく時間がかかる面倒くさい作業。まあ、わからなければ適当な名前とメールアドレスを入れちゃえばよいのですが…。

チェックアウト

```sh
$ svn checkout file:///var/lib/svn/myproj myproj-svn
```

抽出

```sh
$ cd myproj-svn
$ svn log --xml | grep author | sort -u | \
  perl -pe 's/.*>(.*?)<.*/$1 = /' > /tmp/myproj-authors.txt
```

```text
username1 =
username2 =
.
.
```

というファイルができるので

```text
username1 = Taro Yamada <taro@example.com>
username2 = Hanako Toire <hanako@example.com>
.
.
```

`svn checkout` したワーキングコピーはAuthor一覧を抽出するだけなのでここで消しても良い。

### 実際の移行

いろいろなところに書かれている事例の通り、自分も`https://`でやったことがあるがリビジョン数やタグ・ブランチの少ないテスト用のリポジトリーでは成功するが、実運用しているリポジトリーに関しては何度も失敗しました。前に書いたとおり、ローカルに持ってきて、`file:///`でやるとネットワークの影響がなく成功しているのか？しかし、ローカルでもとても時間がかかります。

```sh
$ git svn init file:///var/lib/svn/myproj/ --no-metadata -s myproj
$ cd myproj
$ git svn fetch --authors-file=/tmp/myproj-authors.txt
```

`--no-metadata` はcommit logに`git-svn-id`を付与しないためにつけています。そもそも、`file:///var/lib/svn/myproj/`とローカルのリポジトリを指定しているので`git-svn-id`にローカル情報が見えてかっこ悪くなるし、そもそもGit移行後は不要な情報と自分は判断しました。

### タグとブランチの変換

Pro Git bookでは、
> タグを Git のタグとして扱うには、次のコマンドを実行します。
> 次に、refs/remotes 以下にあるそれ以外の参照をローカルブランチに移動します。

と言うところの方法として、`.git/`ディレクトリの中を`cp`,`rm`にて操作しているが、自分の環境ではこれで失敗しました（24時間以上変換にかかったGitリポジトリーをバックアップ取らずに台無しに…）。~~ちょっと情報元のスニペットのリンクは失念したけど、~~参考として載せた[git-svnでSVN→Gitへの移行をやってみたログ](https://qiita.com/hidekuro/items/4727715fbda8f10b6b11#comments)の次の方法で成功しました。

#### ブランチ

```sh
$ for BRANCH_NAME in $(git branch -r | grep -ve 'origin/tags\|origin/trunk\|.*@\d*' | sed -e 's:origin/::'); do
    git checkout -b "$BRANCH_NAME" "origin/$BRANCH_NAME"
done;
$ git checkout master
```

#### タグ

```sh
$ for TAG_NAME in $(git branch -r | grep -e 'origin/tags' | grep -ve '.*@\d*' | sed -e 's:origin/tags/::'); do
  git tag "$TAG_NAME" "origin/tags/$TAG_NAME"
done
```

### リモートリポジトリーへのpush

こちらは、普通の方法。GitLabにpushしたが何でも良いかと。

```sh
$ git remote add origin git@gitlab.example.com:mygroup/myproj.git
$ git push -u origin --all
$ git push -u origin --tags
```

## つづく

SVNのリポジトリー形式により、変換したGitリポジトリーを更に変換操作した場合もあったので、その点を書くかも。→ [いまさらだけどSubversionからGitへ移行する（その２）](https://qiita.com/hkato/items/d0428b53d2665985ed54)