巨大Subversionリポジトリを一人こっそりGitで扱いたい

会社のプロジェクトのバージョン管理はSubversion。しかも、10G弱でコミット数も相当数あるリポジトリ。だけど、Gitを使いたい！と思って方法を模索したのでそのメモです。Gitをよく知らないのもあり、もっと良い方法があれば教えていただけると幸いです。

# 背景
会社のプロジェクトではSubversionが使われています。しかし、世の中ではGitの方が主流（？）ということもあり、自分もGitを使って開発をしたい、と思いました。

新人のペーペーの力でSubversionからGitへ移行することもできないので、なんとか自分だけでもGitを使えないものかと考え、調査・環境構築を行いました。


# 使用環境
Windows 7 + [MSYS2](https://msys2.github.io/)


# git-svn の利用
「SubversionリポジトリをGitで扱う」ことに関しては、ネットに情報が結構あったので、比較的すんなりできました。

使用するのは ``git svn``コマンドです。参考にしたのは以下のリンクなどです。詳しくはそちらをご覧ください。
[git-svnでSVN→Gitへの移行をやってみたログ - Qiita] (http://qiita.com/hidekuro/items/4727715fbda8f10b6b11)
[現場がSubversionでつらい貴方へ…自分だけこっそりGitで開発する方法](http://interprism.hatenablog.com/entry/git-svn)


## Subversionリポジトリからクローン
``svn checkout``に相当。

```shell-session:clone
git svn clone -s --prefix=svn/ https://subversion/repos/
```

上記を実行するとtrunkのみがローカルブランチとして取り込まれ、Subversion の branches や tagsはリモートブランチとして取り込まれます。ローカルブランチ、リモートブランチ、リモートリポジトリについてはよく知らなかったので以下を参考にしました。
[Git で「追跡ブランチ」って言うのやめましょう - Qiita](http://qiita.com/uasi/items/69368c17c79e99aaddbf)
[追跡ブランチ (tracking branch) というブランチが何なのか調べた](http://snowlong.hatenablog.com/entry/2015/03/12/212455)

clone　中に中断することがあったので、その場合は以下のコマンドを実行。

```shell-session:fetch
git svn fetch
```

## 更新
``svn update``に相当。
作業ディレクトリ（masterブランチにて）を最新のtrunkに更新する際は以下。

```shell-session:update
git svn fetch
git merge svn/trunk
```

or

```shell-session:update
git svn rebase
```

## その他
他に ``git svn dcommit`` とかありますが、今回は使用しておりません。ビビリのため使えず。Subversionリポジトリにcommitするときは、別途``svn checkout``して、Git での変更をパッチとして当てて、commit しています。今後の課題。

## 個人的な問題点
冒頭で述べたとおり、今回扱う Subversion のリポジトリはサイズ・コミット数ともに大きく、巨大なリポジトリでした。``git svn clone``だけで数時間…。何度か中断しつつも完了しましたが、新たな問題が発生しました。

**Gitが重い**

わかっているだけで以下の操作にとんでもなく時間がかかりました。

- ``git status``
- ``git diff <commit> <commit>``
- hash値のタブ補完（zsh使用）
- ``git checkout <branch>`` 

原因を調べてみると以下の記事を発見。
[アホみたいにでかいgit repositoryを上手く扱う方法 - Qiita](http://qiita.com/tarr1124/items/11e890733a692bc9d123)
まんま、2つの原因に当てはまっている…。どうしたものか。


# 巨大なSubversionリポジトリをGitで扱う
さて本題になります。先ほどの問題を解決するために今回は以下のようにしました。

1. Subversionリポジトリから``git svn clone``でGitのローカルリポジトリ作成（←巨大）
2. 1.で作ったローカルリポジトリをリモートリポジト
リとして``git clone``。ただし、shallow cloneを用いることでサイズ減を行う。
3. 2.で作成したローカルリポジトリ上で作業。
4. 2.で作成したローカルリポジトリから、1.で作成したローカルリポジトリに``git push``
5. 1.で作成したローカルリポジトリから``git svn dcommit``

図にすると以下のようになります。
![drawing.png](https://qiita-image-store.s3.amazonaws.com/0/96666/66bb2e22-305e-1428-48d7-0d19df57f858.png)

ここでそれぞれのリポジトリを以下のように定義します。

**Subversion Repository**
サーバー上にあるSubversionリポジトリ。``git svn clone``して持ってくる対象となるリポジトリ。巨大。

**git-svn Repository**
``git svn clone``で作成したローカルリポジトリ。巨大。手順1. で作ったもの。作業リポジトリとSubversionリポジトリの橋渡し的存在。

**Local Repository**
git-svn Rep からcloneして作成したローカルリポジトリ。shallow cloneを用いることでサイズ減を図る。実際に作業するリポジトリとなる。2. で作成したもの。


## git-svn Repository
``git svn clone``を使って作成します。説明済みなので省略。

bareリポジトリにして容量削減したいところでしたが、Subversionリポジトリの内容をローカルブランチに取り込めないため断念。

## Local Repository
git-svn Repository を元にして``git clone``して作成します。実際の開発作業はこのローカルリポジトリ上で行い、git-svn Repository に push します。

### 作成
Local Repositoryの作成には以下のコマンドを実行。

```shell-session:clone
git clone --depth <depth> --no-single-branch　file://path/to/git-svn/repository
```

``--depth <depth>``は shallow clone のためのコマンドです。<depth>に数字を入れることで、取得するコミット履歴を過去<depth>件に限定するのでサイズの削減になります。

``--no-single-branch``はリモートリポジトリ（git-svn Repository）から取得するリモートブランチを1つに絞らないようにするオプションです。これがないと、git-svn Repositoryで、現在アクティブなブランチしかリモートブランチとして登録されません（1つのブランチのみ持ってきたい場合はなくて構いません）。上記の``--depth`` オプションを使用することで、デフォルトで``--single-branch`` が設定されてしまうため、必要となります（@ystmg さん情報提供ありがとうございました）。以下``git clone --help``より引用。

       --depth <depth>
           Create a shallow clone with a history truncated to the specified number of commits. Implies --single-branch unless --no-single-branch is given to fetch the histories near the tips of all branches. If you
           want to clone submodules shallowly, also pass --shallow-submodules.

``file://``は無いと警告を出されたので入れときました。
なお公式サイトの[4.1 Git サーバー - プロトコル](https://git-scm.com/book/ja/v1/Git-%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC-%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB)には以下の記述があります。
>URL の先頭に file:// を明示するかどうかで、Git の動きは微妙に異なります。file:// を明示せずパスだけを指定し、かつコピー元とコピー先が同一のファイルシステム上にある場合は、Git は必要なオブジェクトにハードリンクを張ろうとします。もし異なるファイルシステム上にある場合は、Git はシステムデフォルトのファイルコピー機能を使って必要なオブジェクトをコピーします。一方 file:// を指定した場合は、Git がプロセスを立ち上げ、そのプロセスが (通常は) ネットワーク越しにデータを転送します。一般的に、直接のコピーに比べてこれは非常に非効率的です。file:// プレフィックスをつける最も大きな理由は、(他のバージョン管理システムからインポートしたときなどにあらわれる) 関係のない参照やオブジェクトを除いたクリーンなコピーがほしいということです。本書では通常のパス表記を使用します。そのほうがたいていの場合に高速となるからです。


### 更新
Subversion Repositoryの更新内容を取り込むには2段階の操作が必要になります。これはこの環境の欠点になります。

1. git-svn Repositoryの更新
2. Local Repositoryの更新

つまり、

```shell-session:update
git svn rebase #git-svn Repository上で操作
git pull #Local Repository上で操作
```

### Subversion Repository への変更反映
これも上記と同様に2段階の操作が必要となります。私はビビリなので``git svn dcommit``はやったことありません。申し訳ありません。

```shell-session:update
git push origin <ブランチ名> #Local Repository上で操作
git svn dcommit #git-svn Repository上で操作
```


# 終わりに
これまで巨大SubversionリポジトリをGitで扱う方法について私なりの環境を説明してきました。実際の業務ではコーディング自体はあんまり任されないので、必要が出てきたらLocal Repositoryを作って、作業、git-svn Repositoryにpush、終わったらLocal Repository削除 と言ったように運用しています。

Local Repositoryを削除しても git-svn Repository に自分の作ったものの記録が残るのもこの環境の良いところかなとも思っています。