変則的なレイアウトの Subversion リポジトリで git-svn を使う手引き

以下の内容は [Gist のメモ](https://gist.github.com/uasi/2468927)を移行したものです。

# git-svn の使い方メモ

git-svn の使い方をメモする。他によいプラクティスがあれば指摘していただけるとありがたい。

## 用語
SVN のブランチと git のブランチが混在しているため、ここではブランチという語を以下のように区別する。

* ブランチディレクトリ、 SVN ブランチ：$SVN_REPO/branches 以下にあるディレクトリのいずれか
* ローカルブランチ：git のローカルブランチ
* リモートブランチ：git のリモートブランチ

## 例題の SVN リポジトリの構成
このメモでは SVN リポジトリが以下のような構造になっているとする。

~~~
$SVN_REPO/
  foo/
  bar/
  branches/
    foo-x/
    foo-y/
    bar-new-feature/
~~~

このリポジトリは標準レイアウトではない（trunk/ や tags/ がない）。トップレベルのディレクトリが trunk に相当し、そのサブディレクトリに branches が含まれている。 後述するサブツリーマージの解説のために変則的なレイアウトを用いたが、実環境でも十分ありうるレイアウトであろう。この場合、 trunk/ 以下のディレクトリ構造と各ブランチディレクトリ以下のディレクトリ構造が一致しないため、 trunk での作業と、 trunk と SVN ブランチのマージには注意すべき点がある。それらの注意点は後述の「trunk での開発」「SVN ブランチを trunk にマージする（サブツリーマージ）」で解説する。

なお、標準レイアウトを採用した場合は、 trunk/ 以下と branches/*/ 以下と tags/*/ 以下のディレクトリ構造は一致するはずである。この場合、 trunk/ と tags/*/ 以下も SVN ブランチのひとつであると考え、後述の「SVN ブランチでの開発」および「SVN ブランチ同士のマージ」を参照してほしい。

## SVN リポジトリのクローン

例題のリポジトリをクローンするには以下のようにする。トップレベルのディレクトリを trunk に指定し、 branches ディレクトリにブランチが含まれるとしている。

~~~
git svn clone --trunk= --branches=branches --prefix=svn/ $SVN_REPO_URL
~~~

git のリポジトリは以下のようになる。

~~~
$ git branch
* master

$ git branch -r
svn/trunk
svn/foo-x
svn/foo-y
svn/bar-new-feature

$ ls
foo/
bar/
branches/
~~~

## trunk での開発
svn/trunk をチェックアウトして使う。ワーキングディレクトリには $SVN_REPO/ 以下のファイルすべてが展開されるが、 branches/ 以下には触らないようにする。

~~~
$ git checkout -b trunk svn/trunk
$ edit foo/...; git commit
$ git svn dcommit
~~~

## SVN ブランチでの開発
SVN ブランチと対応するリモートブランチをチェックアウトして使う。ワーキングディレクトリが当該の SVN ブランチの中身だけになってしまうが、 trunk をチェックアウトすれば元に戻るので心配しないように。

~~~
# svn/foo-x をチェックアウトするとワーキングディレクトリには $SVN_REPO/branches/foo-x/ の中身だけ展開される
$ git checkout -b foo-x svn/foo-x
$ ls
foo-x.txt

$ edit ...; git commit
$ git svn dcommit

# trunk をチェックアウトすればワーキングディレクトリは元に戻る
$ git checkout -t -b trunk svn/trunk
$ ls
foo/
bar/
branches/
~~~

## SVN ブランチを trunk にマージする（サブツリーマージ）
例題の SVN リポジトリは、 trunk のサブディレクトリのひとつを branches/ 以下にコピーして SVN ブランチにした構造になっている。 git では、リポジトリ内の類似したサブディレクトリ同士をマージすることができる（サブツリーマージ）。

ここでは SVN ブランチ foo-x を trunk にマージしてみる（つまり \$SVN_REPO/branches/foo-x/ ディレクトリと $SVN_REPO/foo/ ディレクトリのマージ）。

~~~
$ git checkout -b trunk svn/trunk

# $SVN_REPO/foo ディレクトリにローカルブランチ svn/foo-x の中身をマージ
# ここで、 svn/foo-x のトップレベルディレクトリは $SVN_REPO/branches/foo-x/ であることに注意
$ git merge -X subtree=foo svn/foo-x

$ git svn dcommit
~~~

## SVN ブランチ同士をマージする
基本的には git のローカルブランチ同士のマージと同じだが、 必ず --no-ff オプションをつける。そうしないと fast-forward マージが発生することがあり、その場合 `git svn dcommit` のコミット先が別のブランチになってしまう。

ここでは foo-y ブランチを foo-x ブランチにマージする場合を考える。

~~~
$ git checkout -b foo-x svn/foo-x
$ git merge --no-ff svn/foo-y
$ git svn dcommit
~~~

## git のローカルブランチを活用する
実験的な機能を試行錯誤で開発するときなどは、 SVN のコミット単位に満たない粒度のコミットをしたいことがある。そのような場合はローカルブランチで開発し、後で `git merge --squash` や `git rebase` によりコミットをまとめる。

~~~
# 開発
$ git checkout -b foo-x-experimental svn/foo-x
$ edit; git commit; edit; git commit

# SVN へのマージ
$ git checkout -b foo-x svn/foo-x
$ git merge --squash foo-x-experimental
$ git commit
$ git svn dcommit
~~~

用が済んだローカルブランチは削除してよい。 `git svn dcommit` した後も続けて同じローカルブランチを使う場合は、ローカルブランチの内容を最新の内容に追従させる。

~~~
# 削除
$ git b -D foo-x-experimental
# または最新の内容に追従
$ git checkout foo-x-experimental
$ git reset --hard svn/foo-x
~~~