Subversionによる構成管理の運用基本方針

Updated: / Reading time: 11 minutes

あなたの現場では、バージョン管理システムは何を使っていますか?Git?Subversion?VSS?まさかCVS?

GitHubスタイルの運用方針があるのであれば、大変結構です。しかし、SIerの現場ではSubversion(以降、SVN)による運用がまだまだ多いように思います。更に、コミット・ルールはあるけどブランチ運用はされておらず、みんなでtrunkに格納しているとか。

ここでは、SVNを使った場合の運用方針を定義したいと思います。これをベースとして、現場ごとにカスタマイズして運用方針を定義すると良いと思います。

結論

git-flowを参考にブランチ運用方針を定義します。SVN標準フォルダ構成(trunkbranchestagsフォルダ)の上に定義するため、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を基に、次のようなブランチ運用方針を定義します。

subversion branching model

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からユーザーを作成するなど、プロダクトごとに対応してください。

権限を設定

ユーザー作成後、フォルダに読み書きできるユーザーを限定するため、権限を設定します。これは、trunkbranches/developに不用意にコミットしてしまわないようにする処置です。なので、権限設定がメンドウであれば、運用で気を付ければよいと思います。

運用方針を定義

最後に、一番重要ですが バージョン管理運用方針を策定し、文書化 します。現場ごとの書式で文書化すれば良いと思いますが、おおむね次の構成になると考えます。

  • バージョン管理概要
    • バージョン管理とは
    • 管理対象要素
    • 管理対象外要素
    • ステークホルダー
  • SVNリポジトリ、フォルダ構造
    • SVNリポジトリと作業拠点の構造
    • フォルダ構造
  • 基本方針
    • ユーザー管理、権限管理
    • チェックアウト
    • ブランチ
      • branches/xxxの命名規則
      • tags/xxxの命名規則
    • コミット
      • trunkbranches/developのビルドは破壊しない
      • branches/xxxは好きにコミットして良い
      • コミットメッセージ
      • マージにおけるコンフリクト
    • マージ
    • ロック
    • その他
      • ファイルコピー、移動
      • ゴミファイル
      • 作業中ファイル
  • 運用手順
    • 作業開始 - branches/xxxを作成
    • 開発、テスト - branches/xxxの中で作業
    • レビュー - branches/xxxの差分をレビュー
    • マージ - branches/xxxからbranches/developにマージ
    • リリース

作業開始

作業ごとにブランチを作成します。基本的にbranches/developbranches/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/

リリース作業は、例えば次のようなことを行うでしょう。

  • バージョン番号を変更
  • 全テストを実行
  • リリース先環境用に設定ファイルなどを変更
  • ビルド、デプロイ
  • リリース先環境でスモーク・テスト

リリース作業が完了したら、branches/releases/xxxtrunkにマージします。

$ svn merge -m "2017/12/5 1回目のリリースが完了" https://svn.example.com/branches/releases/20171205-1/ https://svn.example.com/trunk/

同時に、tags/xxxも作成します。tags/xxxtrunkをコピーすることで作成します。

$ 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/xxxtrunkにマージして、trunktags/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を直接修正したい!といったケースです。

気持ちは分かりますが、ブランチを作成して対応しましょう。

おわりに

慣れるまでは少々メンドウかもしれませんが、引き換えに気軽にコミットできる環境ができます。このメリットは想像するよりはるかに大きいでしょう。ぜひ、あなたの現場にも導入してください。

参考リンク