SubversionからGitへの移行でチーム影響を最小限にする


この記事は[株式会社リクルートテクノロジーズ Advent Calendar 2014](http://qiita.com/advent-calendar/2014/recruit-technologies)の15日目の記事です。

## Git移行は面倒？

管理の煩わしさや並行開発の難しさという点から、ソースコード管理システムをSubversionからGitへ移行したいということはよくあると思います。

1つのリポジトリに関わっているエンジニアが5名程度のチームであれば、エンジニア同士で相談してサクッと移行できるかもしれません。
しかし実際には1つのプロダクトに5名以上のエンジニアがいて、並行して小さな変更の開発と大きな変更の開発を同時に行っている場合も多いのではないでしょうか。
例えば以下の図のように、短期間でリリースできる修正と、長期間かかる修正とで複数の開発チームが並行して動いている場合です。

<img src="https://qiita-image-store.s3.amazonaws.com/0/5882/7018a2a8-686b-d06e-df33-135a44cce993.png" width="600px">

このような場合はどこかでGit移行のタイミングをつくらなければならず、開発への影響を考えるとすぐには移行できないという状況も考えられます。

## git-svnを使って段階移行する

そこで`git-svn`を使って一時的にSubversionリポジトリとGitリポジトリを併用しながら開発とリリースを行いました。
この方法を使えば、リリースと開発チームに与える影響は最小限にしつつ移行を実施することができます。

## 前提

移行前のSubversionリポジトリで以下のようなブランチ構成になっているものとします。

<img src="https://qiita-image-store.s3.amazonaws.com/0/5882/099b8e0b-4b48-bf0a-952c-60780fd8ede4.png" width="600px">

trunkには本番環境に出しているソースコードと同じものが入っています。
trunk以外にはリリース日ごとにブランチを作成しており、開発者はこのブランチ(上の図ではリリースA/Bブランチ)に対してコミットを行います。
それぞれのブランチで開発が完了すると、リリースの際に各ブランチはtrunkにマージされます。

通常このリリース日ごとのブランチは複数作成され、それぞれ並行して開発を進めています。今回の説明では2つのブランチで以下のように並行開発を進めているものとします。

* リリースA
	* 軽微な修正などを行う
		* 小さなバグ修正、文言修正、画像差し替えなど
	* 決まった間隔(2週間に1回)でリリースされる
* リリースB
	* まとまった機能の開発を行う (例)新しくプロフィール機能を追加する
		* データモデルの修正や新規画面の追加など
	* リリース日は固定されており、リリースAとは別のタイミングでリリースする
	* 開発期間は2ヶ月程度

このようなブランチの構成になっているプロジェクトを、Gitリポジトリに移行します。

### 移行フロー

段階移行は、以下のようにして行います。

<img src="https://qiita-image-store.s3.amazonaws.com/0/5882/ee3baa58-f361-aae8-dd1e-b7bac0624842.png" width="600px">

まず先に開発が完了するリリースAブランチを、開発完了した段階でGitリポジトリへ移行します。また、Gitリポジトリ上でリリース作業を行います。
この段階では並行して開発を進めているリリースBは、通常どおりSubversionのブランチ上で開発を進めます。コミットも続けていて問題ありません。
また、リリースAがGitリポジトリからリリースされた後に新規に始める開発は、Gitリポジトリ上にブランチを作成して開発を進めます。

リリースBの開発が完了したら、リリースAと同じように移行作業を行います。

このように段階的に移行作業を実施すれば、既にSubversionリポジトリで進めている開発への影響を最小限にしてGitリポジトリへの移行を実施することができます。


## git-svnを使った段階的な移行方法

### 前提

Aブランチでの開発が完了し、リリースできる状態となっていることとします。

### `git-svn`を使ってGitリポジトリを作成

まずはじめに、`git-svn`を使ってSubversionリポジトリをGitリポジトリに移行します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/5882/d38808b3-8503-4b9a-3d18-c1372a7b65ca.png" width="600px">

```sh
git svn clone <Subversionのプロジェクトのアドレス> <Gitプロジェクトのパス> --stdlayout --prefix=svn/
```


このコマンドを実行すると、Subversionのリポジトリ上に存在するブランチは`svn/`というプレフィックスがついた状態でGitのブランチとして作成されます。
また、`trunk`はデフォルトでGitの`master`ブランチとして作成されます。

**この時点で、今後Subversionのtrunkはリリースに使わなくなるため凍結します。**
今後は開発完了したSubversionのブランチはGitリポジトリへ移行して、Gitリポジトリのソースを本番環境へリリースすることになります。

### AをGitリポジトリからリリースする

Gitリポジトリ上に移行されたリリースAブランチを、Gitリポジトリからリリースします。
以下の図のような流れになります。

<img src="https://qiita-image-store.s3.amazonaws.com/0/5882/0c1f653c-5375-8893-a662-8132f5339b30.png" width="600px">

上の図で実現したいことを、コマンドと一緒に解説します。説明の末尾につく(数字)は、図の番号と対応します。

SubverionのリリースAブランチを、Gitリポジトリのブランチとして再作成します。(1)

```sh
git checkout –b <リリースA> <svn/リリースA>
```

今後並行開発が行いやすいようにGitリポジトリ上のブランチをGit-flowに合わせて作成します。(2)

```sh
# developmentブランチをmasterから作成
git checkout –b development master
```


リリースAをdevelopmentへマージします。(3)

```sh
git checkout development
git merge <リリースA>
```

developmentブランチからreleaseブランチを作成し、リリース作業を行います。(4)

```sh
git checkout –b release development
# リリース時に必要な作業があればコミット
```

デプロイジョブではGitのreleaseブランチよりソースをチェックアウトして本番環境へのデプロイを行います。

リリース後はmasterブランチへreleaseブランチをマージします。(5)

```sh
git checkout master
git merge release
git branch -d release
```

リリースのタグを作成します。(6)

```sh
git tag <リリースタグ名>
```

developmentへ本番環境の差分を反映させるためにmasterをマージします。(7)

```sh
git checkout development
git merge master
```


#### リリースBの開発では、通常通りSubversion上のリリースBブランチへコミットを続ける

この移行作業中は、 開発中のリリースBは移行作業を気にせずSubversionリポジトリのリリースBブランチへコミットを行うことができます。

#### 以降の開発はGit-flowに則って行う

以降の開発はGit-flowに則って行います。

<img src="https://qiita-image-store.s3.amazonaws.com/0/5882/6ba8453a-1e2e-4f69-ba8e-5ebb0ae09468.png" width="600px">

新規に修正や機能追加が行われる場合はdevelopmentブランチからfeatureブランチを作成し、このfeatureブランチ上で開発を進めます。(1)

```sh
git branch feature-XXX origin/development
```

開発が完了したら、developmentブランチへマージします。(2)
GithubのPull RequestやGitalbのMerge Requestを使ってマージを行うと便利です。
リリース時は[AをGitリポジトリからリリースする](#AをGitリポジトリからリリースする)の(4)以降の作業を行います。

### Subversion上のリリースBブランチと本番との差分をマージする

リリースAの本番反映が完了した段階で、リリースBブランチへ本番との差分をマージします。
`git svn dcommit`というコマンドを使うと、Gitリポジトリ側のコミットをSubversionリポジトリのリビジョンとして追加することができます。
これを利用して、以下のように差分を反映させることができます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/5882/6747d2a3-9366-c6a9-0021-a25bc6b1be18.png" width="600px">

まずSubversion側での更新をGitリポジトリ側で取得します。

```sh
git svn fetch
```

上記を実行すると、最初に作った`svn/`プレフィックスのついたSubversionのブランチの履歴を保持しているブランチに変更が取り込まれます。
この場合は`svn/リリースB`に変更が取り込まれます。

この`svn/リリースB`から`リリースB`ブランチを作成します。(1)

```sh
git checkout –b <リリースB> <svn/リリースB>
```

GitリポジトリのリリースBブランチへ、developmentブランチをマージします。(2)
developmentブランチにはmasterブランチの内容がマージされているため、本番環境と同じ変更が含まれています。
ここでは`git merge --squash`オプションを利用して、developmentとの差分をワークツリーに取り込んでから1つのコミットを作成します。こうすることで、Subversionで行っている本番反映のマージコミットと同じ粒度のコミットが作成できます。

```sh
git merge –-squash development
git commit # 図でのコミット<E>が作成できる
```

GitリポジトリのリリースBブランチで作成したマージコミットを、`git svn dcommit`を使ってSubversionリポジトリのリリースBブランチに取り込みます。(3)

```sh
git svn dcommit
```

### リリースBブランチをGitリポジトリへ移行

リリースBの開発が完了したら、リリースBブランチもGitリポジトリへ移行し、Gitリポジトリからリリース作業を行います。
前述した[AをGitリポジトリからリリースする](#AをGitリポジトリからリリースする)と同じ作業を行うだけです。
リリースBのリリースが完了した時点で、Subversionリポジトリは連結します。これで移行作業完了です！:+1:

### ブランチがもっとある場合

今回はSubversionリポジトリ上に2つのブランチがあるという前提で説明をしていますが、ブランチが3つの場合でも4つの場合でも、

1. 開発が完了したSubversionブランチをGitリポジトリのdevelopmentブランチへマージ
2. リリース作業
3. まだSubversionリポジトリ側に開発進行中のブランチがある場合、`git svn dcommit`を使って本番との差分をSubversion上のブランチにマージ

を繰り返せば同じように移行できます。

## まとめ

`git-svn`を使って移行期間を設けつつ段階的にリポジトリを移行することによって、開発とリリースへの影響を最小限にすることができました。

長期間`git-svn`を使って開発を進めることに関しては、管理が複雑になるためおすすめはできません。
しかし、移行したいけどリリースのタイミングの関係で難しい、という場合には効果的だと思います。一時的に`git-svn`を使って上記のような方法をとれば、開発チームに与える影響を小さく移行を実施することができます。