Subversionによる構成管理の運用基本方針
あなたの現場では、バージョン管理システムは何を使っていますか?Git?Subversion?VSS?まさかCVS?
GitHubスタイルの運用方針があるのであれば、大変結構です。しかし、SIerの現場ではSubversion(以降、SVN)による運用がまだまだ多いように思います。更に、コミット・ルールはあるけどブランチ運用はされておらず、みんなでtrunkに格納しているとか。
ここでは、SVNを使った場合の運用方針を定義したいと思います。これをベースとして、現場ごとにカスタマイズして運用方針を定義すると良いと思います。
結論
git-flowを参考にブランチ運用方針を定義します。SVN標準フォルダ構成(trunk
、branches
、tags
フォルダ)の上に定義するため、git-flowと次の相違点があります。
git-flow | SVN |
---|---|
master ブランチ |
trunk フォルダ |
develop ブランチ |
branches/develop フォルダ |
feature ブランチ |
branches/xxx フォルダ |
releases ブランチ |
branches/releases フォルダ |
hotfix ブランチ |
branches/xxx フォルダ |
注意
- この文書では、SVNサーバーの構築方法やSVNコマンドの解説は行いません。
- SVNコマンドで説明を行いますが、TortoiseSVNを使った方が楽です。TortoiseSVNユーザーは、SVNコマンドをTortoiseSVNの該当する操作に読み替えてください。
よくある状況と問題点
git以前の現場では、次のような状況が見られます。
- SVN、VSSを使ってはいるが、運用方針は定義していない。
trunk
フォルダやルート・フォルダに直接格納している。 - そもそもバージョン管理システムを使用していない。ファイル・サーバーに直接格納しており、日次でバックアップしている。
こういった現場では、次のような問題が発生しがちです。
- コンパイルが失敗するソースコードが混入している!
- リリースを行うから、しばらく触らないで!
- 複数のソースコードを一括で修正するから、しばらく触らないで!
- 現在、リリースされているアプリケーションはどの時点のソースコード?
- リリース物件に作業中のソースコードが混入している…
- レビューが完了するまで、何日~何週間もソースコードを格納できない…
このような問題を解消するため、ブランチ運用方針を定義します。
SVNブランチ運用方針
git-flowを基に、次のようなブランチ運用方針を定義します。
SVN標準フォルダ構成に比べて増えています。次に、フォルダごとの役割を説明します。
フォルダ | 役割 |
---|---|
trunk |
リリースされたソースコードのみを格納する。 |
tags |
リリースされたソースコードにバージョン名を付ける。 |
branches/releases |
リリースを行うための一時フォルダ。 |
branches/develop |
機能ごとのテストまで完了したソースコードを格納する。 |
branches/xxx |
作業ごとの開発中ソースコードを格納する。ここはコンパイルを壊すようなソースコードを格納しても良い。 |
この方針により、次のようなことができるようになります。
- いつでも安全にリリースできるベースラインが欲しい。
trunk
はいつでも安全にリリース可能。
- 作業中に緊急バグ対応など割り込み作業に対応したい。
branches/xxx
に開発中ソースコードを格納して、新たにbranches/yyy
を分岐することで安全に切り替えることができる。
- 作業中ソースコードを複数チームでキャッチボールしたい。
- 業務チームで開発 -> 品質管理チームでレビュー -> 構成管理チームでマージ -> ビルドチームでリリース、といったキャッチボール。
branches/xxx
に開発中ソースコードを格納して、各チームで共有することができる。
- 作業完了まで自PCの中にしかソースコードがないのはツライので、作業中でもコミットしたい。
branches/xxx
に頻繁にコミットしても良い。
次に、これらのフォルダの運用手順を説明します。
最初期
プロジェクト開始時、当ブランチ運用開始時に実施することを説明します。
フォルダを作成
SVNリポジトリを作成直後は、基本フォルダ構成が作成されていると思います。作成されていなければ、手動で作成してください。さらに、trunk
から分岐してbranches/develop
を作成します。branches/releases
は直接作成します。結果、次のようなフォルダ構成になります。
- trunk
- branches
- develop
- releases
- tags
ユーザーを作成
フォルダ作成後、SVNリポジトリにアクセスするユーザーを作成します。Apache HTTP Serverであればhtpasswd管理、Visual SVNであればGUIからユーザーを作成するなど、プロダクトごとに対応してください。
権限を設定
ユーザー作成後、フォルダに読み書きできるユーザーを限定するため、権限を設定します。これは、trunk
やbranches/develop
に不用意にコミットしてしまわないようにする処置です。なので、権限設定がメンドウであれば、運用で気を付ければよいと思います。
運用方針を定義
最後に、一番重要ですが バージョン管理運用方針を策定し、文書化 します。現場ごとの書式で文書化すれば良いと思いますが、おおむね次の構成になると考えます。
- バージョン管理概要
- バージョン管理とは
- 管理対象要素
- 管理対象外要素
- ステークホルダー
- SVNリポジトリ、フォルダ構造
- SVNリポジトリと作業拠点の構造
- フォルダ構造
- 基本方針
- ユーザー管理、権限管理
- チェックアウト
- ブランチ
branches/xxx
の命名規則tags/xxx
の命名規則
- コミット
trunk
、branches/develop
のビルドは破壊しないbranches/xxx
は好きにコミットして良い- コミットメッセージ
- マージにおけるコンフリクト
- マージ
- ロック
- その他
- ファイルコピー、移動
- ゴミファイル
- 作業中ファイル
- 運用手順
- 作業開始 -
branches/xxx
を作成 - 開発、テスト -
branches/xxx
の中で作業 - レビュー -
branches/xxx
の差分をレビュー - マージ -
branches/xxx
からbranches/develop
にマージ - リリース
- 作業開始 -
作業開始
作業ごとにブランチを作成します。基本的にbranches/develop
をbranches/xxx
にコピーすることで、ブランチを作成します。xxx
の命名規則は現場ごとに決めれば良いですが、作業はWBSやチケットやバグ票で定義されるはずなので、WBS番号やチケットIDやバグ番号でブランチを作成すると良いでしょう。こうすることで、そのブランチが何のためのブランチなのかが分かりやすくなります。
例えば、「WBS番号 2-3-5-8 発注画面」という作業を行う場合、次のようにブランチを作成します。
$ svn copy -m "2-3-5-8 発注画面の製造を開始" https://svn.example.com/branches/develop/ https://svn.example.com/branches/2-3-5-8/
開発、テスト
先ほど作成したブランチで作業を行います。このブランチは作業専用のブランチであり他作業者に影響はないので、作業中のコードをコミットしても良いですし、なんなら一時的にビルドが壊れてしまっても良いです。作業が少しでも進捗したら、気軽にコミットしましょう。
レビュー
開発者が作業を終えたらbranches/develop
にマージしますが、その前にもちろんレビューを行います。プロジェクトごとに品質基準があるでしょうし、チームごとにもあるかもしれません。それらの品質確認をパスしたコードのみ、branches/develop
にマージして良いです。次のような基準が良くあるのではないでしょうか。
- コーディング規約に準拠していること
- 静的チェックツールを実行
- 目視で確認
- コード設計に問題がないこと
- 動作すること
- テストをパスしていること
- テストが正しく設計されていること
- マージが失敗しないこと
自動化できることはCIサーバーに任せる
自動化できることは自動化してしまいましょう。CIサーバーで、静的チェックを実行、コード設計の問題報告、テストまでは実行してしまいたいです。
ソースコードの内容確認
ソースコードの内容確認は、ブランチの最初から最新までの差分を表示することで行います。まずは、ブランチがどのリビジョンから派生したのかを確認します。--stop-on-copy
オプションを指定してログを表示することで、派生したリビジョンから最新までのログが表示されます。
$ svn log --stop-on-copy -v https://svn.example.com/branches/2-3-5-8/
派生したリビジョンが確認できたら、そのリビジョンから最新までの差分を表示します。例えばリビジョン35813が派生したリビジョンだとした場合、次のように実行します。
$ svn diff -r 35813:HEAD https://svn.example.com/branches/2-3-5-8/
マージの事前確認
「マージが失敗しないこと」は、--dry-run
オプションを指定してマージすることで確認します。例えば、先ほどの2-3-5-8
ブランチがマージに失敗しないことを確認するには、次のように実行します。
$ svn merge --dry-run https://svn.example.com/branches/2-3-5-8/ https://svn.example.com/branches/develop/
実際にはマージを行わず、マージを行った場合にどうなるかがシミュレーションできます。ログにファイルパスとともに記号が表示されます。記号は次の意味を持ちます。
記号 | 意味 |
---|---|
A | 追加 |
D | 削除 |
U | 更新 |
C | コンフリクト |
G | マージされた |
E | 既に存在する |
R | 置換された |
コンフリクトするとマージに失敗するので、解消する必要があります。解消するには、コンフリクトしているファイルを修正したり、branches/develop
から逆マージを行います。マージ時にpostpone
(競合を後で解決)を選択してから修正してresolved
(競合を解決)しても良いです。
マージ
開発して、テストして、レビューしたら、いよいよbranches/develop
にマージします。マージ作業は、--dry-run
オプションを指定せずにマージを実行します。
$ svn merge -m "2-3-5-8 発注画面の製造、テストが完了" https://svn.example.com/branches/2-3-5-8/ https://svn.example.com/branches/develop/
マージ後のブランチを削除するか放置するかは、どちらでも良いです。branches
の下が見づらいと思えば削除すれば良いし、メンドウであれば放置してもさほど問題はないはずです。ただ、後で作業ログを確認するかもということであれば、放置したほうが良いでしょう。
リリース
開発が進み、いよいよテスト環境や本番環境にリリースする時が来ました。リリース作業にどの程度の時間がかかるかはプロダクトによって異なりますが、リリース作業中も開発作業は進むでしょう。そのため、リリース作業はbranches/releases
で行います。
まず、branches/develop
からbranches/releases/xxx
にコピーします。xxx
はバージョン番号を付けます。バージョン番号の付け方はプロジェクトごとに異なると思いますが、特になければ日付でもつければ良いと思います。
例えば、2017/12/5の1回目のリリースの場合、次のように実行します。
$ svn copy -m "2017/12/5 1回目のリリースを開始" https://svn.example.com/branches/develop/ https://svn.example.com/branches/releases/20171205-1/
リリース作業は、例えば次のようなことを行うでしょう。
- バージョン番号を変更
- 全テストを実行
- リリース先環境用に設定ファイルなどを変更
- The Twelve-Factor Appのように環境変数で制御できるようにした方が良いですが…
- ビルド、デプロイ
- リリース先環境でスモーク・テスト
リリース作業が完了したら、branches/releases/xxx
をtrunk
にマージします。
$ svn merge -m "2017/12/5 1回目のリリースが完了" https://svn.example.com/branches/releases/20171205-1/ https://svn.example.com/trunk/
同時に、tags/xxx
も作成します。tags/xxx
はtrunk
をコピーすることで作成します。
$ svn copy -m "2017/12/5 1回目のリリース" https://svn.example.com/trunk/ https://svn.example.com/tags/20171205-1/
緊急バグ対応
リリースを繰り返すと、きっと、「xxx
バージョンのアプリに致命的バグがあるので緊急で対応してほしい!」という状況が来るでしょう。この時、通常の作業と同様にbranches/develop
から分岐すると、xxx
バージョン以降のソースコードも入ってしまいます。なので、特定バージョンに関連する緊急バグ対応は手順が少し異なります。
バグ対応開始
緊急バグ対応の場合、tags/xxx
からbranches/xxx
に分岐します。例えば、「バグ管理番号 1123 受注画面の確定ボタンをクリックするとクラッシュする」に対応する場合、次のようにブランチを作成します。
$ svn copy -m "バグ 1123 対応を開始" https://svn.example.com/tags/20171205-1/ https://svn.example.com/branches/1123/
リリース
通常の開発と同様に、開発、テスト、レビューを行い、問題がなければリリースを行います。緊急バグ対応のリリースの場合、branches/xxx
からbranches/releases/xxx
に分岐します。
$ svn copy -m "2017/12/5 2回目のリリース" https://svn.example.com/branches/1123/ https://svn.example.com/branches/releases/20171205-2/
リリース作業が完了したら、branches/releases/xxx
をtrunk
にマージして、trunk
をtags/xxx
にコピーします。
$ svn merge -m "2017/12/5 2回目のリリースが完了" https://svn.example.com/branches/releases/20171205-2/ https://svn.example.com/trunk/
$ svn copy -m "2017/12/5 2回目のリリース" https://svn.example.com/trunk/ https://svn.example.com/tags/20171205-2/
また、開発中のソースコードにも反映する必要がある場合(普通、反映する必要がありますが)、branches/develop
にもマージします。
$ svn merge -m "バグ 1123 の対応をマージ" https://svn.example.com/branches/releases/20171205-2/ https://svn.example.com/branches/develop/
branches/releases/xxx
をマージするとリリース先環境用の設定が入ってしまい困る場合、branches/xxx
をマージすると良いでしょう。
$ svn merge -m "バグ 1123 の対応をマージ" https://svn.example.com/branches/1123/ https://svn.example.com/branches/develop/
FAQ
よくあるケースに対する対応を説明します。
プロジェクト途中から導入するには?
もしtrunk
のみで運用しているのであれば、ブランチ運用方針を文書化して周知したのちに、運用手順を最初から実行すると良いでしょう。ぜひ、導入してください!
一つ注意としては、SVNユーザーは(私の観測範囲では)ブランチの扱いに慣れていないことが多いです。ですので、ある程度の学習コストは必要となるでしょう。また、ここではSVNコマンドで説明しましたが、WindowsにはTortoiseSVNとWinMergeという最強コンボがありますので、それらを使うべきです。
作業ごとにブランチを作成するの?
運用手順でも説明しましたが、作業ごとにブランチを作成します。安全な作業のためですので、多少のメンドウは受け入れましょう。この時、1作業の作業量が多くなりすぎないように注意してください。生存期間が長いブランチは、マージでコンフリクトする危険性が高まります。ここら辺は、マネージメント技術やアーキテクトの分野ですが。
ブランチをたくさん作成したら容量が…
VSSではもろにコピーするので容量をバカ食いしますが、SVNのコピーはSVN内部で「コピーした」と記録されるだけですので、容量は(ほとんど)消費しません。気にせずどんどんブランチを作成しましょう。
ただ、SVNリポジトリ全体をチェックアウトしている人はブランチやタグを全てチェックアウトしてしまうため、自PCの容量を消費してしまいます。必要なブランチのみをチェックアウトして、他ブランチを見たい場合はswitch
で切り替えましょう。
間違ったブランチにコミットしないか?
例えば、自分の作業ブランチはbranches/1123
なのに間違えてbranches/1124
をチェックアウトして作業してしまった!というケースですが、これはさすがにチェックアウトするときに気を付けてくださいとしか言えないです。気を付けてください。
レビューのためにブランチ切り替えがメンドウ
はい、メンドウです。正直、GitHubやGitLabが使えたら、SVNコマンドで説明していることのほとんどがWebブラウザでポチポチして終わる話です。GitHubやGitLabを導入できるよう、がんばりましょう。
横展開対応はどのように対応するか?
ある程度開発が進んでから、「xxxの場所にトレース・ログ出力処理を追加すること」のようなポリシーができた、「xxx共通部品を作成したので、yyyという処理は全体的に修正」のようなケースです。プロジェクトによって、「1人が全て対応する」「作業者それぞれが対応する」に分かれると思います。
「1人が全て対応する」場合は、branches/xxx
を1個作成して対応を行い、branches/develop
にマージします。この時、他作業者(他ブランチ)が作業しているファイルも対応範囲である場合、コンフリクトに注意してください。
「作業者それぞれが対応する」場合は、作業ごとにbranches/xxx
を作成して対応を行い、branches/develop
にマージします。この方法はコンフリクトの危険性が非常に低い代わりに、ブランチを複数作成しなければならないメンドウさがあります。
現在、リリースされているアプリケーションのソースコードは?
CIサーバーでデプロイを行っているのであれば、CIサーバーのログを確認することで特定できるでしょう。そうでなくても、何らかの手段でバージョンを特定することができるはずです。実行ファイルのファイル名、ファイルのプロパティ、APIが返すバージョン、など。特定さえできれば、バージョンに対応するタグをチェックアウトすることで、ソースコードを確認することができます。
リリース後に些細なミスを見つけたら?
リリース後に、画面文言のスペルミスを見つけてしまった!修正したいけどブランチ運用は面倒だからtrunk
を直接修正したい!といったケースです。
気持ちは分かりますが、ブランチを作成して対応しましょう。
おわりに
慣れるまでは少々メンドウかもしれませんが、引き換えに気軽にコミットできる環境ができます。このメリットは想像するよりはるかに大きいでしょう。ぜひ、あなたの現場にも導入してください。