git-svn の使い方をメモする。他によいプラクティスがあれば指摘していただけるとありがたい。
SVN のブランチと git のブランチが混在しているため、ここではブランチという語を以下のように区別する。
- ブランチ、 SVN ブランチ:$SVN_REPO/branches 以下にあるディレクトリ
- ローカルブランチ:git のローカルブランチ
- リモートブランチ:git のリモートブランチ
このメモでは 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 ブランチ同士のマージ」を参照してほしい。
例題のリポジトリをクローンするには以下のようにする。トップレベルのディレクトリを 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/
svn/trunk をチェックアウトして使う。ワーキングディレクトリには $SVN_REPO/ 以下のファイルすべてが展開されるが、 branches/ 以下には触らないようにする。
$ git checkout -b trunk svn/trunk
$ edit foo/...; git commit
$ git svn dcommit
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 のサブディレクトリのひとつを 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
基本的には 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
実験的な機能を試行錯誤で開発するときなどは、 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