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


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

背景

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

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

使用環境

Windows 7 + MSYS2

git-svn の利用

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

使用するのは git svnコマンドです。参考にしたのは以下のリンクなどです。詳しくはそちらをご覧ください。
git-svnでSVN→Gitへの移行をやってみたログ - Qiita
現場がSubversionでつらい貴方へ…自分だけこっそりGitで開発する方法

Subversionリポジトリからクローン

svn checkoutに相当。

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

上記を実行するとtrunkのみがローカルブランチとして取り込まれ、Subversion の branches や tagsはリモートブランチとして取り込まれます。ローカルブランチ、リモートブランチ、リモートリポジトリについてはよく知らなかったので以下を参考にしました。
Git で「追跡ブランチ」って言うのやめましょう - Qiita
追跡ブランチ (tracking branch) というブランチが何なのか調べた

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

fetch
git svn fetch

更新

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

update
git svn fetch
git merge svn/trunk

or

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
まんま、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

図にすると以下のようになります。

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

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の作成には以下のコマンドを実行。

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

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

--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 サーバー - プロトコルには以下の記述があります。

URL の先頭に file:// を明示するかどうかで、Git の動きは微妙に異なります。file:// を明示せずパスだけを指定し、かつコピー元とコピー先が同一のファイルシステム上にある場合は、Git は必要なオブジェクトにハードリンクを張ろうとします。もし異なるファイルシステム上にある場合は、Git はシステムデフォルトのファイルコピー機能を使って必要なオブジェクトをコピーします。一方 file:// を指定した場合は、Git がプロセスを立ち上げ、そのプロセスが (通常は) ネットワーク越しにデータを転送します。一般的に、直接のコピーに比べてこれは非常に非効率的です。file:// プレフィックスをつける最も大きな理由は、(他のバージョン管理システムからインポートしたときなどにあらわれる) 関係のない参照やオブジェクトを除いたクリーンなコピーがほしいということです。本書では通常のパス表記を使用します。そのほうがたいていの場合に高速となるからです。

更新

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

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

つまり、

update
git svn rebase #git-svn Repository上で操作
git pull #Local Repository上で操作

Subversion Repository への変更反映

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

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 に自分の作ったものの記録が残るのもこの環境の良いところかなとも思っています。