SVN環境で自チームだけGitを使う方法

最近、運用ルールが非常に保守的なSVNでコードを管理しているプロジェクトに参加した。
ここ5年くらいはGit環境だったので、不便過ぎて仕方がない。
自チームだけGitを使用できる環境の構築方法を記載する。
SVN環境だけど、特定の範囲だけGitを適用して、開発用ブランチをガンガン作成したり、オンラインレビューのフローを入れたい人の参考になれば。

# 構築する環境
今のプロジェクトのSVN環境と、構築しようとしているGit環境を示す。
図では、チームメンバー全員Gitを使うことを強制するように書いてしまったが、
SVNを使いたい人はSVNを使えば良いというスタンス。

![無題 1.png](https://qiita-image-store.s3.amazonaws.com/0/196890/1eb9d15e-3d1d-5aa1-fcbb-b6e20c537d98.png)

# 使用するツール
## SubGit
https://subgit.com/

SVNリポジトリとGitリポジトリをミラーリングするのに使用。
チームでの使用に耐えうる、git-svnより高機能、自分の利用形態なら無料ライセンスで使えたため。
(Gitにpushするユーザ数が10人以下なら無料ライセンスあり)

この記事では、SubGitの導入方法、基本的な操作方法を説明しない。それらは以下のページを参照。
ダウンロードページ
https://subgit.com/download

Quick How-To
https://subgit.com/documentation/howto.html

以降、SubGitをGitサーバマシンに導入済み前提で説明する。
後、SubGitのバージョンは3.3.4前提の説明。

## GitLab
Gitサーバとして使用。
社内のPCにしかソースコードを置けないので、社内PCに簡単に導入できるGitLabを採用。

GitLabの導入方法については、GitLabの公式ページの手順を参照。
https://about.gitlab.com/install/#ubuntu

以降、GitLabをGitサーバマシンに導入済み前提で説明する。


# SVNリポジトリのミラーGitリポジトリを作成する手順
## GitLabで空のGitリポジトリを作る
GitLabの一般的な使い方なので手順は省略。
以降の説明では、作成したリポジトリ名をreposとする。

## Gitリポジトリ内にSubGitの設定ファイルを生成する
Gitサーバマシンで以下のコマンドを実行(/var/opt/gitlab/にGitLabがインストールされているものとする)。

```
cd /var/opt/gitlab/git-data/repositories/
subgit configure SVN_PROJECT_URL repos.git
```
SVN_PROJECT_URLにはSVNリポジトリへのURLを記述すること。

## SubGitのミラーリング動作を設定する
```
vi repos.git/subgit/config
```
記述のポイントは後述。

## SVNリポジトリとGitリポジトリのミラーリングを開始する
```
subgit install repos.git
```

このコマンドの実行により、SVNリポジトリのファイル、ブランチ、タグ、履歴などの情報がGitリポジトリに取り込まれる(かなり時間がかかる)。コマンド完了後は、こちらで何か手動でコマンドを実行しなくてもSVNとGitのミラーリングが行われるようになる。

## GitLabにリポジトリの変化を認識させる
ここまでの手順を実施しても、GitLab上でリポジトリを見たとき、空のリポジトリと表示される。
GitLabに空のリポジトリでないことを認識させるために、適当なブランチをpushする。
(後述の、Gitの開発用ブランチをSVNリポジトリを作らないようにする設定をしていること前提)

GitLabからgit cloneできるマシンから以下のコマンドを実行。
コマンド実行後、GitLabを見ると、空のリポジトリでなくなっているはず。

```
git clone <gitリポジトリのURL>
git checkout -b git/foo
git push origin git/foo
```

# SubGitのミラーリング動作の設定の詳細
デフォルトの設定のSubGitだと、色々問題を起こしてしまう。
たとえば、Gitで開発用ブランチをpushしたとき、SVNにも開発用ブランチができてしまう。
ここでは、なるべくGitだけの環境と同じ感覚でGitを扱えるようにするためのSubGitの設定を説明する。
この種のSubGitの設定はrepos.git/subgit/configというファイルに記述する。

設定ファイルの詳細を知りたい場合は以下を参照。
https://subgit.com/documentation/config-options.html

## Gitの開発用ブランチをSVNリポジトリに作らないようにする設定
デフォルトでは、全てのブランチとタグを、SVN-Git間でミラーリングする設定になっている。
なので、Gitでfeature/git_branchという名前の開発用ブランチをpushすると、SVNにもfeature/git_branchというブランチが作成されてしまう。
(SVNを採用しているプロジェクトでは開発用ブランチの作成が厳しく制限されていることがしばしばあるので、Gitのノリで開発用ブランチを作成するとルール違反になってしまう)

Gitの開発用ブランチをSVN側に作らせないようにするために、設定ファイルのセクション[svn」に以下を記述する。

```
[svn]
...
trunk = trunk:refs/heads/master
branches = branches/*:refs/heads/svn/*
tags = tags/*:refs/tags/svn/*
```

ここで指定しているのは、どのSVNリポジトリ/タグを、どのGitリポジトリ/タグとミラーリングさせるかという対応づけのルール。
この記述の詳細を説明すると、以下の通り。

1. SVNリポジトリのtrunkと、Gitリポジトリのmasterブランチを対応づける
1. SVNリポジトリのbranches配下のブランチと、Gitリポトリのsvn/*というブランチと対応づける
 - SVNリポジトリ/branches/fooというブランチは、Gitリポトリではsvn/fooというブランチに対応づける
1. SVNリポジトリのtags配下のタグは、Gitのsvn/*というタグと紐づける
 - branchと同じ考え方
1. 上記の対応付けルールに該当しないリポジトリ/タグは、SVN-Git間でミラーリングしない。

この設定をしておくと、Gitでfeature/git_branchをpushしたとき、1にも3にも該当しないため、SVN-Git間でミラーリングは行われない。
よって、SVN側にはfeature/git_branchは作成されない。

## SubGitがSVN上にshelvesブランチを作ることを抑止する
上記の設定をしていても、SubGitはGitの開発用ブランチをSVNリポジトリに作成してしまうケースがある。
それは以下のようなケースである。

1. Gitでmasterからfeature/fooという開発用ブランチを作成する
2. feature/fooに何らかしらの修正をpushする
3. feature/fooをmasterに、マージコミット有りでマージする

このとき、SubGitは、SVNリポジトリ上にブランチfeature_fooを作成してしまう。
というのは、SubGitはtrunkへのマージの履歴をSVN上にも残そうとするが、SVNにはブランチfeature/fooはない。
そこで、SubGitはfeature/fooをSVNリポジトリ配下の所定の場所に作成してから(デフォルトはshelves配下)、feature/fooをtrunkにマージしたという履歴を残す。

この動作を無効にするためには、設定ファイルのセクション[svn]の以下の部分をコメントアウトする。

```
[svn]
...
# shelves = shelves/*:refs/shelves/*
```

この動作を無効にしても、SVN上にブランチのマージ履歴が残らないだけで、修正自体はSVNにコミットされる。
ここでのケースでいえば、Git上で行ったfeature/fooの修正はSVNのtrunkにも反映される。

(自分の場合、SVN上に許可無しでブランチを作るのがNGなプロジェクトだからこうしている。NGでないプロジェクトならこの設定は必須というほどではないと思う)

## SVN-Git間でのミラーリングの対象外フォルダ/ファイルの指定
デフォルト設定では全てのファイルがSVN-Git間でミラーリングされる。
ただ、以下のようにファイルをミラーにしたくないこともある。

- SVNリポジトリにサイズが大きいビルド生成物が管理されている
　- 素のGitはそのようなファイルの管理に向かないのと、ビルドすれば生成できるので、Gitで管理するモチベーションなし
- GitLabのCIの設定ファイルをSVNリポジトリに見せたくない

このような場合はexcludePathの設定を記述する。
SVN上のビルド生成物が<SVNリポジトリ/binary>配下にあり、GitLabのCIの設定ファイルが<Gitリポジトリ/.gitlab-ci.yml>だとすると、設定ファイルの[svn]というセクションに以下を記述する。

```
[svn]
...
excludePath = /binary
excludePath = /.gitlab-ci.yml
```
ここでは、使用していないだけでワイルドカードによる指定も可能。

ここでミラーリング対象外にしたファイルをGitで修正してpushすると、空のコミット履歴が作成される点には注意。
具体的には、Git側で.gitlab-ci.ymlを修正してmasterにpushすると、SVNに空のコミット履歴が作成される(GitのmasterとSVNのtrunkを対応付けているものとする)。これを回避するためには、後述の空のコミット履歴の作成を無効にする設定が必要。

## 空のコミット作成を無効にする設定
設定ファイルのセクション[translate]に以下を記述する。

```
[translate]
...
createEmptySvnCommits = false
```

# 各開発者のローカルGitリポジトリ用にすると良い設定
## git logで履歴を見た時に、対応するSVNのリビジョンを表示する設定
.git/configに以下を記述。

```
[remote "origin"]
...
fetch = +refs/svn/map:refs/notes/commits
```

# ミラーGitリポジトリの運用ルール(実績ないので注意)
ミラーGitリポジトリの運用ルールとして以下がよさそう。

- SVNとのブランチのマッピング
 - SVNのtrunkとGitのmasterを紐付ける
 - 名前の先頭に"svn/"がつくブランチはSVNのブランチと紐付ける
 - Gitだけで管理したい開発用ブランチには先頭に"git/"と付ける
- SVNブランチと紐付けたGitブランチに対して、修正を直接pushすることは禁止する(GitLabの設定で強制できる)
 - Gitの開発用ブランチに修正をpush -> GitLabなどでレビュー -> レビューOKならSVN側にも公開する、というフローを強制するため
 - Gitに不慣れなメンバがGitを使う場合に、SVNリポジトリを汚さないようにするため
- バージョン管理したいが、SVNリポジトリに公開できない/したくないファイルは、Gitフォルダ配下の「git-only」に置く
 - 前述のSubGitの設定のexcludePathに「/git-only」を指定する

# 最後に
ここで構築した環境の適用範囲をチームからプロジェクト全体に広げ、
SVNを使いたい人はSVNを使い、Gitを使いたい人はGitを使うという運用もできると思う。