この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
こんにちは、SHIMADAです。
今回は、git-svnを使って上流のSVNリポジトリと同期をとりながら開発を進めていく方法について紹介します。
■ 前提
前回のエントリと同様にSVNリポジトリのクローンがローカルに作られている状態から説明します。
■ 上流との同期
SVN側にコミットされた他のメンバーのコードをローカルに取り寄せるコマンドを二つ紹介します。
git-svn-fetch
はじめに、リポジトリ全体の同期を取るコマンドです。
$ git svn fetch
これは、trunkとすべてのブランチにコミットされた変更をローカルに取り込みます。
取り込まれる先は、 .git/ 以下にあるローカルのリポジトリです。
$ git branch -a
で表示される、 remotes/svn/ 以下のブランチが最新の状態にアップデートされます。
git-svn-rebase
次に、現在チェックアウトしているブランチとワーキングファイルを同期するコマンドです。
$ git svn rebase
このコマンドを実行するには条件があり、ローカルでの変更がすべてコミットされた状態でなければなりません。
前回のおさらいになりますが、下記のような操作になります。
$ git add app/models/user.rb
$ git commit -m "svnとgit-svnのテスト"
もしコミットしない状態でgit-svn-rebaseを実行しようとすると、
$ git svn rebase
app/models/user.rb: needs update
update-index --refresh: command returned error: 1
という感じでエラーになりますので、間違えてローカルでの作業内容が消えてしまうということはありません。
■ 超便利なgit-log
git-svn-rebaseが成功すると、リモートでの変更がマージされた状態となり、ワーキングツリーも更新されます。
どのような変更が加わったかを見るには、
commit 0a5c6053915709f0872655099236c3ecbb28dadc
Author: shimada <shimada @bc2c4182-fb10-4788-ac18-b0505eda9c62>
Date: Wed Jul 21 18:51:51 2010
svnとgit-svnのテスト
git-svn-id: https://example.com/svn/repos/myproj/branches/new_feature@123 bc2c4182-fb10-4788-ac18-b0505eda9c62
というコマンドでログを見ることができます。
git-svn経由で取得されたコミットには git-svn-id: というSVNのリビジョンを表す一行が表示されます。
また、SVNの場合は svn log -v というオプションで、リビジョンごとに変更されたファイルのリストが表示されましたが、gitの場合はさらに強力で、git log -p というオプションをつけるとコミットごとの変更内容をunified-diffの形で見ることができます。
commit 0a5c6053915709f0872655099236c3ecbb28dadc
Author: shimada <shimada @bc2c4182-fb10-4788-ac18-b0505eda9c62>
Date: Wed Jul 21 18:51:51 2010
svnとgit-svnのテスト
git-svn-id: https://example.com/svn/repos/myproj/branches/new_feature@123 bc2c4182-fb10-4788-ac18-b0505eda9c62
diff --git a/app/models/user.rb b/app/models/user.rb
index df685f1..77efc53 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -4,7 +4,7 @@ class User < ActiveRecord::Base
def test
- "this is test"
+ "this is test for svn and git-svn"
end
これを使うと、コミットログを流し見するだけで、他のメンバーがどんな変更を行ったか概略を掴むことができます。
■ コンフリクトとその解消
git-svn-rebaseの途中でローカルのコミットとのコンフリクトが検出されると、
$ git svn rebase
M app/models/user.rb
r123 = 6c10697b941ac5537d12b94a206a89fa540fc062 (svn/new_feature)
First, rewinding head to replay your work on top of it...
Applying: svnとgit-svnのテスト
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging app/models/user.rb
CONFLICT (content): Merge conflict in app/models/user.rb
Failed to merge in the changes.
Patch failed at 0001 svnとgit-svnのテスト
When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort".
rebase refs/remotes/svn/new_feature: command returned error: 1
というメッセージが表示されてrebaseの動作が一時停止されます。
コンフリクトを起こしたファイルを確認してみます。
$ git st
app/models/user.rb: needs merge
# Not currently on any branch.
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# unmerged: app/models/user.rb
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# .gitignore
# config/database.yml
# log/
no changes added to commit (use "git add" and/or "git commit -a")
「unmerged:」と表示されているファイルがコンフリクトしたファイルです。
なお、コンフリクトマーカーはSVNとほぼ同じ形式で、
<<<<<<< HEAD
"this is test"
=======
"this is test for git-svn"
>>>>>>> svnとgit-svnのテスト
となっています。上半分がSVNのHEADの状態、下半分がローカルの状態です。
ローカル側のマーカーの後ろにはコミットメッセージが表示されています。
編集して、コンフリクトを解消したら、
$ git add ファイル名
で解消したことをGitに伝えた後、
$ git rebase --continue
でrebaseが再開されます。(このコマンドは上の方で引用したコンフリクト時のメッセージに出てきます。)
■ 上流へのコミット
SVNリポジトリの変更を取り込んだあと、コードに問題がなければローカルの変更をSVNリポジトリにコミットします。
$ git svn dcommit
Committing to https://example.com/svn/repos/myproj/branches/new_feature ...
M app/models/user.rb
Committed r124
M app/models/user.rb
r124 = 0a5c6053915709f0872655099236c3ecbb28dadc (svn/new_feature)
No changes between current HEAD and refs/remotes/svn/new_feature
Resetting to the latest refs/remotes/svn/new_feature
app/models/user.rb: locally modified
M app/models/user.rb
Committed r125
M app/models/user.rb
r125 = 5b080870f969045ab0787170fc06001908c7b0bb (svn/new_feature)
No changes between current HEAD and refs/remotes/svn/new_feature
Resetting to the latest refs/remotes/svn/new_feature
こんな感じで、ローカルでのコミットがひとつずつSVNのチェンジセットとして送信され、リビジョン番号が振られていきます。コミットメッセージもGitに登録されているものがそのまま送られます。
git-svn-rebaseを忘れて、あるいは他のメンバーのコミットを気づかずににgit-svn-dcommitを行ってしまった場合、Gitがリモートの更新を検出し、事前に git-svn-rebase を実行してくれるので、ローカルの古い状態でSVNリポジトリを上書きしてしまうということは起こりません。
しかし、Gitはテキストベースでのマージとコンフリクト検出以上のことはできないので、コードが正常に動作する状態でコミットされたという保証がありません。
マージされた変更の内容を確認して、問題があればメンバーに連絡して、リポジトリへのアクセスを一時控えてもらうなどの措置が必要です。
■ まとめ
git-svnを使った開発のサイクルは、
- コードを書く
- 細かい作業の区切りごとにgit-commitでバージョン管理する
- 大きな区切り(機能をひとつ実装した!など)でSVNにコミットする
- git-svn-rebaseでローカルブランチを最新のものに更新する
- コンフリクトがあれば解消する
- git-svn-dcommitでコミットする
- はじめに戻る
という感じになります。
コードが不安定な状態でも、ローカルへのコミットでチームに迷惑をかけずにバージョン管理ができるgit-svnはかなり便利です。
みなさんも是非トライしてみて下さい。