読者です 読者をやめる 読者になる 読者になる

LAN内のSubversionで管理されているソースをGitでなんとかしたい(その2)

Subversion Git bitbucket

開発プロセス

このプロジェクトでは機能X、機能Y、機能Zといった機能単位でのコミットをすることになっていました。とは言え1つの機能に数日かかるような規模でしたので、全て完了してからコミットするというのは非現実的です。Gitはコミットに対してあとからいろいろ変更できますから、開発中はあとのことを気にすることなく自分が好きなタイミングでコミットしていきます。

$ git checkout master # マスターブランチにいる
$ git checkout -b feature-x # 機能Xブランチ作って切り替え
$ git commit # 好きなだけやりましょう

機能Xが終わったら機能Y、機能Zも同じような感じでやります。

$ git checkout master
$ git checkout -b feature-y
$ git commit # commit and commit and …

もちろん、開発マシンのmasterブランチはpullするたびに新しくなっていきます。区切りのいいタイミングでfeature-xブランチやfeature-yブランチに最新の状態を反映させてあげたほうがいいでしょう。まとめてやろうとするとコンフリクトがすごかったりしますから。

$ git checkout feature-x
$ git rebase master # 最新のmasterブランチを元としたリビジョンツリーにする
$ git checkout feature-y
$ git rebase master
...

git rebaseについてはこちらを参考にしてください→Git - リベース

最後に機能X、機能Y、機能Zそれぞれを1つのコミットにまとめましょう。

$ git checkout feature-x # 機能Xについてコミットが5個ぐらいあったとします
$ git rebase -i HEAD~7 # 7つ分のコミットに対して何かする、みたいなコマンド

こんな感じのメッセージ付きでエディターが起動します。上から古い順にコミットが並んでいます。

pick 4a8dadf あれを変更
pick 39fa792 これを修正(←ここまでmasterのコミットだとする)
pick 9e28c26 あれを追加
pick add4565 これを訂正
pick 3a1a761 これを削除
pick abfd88c あれを追加
pick 6574061 それを追加

3行目以降が機能Xのコミットだとすると、3行目のpickはそのままにし4行目以降のpickをs(またはsquash)として保存して終了します。3行目〜7行目に書かれているコミットが一つのコミットにまとめられます。同じように機能Y、機能Zも一つにまとめます。

ここまでで機能XYZはそれぞれマスターブランチ+1コミットの状態になっています。ここまで来たらあとはクライアントのSubversionに入れるというところまであと少しです。

次は機能XYZを一つのブランチにまとめます。ここは問題を簡単にするために機能XYZは互いにコンフリクトしない修正ってことにしてください。

$ git checkout -b commit-branch
$ git merge feature-x
$ git merge feature-y
$ git merge feature-z

マージ後はこんなふうになっています。

Subversionリポジトリへのコミットなど

SubversionへのコミットはクライアントLAN内のマシンAでこんなふうにやりました。機能Xで変更したファイル郡を上書きコピーしてSubversionにコミット、機能Yで変更したファイル郡を上書きコピーして(以下略

かなりアナログですね。この方法をとってしまったことは残念だと思っています。とは言え、短期のプロジェクトだったのでこれでも回りました。

それと、Subversionへのコミットに先立ってクライアント側でのSubversionへのコミットを止めてもらいました。そうするとSubversionリポジトリとBitbucketのリポジトリが同じ状態になりますよね。その間に先に書いたようなrebase master、rebase -iなどをおこなっておきます。これで開発マシンでの修正による差分をSubversionにそのまま使える状況にできました。

必要になるのは各コミットで変更があったファイル郡です。Gitでは各リビジョン間で変更があったファイルを一覧にしてくれるコマンドがあります。

$ git diff --name-status HEAD~ HEAD

出力された一覧をこちら→GitやSvnなどの差分ファイル抽出決定版 - なんたらノート第三期ベータのスクリプトに渡すと、該当するファイルを集めてくれます。

$ git diff --name-status HEAD~ HEAD | ../export-diff.py ../feature-z

このコマンドで機能Yのコミットから機能Zのコミットの間に修正されたファイルをfeatuer-zディレクトリにコピーしてくれます。もちろんディレクトリツリーはキープされています。

同じように機能Yのコミットで変更されたファイルを集めようとしてこのようにやりたいところですが、

$ git diff --name-status HEAD~2 HEAD~ | ../export-diff.py ../feature-y

これだと実はうまくいきません。確かに集められるファイルは間違いないのですが、そのファイルの中身に問題があります。だってコピーしてるのは機能Zまでの変更が入ったファイルだから。現在のソースコードを機能Yコミット完了時にまで戻すことが必要となります。

正しくはこうです。

$ git checkout HEAD~ # 一つ前のリビジョンをチェックアウト、コミットYまでのソースになる
$ git diff --name-status HEAD~ HEAD | ../export-diff.py ../feature-y # HEAD~とHEADのdiffであることに注意

機能Xの変更ファイルを集めるにはその状態からさらに遡ってあげればオーケーです。

$ git checkout HEAD~ # 一つ前のリビジョンをチェックアウト、コミットXまでのソースになる
$ git diff --name-status HEAD~ HEAD | ../export-diff.py ../feature-x 

これで変更があったソース郡が集まりました。

最後にマシンAでSubversionリポジトリへのコミットを行います。

  1. 機能Xのソース一式を上書きコピー→コミット
  2. 機能Yのソース一式を上書きコピー→コミット
  3. 機能Zのソース一式を上書きコピー→コミット

Subversion管理されているプロジェクトに対して、サーバーに接続できない環境からでも開発を行うという目的を達成できるようになりました。

理想形を考えてみる

このプロセスはだいたい良い感じに回っていますが、最後のSubversionへのコミットは本当にいけてませんでした。せっかくgit-svnを使っているのでそれを活かした形にできなかったかを考えてみたいと思います。

開発するほうは問題ないので、まずはここまでいきます。

マシンAは必要ですね。LAN内からでないとSubversionへのコミットはできません。また、全て自動化するのも危険な香りがします。Subversionへのコミットを止めてもらい、現地でこちらの開発分をコミットする時間を設けてもらう必要はやはりあるでしょう。

マシンAにもう1つgit svn cloneしたGitリポジトリを用意してそこをSubversionへコミットするための環境とするのはどうでしょうか。

このリポジトリのremoteにBitbucketのリポジトリを追加して機能XYZが入ったブランチをpullします。そしてSubversionと連携しているブランチ(master)に機能XYZのブランチをマージした後でSubversionへ反映してあげるという流れです。

$ git svn rebase
$ git remote add online https://username@bitbucket.org/username/project.git
$ git pull
$ git merge feature-xyz
$ git svn dcommit

この流れでうまくいくかどうかはわかりません。多少調整が必要なところがあるかもしれません。
手動の部分を減らせるので少しはマシにできたかもしれません。

最後に

分散型おすすめ。