製作著作 © 2002, 2003, 2004, 2005 Ben Collins-Sussman, Brian W. Fitzpatrick, C. Michael Pilato
This work is licensed under the Creative Commons Attribution License. To view a copy of this license, visit http://creativecommons.org/licenses/by/2.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
2006-02-03 14:18(JST) [Revision:N.1906-1] + 13124
目次
図目次
表目次
例目次
だめな「よくある質問集(FAQ)」には実際にユーザが聞きたいことでは なく、著者がユーザに聞いて欲しいことが書いて あります。おそらく経験があるでしょう:
Q: チームの生産性を最大にするにはどうやって Glorboソフト社の XYZ を使えばよいのでしょう?
A: 顧客の多くは私たちの特許であるオフィスグループ ウェアテクノロジを通じた生産性の向上の方法について知りたいと考えて います。答えは簡単: まず「ファイル」 メニューをクリックし、「生産性 向上」 メニューを選択しましょう、それから …
このような FAQ の問題点は、文字通り FAQ でも何でもないというところ です。技術サポートに電話をして「どうやったら生産性が最大になる のでしょうか?」などと聞く人は一人もいないのです。そうではなく、 本当はもっとずっと具体的な質問がしたいのです、たとえば「どうやったら カレンダーシステムを変更して一日前でなく、二日前に通知するように できますか?」のような。しかし本当の問題点を明らかにするより、 仮想的な FAQ を作るほうがずっとやさしいのです。本当の FAQ を作るには 忍耐強い、組織的な努力が必要なのです: ソフトウェアの一生を通じて やってくる問いを追いかけ、それに対する答えを見守り、それらすべてを 集めて経験の浅いユーザの集約的な経験を反映するように検索可能な形に まとめる必要があります。それは忍耐が必要で、自然主義者のように物事 を黙って観察する態度が必要になります。ここには権威に基づいた仮定や 希望的な観測が入り込む余地はありません—開かれた態度と正確に 物事を記録する態度こそが必要なのです。
この本について私が気に入っているところは、そんな過程を通じて絶えず 本が育っていくところであり、それはすべてのページに現れています。 この本はユーザに対する著者の対峙そのものの結果なのです。それは Subversion メーリングリストで繰り返し問われた基本的な質問を Ben Collins-Sussman が観察することから始まりました: Subversion を使う場合の標準的なワークフローとはいったいどのような ものなのだろうか? ブランチやタグは他のバージョン管理システムと 同じように機能するのだろうか? 誰が特定の変更を加えたということを どうやって把握すればよいのだろうか?
毎日毎日同じ質問を目にすることに強い不満を感じ、Ben は 2002 年の 夏に一ヵ月以上かけて The Subversion ハンドブック を書き上げました。これは 60 ページのマニュアルで、Subversion を利用 する際のすべての基本を扱っていました。マニュアルは完成したような 顔をしませんでしたが、Subversion と共に配布され、学習曲線の最初の 障害を取り除きました。O'Reilly and Associates が完全な Subversion の本を出版しようと決めたとき一番手っ取り早い方法は明らかでした: 単に Subversion ハンドブックを拡張すればよかったのです。
新しい本の三人の共著者は普通ではない幸運に恵まれていました。公式には 彼らの仕事は本をトップダウンに書き下すために目次を作ることからはじめ、 最初の版を作ることでした。しかし彼らはまた確固とした— 確かにそれは制御不能な形でわきあがるような性質のものでしたが— 生の素材に直接触れることもしました。Subversion はすでに何千と言う 初期ユーザの手にあり、それらのユーザは無数のフィードバックをもたらし それは Subversion 本体のみならず、すでに存在しているドキュメントに 対してもそうなのでした。
彼らがこの本を書いている間じゅう、Ben, Mike そして Brian は Subversion メーリングリストとチャットチャンネルをうろつき、 注意深く実際の状況下でユーザが実際に陥る本当の問題を記録してきました。 そのようなフィードバックを監視することは、とにかく CollabNet での彼ら の作業の一部だったわけで、このフィードバックは Subversion をドキュメン ト化する上で非常に有益なものでした。 彼らが書き上げたこの本は、そんな作業を反映しています。しっかりとした 経験を基礎とし、希望的観測に流されず、この本はユーザマニュアルと FAQ の最良の部分をまとめたものです。この二重性は一度読んだだけでは 気がつかないでしょう。順序良く、最初から最後まで、この本はソフトウェア の一片の率直な記述になっています。概略について書かれ、不可欠な同伴 ガイドがあり、管理用設定の章があり、いくつかの進んだトピックに触れ、 そしてもちろんコマンドリファレンスと、障害時の対応法があります。 それは具体的な問題の解法を探しに後で戻ってきてはじめて意味が理解 できるでしょう: そこで語られている詳細は不測の事態に陥った時にしか 関係してきませんし、利用例は本当のユースケースを洗練したものですし ほとんどすべての部分がユーザのニーズとユーザの視点への配慮であふれて います。
もちろん、誰もこの本がSubversion についてのすべての疑問に 答えられるとは約束できる人はいません。質問の期待に、ときどき テレパシーのような精密さで答えることがあるかと思えば、Subversion コミュニティーの知識の中の落とし穴にはまりこんでしまい、手ぶらで 出てくるようなことも、しばしばおこるでしょう。そんな時の一番よい 方法は、<users@subversion.tigris.org>にメールを 送って自分の問題を示すことです。著者らはまだそこにいますし、依然と してリストを監視していますし、本の扉に書かれた三人以外にもたくさんの 人が誤りの訂正や最初の資料について貢献してくれています。コミュニティー の観点から言うと、あなたの問題の解決は単にもっとずっと大きなプロジェクト の喜ばしい副作用でしかありません— そのプロジェクトとはつまり、 ゆっくりとこの本の内容を調整し、そして最終的には Subversion そのものが 実際に利用する人々に、より役立つものにすることです。皆は単にあなたを 助けることができるということだけではなく逆に皆を助けるということができる という理由であなたの話しによろこんで耳を貸すでしょう。これはSubversion も 他のすべての活発なフリーソフトウェアプロジェクトでも同じです。 あなたは一人ではないのです。
どうかこの本をあなたの最良の共とせんことを。
— , Chicago, 14 March, 2004
目次
「If C gives you enough rope to hang yourself, think of Subversion as a sort of rope storage facility.」 —Brian W. Fitzpatrick
オープンソースの世界では、コンカレントバージョン管理システム(CVS) が長い間よく使われてきました。またそれは正しい選択でした。CVSは フリーソフトですし、その制約のないワークフローモデル と、ネットワーク機能のサポート— それは地理的に さまざまな場所に分散したプログラマに作業内容を共有させるものですが— は、オープンソースの世界での共同作業のやり方に非常によく合っています。 CVSと、CVSのある程度ルーズな開発手法モデルは、オープンソース文化のかなめになりました。
しかし、どんなツールでもそうですが、CVSも年をとりました。 Subversionは比較的新しいバージョン管理システムで、CVSの後継となるように 設計されています。設計者は二つの方法で CVSユーザのハートをつかもうとして います。CVSとよく似たデザイン(と、「見栄え」)を持ったオープンソースシステムを 作ることによって、もう一つは、CVSでわかっている欠点のほとんどを 解決しようとすることによって、です。その結果は、バージョン管理システム ソフトの世界に次世代革命をもたらすものではないかも知れませんが、Subversion は確かに とても強力で使いやすく柔軟です。
この本は Subversion バージョン管理システムのバージョン1.2系のために 書かれたものです。私たちはできるだけ完全な記述を目指しましたが、Subversion は 活発で精力的な開発コミュニティーを持ち、既に今後計画されているさまざまな機能や 改良点があるため、この本にあるコマンドや特殊な注意事項のいくつかは変更される かも知れません。
この本はデータを管理するのにSubversionを使おうとする コンピュータの知識のある人たちのために書かれています。Subversionはいろいろな オペレーティングシステム上で動きますが、一番力を入れているユーザインターフェースは コマンドラインベースのものです。それでこの本で議論したり利用するのもコマンド ラインツール((svn)が対象になります。一貫性を保つためこの 本での例は読者が Unix 風のオペレーティングシステムを利用し、Unix と Unix の コマンドラインインターフェースに比較的慣れていることを前提にしています。
しかしsvnはMicrosoft Windowsのような Unix 以外の プラットフォームでも動かすことができます。バックスラッシュ(\) をスラッシュ (/)のかわりにパス区切り文字として利用しなくては ならないなどの僅かな違いをのぞけば、Windows 上でこのツールを動作させた時の 入力と出力の内容は Unix のものと同一です。しかし、Windows ユーザは Cygwin Unix エミュレータ環境下でこの本の例を実行すれば、よりよい結果を 得られるかも知れません。
ほとんどの読者はおそらくプログラマか管理者で、ソースコードの 変更内容を追う必要のある人になると思います。それが Subversionの 一番普通の使い方なので、この本の例もそういう状況を前提にしています。 ただ、Subversionはどのようなタイプの情報に対しても変更点を管理するのに 使えます。画像、音楽、データベース、ドキュメント、 などなどにも利用できます。Subversionにとっては、どんな種類のデータも、 単なるデータにすぎません。
この本は読者がいままでバージョン管理システムを一度も使ったことは ないものとして書かれていますが、CVSの利用者に対しては、Subversion への 移行を楽にするように工夫しました。しばしば補足としてCVSに触れるかも知れませんし、 特に用意した補遺では、CVSとSubversionの大部分の相違点をまとめて あります。
この本は非常にさまざまな背景を持った人々にとって有用であることを 目的としています— つまりバージョン管理についてまったく経験のない人から、経験を 積んだシステム管理者までのすべての人たちです。どのような知識を既に持っているか に応じて、特定の章が何らかの意味で重要になるでしょう。以下はさまざまな読者層 ごとの「おすすめの読み方」と考えてください。
あなたはおそらく既に CVS を利用したことがあり、とっとと Subversion を ダウンロードしてサーバを立ち上げたいのでしょう。第5章 リポジトリの管理 と 第6章 サーバの設定を読めばどのよう にして最初のリポジトリを作り、ネットワーク越しに利用できるようになるかが わかるでしょう。それが済んだら第3章 同伴ツアー と 付録 A. CVSユーザのためのSubversion をものにするのが CVS 経験者と してのあなたが Subversion クライアントを理解するのに一番早い方法です。
あなたの管理者は多分もうSubversion を設定しているはずで、あなたに 必要なのはどうやって Subversion クライアントを利用するかを理解する ことだけです。CVS を利用した経験がないのなら(あるいはバージョン管理 システムなどといったものを一度も使ったことがないのなら)、 第2章 基本概念 と 第3章 同伴ツアー は粋な とっかかりになります。既に CVS を使ったことがあるので あれば第3章と補遺A から始めるのが最良でしょう。
ユーザであれ管理者であれ、最終的にはあなたのプロジェクトは大きくなって いくでしょう。そして Subversion のより進んだ機能を理解したくなるはず です。たとえばブランチ化とマージ(第4章 ブランチとマージ)、 メタデータの設定と実行時オプションの設定(第7章 より進んだ話題)、 などなどです。このふたつの章は最初はピンとこないかも知れません が、基本的なことを理解した後でぜひ読んでみてください。
おそらくあなたは既に Subversion になじんでいて、どうやってそれを拡張するか、 あるいは Subversion のたくさんある API を使ってどうやって新しいソフトウェア を作るかに興味があるでしょう。第8章 開発者の情報はまさにそんな人に うってつけです。
この本のしめくくりはリファレンス情報です—第9章 Subversion リファレンスは、 すべてのSubversionコマンドのリファレンスガイドと、いろいろな役に立つトピック に関する補足情報です。この本全体を一度読み終えたあとで戻ってくる のはきっとこの章でしょう。
ここではこの本で利用されるさまざまな規約について触れます。
このアイコンは周りにあるテキストに関連した注意を 表します。
このアイコンは周りにあるテキストに関連したヘルプ 情報を表します。
このアイコンは回りにあるテキストに関連した警告を 表します。
ソースコードのサンプルは、単なる一例です. 普通のやり方でコンパイルできるとは思いますが、問題点を簡単に示すためのものであり、 良いプログラミングスタイルの例として載せたものではありません。
以下の章とその内容をここで一覧にしておきます:
Subversionの歴史、その機能、構成、 構成要素、そしてインストール方法についての章です。またクイック スタートガイドもあります。
バージョン管理の基礎と異なるバージョン管理モデルを、Subversion の リポジトリ、作業コピー、リビジョンとの関連で説明します。
Subversion ユーザとしての日常的な利用方法に沿った説明をします。 Subversion を使ってどのようにデータを取得し、修正し、コミットするか についてのデモンストレーションです。
ブランチ、マージ、そしてタグについて議論しますが、これにはブランチと マージの最良の方法、一般的な利用例、変更をどうやって取り消すか、 そしてあるブランチから別ブランチにどうやって簡単に乗り換えるかなども 含まれます。
Subversion リポジトリの基本について議論します。どうやってリポジトリを 作成し、設定し、管理するかについて、また、そのためにどんなツールを 利用できるかについても議論します。
Subversion サーバの設定方法と、リポジトリにアクセスする三種類の方法に ついて説明します: HTTP、svn プロトコル、そしてローカルアクセスです。また認証、認可、匿名アクセス についての詳細にも触れます。
Subversion クライアントの設定ファイル、ファイルとディレクトリの 属性、作業コピー中のファイルを無視する方法、 作業コピー中に外部ツリーを含める方法、そして最後にベンダーブランチ の取り扱いについて説明します。
Subversion の内部構造、Subversion ファイルシステム、そして作業コピー の管理領域についてプログラマーの視点から説明します。 Subversion を利用するプログラムを書くために公開された API を使う 例をあげ、そして最も大切なことですが、どうやって Subversion の開発に 貢献するかを示します。
svn、svnadmin、そしてsvnlook のそれぞれのサブコマンドについてすべてのケースでの豊富な例をまじえながら詳細に 説明します。
Subversion と CVS の間の類似点と相違点に触れ、CVS を長年使ってきたことによる 悪い習慣からどうやって抜け出すかについてのさまざまなアドバイスをします。 具体的には Subversion のリビジョン番号、バージョン化されたディレクトリ、 オフラインでの操作、updateとstatus の違い、ブランチ、タグ、メタデータ、衝突の解消、そして認証です。
WebDAVとDeltaVの詳細と、DAV共有を読み書き可能な形にマウントするために どうやってSubversion リポジトリを設定するかを説明します。
Subversion を支援したり利用したりするツールについて議論します。これには 別のクライアントプログラム、リポジトリ参照ツール、などが含まれます。
この本はSubversionプロジェクト開発チームによって書かれたちょっとしたドキュメント から始めたものを、一つにまとめて書き直したものです。そんなわけで、この本は 常にフリーライセンス下にあります(付録 D. Copyrightを参 照してください)。実際、この本は公開された状況のもとで、Subversionの一部として書かれました。 これは二つのことを意味します:
この本の最新版は、この本専用のSubversionリポジトリにあります。
フリーライセンスの下で、誰でもこの本を好きなように変更し、配布することができます。 もちろんこの本のプライベートバージョンを配布するよりも、Subversion開発チームに パッチの形で送ってもらうほうがずっと助かりますが。 コミュニティへの参加方法についてはSubversionへの貢献の項 を参照してください。
この本の比較的最近のバージョンは、 http://svnbook.red-bean.comにあります。
この本は Subversion が存在しなければ不可能でした(し、役に立つものに なることもありませんでした)。そういうわけで、著者はこのようなハイリスク で野心的な新しいオープンソースプロジェクトを支援してくれたBrian Behlendorf とCollabNetにまず感謝します; 次にSubversion の名称とその原型を設計したJim Blandy に対して感謝します— Jim、みな君を愛しているよ; そして最後に 良き友であると同時に偉大なコミュニティー指導者であるKarl Fogelに感謝します。 [1]
O'Reilly と我々の編集者であるLinda Muiと、Tatiana Diaz の忍耐と支援にたいして感謝します。
最後に、この本に対して非公式のレビュー、示唆、修正をしてくれた無数の人々に 感謝します: 確かに完全なリストではありませんが、この本は以下の人々の支援なしに は不完全で不正確なものだったでしょう: Jani Averbach, Ryan Barrett, Francois Beausoleil, Jennifer Bevan, Matt Blais, Zack Brown, Martin Buchholz, Brane Cibej, John R. Daily, Peter Davis, Olivier Davy, Robert P. J. Day, Mo DeJong, Brian Denny, Joe Drew, Nick Duffek, Ben Elliston, Justin Erenkrantz, Shlomi Fish, Julian Foad, Chris Foote, Martin Furter, Dave Gilbert, Eric Gillespie, Matthew Gregan, Art Haas, Greg Hudson, Alexis Huxley, Jens B. Jorgensen, Tez Kamihira, David Kimdon, Mark Benedetto King, Andreas J. Koenig, Nuutti Kotivuori, Matt Kraai, Scott Lamb, Vincent Lefevre, Morten Ludvigsen, Paul Lussier, Bruce A. Mah, Philip Martin, Feliciano Matias, Patrick Mayweg, Gareth McCaughan, Jon Middleton, Tim Moloney, Mats Nilsson, Joe Orton, Amy Lyn Pilato, Kevin Pilch-Bisson, Dmitriy Popkov, Michael Price, Mark Proctor, Steffen Prohaska, Daniel Rall, Tobias Ringstrom, Garrett Rooney, Joel Rosdahl, Christian Sauer, Larry Shatzer, Russell Steicke, Sander Striker, Erik Sjoelund, Johan Sundstroem, John Szakmeister, Mason Thomas, Eric Wadsworth, Colin Watson, Alex Waugh, Chad Whitacre, Josef Wolf, Blair Zajac, そして Subversion コミュニティー全体に対して。
Thanks to my wife Frances, who, for many months, got to hear, 「But honey, I'm still working on the book」, rather than the usual, 「But honey, I'm still doing email.」 I don't know where she gets all that patience! She's my perfect counterbalance.
Thanks to my extended family for their sincere encouragement, despite having no actual interest in the subject. (You know, the ones who say, 「Ooh, you're writing a book?」, and then when you tell them it's a computer book, sort of glaze over.)
Thanks to all my close friends, who make me a rich, rich man. Don't look at me that way—you know who you are.
Huge thanks to my wife Marie for being incredibly understanding, supportive, and most of all, patient. Thank you to my brother Eric who first introduced me to UNIX programming way back when. Thanks to my Mom and Grandmother for all their support, not to mention enduring a Christmas holiday where I came home and promptly buried my head in my laptop to work on the book.
To Mike and Ben: It was a pleasure working with you on the book. Heck, it's a pleasure working with you at work!
To everyone in the Subversion community and the Apache Software Foundation, thanks for having me. Not a day goes by where I don't learn something from at least one of you.
Lastly, thanks to my Grandfather who always told me that 「freedom equals responsibility.」 I couldn't agree more.
Special thanks to my wife, Amy, for her love and patient support, for putting up with late nights, and for even reviewing entire sections of this book—you always go the extra mile, and do so with incredible grace. Gavin, when you're old enough to read, I hope you're as proud of your Daddy as he is of you. Mom and Dad (and the rest of the family), thanks for your constant support and enthusiasm.
Hats off to Shep Kendall, through whom the world of computers was first opened to me; Ben Collins-Sussman, my tour-guide through the open-source world; Karl Fogel—you are my .emacs; Greg Stein, for oozing practical programming know-how; Brian Fitzpatrick—for sharing this writing experience with me. To the many folks from whom I am constantly picking up new knowledge—keep dropping it!
Finally, to the One who perfectly demonstrates creative excellence—thank you.
バージョン管理は情報に対する変更を管理するための技法です。 それは小さな変更をソフトウェアにしたあと、次の日にはその変更を取り消すという ような作業をするプログラマにとっては、長い間非常に重要なことでした。 しかしバージョン管理ソフトウェアの有用性はソフトウェア開発の世界をはる かに越えた汎用性があります。頻繁に変更されるような情報を管理しなくては ならないようなコンピュータを使っている人々がいる場所では常にバージョン 管理システムを導入する余地があります。そしてSubversionが力を発揮する のはそのような場所においてです。
この章にはSubversion の高レベルの導入があります—つまりそれは何であり、 何をするものであり、そしてそのためにはどうしたらよいか、についての導入 です。
Subversion は、フリーなオープンソースのバージョン管理システムで、 時間とともに変化するファイルやディレクトリを管理します。 ファイルの階層構造全体は、リポジトリと呼ばれる 中心的な場所に置かれます。リポジトリは通常のファイルサーバとよく似ていますが メンバーがファイルやディレクトリにしたすべての変更を記録しています。 このため、メンバーは古いバージョンのデータを戻したり、変更履歴を確認したり することができます。この意味で、バージョン管理システムを、 「タイムマシン」の一種と考える人も います。
Subversion はリポジトリにネットワーク越しにアクセスするので、別々の コンピュータで作業する人々によって利用することができます。ある範囲で それぞれの場所からの同じデータの集まりをさまざまな人が修正し管理する 仕組みは共同作業を支援することができます。すべての変更を一つの流れに そって行うわけではないので作業効率をより高めることができます。 さらに作業はバージョン化されているので、作業品質が流れを中断 するかどうかの兼ね合いであるかどうかを心配する必要はありません— データに対して間違った変更をしてしまった場合には単にそれを取り消せばよい のです。
バージョン管理システムのいくつかは、ソフトウェア構成管理システム(SCM) でもあります。そういうシステムは、ソースコードのツリーを管理するために 特別便利に作られています—たとえばプログラム言語をじかに理解する ことができたり、ソフトウェアを構成するのに必要なツールが付属していたり といった具合です。しかし Subversion はそのような種類のシステムでは ありません。 Subversionはどのような タイプのファイルの集合も管理できる一般的なシステムです。あなたにとって それはプログラムのソースコードかも知れません—しかし別の人にとっては 食料品の買い物リストから、デジタルビデオの編集、そしてもっと他のもの ですらあるでしょう。
2000年の初め、CollabNet, Inc. (http://www.collab.net)は CVS の置き換えを 書く開発者を探し始めていました。CollabNet は CollabNet Enterprise Edition (CEE) [2] という 共同作業用のソフトウェアを提供しています。それはバージョン管理システムをその 一部として含んでいました。CEE は最初のバージョン管理システムと して CVS を利用していましたが、CVS の持っている制限は最初から明らかで あり、CollabNet は最終的にもっと良いものを見つけなくてはならないと悟り ました。不幸にも CVS はオープンソースの世界において 事実上の標準となって いましたが、それは単に、少なくともフリーライセンスの下ではそれより良いも のが何もなかったというのが理由の大部分でした。 そこで CollabNet は一から新しいバージョン管理システムを開発することを決め ました。ただし、CVS の基本的な考え方は保持したまま、バグやまずい実装を 含まないようにする形で、です。
2000年の 2 月、彼らはOpen Source Development with CVS (Coriolis, 1999)の著者である Karl Fogel に連絡をとり、この新しい プロジェクトに参加する気はないかどうかたずねました。ちょうど同じころ Karl は既に新しいバージョン管理システムの設計について友人の Jim Blandy と 議論していました。1995 年に二人は CVS のサポート契約を提供する会社、 Cyclic Software を設立し、後にそのビジネスを売却しはしましたが、やはり 自分たちの日常の作業に CVS を利用していました。CVS に関する不満がもと で Jim はバージョン化されたデータの管理について、より良い方法を注意深く 考えることになり、「Subversion」 という名前だけではなく、Subversion リポジトリ の基本的な設計についても既に思いついていました。CollabNet が Karl を 呼ぶと彼はすぐにそのプロジェクトで働くことに同意し、また Jim は 雇用主である Red Hat Software が、不定期の期間にわたって彼を事実上 そのプロジェクトに無償で送り込ませることに成功しました。CollabNet は Karl と Ben Collins-Sussman を雇い、5月から詳細設計が始まりました。 CollabNet の Brian Behlendorf と Jason Robbins、そして Greg Stein (当時はWebDAV/DeltaVの仕様決めを独立した開発者として行っていました が) からのタイミングの良い刺激に助けられ、Subversion は急速に活発な 開発者コミュニティの注意を引きました。多くの人々は CVS について不満を 持っていたことがわかり、最終的に自分たちがその企画に対して何らか貢献 できることを歓迎しました。
最初の設計チームはいくつかのシンプルな目標を決めました。それはバージョン 管理手法の新しい地平を切り開くようなことを目的とはせず、単に CVS の 不具合を修正するものであるとされました。Subversion は CVS の機能に合致し、 同じ開発モデルを踏襲するが、CVS のほとんどの明らかな不具合については 繰り返さないと決められました。そしてそれは CVS を単純な置き換えである必要 はないにせよ、CVS ユーザがわずかな労力によって移行できる程度には十分 似ているべきであるとされました。
14ヶ月のコーディングの後、Subversion は 2001/8/31 に 「自分で自分自身のソースコード管理」が できるようになりました。Subversion開発者は、Subversionの自身のソースコード 管理にCVSを使うのをやめてSubversion自身を使えるようになったということです。
CollabNet がこのプロジェクトを始め、いまだに作業の大部分に出資している わけです(Subversion のフルタイム開発者の給料を払っています)が、Subverion は大部分のオープンソースプロジェクトのように実力主義を促進するような緩やかで オープンないくつかの規則によって成り立っています。CollabNet のコピーライト ライセンスは Debian Free Software Guidelines に完全に合致したものです。 言い換えると、 誰でも自由にSubversionをダウンロードし、修正し、再配布できるということです。 CollabNet や他の誰かの許可を得る必要はありません。
Subversion がバージョン管理の問題に提供しようとする機能についての議論 は CVS のデザインをどのように改良したかという観点から話しをすることが しばしば有用です。CVS になじみがないのであればこれらのすべての機能を 理解する必要はありません。そしてバージョン管理についてまったく知らない のであれば、眠くなるだけかも知れません。まず最初に第2章 基本概念を読んでください。バージョン管理システム一般 についての親切な手引きを用意してあります。
Subversion は以下の機能を提供します:
CVS は個々のファイルの履歴を追うことができるだけですが、 Subversion は時間とともにディレクトリツリー全体の変化も追うことのできる、 「仮想的な」バージョン化ファイルシステムを実装しています。 ファイルと、さらに ディレクトリもバージョン付け します。
CVS はファイルのバージョン化に機能が制限されているので、コピー や名称変更— これはファイルだけではなくディレクトリの内容も 変更する可能性があります— は CVS ではサポートされていません。 さらに CVS では古い履歴を継承しなければ同じ名前の全く新しい ファイル— おそらく全く無関係のファイル—によってすでに バージョン化されているファイルを置き換えることはできません。 Subversion ではファイルとディレクトリの両者に対して追加、削除、 コピー、名称変更をすることができます。そして新規追加されるすべての ファイルは、そこから新しく始まるきれいな履歴を持つことになります。
変更点の集まりは、それ全体がリポジトリに完全に反映されるか、 まったく反映されないかのどちらかです。これにより開発者は 論理的にひとまとまりの変更を作りコミットすることができ、 一部だけがリポジトリに反映されてしまうような問題を回避する ことができます。
ファイルとディレクトリはそれぞれ関連した属性— キーと 値の組のことです— を持つことができます。任意の キー/値の組を生成し保存することができます。属性もファイルの内容と 同じようにバージョン化されます。
Subversion はリポジトリアクセス用の抽象レイアがあり、新しい ネットワークプログラムを簡単に実装できるようになっています。 Subversion は HTTP サーバの拡張モジュールとして組み込むことも できます。こうすると Subversion は信頼性や相互連携性において 非常に有利になりサーバが提供している既存の機能をすぐに利用 できるようになります—認証、認可、データ圧縮、などです。 より簡易なスタンドアロンの Subversion プロセスも利用できます。 このサーバは独自のプロトコルによって SSH を利用したトンネル 通信を簡単に実行できます。
Subversion は、バイナリ差分アルゴリズムを使ってファイルの差分を 表現します。これはテキスト(読むことのできるデータ)にも、バイナリ( 簡単に読むことのできないデータ)に対しても同じ方法で働きます。 どちらのタイプのデータもリポジトリ中に同じ形式で圧縮されて格納され、 差分はネットワーク上どちらの方向にも転送されます。
ブランチとタグを作成するコストはプロジェクトのサイズに比例するわけでは ありません。Subversionはハードリンクとして知られている方法とよく似た方法を使って、 単にプロジェクトをコピーすることでブランチとタグを作ります。そのため ブランチ、タグの作成は非常に短い、一定の時間しかかかりません。
Subversionは歴史的な遺物ではありません。よく設計された APIでできたCの共有ライブラリの集まりとして実装されています。 このことはSubversionの保守をとてもやりやすいものにしますし、 他のアプリケーションや 言語から利用しやすいものにします。
図 1.1. 「Subversion の構成」は Subversion の「概略」と でも呼べるようなものです。
一方の端はバージョン化されたすべてのデータがある Subversionリポジトリ です。もう一方はクライアントプログラムで、バージョン化されたデータの ローカルマシン上のコピー(これを「作業コピー」と言います)を 管理します。この二つの間にさまざまなリポジトリアクセス(RA)層を通じた 通信路があります。そのいくつかはコンピュータネットワークをまたいで リポジトリにアクセスするためのネットワークサーバ越しに通信します。 他のものはネットワークを利用せず直接リポジトリにアクセスします。
Subversion はAPR (the Apache Portable Runtime library)と呼ばれる 可搬性のあるインターフェースの上に作られています。これで Subversionは Apache の httpd サーバが使えるオペレーティングシステムならどれでも 実行させることができます: Windows, Linux, すべての BSDの変種、Mac OS X, ネットウェア、その他です。
Subversion is built on a portability layer called APR—the Apache Portable Runtime library. The APR library provides all the interfaces that Subversion needs to function on different operating systems: disk access, network access, memory management, and so on. While Subversion is able to use Apache as one of its network server programs, its dependence on APR does not mean that Apache is a required component. APR is a standalone library useable by any application. It does mean, however, that like Apache, Subversion clients and servers run on any operating system that the Apache httpd server runs on: Windows, Linux, all flavors of BSD, Mac OS X, Netware, and others.
Subversionを手に入れる一番簡単な方法は自分のオペレーティング システム用のバイナリパッケージをダウンロードすることです。Subversionの ウェブサイト(http://subversion.tigris.org) には、ボランティアによって作られたダウンロード可能なパッケージが たくさんあります。このサイトには普通、Microsoft Windows のための グラフィックインストーラパッケージもあります。Unix系のオペレーティング システムを使っているなら、(RPMs, DEBs, ports tree などといった、)システム 固有のパッケージ配布システムを使うこともできます。
あるいは直接ソースコードからSubversionを作ることもできます。Subversion ウェブサイトから最新のソースコードリリースを取得してください。解凍した あとINSTALL ファイル中の説明に従って作ってください。ソースパッケージには リモートリポジトリにアクセスするためのコマンドラインクライアントを 作るのに必要なものはすべてそろっていますが、(特に apr, apr-util, そして neon ライブラリなど)、Subversionのオプション部分は Berkeley DB や、 潜在的にはApache httpd など、ほかのいろいろなソフトに依存していることに 注意してください。もし完全に ビルドしようとするなら、INSTALLファイルに書かれた すべてのパッケージが手元にあることを確認してください。既にある Subversion リポジトリの上で作業するなら、クライアントプログラムを使っ て、最新の一番新しいソースコードを取得することができます。 このやり方はソースコードの取得の項 の章に書いてあります。
Subversion はさまざま部品からできています。以下はその簡単な概要です。 ここでの簡単な説明で混乱してもあわてないでください; —混乱を 減らすために非常に多くのページがこの後に用意して ありますので。
コマンドラインのクライアントプログラムです。
作業コピーの(アイテムが存在するリビジョンに関係した)状態についての報告をするプログラムです。
Subversionのリポジトリを調べるためのツールです。
Subversionのリポジトリを調整したり修復するためのプログラムで主 システム管理者によって使われます。
Subversion リポジトリのダンプファイル形式のデータに対 するフィルタプログラムです。
Apache HTTP サーバ用のプラグインモジュールです。 リポジトリをネットワーク上の別のユーザが利用できるようにするものです。
デーモンとして、またはSSHから起動されるスタンドアロンのサーバプログラムです。 ネットワーク越しにリポジトリを使えるようにする別の方法です。
Subversionが正しくインストールされていれば、これで利用できるようになって いるはずです。次の二つの章では、コマンドラインクライアントプログラム、 svnの使い方を説明します。
人によってはこの本での「トップダウン」的なアプローチによって 新しい技術を習得するのが困難かも知れません。この節では Subversion の 非常に短い導入方法を用意し、「ボトムアップ」的な読者にも挑戦の機会を与える ことにします。あなたが経験によって学ぶやり方を好むようなタイプの人であ れば、以下のやり方がうまくいくでしょう。途中、この本の関連した章へのリ ンクをつけてあります。
バージョン管理モデルや CVS と Subversion の両者で利用される 「コピー・修正・マージ」モデルについて全く聞いたことがない のであればまず第2章 基本概念を読んでから先に進んだほうが よいでしょう。
以下の例では Subversion のコマンドラインクライアントで あるsvn、管理用ツールである svnadminが利用可能な形で手元にあることを前提とします。 さらに Subversion 1.2 かそれ以降を利用していることも仮定します (これを確認するにはsvn --versionを実行してください)。
Subversion はすべてのバージョン化されたデータを中心的な リポジトリに格納します。最初に新しいリポジトリを作りましょう:
$ svnadmin create /path/to/repos $ ls /path/to/repos conf/ dav/ db/ format hooks/ locks/ README.txt
このコマンドはSubversion リポジトリを含む新しいディレクトリ /path/to/reposを作ります。 この新しいディレクトリには(他のファイルに混じって)データベースファイルの 集まりを含んでいます。内部を詳細に知る必要がないのであれば このバージョン化されたファイルを見る必要はないでしょう。 リポジトリ生成と保守についてのより詳しい情報は 第5章 リポジトリの管理を見てください。
Subversion には「プロジェクト」という概念はありません。 リポジトリは単なる仮想的にバージョン化されたファイルシステム であり、どんなデータも含むことのできる大きなツリー構造です。 管理者によってはひとつのリポジトリにひとつのプロジェクトだけを 入れることを好みますが、他の管理者はディレクトリを分割した形で 複数のプロジェクトを格納することを好みます。両者のメリット、 デメリットについてはリポジトリレイアウトの選択の項 で議論します。どちらの方法でもリポジトリは単にファイルとディレクトリ を管理するだけなので、特定のディレクトリを「プロジェクト」 であると解釈するかどうかは人間にまかされています。それでこの本をつうじて プロジェクトを参照するときには、リポジトリに存在する、いま言ったような形 のいくつかのディレクトリ(あるいはディレクトリの集まり)についてだけ話を することに注意してください。
この例では、新しく作った Subversion リポジトリにインポートを済ませた 何かのプロジェクト(ファイルとディレクトリの集まり)があるものと 仮定しています。このデータ内容は myproject という単一のディレクトリに編成されているものとしましょう( もちろん実際には好きな名前にすることができます)。 後で説明する理由により(第4章 ブランチとマージ 参照)、ツリーの構造はbranches, tags, そして trunkという 名前の三つの最上位ディレクトリを含む必要があります。 trunkディレクトリはすべてのデータを含んでいる はずですが、branchesとtags ディレクトリは空です:
/tmp/myproject/branches/
/tmp/myproject/tags/
/tmp/myproject/trunk/
foo.c
bar.c
Makefile
…
branches, tags, trunkサブディレクトリは実際には Subversion に 必要なものではありません。後で利用する時におそらくもっとも便利になる ように考えられた、よく利用される命名規約にすぎません。
ツリー中にデータを作ったらsvn import コマンドでリポジトリにインポートします(svn importの項を見てください):
$ svn import /tmp/myproject file:///path/to/repos/myproject -m "initial import" Adding /tmp/myproject/branches Adding /tmp/myproject/tags Adding /tmp/myproject/trunk Adding /tmp/myproject/trunk/foo.c Adding /tmp/myproject/trunk/bar.c Adding /tmp/myproject/trunk/Makefile … Committed revision 1. $
これでリポジトリにツリーのデータが入りました。この時点で trunkディレクトリの「作業コピー」 を作ります。ここが実際の作業を行う場所になります:
これでリポジトリにツリーのデータが入りました。 すでに注意したように、リポジトリ中のファイルやディレクトリを詳しく 調べる必要はありません; すべてはデータベース中に格納されているもの だからです。しかしリポジトリの仮想的なファイルシステムを考えると、 いまの場合、最上位にディレクトリmyprojectが あり、その下にあなたのデータが含まれている形になります。
もとの /tmp/myprojectにはなにも変更がないことに注意してく ださい。(実際、必要ならこのディレクトリを消してしまうこともできま す)。リポジトリのデータを操作するためには、このデータのために、一種の 個人用の作業領域となる新しい「作業コピー」を作らなくてはな りません。Subversion に、リポジトリのmyproject/trunkディレ クトリ用の作業コピーを「チェックアウト」するように指示して みましょう:
$ svn checkout file:///path/to/repos/myproject/trunk myproject A myproject/foo.c A myproject/bar.c A myproject/Makefile … Checked out revision 1.
これでmyprojectという名前の新しい ディレクトリ中にリポジトリのプライベートなコピーを手にしたことに なります。作業コピー中のファイルを編集し、その変更点をリポジトリに 書き戻すためにコミットすることができます。
作業コピーに行ってファイル内容を修正します。
svn diff を実行して 変更点に対する unified diff 出力を確認します。
svn commit を実行して リポジトリに自分のファイルの新しいバージョンをコミットします。
svn update を実行して リポジトリの「最新の」状態を自分の作業コピーに反映します。
作業コピーに対してできるすべてのことについての完全な手引き については第3章 同伴ツアーを読んでください。
この時点で、ネットワーク越しに別の人々にリポジトリを 利用可能にすることもできます。 第6章 サーバの設定を読んで利用可能ないくつかのサーバプロセス の違いについて把握し、どのように設定すれば良いかを理解してください。
目次
この章ではSubversionの概要を説明します。 バージョン管理システムの利用が初めての人は、必ずこの章を読んでください。 一般的なバージョン管理の概念から始めて、Subversionの背後にあるアイディア を説明し、Subversionの使い方の簡単な例をお見せします。
この章の例では複数のプログラムソースコードの共有を扱いますが、Subversion はどのようなファイルの集まりも管理できることに注意してください— コンピュータプログラマだけを助けるものではないのです。
Subversion は共有情報の一元管理システムです。最も重要なのは リポジトリと呼ばれる、データの格納庫 です。リポジトリは情報をファイルシステムツリー —一般的なファイルとディレクトリの階層構造—の形で格納します。 任意の数のクライアントがリポジトリにアクセスし このようなファイルの読み書きをします。データを書き込むことでクライアントは 他の人たちがその情報を使えるようにします。データを読み出すことでクライアントは 他の人たちの情報を受け取ります。図 2.1. 「典型的なクライアント/サーバシステム」はこれ を表したものです。
どうしてこんなことが興味深いのか? ここまでのところでは、典型的なファイルサーバの 定義にすぎないように思います。そして実際、リポジトリはファイルサーバの 一種です。が、普通言うようなものとは少し 違います。Subversionのリポジトリの特徴はそれまで書き込まれた すべての修正をすべて憶えているところ です。すべてのファイル変更についても、また、ディレクトリツリーの自身の変更に ついてもそうです。このような変更は、ファイルやディレクトリの追加、削除、 再配置、などによって起こります。
クライアントがリポジトリからデータを読み出すときには、普通はファイルシステム ツリーの最後のバージョンだけが見えます。が、ファイルシステムの 以前の状態も閲覧することができます。 たとえばクライアントは、 「先週の水曜日にこのディレクトリにはどのファイルがあったの?」、とか 「最後にこのファイルを変更したのは誰で、その人は何を変更したの?」 といった履歴に関する質問をすることができます。 この手の質問はすべてのバージョン管理システム のキモになるような質問です。つまりバージョン管理システムとは時間と共に 修正されるデータを記録したり、修正内容を追跡したりするようにデザイン されています。
バージョン管理システムの中核となる役割は共同作業での編集とデー タの共有を可能にすることです。しかしこれにはシステムごとに違った戦略が必要 になります。
あらゆるバージョン管理システムはどれも基本的な一つの問題を解かなくてはなりません: どうやってユーザに情報を共有させつつ、お互いの変更点が重ならないようにするか、です。 リポジトリ上の別の人の変更を間違って上書きしてしまうことは簡単に 起こりえます。
図 2.2. 「避けなくてはならない問題」に示したこんな状況を考えてみてください: 二人の同僚、Harry と Sally がいます。 二人は同時に同じリポジトリ内のファイルを編集することにしました。 もし Harry が先に彼の変更をリポジトリに書き込めば、多分、(その少し あとで) Sally は間違って彼女の新しいバージョンでそれを上書きしてしまう でしょう。Harry のバージョンは永久に失われることはありません(と、いうのは バージョン管理システムはすべての変更を記録しているため)が、 Harry がやった修正は、どれも Sally の新しいバージョンには 現れることがありません。編集時には 彼女は Harry の変更を見ることはできないからです。Harry の作業は、 実質的には失われてしまい、—あるいは少なくとも最新のバージョンからは 失われてしまい、— しかもおそらくそれは二人が意図したことではないで しょう。これこそわれわれが避けなくてはならない状況です。
多くのバージョン管理システムでは、 ロック・修正・ロック解除のモデルを使ってこの問題 を扱います。そのようなシステムでは リポジトリ中のファイルを変更できるのは一度に一人だけです。 最初 Harry はファイルに変更を加える前に、「ロック」しなくては なりません。ファイルのロックは、図書館から本を借りるのにいろんな意味で よく似ています。もし Harry がファイルをロックすると、Sally は同じ ファイルに変更することができなくなります。ロックしようとすれば、 リポジトリはその要求を拒否します。彼女ができるのはそのファイルを 読むことと、Harryが仕事を終えてロック解除してくれるのを待つことだけ です。Harry がロックを解除したあと、彼の番は終わり、今度はSallyが ロックして編集することができる番になります。図 2.3. 「ロック・修正・ロック解除の解法」はこの単純な解法の例です。
ロック・修正・ロック解除のモデルの問題は、ファイル管理が少し厳しすぎる ことで、しばしば、ユーザとって作業の障害になります:
ロックすることは管理上の問題を起こすかも知れません。 ときどきHarryはファイルをロックしたあとでそのことを忘れてしまいます。 いっぽう Sally はずっと自分の番を待っているので、その間何もすることが できません。そしてHarryはそのままバカンスに行ってしまい、Sallyとしては 管理者に対してHarryのロックを解除してもらうように頼まなくてはならなく なります。この状況は不要な遅れと、時間の無駄を起こします。
ロックは不要な直列化を起こすかも知れません。 Harryはそのテキストファイルの先頭の部分を修正して、Sally は同じファイルの 最後の部分を修正したいだけだとしたら? 二人の修正はまったく重なって いません。適当な形でマージされることさえ保証できれば、二人は同じファイル を同時に編集することができ、それが大きな問題にはならないでしょう。
ロックは間違った意味の安心感を与えてしまう場合 があります。 HarryがファイルAをロックしてから編集し、一方Sallyは同時にファイルBを ロックしてから編集しているとします。しかしここでAとBとは意味的に 依存しあっていて、それぞれに対する独立した変更は両立しないとしましょう。 突然 A と B はもう一緒に動作しなくなります。ロックを使ったシステムは このような状況には無力です。— これはある意味で、間違った意味の 安心感を与えてしまっています。HarryやSallyが、ファイルをロックする ことでそれぞれ安全な状態に入り、自分の作業は他人から分離されていると 錯覚することは簡単に起こりえます。このことが、最初に述べたような 実は両立しない変更についての議論を妨げてしまうかも知れません。
Subversion, CVS, その他のバージョン管理システムはロックに変わる アイディアとしてコピー・修正・マージモデルを 使います。このモデルではユーザごとのクライアントプログラムはプロジェクト リポジトリにアクセスして自分だけの作業コピーを 作ります—それはリポジトリにあるファイルやディレクトリをローカルに コピーしてきたものです。それからユーザは ひとりひとりが平行して作業をし、自分の作業コピーを修正します。 最後に自分のコピーは最終的な新しいバージョンにマージされます。 このバージョン管理システムは大部分のマージを手伝いますが 最終的には正しいマージかどうかについては人が責任を持ちます。 ユーザは平行して作業し、変更を同じファイル、ただしそれぞれの作業コピー である"A"に対して行います。
例をあげます。 Harry とSally が同じプロジェクトに対するそれぞれの作業コピーをリポジトリ の内容をコピーして作ったとします。 彼らは平行して作業し、変更をまずは自分の作業コピーの同じファイルAに対して 行います。Sally は自分の変更を先にリポジトリに保存します。 Harry が変更をあとで保存したいと思ったとき、リポジトリは、彼に対して Aは既に最新ではないことを伝えます。 言い換えると、リポジトリにあるファイルAは彼がそれをコピーした後で 別の人によって修正されていることを伝えます。そこで Harry は、Subversion のクライアントプログラムに、自分の作業コピーAに対して、リポジトリにある 新しい変更点をマージするように要求します。 Sally の変更が彼のもので上書きされることはありえません。ひとたび彼が 両方の変更を統合してしまえば、自分の作業コピーをリポジトリに書き戻す ことができます。図 2.4. 「コピー・修正・マージの解法」と 図 2.5. 「コピー・修正・マージの解法(続き)」 はこの処理を示しています。
しかし、Sallyの変更点がHarryのと重なって いたら? そのときはどうなるのでしょう? この状況は衝突と 呼ばれ、普通はあまり大きな問題にはなりません。 Harry がSubversionクライアントプログラムにリポジトリの最新の変更を 自分の作業コピーにマージするように要求したとき、彼のAファイルの作業 コピーは、衝突の状態としてマークされます。彼は両方の変更の衝突した 部分を見ることができ、どちらを選ぶかを選択します。ソフトウェア自体が 自動的に衝突を解決することはできないのに注意してください; 人間だけが 理解し、正しく選択する力を持っています。Harry がいったん重なっている 部分の修正を手で解消したら—たぶんSallyと衝突について話し合った あと—マージされたファイルをリポジトリに安全に書き戻すことが できます。
コピー・修正・マージのモデルは少々混沌としているように 思うかも知れませんが、実際にはとてもスムーズに行きます。 ユーザは平行して作業することができ、相手の修正を待つことはありません。 同じファイルに対して変更するときでも、ほとんどの変更は、まったく重ならない ことがわかります。そして、衝突を解消するのにかかる時間は、ロックする システムで失われる時間よりもずっと短いのです。
最終的に、これは一つの重要な要因に行き着きます: ユーザ間の コミュニケーションです。ユーザがお互いにあまり意見のやり取りをしなければ、 両方の構文上の、また意味の上の衝突は増えます。どんなシステムも ユーザに完全な意思の疎通を強制することはできないので、意味上の衝突を 検出することはできません。そういうわけで、ロックするシステムが衝突を 回避することができるという間違った保証に安心する理由はありません。 実際には、ロックは生産性を落とす以外のなにものでもないように見えます。
そろそろ抽象論から具体的な議論に移るときがきました。この章で はSubversion が利用される実際の例をお見せします。
既に作業コピーについて読んできたことと思いますので、Subversionのクライアント プログラムが作業コピーを作ったり使ったりする様子を見てみます。
Subversion 作業コピーは、自分のローカルシステム上の普通の ディレクトリツリーで、その中には複数のファイルがあります。あなたは 望むファイルを編集することができ、ソースコードファイルなら、それを 普通にコンパイルすることができます。作業コピーは自分だけの作業領域 です: Subversion はほかの人の変更を持ち込んだりしませんし、明示的に そうしてくれと言うまで、自分の変更を他の人に見せたりすることも ありません。同じプロジェクト用に一人で複数の作業コピーを持つことさえできます。
作業コピーのファイルに変更を加え、それがうまく動作することを 確認したあとで、Subversion はその変更を同じプロジェクトであなたと一緒に 作業しているほかの人に「公開」するためのコマンドを(リポジトリに 書き込むことで)用意します。もし他の人が自分自身の変更を公開したとき にはSubversionはその変更を自分の作業コピーにマージするコマンドを用意します。 (リポジトリの内容を読み出すことで。)
作業コピーには、Subversionによって管理される、いくつかの特殊な ファイルもあり、その助けによって(読み出し、書き込みなどの)コマンドを 実行します。特に作業コピー中のディレクトリには.svn という名前の、管理ディレクトリとして知られる サブディレクトリがあります。管理ディレクトリのそれぞれのファイルは Subversionがどのファイルにまだ公開していない変更があるか、どのファイルが 他の人の作業によって最新でなくなっているか理解するのを助けるものです。
典型的な Subversion リポジトリは複数のプロジェクトのファイル (またはソースコード)をつかんでいます。普通、それぞれのプロジェクトは リポジトリのファイルシステムツリー中のサブディレクトリになっています。 この構成によって、ユーザの作業コピーは普通、リポジトリの特定の 部分木に対応しています。
たとえば二つのソフトウェアプロジェクト、paintと calcを含むリポジトリが あるとします。それぞれのプロジェクトはそれぞれの最上位サブディレクトリ にあります。図 2.6. 「リポジトリのファイルシステム」のような状況です。
作業コピーを持ってくるため、リポジトリ中のどれかのサブツリーを チェックアウトしなくてはなりません。 (「check out」 という言葉は何かをロックしたり保護したり するような響きがありますがそうではありません; それは単に自分のための プロジェクトのコピーを作るだけです。) たとえば/calcをチェックアウトするとこんな 感じで作業コピーを手に入れることができます:
$ svn checkout http://svn.example.com/repos/calc A calc/Makefile A calc/integer.c A calc/button.c Checked out revision 56. $ ls -A calc Makefile integer.c button.c .svn/
Aの文字で始まる一覧はSubversionがあなたの作業コピーにいくつかの ファイルを追加したことを示しています。これでリポジトリにある /calcディレクトリの作業コピーを 持ってくることができました。最初に言ったように、この取得時に は、もう一つ、.svnが 作成されますが、これがSubversionに必要な追加情報を格納する ための場所になります。
button.cに変更を加えることを考えてみます。 .svnディレクトリがファイルの修正時刻と もともとの内容を記憶しているので、Subversionはあなたがファイルを 変更したかどうかを見分けることができます。しかしSubversionは明示的に そうしてくれと言われるまでその変更を公にはしません。 自分の変更を公開する操作のことを変更点のコミット (あるいは チェックイン)と言います。
変更点を他の人に公開するにはSubversionのcommit コマンドを使います:
$ svn commit button.c Sending button.c Transmitting file data . Committed revision 57.
これでbutton.cへの変更はリポジトリに コミットされました。もし別のユーザが/calc の作業コピーを作るのにチェックアウトすれば、最新バージョン中に あなたの変更点を見ることになるでしょう。
一緒に作業している Sally が、あなたがチェックアウトしたのと同じ時刻に /calc の作業コピーを自分用にチェックアウト したとしましょう。あなたがbutton.cへの自分の 変更をコミットしても、Sallyの作業コピーは変更されない状態のままです。 Subversionはユーザの要求によって初めて作業コピーの内容を変更します。
作業内容をプロジェクトの最新の状態にするには、SallyはSubversionに 自分の作業コピーを更新 するように依頼しなくては なりません。これにはupdate コマンドを使います。 これはあなたの変更を彼女の作業コピーにマージしますし、彼女がチェック アウトしたあとで他の人がコミットしたすべての部分についてもマージします。
$ pwd /home/sally/calc $ ls -A .svn/ Makefile integer.c button.c $ svn update U button.c Updated to revision 57.
svn updateコマンドからの出力は Subversionがbutton.cの内容を 更新したことを示しています。Sally はどのファイルを更新 するかを指定する必要がないのに注意してください。Subversion は .svnディレクトリの情報と リポジトリの情報を使って、どのファイルを更新しなくてはならないか を決定します。
svn commit操作は一つのトランザクション として任意の数のファイル、ディレクトリに対する変更点を公開する ことができます。作業コピー中で、ファイルの内容を変えたり、新しい ファイルを作ったり、削除したり、名前を変えたり、ファイルや ディレクトリをコピーしたあと、それらの変更点の全体を完全なひと かたまりのものとしてコミットすることができます。
リポジトリでは、それぞれのコミットは、一つの分割できないひと かたまりのトランザクションとして扱います。すべてのコミットに よる変更は、完全に実行されるか、まったく実行されないかの どちらかです。Subversionは、この不分割の性質を、プログラム 障害、システム障害、ネットワーク障害、その他の操作があった 場合でも保とうとします。
リポジトリがコミットを受け付けるときは常に リビジョンと呼ばれるファイルシステム ツリーの新しい状態を作ります。それぞれのリビジョンには 一意な自然数が割り当てられます。前のバージョンよりも 後のバージョンのほうが数が大きくなります。 リポジトリ新規作成時の最初のバージョンはゼロで、ルートディレクトリ 以外には何も含まれていません。
図 2.7. 「リポジトリ」はリポジトリを視覚化するうまい方法 を示しています。0から始まるリビジョン番号が、左から右に追加されていく 状況を想像してください。それぞれのリビジョン番号には対応した ファイルシステム木があり、それぞれの木はコミット 後のリポジトリの状態を示す「スナップショット」 です。
作業コピーは常にリポジトリのどれか一つのリビジョン対応しているとは 限らないことに注意してください。複数の異なるリビジョンのファイル を含んでいるかも知れません。たとえば、最新リビジョン番号が4である リポジトリから作業コピーをチェックアウトしたとします:
calc/Makefile:4
integer.c:4
button.c:4
この時点では、作業コピーはリポジトリのリビジョン4と完全に一致しています。 しかし、ここで button.cに変更を加え その変更をコミットしたとします。他にコミットした人がいない場合、 今回のコミットはリポジトリのバージョンを5にあげ、作業コピーの内容は 以下のようになります:
calc/Makefile:4
integer.c:4
button.c:5
この時点でSallyがinteger.cに対する修正を コミットし、リビジョンを6にあげたとします。ここでもし、svn update コマンドであなたの作業コピーを更新すると、次のようになるでしょう:
calc/Makefile:6
integer.c:6
button.c:6
Sallyのinteger.cへの変更は あなたの作業コピーに現れますが、button.c に対するあなたの変更はそのままです。この例では、 Makefileのテキストは、リビジョン4,5,6で まったく同一のものですが、Subversionはあなたの作業コピー中の Makefileのリビジョンを6として、 それが最新であることを表現します。それで自分の作業コピーに きれいなアップデートをかけたときには、一般に作業コピーは リポジトリのある特定のバージョンと完全に一致します。
作業コピー中のそれぞれのファイルについて、Subversionは 二つの本質的な情報を.svn/管理領域に 記録します:
あなたの作業ファイルは、どのリビジョンに基づいているか (これはファイルの作業リビジョンと呼ばれます)、 そして
リポジトリとの対話によって作業コピーが最後に更新された時刻
これらの情報とリポジトリとの対話によって、Subversionは作業ファイルの それぞれが、以下の四つの状態のどれにあるかを見分けることができます:
作業コピーのファイルは変更されていないし、その作業リビジョン 以降に起きたリポジトリに対するコミットでもそのファイルに対する変更が ない状態。 そのファイルに対するsvn commitは何も実行しませんし、 svn updateも何もしません。
作業コピー中のファイルは変更されましたが、そのベースリビジョン以降の リポジトリへのコミットで、そのファイルに対する変更が何もなかった 場合。作業コピーにはまだリポジトリにコミットしていない変更がある ので、そのファイルに対するsvn commitは、 あなたの変更点をそのまま公開することで成功します。svn update は何も実行しません。
ファイルは作業コピー中では変更されていませんが、リポジトリには 変更がありました。このファイルは、公開リビジョンによって最新とするために どこかで更新する必要があります。そのファイルに対するsvn commit コマンドは何もしません。そのファイルに対するsvn updateは あなたの作業コピーに最新の修正点をマージします。
ファイルは作業コピーでも、リポジトリでも変更されています。 ファイルに対するsvn commitは「out-of-date」 エラーになります。そのファイルはまず更新しなくてはなりません。 ファイルに対するsvn updateは公開されている変更点 を作業コピーの変更にマージしようとします。これが自動的にできないような 状況の場合、Subversionはユーザに衝突の解消をさせるためそのままにして おきます。
これにはいろいろな情報の変化を追う必要があるように思いますが、 svn status コマンドを使えば、あなたの作業コピーの どのファイルの状態も表示できます。このコマンドについてのより詳しい情報は svn statusの項 を見てください。
原則として、Subversionはできる限り柔軟であろうとします。 この特別な例として、作業コピーに、いろいろな異なる作業リビジョン番号を もったファイルとディレクトリを共存させることが できます。前の例での混合リビジョンに戸惑っている人のために、 なぜこのような機能が必要で、どのように利用したらよいかを以下に示します。
Subversion での基本的な原則の一つは、「作業コピーへの取得(push)」の動作 が「リポジトリへの反映(pull)」動作を自動的に引き起こすことは ないし、逆もないということです。これは、あなたがリポジトリへの新しい変更点を 送信する用意ができているということが、他の人たちの変更点を受け取る用意が できていることを意味していることはないという、当たり前の理由によります。 そして自分がまだ引き続き新しい修正を加えている場合、 svn update は自分自身の作業コピー中にリポジトリの内容をうまく反映してくれるはずで、 このさい、あなたの側の変更点を強制的に他の人々に公開する必要はありません。
この規則はまた副次的に、作業コピーには混合リビジョンの状態を記録する ための特殊な仕組みが必要になり、またその状態に対して寛容でなければならない ことを意味しています。これはディレクトリ自身もバージョン管理できる ためさらに複雑な話になります。
たとえば、作業コピーが完全にリビジョン 10 にあるとします。foo.html を編集してsvn commitを実行した結果、リポジトリにリビジョン 15 ができたとします。このコミットが成功した直後では、多くの不慣れなユーザは 作業コピーは完全にリビジョン 15 にあるだろうと期待するかも知れませんがそうでは ないのです!。リビジョン10 とりビジョン15 までの間にリポジトリに対していろいろな変更が 起こったかも知れないのです。クライアント側ではリポジトリに起きたこの変更については 何も知りません。まだ svn update を実行していませんし、 svn commit は新しい変更点をリポジトリから取得したりはしないからです。 一方、もしかりにsvn commitが自動的に最新の変更点をダウンロード するとすれば、作業コピー全体を完全にリビジョン 15 に設定することも可能 でしょう—しかしこれでは「push」と「pull」 が独立した処理であるという基本的な原則を侵すことになります。 このため Subversion クライアントができる唯一の安全な方法は、ある特定の ファイル—foo.html—がリビジョン 15 にある という印をつけることだけです。作業コピーの残りのファイルはリビジョン 10 の ままなのです。svn updateを実行することだけが、最新の変更点 をダウンロードする方法であり、これで作業コピー全体にリビジョン 15 の印がつきます。
事実として、svn commitを実行するときは 常に、あなたの作業コピーはあるいくつかのリビジョンの 混合状態となります。コミット対象となったファイルだけは、それ以外のファイル よりも新しい作業リビジョンになります。何度かのコミットの後で( その間に update を含めなければ)、作業コピーはいくつかのリビジョンの 混合状態になります。あなたがリポジトリを利用している唯一のユーザであったと してもやはりこの現象に出会うでしょう。作業リビジョンの混合状況を見る にはsvn status --verboseコマンドを利用して ください(さらに詳しい情報については svn statusの項を見てください)。
不慣れなユーザは自分の作業コピーが混合リビジョンになっていることには まったく気づかないことがよくあります。多くのクライアントコマンドは 処理対象となるアイテムの作業コピー上でのリビジョンが問題になるので 混乱することになります。例えばsvn logコマンドは ファイルあるいはディレクトリの変更履歴を表示するために利用されます (svn logの項参照)。ユーザがこのコマンド を作業コピー上野オブジェクトに対して実行するとき、そのオブジェクト の完全な履歴を見れるものだと考えるでしょう。しかしそのオブジェクトの 作業コピー上のりビジョンが非常に古いものであった場合(これは svn updateが長い期間にわたって実行されなかったような 場合におこります)、そのオブジェクトの より古い バージョン履歴が表示されるでしょう。
プロジェクトが非常に複雑になった場合、作業コピー中の一部のファイル を強制的に「古い日付」をもった以前のリビジョンに戻すことが 有用であることに気づくでしょう; どうやるかについては 3 章で説明します。 たぶん、あるサブディレクトリにあるサブモジュールの以前のバージョンを テストしたいか、特定のファイルに存在するバグが最初に紛れ込んだ リビジョンを知りたいとかいった場合でしょう。これはバージョン管理 システムの 「タイムマシン」としての性質の一つです— つまり、作業コピーの任意の部分を履歴の中のより新しい状態や古い状態に 移動することができるのです。
作業コピー中を混合リビジョン状態に置くことはできますが、 この柔軟性には制約があります。
まず、完全に最新状態ではないファイルやディレクトリの削除を コミットすることができません。より新しいバージョンのアイテムが リポジトリに存在する場合、この試みは拒否されます。まだあなたが 見ていない変更点を間違って消してしまうことを防ぐためです。
次に、完全に最新状態ではないディレクトリに対するメタデータの変更 はコミットできません。アイテムに対する 「属性」の 付与は 6 章で扱います。ディレクトリの作業リビジョンは特定の エントリと属性の組を定義し、最新のディレクトリへの属性の 変更点のコミットは、やはりまだ見ていない変更点を間違って消して しまうかも知れないからです。
この章では、さまざまなSubversionの基本的な概念を扱いました:
中心となるリポジトリ、クライアント作業コピー、リポジトリリビジョンツリーの 並び、といった概念を導入しました。
どのように二人の共同作業者がSubversionを利用してお互いの修正点を公開したり 受け取ったりするかの簡単な例を見てきました。これには、「コピー・修正・マージ」 のモデルを利用するのでした。
Subversionが作業コピー内の情報をたどったり管理したりする方法について 少し触れました。
ここでは、最も一般的な意味で、Subversionがどのように動作するかについての 良い考え方が身に着いたはずです。この知識をもとに、次の章に進むことが できます。ここは、Subversionのコマンドと機能についての詳しいツアーに なっています。
さて、Subversionを使った詳細を見ていくことにしましょう。この章を 終えるころには、Subversionを使った日常的にしなくてはならない操作のほとんど すべてをやることができるようになっているでしょう。 ソースコードの最初のチェックアウトから始まって、修正し、その修正内容を 調べます。他の人の修正をどうやって自分の作業コピーにマージし、それが どのようなものかを調べ、起きるかも知れない衝突をどのように扱えば 良いかもわかるでしょう。
この章は、Subversionコマンドの全体を列挙するのではないのに注意してください。 —そうではなく、普段一番よく利用するSubversionの操作についての対話的な手引き にしてあります。この章は、 第2章 基本概念 を読み、理解していることと、Subversionの一般的モデルをよく知っていることを前提 としています。コマンドの完全なリファレンスは、 第9章 Subversion リファレンス を見てください。
読み進める前に、Subversionを使うときに必要な一番重要なコマンドを載せて おきます: svn help Subversionコマンドラインクライアントは、自分自身の中にドキュメントを 持っています。—いつでも svn help <サブコマンド>とやれば 構文、オプションスイッチ、その サブコマンドの振る舞いを見ることができます。
svn importで、Subversionのリポジトリに新しいプロジェクトを インポートできます。Subversionサーバを設定するときには、一番最初に 実行するコマンドかも知れませんが、それほどしばしば利用するものでは ありません。import の詳細についてはこの章の後のほうにある、 svn importの項 を見て ください。
話を進める前に、リポジトリ中の特定のリビジョンを特定する方法について少し 知っておく必要があります。 リビジョンの項 で 見たように、リビジョンは、リポジトリのある特定の時点における 「スナップショット」です。 コミットを繰り返してリポジトリが大きくなるにつれ、スナップショットを特定する 仕組みが必要になってきます。
リビジョンの特定には、 --revision (-r) スイッチの あとにほしいリビジョン番号を続けます。 (svn --revision REV) とするか、 二つのリビジョンをコロンで区切って範囲指定することもできます (svn --revision REV1:REV2). また、リビジョンは、番号、キーワード、日付で参照することもできます。
新しいSubversionのリポジトリを作ると、最初はリビジョンゼロとなり、その後の コミットのたびにリビジョン番号は一つずつ大きくなります。 コミットが完了した後では、Subversionクライアントはあなたに一番新しい リビジョン番号を知らせます:
$ svn commit --message "Corrected number of cheese slices." Sending sandwich.txt Transmitting file data . Committed revision 3.
その後、いつでもこのリビジョンを参照したければ、 (この章の後のほうで、どうしてそんなことが必要かを説明します。) 「3」として参照することができます。
Subversionクライアントはさまざまな リビジョンキーワードを理解できます。 このようなキーワードは --revisionスイッチの整数引数のかわりに 使うことができ、Subversionによって、特定のリビジョン番号に変換されます。:
作業コピーのすべてのディレクトリには.svn と呼ばれる管理用のサブディレクトリがあります。Subversionはディレクトリ 中のそれぞれのファイルごとの コピーをこの管理領域中に保存しています。このコピーは修正されることは なく(キーワード展開は起こりませんし、行末変換もしませんし、その他 いっさい何もしません)、最後のリビジョン(これを「BASE」リビジョン といいます)に存在しているファイルで、作業コピーを更新したときのファイルその もののコピーです。このファイルのことを、 プリスティン・コピーあるいはファイルの テキスト-ベースバージョンと言い、 リポジトリに存在している元のファイルの厳密なコピーです。
リポジトリにある最新のリビジョンです。
作業コピーにあるファイル、ディレクトリの、「修正元」リビジョン です。
ファイル、ディレクトリが変更されたBASE以前の(または BASEリビジョンを含む)最後のリビジョンです。
変更があった最後のリビジョンの 直前 のリビジョンです。 (COMITTED - 1 番ということになります)。
PREV, BASE, そして COMMITTED はローカルパス名として参照する のに利用できますが、URL では利用できません。
コマンド実行時のリビジョンキーワードの例です。 コマンドの意味はわからなくても大丈夫です。章を進めるごとに 説明します:
$ svn diff --revision PREV:COMITTED foo.c
# foo.c にコミットした最後の変更を表示
$ svn log --revision HEAD
# 最後のリポジトリへのコミットで付けたログメッセージを表示
$ svn diff --revision HEAD
# 作業コピー内ファイルを、リポジトリの最新バージョンと比較
$ svn diff --revision BASE:HEAD foo.c
# 作業コピーの「修正元」 foo.c を
# リポジトリの最新バージョンと比較
$ svn log --revision BASE:HEAD
# 最後に更新した後のすべてのコミットログを表示
$ svn update --revision PREV foo.c
# foo.c の最後の変更をもとに戻す
# (foo.c の作業リビジョン番号は減少する)
これらのキーワードを使って特定のリビジョン番号や作業コピーの正確な リビジョンを憶えておくことなしに、いろいろな(役に立つ)処理を することができます。
リビジョン番号やリビジョンキーワードを指定できるところではどこでも、 中かっこ、「{}」 の中に日付を入れて指定することもできます。 日付とリビジョン番号の両方を使ってリポジトリ中の変更範囲にアクセスする ことさえできます。
ここでは Subversion が受け入れることのできる日付形式の例をあげておきます。 空白を含むような日付は常に引用符でくくるのを忘れないでください。
$ svn checkout --revision {2002-02-17}
$ svn checkout --revision {15:30}
$ svn checkout --revision {15:30:00.200000}
$ svn checkout --revision {"2002-02-17 15:30"}
$ svn checkout --revision {"2002-02-17 15:30 +0230"}
$ svn checkout --revision {2002-02-17T15:30}
$ svn checkout --revision {2002-02-17T15:30Z}
$ svn checkout --revision {2002-02-17T15:30-04:00}
$ svn checkout --revision {20020217T1530}
$ svn checkout --revision {20020217T1530Z}
$ svn checkout --revision {20020217T1530-0500}
…
リビジョンとして日付を指定するときはSubversionはその日付に 一番近いリビジョンを見つけようとします:
$ svn log --revision {2002-11-28}
------------------------------------------------------------------------
r12 | ira | 2002-11-27 12:31:51 -0600 (Wed, 27 Nov 2002) | 6 lines
…
日付範囲を使うこともできます。 Subversionは両方の日付の間にあるすべてのリビジョンを検索対象と します。両端の日付は検索に含みます:
$ svn log --revision {2002-11-20}:{2002-11-29}
…
既に指摘したように、日付とリビジョン番号を混在させることもできます:
$ svn log --revision {2002-11-20}:4040
Subversion で日付を扱う場合、面倒なことが起こるかも知れないことに注意 してください。リビジョンのタイムスタンプはリビジョン属性として保存 されます—つまりバージョン化されていない、修正可能な属性と して、ということです—リビジョンのタイムスタンプは本当の時間 を完全に偽造する形で変更可能ですし、削除してしまうことすら可能です。 このようなことは Subversion が処理する内部的な日付-リビジョン変換 に大混乱を与えるかも知れません。
たいていの場合、Subversionはリポジトリからプロジェクトを チェックアウト することで始まります。 リポジトリをチェックアウトすると あなたのマシンにはリポジトリの作業コピーができます。 このコピーはコマンドラインで指定したSubversionリポジトリの HEAD (最新のリビジョン) になります:
$ svn checkout http://svn.collab.net/repos/svn/trunk A trunk/subversion.dsw A trunk/svn_check.dsp A trunk/COMITTERS A trunk/configure.in A trunk/IDEAS … Checked out revision 2499.
上の例はtrunkディレクトリのチェックアウトでしたが、チェックアウトの URL中にサブディレクトリを指定することでどのような深い階層にある サブディレクトリも簡単にチェックアウトできます:
$ svn checkout http://svn.collab.net/repos/svn/trunk/doc/book/tools A tools/readme-dblite.html A tools/fo-stylesheet.xsl A tools/svnbook.el A tools/dtd A tools/dtd/dblite.dtd … Checked out revision 2499.
Subversionは 「ロック・修正・ロック解除」 のかわりに 「コピー・修正・マージ」 モデルを使うので (第2章 基本概念)、すでに作業コピーのファイルやディレクトリ に対して変更する準備ができています。作業コピーはあなたのシステムに ある他のファイルやディレクトリのようなものです。編集したり変更を 加えたり移動することもできますし、作業コピー全体を削除してから、その ことを忘れてしまうこともできます。
作業コピーは「システム中のほかのファイルやディレクトリ の集まり」となんら変わることはありませんが、作業コピー中のファイル やディレクトリを編成しなおした場合には常にSubversionにそのことを知らせ なくてはなりません。もし作業コピー中のファイル、ディレクトリをコピーまたは 移動したい場合には、オペレーティングシステムで用意されているコピーや 移動コマンドを使うかわりにsvn copyやsvn move を使ってください。この章の後のほうでこれについてもっと詳しく 説明します。
新しいファイルやディレクトリを作ったり、既に存在するものを 変更したりした結果をコミットする用意が できるまで、何をやろうと Subversionサーバに追加報告する必要はまったく ありません。
リポジトリのURLを唯一の引数として作業コピーをチェックアウトする こともできますが、リポジトリURLの後に、ディレクトリを指定することも できます。この場合、指定した新規のディレクトリ中に作業コピーを作ろうとします。 たとえば:
$ svn checkout http://svn.collab.net/repos/svn/trunk subv A subv/subversion.dsw A subv/svn_check.dsp A subv/COMITTERS A subv/configure.in A subv/IDEAS … Checked out revision 2499.
これは、既にやったような trunk という名前のディレクトリのかわりに subv という名前のディレクトリに、作業コピーを作ります。
Subversionはたくさんの機能、オプション、おまけが付いていますが、 日々の作業では、おそらくその中のいくつかを使うだけでしょう。 この章では一番よく起こることを説明します。
典型的な作業サイクルは次のようなものです:
作業コピーの更新
svn update
変更
svn add
svn delete
svn copy
svn move
自分の変更点の確認
svn status
svn diff
svn revert
他の人の変更の、作業コピーへのマージ
svn update
svn resolved
自分の変更のコミット
svn commit
チームを作って作業してるプロジェクトでは、自分の作業コピーを 更新してプロジェクトの他のメンバーが自分の 更新処理後に加えた変更点をすべて受け取りたくなるでしょう。 svn updateを使って自分の作業コピーを リポジトリの最新バージョンにあわせてください。
$ svn update U foo.c U bar.c Updated to revision 2.
この場合、あなたが最後に更新してから、誰か別の人が foo.c と bar.c の両方に加えた変更をコミットし、Subversionはこの変更をあなたの 作業コピーに加えるために更新しました。
svn update の出力をもう少し詳しく見てみましょう。 サーバが変更点を作業コピーに送るとき、文字コードがそれぞれのファイル の横に表示されて、あなたの作業コピーを最新にするために、どのような 動作を起こしたかを知らせます:
ファイル foo は 更新(Updated) されました(サーバから 変更を受け取りました)。
ファイルかディレクトリである foo は あなたの作業コピーに追加(Added) されました。
ファイルかディレクトリである foo は あなたの作業コピーから削除(Deleted) されました。
ファイルかディレクトリである foo は あなたの作業コピー中で置き換え(Replaced) られました。つまり foo は削除されて、同じ名前の 新しいファイルまたはディレクトリが追加されました。両方は同じ名前ですが、 リポジトリはそれらを別の履歴を持った別のものであるとみなします。
ファイル foo は新しい変更点を リポジトリから受け取りましたが、そのファイルのローカルコピーにも 修正が加えられていました。しかし両方の修正は重なっていないか、あるいは 変更の内容が自分自身のものとまったく同じであったため、Subversion はリポジトリの変更を、問題を起こすことなしにマージ (merGed)しました。
ファイル foo は、サーバから 衝突(Conflicting)のある変更を 受け取りました。サーバからの変更は、あなた自身の変更と直接重なっています。 でも心配はいりません。この衝突は人間(つまりあなた)が解消しなくては なりません。この章の後でこの状況について議論します。
さて、これで自分の作業コピーに変更を加えることができます。 以下のような、比較的特殊な変更をすることもできます。新しい機能 を書いたり、バグをフィックスしたり、などです。このような場合に 使うSubversionコマンドは、 svn add, svn delete, svn copy, svn moveなどです。しかし、既にSubversion 管理下にあるファイルを単に編集するだけなら、コミットするまでに そのようなコマンドを使う必要はありません:
これは一番単純なタイプの変更です。ファイルを変更することについて Subversionに報告する必要はありません。どのファイルが変更された についてはSubversion自身が自動的に検出することができます。
Subversionに対して、削除、追加、コピー、移動の予告として ファイルやディレクトリを「マーク」するように 依頼することができます。このような変更は作業コピー上では直ちに 起こりますが、次にあなたがコミットするまでリポジトリ上では 追加削除は一切起きません。
ファイルを変更するには、テキストエディタ、ワードプロセッサ、 グラフィックプログラム、その他の通常利用しているツールなら なんでも使うことができます。Subversionはバイナリファイルを テキストファイルを扱うのと同じくらい簡単に扱うことができます— し、十分効率的にあつかえます。
ここでは、Subversionでツリーの変更として一番よく利用される 四つのサブコマンドを概観しておきます (あとで、svn import と svn mkdir も見ていきます)。
どんなツールを使ってファイルを編集する場合でも、その内容 を Subversion に伝えずに作業コピーの構成を変えるべきではあり ません。作業コピーの構成を変えるときには svn copy, svn delete, svn move コマンドを使い、新たにファイルやディレクトリ をバージョン管理下におく場合にはsvn addコ マンドを使うようにしてください。
通常ファイル、ディレクトリ、シンボリックリンクのどれかである foo をリポジトリに追加する予告をします。 次のコミットでfoo は正式に親ディレクトリの 子供になります。fooがディレクトリの場合は fooにあるすべてのファイルは追加予告の対象に なります。fooだけを追加予告したい場合は --non-recursive (-N) スイッチを指定してください。
通常ファイル、ディレクトリ、シンボリックリンクのどれかである foo をリポジトリから削除する予告をします。 foo が通常ファイルまたはシンボリックリンクの場合は作業コピーから直ちに 削除されます。ディレクトリの場合は削除されませんが、Subversion はそれを削除予告の状態に設定します。変更をコミットすると foo は作業コピーとリポジトリから削除されます。 [3]
新しいアイテムbar をfoo の複製として作ります。barは自動的に 追加予告されます。barが次のコミットでリポジトリ に追加される時点で、コピーの履歴が記録されます(それが fooのコピーである、という履歴)。 svn copyは中間ディレクトリを作成しません。
このコマンドはsvn copy foo bar; svn delete foo を実行することとまったく同じです。つまり、 bar はfooのコピーとして 追加予告され、foo は削除予告されます。 svn moveは中間ディレクトリを作成しません。
変更が完了したら、リポジトリにコミットする必要がありますが、普通 そうする前に、正確には自分が何を変更したのかを見ておくのは良い考え です。コミットの前に変更点を確認することで、より正確なログメッセージ を付けることができます。また、不十分な修正をしただけであることを 発見するかも知れませんし、コミットする前にその変更を破棄したりする 機会にもなります。さらに、公開する前に変更点を再検討したり詳しく調査 する機会にもなります。 svn status, svn diff, svn revert を使って正確にはどんな変更をしたかを 見ることができます。最初の二つのコマンドで、作業コピー中のどのファイル を変更したかを調べ、三番目のコマンドでそのうちのいくつか(あるいは全部) の変更を取り消すかも知れません。
Subversionはこの作業をやるために効率よく作られていて、多くの操作に ついてはリポジトリと通信することなしに実行できます。特に、 作業コピーには、.svn という隠れたディレクトリが あり、ここに作業コピーの「元なるリビジョン」 のコピーが あります。これをうまく使って Subversionは、あなたの作業ファイルのどれが 変更されたかをすばやく知ることができますし、リポジトリと通信すること なしに、変更を取り消すことすらできます。
多分、どのSubversionコマンドよりもsvn status コマンドはよく利用されるはずです。
自分の作業コピー最上位階層で引数なしにsvn status を実行すると、自分がツリーにしたすべての修正が検出できます。 以下の例はsvn status が返すことのできる 異なる状態コードです。 (以下で、# の後に書いてあるテキストは svn statusからのものではないのに注意してください。)
L some_dir # svn left a lock in the .svn area of some_dir
M bar.c # the content in bar.c has local modifications
M baz.c # baz.c has property but no content modifications
X 3rd_party # dir is part of an externals definition
? foo.o # svn doesn't manage foo.o
! some_dir # svn manages this, but it's missing or incomplete
~ qux # versioned as file/dir/link, but type has changed
I .screenrc # svn doesn't manage this, and is set to ignore it
A + moved_dir # added with history of where it came from
M + moved_dir/README # added with history and has local modifications
D stuff/fish.c # file is scheduled for deletion
A stuff/loot/bloo.h # file is scheduled for addition
C stuff/loot/lump.c # file has textual conflicts from an update
C stuff/loot/glub.c # file has property conflicts from an update
R xyz.c # file is scheduled for replacement
S stuff/squawk # file or dir has been switched to a branch
K dog.jpg # file is locked locally; lock-token present
O cat.jpg # file is locked in the repository by other user
B bird.jpg # file is locked locally, but lock has been broken
T fish.jpg # file is locked locally, but lock has been stolen
この出力形式の中で、svn statusは 五つの文字を表示していて、その後にいくつかの空白が続き、 ファイルまたはディレクトリ名称がそのあとに続いています。 最初のコラム (左から一文字目の部分) は、ファイルまたはディレクトリの 状態をあらわしています。ここで表示されているコードは:
通常ファイル、ディレクトリ、シンボリックリンクのいずれかである item はリポジトリに追加予告されています。
ファイルitem は衝突の状態にあります。つまり、自分の作業コピーにあるローカルな変更が 更新時にサーバから受け取った変更部分と重なっています。リポジトリに 自分の変更点をコミットする前にこの衝突を解決しなくてはなりません。
通常ファイル、ディレクトリ、シンボリックリンクのいずれかである item は リポジトリからの削除予告をされています。
ファイルitemの内容は 修正されています。
ファイル、ディレクトリ、シンボリックリンクのいずれか であるitem はリポジトリ中の item を置き換えるように準備されています。 これはまずそのオブジェクトがいったん削除され、次に同じ名前の 別のオブジェクトが追加されます。そしてそれは単一のリビジョンで ひとまとまりに実行されます。
ディレクトリ item はバージョン化 されていませんがSubversionの外部定義に関連付けられています。外部定義についての 詳細は外部定義の項を見てください。
通常ファイル、ディレクトリ、シンボリックリンクのいずれかである item は バージョン管理下にはありません。--quiet (-q) スイッチをsvn status に渡すか、親ディレクトリにsvn:ignore 属性を 設定することで疑問符の表示を抑制できます。 無視できるファイルについての詳細は svn:ignoreの項を見てください。
通常ファイル、ディレクトリ、シンボリックリンクのいずれかである item は バージョン管理下にありますが、それは失われているか、何か不完全な 状態にあります。Subversion以外のコマンドを使って削除された場合 には、そのアイテムは失われてしまいます。ディレクトリの場合、 チェックアウトか、更新が中断された場合、不完全な状態になることが あります。svn updateを使えばすぐにリポジトリから ファイルまたはディレクトリをもう一度取り出すことができます。 svn revert fileを使えば、失われたファイルを 復元することができます。
通常ファイル、ディレクトリ、シンボリックリンクのいずれかである item は あるタイプのオブジェクトとして存在しますが、作業コピーには別の タイプのオブジェクトとして存在しています。 たとえばSubversionはリポジトリ中にファイルを持っているが、 svn delete や svn addを 使わずに、作業コピー中の対応するファイルを削除し、同じ名前の ディレクトリを作ったような場合です。
ファイル、ディレクトリ、シンボリックリンクのいずれかである item はバージョン管理下にはなく、Subversion は svn add, svn import svn status の実行時にはこれを 無視します。無視されるファイルについてのより詳しい情報は svn:ignoreの項 を見てください。 このシンボルは svn statusに--no-ignore オプションを渡したときにだけ表示されることに注意してください。 —そうでなければファイルは無視され、まったく表示されません!
二番目のコラムはファイルまたはディレクトリの属性を示しています (詳しくは属性の項 参照してください)。 もしM が表示されていれば 属性は修正されたことを示しています。そうでなければ空白が 表示されます。
三番目のコラムは空白か、L が 表示され、後者の場合は Subversionがそのディレクトリの .svn 作業領域をロックしていることを意味して います。svn commit が実行されている途中で svn status を実行すると L が表示されます— 多分ログメッセージを変更している最中かも知れません。 Subversionが実行されていないのなら、Subversionは多分中断され たため、ロックは、svn cleanupの実行によって 解除しなくてはなりません。(これについてはこの章の後で触れます)
四番目のコラムは空白か+ が 表示され、あとの場合はファイルまたはディレクトリは 追加または修正され、それが履歴に追加予告されていることを意味します。 これはファイルやディレクトリに対してsvn move か svn copy をしたときによく起こります。 A +の表示がある場合 そのアイテムは履歴付きの追加予告されていることを意味します。 それはファイルか、コピーされたディレクトリのルートであるかです。 +はそのアイテムが、履歴に追加 予告されたサブツリーの一部であることを意味します。つまり、 そのアイテムのどれかの親がコピーされ、コミットを待っています。 M + はアイテムが履歴 に追加予告されたサブツリーの一部であり、 かつ ローカルの修正も受けているという場合です。 コミットするとき、最初に親が履歴付き追加されます(コピーされます) その意味はこのファイルはコピーによって自動的に存在するということ です。次いでローカルの修正はコピーにアップロードされます。
五番目のコラムは空白か、Sに なります。これはファイルかディレクトリは作業コピーの残り パスから、ブランチに(svn switchコマンドで) 切り替わっていることを意味します。
六番目のコラムはロックに関する情報を示しています。 詳細は ロックの項で説明します。
svn statusにパスを指定すると、そのアイテムに 関する情報のみを表示します:
$ svn status stuff/fish.c D stuff/fish.c
svn status も --verbose (-v) スイッチを とりますが、その場合作業コピー中のすべてのアイテム に対して、たとえ変更がなくてもステータスを表示するという意味になります:
$ svn status --verbose
M 44 23 sally README
44 30 sally INSTALL
M 44 20 harry bar.c
44 18 ira stuff
44 35 harry stuff/trout.c
D 44 19 ira stuff/fish.c
44 21 sally stuff/things
A 0 ? ? stuff/things/bloo.h
44 36 harry stuff/things/gloo.c
これは svn statusの「長い表示形式」 の出力です。再処理コラムは同じですが、二番目はアイテムの作業リビジョン になります。三番目と四番目はそれぞれアイテムが最後に変更されたリビジョン と、誰がそれをしたかの表示です。
いままで出てきたsvn status の実行は いずれもリポジトリと通信をしません。それは単に作業コピー中の .svn ディレクトリのメタデータを比較する ことによって、ローカルマシン上だけで動作します。 最後に、--show-updates(-u) スイッチがありますが、これはリポジトリと通信して、古くなった ファイルなどの情報を追加表示します:
$ svn status --show-updates --verbose
M * 44 23 sally README
M 44 20 harry bar.c
* 44 35 harry stuff/trout.c
D 44 19 ira stuff/fish.c
A 0 ? ? stuff/things/bloo.h
Status against revision: 46
二つのアスタリスク ('*') に注意してください:この状態で svn update を実行すると READMEとtrout.c の変更点を受け取ることになります。 これは非常に役に立つ情報です—コミットする前には更新して README に関するサーバ上の変更点を取得 しなくてはなりません。さもなければ、最新でないという理由で コミットは失敗するでしょう(詳しくは後で述べます)。
自分の変更点を調べる別の方法は、svn diff コマンドを使うことです。svn diff を引数 なしに実行することで、自分がどんな変更をしたかを 正確に 知ることができます。このときの 出力形式はunified diff 形式です: [4]
$ svn diff
Index: bar.c
===================================================================
--- bar.c (revision 3)
+++ bar.c (working copy)
@@ -1,7 +1,12 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <stdio.h>
int main(void) {
- printf("Sixty-four slices of American Cheese...\n");
+ printf("Sixty-five slices of American Cheese...\n");
return 0;
}
Index: README
===================================================================
--- README (revision 3)
+++ README (working copy)
@@ -193,3 +193,4 @@
+Note to self: pick up laundry.
Index: stuff/fish.c
===================================================================
--- stuff/fish.c (revision 1)
+++ stuff/fish.c (working copy)
-Welcome to the file known as 'fish'.
-Information on fish will be here soon.
Index: stuff/things/bloo.h
===================================================================
--- stuff/things/bloo.h (revision 8)
+++ stuff/things/bloo.h (working copy)
+Here is a new file to describe
+things about bloo.
svn diff コマンドは.svn 領域 にある、「修正元リビジョン」 のコピーに対して作業コピー 中のファイルを比較した結果を出力します。 追加予告ファイルはすべて追加されたテキストとして表示され 削除予告されているファイルはすべて削除されたファイルとして表示 されます。
出力は、unified diff 形式で表示されます。 つまり、削除された行は先頭に - が付き、追加された 行は先頭に +がつきます。svn diff はさらにpatch に便利なようにファイル名称とオフセット 情報を表示します。このためdiffの出力をファイルにリダイレクトすることで 「パッチ」 を生成することができます:
$ svn diff > patchfile
たとえば、パッチファイルを別の開発者に送り、コミット前に再検討や テストをすることができます。
上のdiff出力を見て、README に対する修正が間違っていることがわかったとしましょう: 多分 エディタで間違ったファイルに保存してしまったりしたのでしょう。
これは、svn revertを使うことの できるとても良い機会です。
$ svn revert README Reverted 'README'
Subversion はそのファイルを.svn 領域に ある「修正元リビジョン」のコピーを上書きすることに よって、修正以前の状態に戻します。 しかし、svn revert はどのような 予告操作も取り消すことができるのに注意してください—たとえば 最終的に新しいファイルを追加することをやめることができます:
$ svn status foo ? foo $ svn add foo A foo $ svn revert foo Reverted 'foo' $ svn status foo ? foo
svn revert ITEM は、作業コピーから ITEMを削除し、それからsvn update -r BASE ITEMを実行したのとまったく同じ効果があります。 しかし、もしファイルをもとに戻そうとしているのなら、 svn revertには一つ重要な違いがあります—それはファイル を元に戻すにあたってリポジトリと通信する必要がないのです。
あるいは間違ってバージョン管理からファイルを消して しまったのかも知れません:
$ svn status README
README
$ svn delete README
D README
$ svn revert README
Reverted 'README'
$ svn status README
README
いままでで、svn status -uがどうやって衝突を 予告できたかを知っています。svn update を実行して、面白いことが起こったとします:
$ svn update U INSTALL G README C bar.c Updated to revision 46.
U と G のコードは考える ことはありません。この二つはリポジトリからの変更を きれいに吸収することができました。 U でマークされたファイルは ローカルでは何の変更もありませんでしたが、リポジトリからの 修正分で更新(Updated)されました。 Gはマージ( merGed)されたことを意味して いますが、これは、ファイルはローカルで変更されていたが、 リポジトリからの変更部分とまったく重ならなかったことを意味 しています。
しかし C は衝突を あらわしています。これはサーバからの変更場所があなた自身の ものと重なっていることを意味していて、あなたは手で どちらかを選択しなくてはなりません。
衝突が起こると、普通はその衝突を知らせて解決することができるように 三つのことが起こります:
そのファイルがマージ可能なタイプのときには Subversion は更新処理中にC を 表示して、そのファイルが「衝突している」ことを知らせます。 (行番号に基づいた文脈マージ可能なファイルかどうかは svn:mime-type属性によって決まります。 詳しくはsvn:mime-typeの項を見てください。)
Subversion は衝突マーカ —衝突を 起こした「両方」の内容を区切る特別なテキスト文字列 のことです—を重なっている場所に置き、衝突内容を見てわかるように します。
衝突しているファイルのそれぞれについて、Subversionは最大で三つの バージョン管理対象にはならない特殊なファイルを作業コピーに置きます:
これは作業コピーを更新する前に作業コピー中にあったファイル です—つまり、衝突マーカを含んでいません。このファイルは 自分のやった最後の変更が含まれているだけのものです。(Subversion がこのファイルがマージ可能なものではないとみなした場合には .mineファイルは作成されませんが、それは 作業ファイルと同一の内容になってしまうだろうからです。)
これは、作業コピーを更新する前のBASEリビジョンにあったファイル の内容です。つまり、そのファイルは最後にした編集の直前にした チェックアウト時点でのファイルです。
これは Subversionクライアントプログラムが作業コピーを更新したときに サーバから受け取ったファイルです。これは、リポジトリのHEAD リビジョンに対応しています。
ここで OLDREV は .svn ディレクトリにあるファイルのリビジョン番号で、NEWREVは HEAD リポジトリのリビジョン番号です。
たとえば Sally がリポジトリにあるsandwich.txt に変更を加えるとします。たった今、Harryは自分の作業コピーのそのファイルを変更 してコミットしました。Sally は自分が加えた変更をコミットする前に 作業コピーを更新しますが、そのとき衝突の報告を受けます:
$ svn update C sandwich.txt Updated to revision 2. $ ls -1 sandwich.txt sandwich.txt.mine sandwich.txt.r1 sandwich.txt.r2
このときSubversionは三つの一時ファイルが削除されるまで sandwich.txtのコミットを許可 しません。
$ svn commit --message "Add a few more things" svn: commit failed (details follow): svn: Aborting commit: '/home/sally/svn-work/sandwich.txt' remains in conflict
もし衝突があった場合は、三つのうちのどれかを する必要があります:
「手で」 衝突テキストをマージします。( ファイル中の衝突マーカを調べ編集することによって)。
作業ファイルに、一時ファイルのどれかを上書きします。
svn revert <filename>を 実行して、ローカルでしたすべての変更を捨てます。
ひとたび衝突を解消したら、svn resolvedを実行 してSubversionにそのことを伝えます。これは三つの一時ファイルを 削除して、Subversionはもうそのファイルが衝突の状態にあるとは 考えなくなります。 [5]
$ svn resolved sandwich.txt Resolved conflicted state of 'sandwich.txt'
手で衝突をマージするのは最初とても嫌なものですが、 少し練習すればバイクから降りるのと同じくらい簡単に できるようになります。
例をあげます。コミュニケーション不足により、あなたとあなたの同僚 である Sally の両方がsandwich.txtというファイルを同時に 編集したとします。Sallyは自分の変更をコミットし、それからあなた が作業コピーを更新しようとすると、衝突を受け取ります。それで sandwich.txt を編集しなくてはなりません。 最初にファイルを見てみます:
$ cat sandwich.txt Top piece of bread Mayonnaise Lettuce Tomato Provolone <<<<<<< .mine Salami Mortadella Prosciutto ======= Sauerkraut Grilled Chicken >>>>>>> .r2 Creole Mustard Bottom piece of bread
小なり記号の文字列、イコールサイン、そして 大なり記号の文字列を衝突マーカと呼びますが、これは実際の 衝突を起こしたデータの一部ではありません。一般的には次のコミットの 前に取り除く必要があります。最初の二つのマーカの間のテキストは衝突領域に あなた自身がした変更です:
<<<<<<< .mine Salami Mortadella Prosciutto =======
二番目と三番目の衝突マーカの間のテキストは、 Sallyのコミットからのテキストです:
======= Sauerkraut Grilled Chicken >>>>>>> .r2
通常、衝突マーカとSally の変更部分を単に削除するわけには いきません—そのようなことをするとSallyはsandwichを 受け取ったときにびっくりしますし、それは彼女が望んでいる ものではないでしょう。あなたは電話をかけるか、オフィスを またいで、Sallyに、二人の変更が衝突していることを説明 します。 [6] ひとたびコミットする変更内容について合意がとれたら、 ファイルを編集し衝突マーカを削除します。
Top piece of bread Mayonnaise Lettuce Tomato Provolone Salami Mortadella Prosciutto Creole Mustard Bottom piece of bread
これで、svn resolved を実行し 自分の変更をコミットする用意ができました:
$ svn resolved sandwich.txt $ svn commit -m "Go ahead and use my sandwich, discarding Sally's edits."
衝突ファイルを編集中に混乱したら、Subversionがあなたのために作った、 作業コピーにある三つの一時ファイルを見てどうするかを考えることができます —その中には更新前にあなたが修正したバージョンのファイルもありま す。この三つのファイルを確認するためにサードパーティー製の対話的な マージツールを使うこともできます。
衝突が起こり、自分のした変更を捨てようとするときにはSubversionが作った 一時ファイルのどれかを単に作業コピー上に上書きすることができます:
$ svn update C sandwich.txt Updated to revision 2. $ ls sandwich.* sandwich.txt sandwich.txt.mine sandwich.txt.r2 sandwich.txt.r1 $ cp sandwich.txt.r2 sandwich.txt $ svn resolved sandwich.txt
衝突が起こり、調査の結果、自分の変更を捨てて編集をやり直す場合は 単に変更を revert することができます:
$ svn revert sandwich.txt Reverted 'sandwich.txt' $ ls sandwich.* sandwich.txt
衝突ファイルを元に戻すときはsvn resolvedを 実行する必要はないことに注意してください。
これで自分の変更をコミットする用意ができました。 svn resolvedはこの章であつかう ほかのほとんどのコマンドとは違って、引数を必要とします。 どのような場合でも十分注意して、ファイル中の衝突を 解消したことが確かな場合だけsvn resolved を実行してください—一時ファイルが削除されてしまうと、 Subversion はファイルが衝突マーカを含んでいたとしてもコミット します。
やっとここまできました。編集は終了し、サーバからの変更を すべてマージしました。これで自分の変更をリポジトリにコミット する準備ができました。
svn commit コマンドは自分の変更点の すべてをリポジトリに送ります。変更をコミットするときには 変更点を説明するログメッセージを 与えてやる必要があります。ログメッセージは自分が作った 新しいリビジョンに付けられます。ログメッセージが簡単な 場合は--message (あるいは-m) オプションを使ってコマンドライン上で指定することができます:
$ svn commit --message "Corrected number of cheese slices." Sending sandwich.txt Transmitting file data . Committed revision 3.
しかし、既にログメッセージを作ってある場合は、 --file スイッチでファイル名称を指定することで、Subversionに そのファイルの内容を使うように指示できます:
$ svn commit --file logmsg Sending sandwich.txt Transmitting file data . Comitted revision 4.
--messageも--file も 指定しなかった場合は、Subversionは自動的にエディタを 起動し、(configの項の editor-cmdセクションを見てください) ログメッセージを作成しようとします。
もしコミットメッセージをエディタを起動して書いて いて、そのコミットを中止したいと思った場合には、単に保存せずに そのエディタを抜けてください。既にコミットメッセージを保存して しまった場合であれば、テキストを削除してもう一度保存してください。
$ svn commit Waiting for Emacs...Done Log message unchanged or not specified a)bort, c)ontinue, e)dit a $
リポジトリは、変更点の内容に意味があるかどうかはまったく 気にしません。Subversionはあなたが見ていないところで、同じファイルに 他の人が修正していないことだけを確認します。もし他の人がそのような 変更を していたら 、コミットはあなたの変更 したファイルのどれかが最新ではないというメッセージを出して失敗します:
$ svn commit --message "Add another rule" Sending rules.txt svn: commit failed (details follow): svn: Out of date: 'rules.txt' in transaction 'g'
このような場合は、svn updateを実行し その結果のマージや衝突を解消し、もう一度コミットしてください。
これでSubversionを使う基本的な作業サイクルを説明しました。 Subversionにはこのほかにもたくさんのリポジトリや作業コピーを管理するための 機能がありますが、この章でいままで説明してきたコマンドだけを使っても、 非常に多くのことができます。
以前指摘したように、リポジトリはタイムマシンのようなところが あります。いままでコミットされたすべての変更を記録し、 ファイルやディレクトリ、それに付随したメタデータの以前のバージョン を見ることによって履歴を調べることができます。一つのSubversion コマンドを使って、過去の任意の日付やリビジョン番号時のリポジトリ の状態をチェックアウト(あるいは既にある作業コピーの復元)すること ができます。しかし、過去に戻る のではなく、 単に過去がどうだったかをちょっと覗いて みたい こともよくあります。
リポジトリからの履歴データをあつかうためのコマンドがいくつか あります:
全般的な情報を表示します: リビジョンに付随した日付、修正者つきの ログメッセージとそれぞれのリビジョンでどのパスが変更されたかを表示します。
時間とともにあるファイルがどのように変更されてきたかを 表示します。
これは特定のリビジョン番号時点でのファイルを抽出し 画面に表示します。
任意の指定したリビジョンのファイルやディレクトリを一覧 表示します。
ファイルやディレクトリの履歴に関する情報を見たいときは svn logコマンドを使ってください。 svn log は、あるファイルやディレクトリを 誰が変更したかの記録を表示し、どのリビジョンでそれが変更され たか、そのリビジョンの時刻と日付、さらにもし存在すれば、 コミットに付随したログメッセージを表示します。
$ svn log ------------------------------------------------------------------------ r3 | sally | Mon, 15 Jul 2002 18:03:46 -0500 | 1 line Added include lines and corrected # of cheese slices. ------------------------------------------------------------------------ r2 | harry | Mon, 15 Jul 2002 17:47:57 -0500 | 1 line Added main() methods. ------------------------------------------------------------------------ r1 | sally | Mon, 15 Jul 2002 17:40:08 -0500 | 1 lines Initial import ------------------------------------------------------------------------
ログメッセージはデフォルトでは 時間と逆の順序で 表示されることに注意して ください。別の順序であるリビジョン範囲を見たい場合や、一つの リビジョンを見たいときには、--revision (-r)スイッチを渡します:
$ svn log --revision 5:19 # shows logs 5 through 19 in chronological order $ svn log -r 19:5 # shows logs 5 through 19 in reverse order $ svn log -r 8 # shows log for revision 8
一つのファイルやディレクトリのログ履歴を見ることもできます。 たとえば:
$ svn log foo.c … $ svn log http://foo.com/svn/trunk/code/foo.c …
これは作業ファイルが(またはURLが)変更されたリビジョン だけを表示します。
もしファイルやディレクトリについてもっと詳細な 情報がほしいときには、svn log は --verbose (-v) スイッチを とることもできます。Subversionはファイルやディレクトリの 移動やコピーもできるので、ファイルシステム中のパスの 変化を追えることは重要です。冗長モードでは、 svn log は出力リビジョンの中に 変更されたパス情報の一覧も含めます:
$ svn log -r 8 -v ------------------------------------------------------------------------ r8 | sally | 2002-07-14 08:15:29 -0500 | 1 line Changed paths: M /trunk/code/foo.c M /trunk/code/bar.h A /trunk/code/doc/README Frozzled the sub-space winch. ------------------------------------------------------------------------
svn log は --quiet (-q) スイッチも指定でき、これはログメッセージの本文を 表示しません。--verboseと組み合わせて指定すると 変更したファイルの名前だけを表示します。
svn diffは既に見てきました— unified diff形式でファイルの差分を表示するのでした。 リポジトリにコミットする前に作業コピーにされたローカル 修正点を表示するのに使えます。
実際にはsvn diffには異なる 三種類 の使い方があります:
ローカルの変更内容の確認
作業コピーとリポジトリの比較
リポジトリとリポジトリの比較
見てきたように、スイッチなしでsvn diff を 実行すると、作業コピーの内容と、.svn 領域にキャッシュされている「修正元リビジョン」 のコピー とを比較します:
$ svn diff Index: rules.txt =================================================================== --- rules.txt (revision 3) +++ rules.txt (working copy) @@ -1,4 +1,5 @@ Be kind to others Freedom = Responsibility Everything in moderation -Chew with your mouth open +Chew with your mouth closed +Listen when others are speaking $
--revision(-r) を一つ 指定すると、作業コピーはリポジトリの特定のリビジョンと比較 されます。
$ svn diff --revision 3 rules.txt Index: rules.txt =================================================================== --- rules.txt (revision 3) +++ rules.txt (working copy) @@ -1,4 +1,5 @@ Be kind to others Freedom = Responsibility Everything in moderation -Chew with your mouth open +Chew with your mouth closed +Listen when others are speaking $
--revision(-r)の引数と してリビジョン番号を二つ、コロンで区切って指定すると 二つのリビジョンが直接比較されます。
$ svn diff --revision 2:3 rules.txt Index: rules.txt =================================================================== --- rules.txt (revision 2) +++ rules.txt (revision 3) @@ -1,4 +1,4 @@ Be kind to others -Freedom = Chocolate Ice Cream +Freedom = Responsibility Everything in moderation Chew with your mouth open $
作業コピーとリポジトリのファイルを比較するためにだけ svn diff を利用できるのではなく、URL引数を与えることで作業コピー を用意しなくてもリポジトリ中のアイテムの間の差を調べることができます。 これは、ローカルマシンに作業コピーがないときに、ファイルの変更点を 知りたいような場合に非常に便利です:
$ svn diff --revision 4:5 http://svn.red-bean.com/repos/example/trunk/text/rules.txt … $
もし、以前のバージョンのファイルを見たいが、二つのファイル間の違い を見る必要はないような場合には、 svn catが使えます:
$ svn cat --revision 2 rules.txt Be kind to others Freedom = Chocolate Ice Cream Everything in moderation Chew with your mouth open $
直接ファイルに出力することもできます:
$ svn cat --revision 2 rules.txt > rules.txt.v2 $
もしかすると、どうして古いリビジョンに戻すためのファイルの更新に 単にsvn update --revisionを使わないのか、と 思うかも知れません。 svn catを使ったほうが 良い理由がいくつかあります。
まず、外部のdiff(多分、GUIかも知れないし、unified diff 形式の出力が 意味を持たないようなファイルなのかも知れません) プログラムによって二つのリビジョンのファイル間の差分を見たいかも知れません。 この場合、古いバージョンのコピーを取得する必要があり、その内容をファイル に出力したものと、作業コピー中のファイルの両方を外部diffプログラムに 渡さなくてはなりません。
しばしば、他のリビジョンとの間の差分をとるよりも、その古いバージョン のファイル全体を見るほうが簡単なことがあります。
svn list コマンドはローカルマシンに 実際にファイルをダウンロードすることなしに、リポジトリ にどんなディレクトリがあるかを表示します:
$ svn list http://svn.collab.net/repos/svn README branches/ clients/ tags/ trunk/
もっと詳しい表示がほしいときには --verbose (-v) フラグを 指定します。出力は以下のようになります:
$ svn list --verbose http://svn.collab.net/repos/svn 2755 harry 1331 Jul 28 02:07 README 2773 sally Jul 29 15:07 branches/ 2769 sally Jul 29 12:07 clients/ 2698 harry Jul 24 18:07 tags/ 2785 sally Jul 29 19:07 trunk/
それぞれの項目の意味は、左から順に、ファイルまたはディレクトリが最後に 更新されたリビジョン、修正した人、ファイルであればそのサイズ、日付、そして そのアイテムの名前になります。
いままで述べてきたすべてのコマンドに加えて svn update と svn checkout を、--revision 付きで実行することも できます。これは作業コピー全体を「過去のある時点」 に戻します。 [7]:
$ svn checkout --revision 1729 # Checks out a new working copy at r1729 … $ svn update --revision 1729 # Updates an existing working copy to r1729 …
この章でいままで述べてきたほど利用されるわけではありませんが、 以下のコマンドがときどき必要になります。
Subversionが作業コピー(や.svnに ある情報)を修正するときには、できるだけ安全にやろうと します。 作業コピーの内容を変更する前にSubversionはまず変更手順をログファイル に書きます。次に実際に変更を適用するためにログファイルの中の コマンドを実行していきます。最後にSubversionはログファイルを削除します。 プログラムの構成という意味では、これはジャーナル化ファイルシステムと よく似ています。 Subversionの操作が中断されると(プロセスが異常終了 したり、マシンがクラッシュしたり、といった場合)ログファイルは ディスクに残ります。ログファイルを再実行することでSubversionは 以前に開始された操作を完結することができ、作業コピーを正常で 一貫した状態に戻すことができます。
svn cleanupがやるのは、まさにこのことです。 作業コピーを探して、残ったログを実行し、プロセスのロックを 取り除きます。Subversionに作業コピーのどこかが「ロック」 されていると言われたときには、このコマンドを実行してください。 同様にsvn status はロックされているアイテム の隣に L を表示してそのことを示します:
$ svn status L somedir M somedir/foo.c $ svn cleanup $ svn status M somedir/foo.c
svn importコマンドはバージョン管理されていない複数のファイル をリポジトリにコピーし、必要に応じて直ちにディレクトリを作るための簡単 な方法です。
$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree file:///usr/local/svn/newrepos/some/project \
-m "Initial import"
Adding mytree/foo.c
Adding mytree/bar.c
Adding mytree/subdir
Adding mytree/subdir/quux.h
Committed revision 1.
上の例はディレクトリmytree の内容を リポジトリ中のsome/project ディレクトリの下に コピーしています:
$ svn list file:///usr/local/svn/newrepos/some/project bar.c foo.c subdir/
インポートが終わった後で、もとのツリーが作業コピーに変換さ れたわけではないのに注意してください。作業を始めるには、さらにこのツリー のための最初の作業コピーをsvn checkoutする 必要があります。
これで、Subversionクライアントのコマンドの大部分について 説明しました。触れなかったもののうちで重要なのはブランチと マージ (第4章 ブランチとマージ参照) 、そして属性です (属性の項参照)。 Subversionが持っているたくさんのコマンドの感じをつかむには 第9章 Subversion リファレンス をざっと見るのもいいかも知れません —自分の仕事がどれだけ楽になるか、わかるでしょう。
[3] もちろんリポジトリから完全に削除されてしまうわけではありません— 単に、リポジトリのHEADから削除されるだけです。削除したリビジョンより 前のリビジョンを指定してチェックアウトすれば(あるいは作業コピーを 更新すれば)削除前の状態に戻ることができます。
[4] Subversion は内部 diff エンジンを利用し、デフォルトでは unified diff 形式を生成します。もし別の形式の diff 出力がほしい場合には、 --diff-cmdで外部diffプログラムを指定し、 --extensionsスイッチを使ってフラグを渡して ください。たとえばファイルfoo.cのローカルな 変更点を context 出力形式で見たいが、空白の変更は無視したい場合、 svn diff --diff-cmd /usr/bin/diff --extensions '-bc' foo.c のように実行することができます。
[5] 一時的なファイルは常に自分で削除することができます が、Subversionがせっかくコマンドを用意しているのに本当にそうしたい のでしょうか? そうは思えませんが。
[6] そして、あなたが頼めば、彼らは電車で町の外まであなたを つれていってくれるかも知れませんよ。
[7] おわかりでしょうか? これが Subversionが タイムマシンだと言った意味です。
目次
ブランチ、タグ、マージはほとんどすべてのバージョン管理システムで 共通の概念です。もしあまりなじみがないのであれば、この章は良い とっかかりになるでしょう。既に詳しいのであれば、これらの概念 をSubversionがどのように実装しているかを知るのに興味深い章である ことがわかるでしょう。
ブランチ化は、バージョン管理の基本にあります。Subversionで自分の データをマージするときには、この機能はときどき必要となる機能です。 この章では、あなたがSubversionの基本コンセプトを既に理解している ことを前提とします(第2章 基本概念)。
あなたの仕事が、何かのハンドブックを扱う企業の一部署で、ドキュメントの 管理をすることだとします。ある日別の部署から同じハンドブックが必要 なのだが、ある部分を「ちょっとだけ」変えたものがほしい、ほんの少しだけ 業務形態に違いがあるから、といわれたとします。
この状況で、あなたはどうしなくてはならないでしょうか? 答えはあたりまえです: ドキュメントのコピーを作って二つのコピーを 別々に管理することにします。それぞれの部署が小さな変更を依頼して くるたび、一方を修正したり、もう一方を修正したりします。
両方のコピーに同じ修正を加えたいこともよくあります。たとえば 最初のコピーにスペルミスがあったとします。もう一方のコピーにも おそらく同じ間違いがあるでしょう。両方のドキュメントはほとんど同じ なのですから。二つはほんの少し違っているだけです。
これは ブランチの基本的な概念です— つまり、一つの開発の流れが、もう一方と独立して存在しているが、 もし過去にさかのぼれば、同じ履歴を共有している、という状況です。 ブランチは必ず、何かのコピーから始まり、枝分かれして、 自分自身の歴史を持っていくようになります(図 4.1. 「開発のブランチ」を参照してください)。
Subversionはファイルやディレクトリの平行したブランチを管理するのを 手助けするコマンドがあります。データをコピーしてブランチを作ったり、 どのように二つのコピーが関係しているかを記憶しておくことができます。 片方のブランチに対する修正をもう一方にも追加する作業を助けることも できます。最後に、作業コピーの一部だけ別のブランチにすることもできる ので、通常の作業で、別の作業のラインを「混ぜあわせる」こともできます。
これまでのところで、それぞれのコミットがどうやってリポジトリに完全に新しい ファイルシステムツリー(「リビジョン」と呼ばれます)を作るかを知っていると思います。 まだ知らないのであれば、戻ってリビジョンに関するリビジョンの項を 読んでください。
この章では、第2章と同じ例を使います。同僚のSally とあなたが paint とcalcという 二つのプロジェクトのあるリポジトリを共有していたことを思い出して ください。しかし、図 4.2. 「リポジトリレイアウトの開始」を見ると、個々の プロジェクトディレクトリはtrunkと branchesというサブディレクトリを含んでいること に注意してください。この理由はすぐに明らかになります。
以前と同様、あなたと Sally はそれぞれ 「calc」 プロジェクトの作業コピーを持っているとします。特に両者はそれぞれ /calc/trunkの作業コピーを持っています。 プロジェクトのすべてのファイルは /calcに ではなくこのサブディレクトリ中にありますが、それは皆が開発の「主系」 を /calc/trunkに置くことに決めたからです。
あなたはプロジェクトの大胆な再編成を任されたとします。 それには長い時間が必要で、プロジェクトの全ファイルに影響 を与えます。問題はあなたは Sally に干渉したくない ということにあります。彼女はまだあちこちにある小さなバグを 潰している最中だからです。彼女はプロジェクトの最終バージョンが (これは/calc/trunkにあるのですが) 利用可能だということに依存しています。もし、あなたが自分の 変更をちょっとづつコミットすれば、Sallyの作業を確実に中断させて しまうでしょう。
一つのやり方として、閉じこもってしまう方法があります:あなたと Sallyは1,2週間、情報を共有するのをやめます。つまり、自分の 作業コピー中の全ファイルに対する大手術を始めるのですが、 それが完了するまで、コミットも更新もしないという方法です。 しかしこれにはいろいろな問題があります。まず安全ではありません。 ほとんどの人は、作業コピーにヘンなことが起こらないように、 リポジトリに対してこまめに自分の作業を保存するのを好みます。 次に、まったく柔軟ではありません。もし、あなたがたが別の マシンで仕事をしているなら、(多分二つの別のマシンに /calc/trunk の作業コピーがあるのでしょう) 自分の変更を手であちこちにコピーしなくてはならないか、 一つのマシン上に作業全体をフルコピーするかになります。 同じようにして他の誰との間でも自分の進行中の変更部分を共有する ことは困難です。通常のソフトウェア開発で「一番よいやり方」 はあなたの作業の進行状況を他の人からも参照できるようにすることです。 もしあなたの中間的なコミットを誰も見ることができないとすると あなたは他の人からフィードバックしてもらうことができなくなって しまいます。 最終的に自分の変更作業が完了したとき、その変更を コミットするのは非常に困難であることに気づくでしょう。Sally(と 他のメンバー)はリポジトリに対してたくさんの別の変更を加えており、 それをあなたの作業コピーにマージするのは困難です— 何週間も孤立した作業の後にsvn updateを実行する ような場合には特にそうです。
もっとましなやり方はリポジトリに自分用のブランチ、あるいは 自分用の作業の別ラインを作ることです。これは他の人に干渉 せずに、自分の中途半端な作業をときどき保存できるように しますが、それでも同僚との間で、一部の情報については共有する ことができます。どうやったらこんなことができるかは後で説明 します。
ブランチの作成はとても簡単です — svn copy コマンドでリポジトリ中のプロジェクトをコピーするだけです。 Subversionでは一つのファイルをコピーするだけでなく、ディレクトリ 全体をコピーすることができます。今回は、/calc/trunk ディレクトリのコピーがほしいでしょう。新しいコピーはどこに置けば 良いのでしょう? 好きな場所に置けます — あとはプロジェクトのポリシーに よります。チームのポリシーは、リポジトリの/calc/branches 領域にブランチを作ることで、ブランチ名は 「my-calc-branch」 としましょう。この場合、/calc/trunkのコピーとして、 /calc/branches/my-calc-branchという新しいディレクトリ を作る必要があります。
コピーを作るには、二つの方法があります。面倒な方法を最初に 説明して、概念をはっきりさせます。最初にプロジェクトのルートディレクトリ である /calcを作業コピーにチェックアウトします:
$ svn checkout http://svn.example.com/repos/calc bigwc A bigwc/trunk/ A bigwc/trunk/Makefile A bigwc/trunk/integer.c A bigwc/trunk/button.c A bigwc/branches/ Checked out revision 340.
あとは、 svn copyコマンドに作業コピーパスを 二つ渡すだけでコピーを作れます:
$ cd bigwc $ svn copy trunk branches/my-calc-branch $ svn status A + branches/my-calc-branch
この場合、svn copy コマンドは再帰的に trunk作業ディレクトリの内容を 新しい作業ディレクトリbranches/my-calc-branch にコピーします。svn status コマンドで 確認できますが、これで新しいディレクトリはリポジトリへの追加として 予告されます。ただ、Aの後に、「+」サインが表示されるのに注意して ください。これは、追加予告が、新規のものではなく、何かの コピー であることを示しています。 変更をコミットすると、Subversionは、ネットワーク越しに 作業コピーデータの全体を再送信するのではなく、 /calc/trunkをコピーすることで リポジトリに /calc/branches/my-calc-branch を作ります:
$ svn commit -m "Creating a private branch of /calc/trunk." Adding branches/my-calc-branch Committed revision 341.
さて、ブランチを作るもっと簡単な方法は、先に説明すべきでした が: svn copy は 引数に直接URLを二つとることが できるということです。
$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/branches/my-calc-branch \
-m "Creating a private branch of /calc/trunk"
Committed revision 341.
この二つの方法には何の違いもありません。 両方とも新しいリビジョン341のディレクトリを作り、新しい ディレクトリは/calc/trunkのコピーに なります。図 4.3. 「新しいコピーのあるリポジトリ」にこれを示しました。 ただし二番目の方法は同時に コミットも発行します。 [8] 二番目のほうが楽です。リポジトリの 大きなコピーをチェックアウトしなくていいからです。 実際、この方法では、作業コピーそのものを用意する必要 すらありません。
これでプロジェクトにブランチを作ることができたので それを使った新しい作業コピーをチェックアウトできます:
$ svn checkout http://svn.example.com/repos/calc/branches/my-calc-branch A my-calc-branch/Makefile A my-calc-branch/integer.c A my-calc-branch/button.c Checked out revision 341.
この作業コピーについては何も特別なことはありません。単に 別のディレクトリにあるリポジトリのコピーだというだけです。 ただし、あなたが変更をコミットして、その後にSallyが更新しても その変更を見ることはありません。彼女の作業コピーは、 /calc/trunkからのものだからです。 (この章の作業コピーの切り替えの項を読んでください: svn switchコマンドはブランチの作業コピー を作る別の方法です。)
一週間が経過する間に、以下のコミットが起こったとしましょう:
/calc/branches/my-calc-branch/button.c, に変更を加え、リビジョン342を作った。
/calc/branches/my-calc-branch/integer.c, に変更を加え、リビジョン343を作った。
Sallyは /calc/trunk/integer.cに 修正を加え、リビジョン344を作った。
これで、図 4.4. 「あるファイルの履歴のブランチ化」に示すように integer.cに二つの独立した開発ラインができました:
integer.cのコピーに起きた変更履歴を 見ると面白いことがわかります:
$ pwd /home/user/my-calc-branch $ svn log --verbose integer.c ------------------------------------------------------------------------ r343 | user | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: M /calc/branches/my-calc-branch/integer.c * integer.c: frozzled the wazjub. ------------------------------------------------------------------------ r341 | user | 2002-11-03 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: A /calc/branches/my-calc-branch (from /calc/trunk:340) Creating a private branch of /calc/trunk. ------------------------------------------------------------------------ r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: changed a docstring. ------------------------------------------------------------------------ r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: adding this file to the project. ------------------------------------------------------------------------
Subversion はブランチにあるinteger.cの履歴を 時間を逆向きにたどり、これにはコピーされた地点も含まれることに注意 してください。それはブランチの生成を履歴上の一つのできごととして 表示しますが、それはinteger.cも /calc/trunk/全体がコピーされたときに暗黙に コピーされたものだからです。今度は Sally が自分のファイルコピー上 で同じコマンドを実行した結果を見てみましょう:
$ pwd /home/sally/calc $ svn log --verbose integer.c ------------------------------------------------------------------------ r344 | sally | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: fix a bunch of spelling errors. ------------------------------------------------------------------------ r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: changed a docstring. ------------------------------------------------------------------------ r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: adding this file to the project. ------------------------------------------------------------------------
Sallyは自分のリビジョン344の変更を見ることができますが、あなたが リビジョン343にやった変更は見ることができません。Subversionでは、 この二つのコミットはリポジトリの別の場所にある別のファイルに 対して起こります。しかし、Subversionは、二つのファイルが共通の履歴を 持っていることを示してもいます。リビジョン341 で起きたブランチコピーの前は両者は同じファイルを使っていました。 Sallyとあなたがどちらもリビジョン 303と98での変更を見ることができるのは そのためです。
この節での重要事項は二つです。
他のたくさんのバージョン管理システムとは違ってSubversionの ブランチはリポジトリ中の普通のファイルシステム のディレクトリ として存在します。特別な仕組みが あるわけではありません。これらのディレクトリは単にある特別な 履歴情報も保持しているというだけのことです。
Subversionは内部的にはブランチという概念を持ちません —それはただのコピーです。ディレクトリをコピーした とき、結果としてできたディレクトリが「ブランチ」であるのは、 あなたが そのような意味で見ることに したからです。そのディレクトリを別の意味合いにとらえたり 取り扱ったりすることもできますが、いずれにせよSubversionに とっては、コピーによって作成された普通のディレクトリの 一つにすぎません。
さて、あなたとSallyはプロジェクト上の平行したブランチで作業しています。 あなたは自分のプライベートなブランチで作業していて、Sally は trunk、あるいは、開発の主系の上で作業していると します。
たくさんの貢献者がいるようなプロジェクトでは、ほとんどの人たちは trunkのコピーを持っているのが普通です。trunk を壊してしまうかも知れない ような長い期間をかけての変更を加える必要がある場合は常に、標準的な手続き としてはまずプライベートなブランチを作り、すべての作業が完了するまで変更 点をそのブランチにコミットします。
そのようなやり方の利点としては、二人の作業はお互いに干渉しないところです。 欠点は二人の作業内容はすぐにひどく 違っていって しまうことです。「引きこもり」戦略の問題の一つは自分のブランチの作業が 完了するときに起こることを思い出してください。恐ろしくたくさんの衝突 なしに、あなたの変更をtrunkにマージするのはほとんど不可能でしょう。
そのかわりに、作業中に、あなたとSallyは変更を共有し続けるのが良い でしょう。どのような変更が共有する価値があるのかはあなたが決める ことです。Subversionを使うとブランチ間の選択的な「コピー」ができます。 そしてブランチ上での作業が完全に終ったら、ブランチ上にした変更点の 全体をtrunkに書き戻すことができます。
前の節で、あなたとSallyは別ブランチ上でinteger.c に変更を加えたと言いました。もしリビジョン344のSallyのログメッセージ を見れば、何かのスペルミスを直したことがわかるかも知れません。 この場合間違いなく、同じファイルのあなたのコピーもやはり同じスペルミスが あるはずです。このファイルに対する今後のあなたの修正はスペルミスのある 場所に影響を与えるかも知れず、自分のブランチをいつかマージするときに は衝突が起こってしまいます。そうなるくらいなら、あまりひどいことになる 前に、Sallyの修正をいま受け取ったほうが良いでしょう。
svn merge コマンドを使うときがやってきました。 このコマンドは、 svn diff に非常に近い 親戚だということがわかります。(このコマンドは第3章で説明しました)。 両方ともリポジトリ中の二つのオブジェクトを比較して、その差を 調べることができます。たとえばsvn diff に Sallyがリビジョン344でやった変更点を正確に表示することができます:
$ svn diff -r 343:344 http://svn.example.com/repos/calc/trunk
Index: integer.c
===================================================================
--- integer.c (revision 343)
+++ integer.c (revision 344)
@@ -147,7 +147,7 @@
case 6: sprintf(info->operating_system, "HPFS (OS/2 or NT)"); break;
case 7: sprintf(info->operating_system, "Macintosh"); break;
case 8: sprintf(info->operating_system, "Z-System"); break;
- case 9: sprintf(info->operating_system, "CPM"); break;
+ case 9: sprintf(info->operating_system, "CP/M"); break;
case 10: sprintf(info->operating_system, "TOPS-20"); break;
case 11: sprintf(info->operating_system, "NTFS (Windows NT)"); break;
case 12: sprintf(info->operating_system, "QDOS"); break;
@@ -164,7 +164,7 @@
low = (unsigned short) read_byte(gzfile); /* read LSB */
high = (unsigned short) read_byte(gzfile); /* read MSB */
high = high << 8; /* interpret MSB correctly */
- total = low + high; /* add them togethe for correct total */
+ total = low + high; /* add them together for correct total */
info->extra_header = (unsigned char *) my_malloc(total);
fread(info->extra_header, total, 1, gzfile);
@@ -241,7 +241,7 @@
Store the offset with ftell() ! */
if ((info->data_offset = ftell(gzfile))== -1) {
- printf("error: ftell() retturned -1.\n");
+ printf("error: ftell() returned -1.\n");
exit(1);
}
@@ -249,7 +249,7 @@
printf("I believe start of compressed data is %u\n", info->data_offset);
#endif
- /* Set postion eight bytes from the end of the file. */
+ /* Set position eight bytes from the end of the file. */
if (fseek(gzfile, -8, SEEK_END)) {
printf("error: fseek() returned non-zero\n");
svn merge コマンドもほとんど同じです。差分を 画面に表示するかわりに、それはローカルな 修正分として直接あなたの作業コピーに適用 します:
$ svn merge -r 343:344 http://svn.example.com/repos/calc/trunk U integer.c $ svn status M integer.c
svn merge の出力は、あなた用の integer.c のコピーがパッチされた 結果です。これでSallyの変更が含まれるようになりました— それはtrunkからあなたのプライベートなブランチの作業コピーに 「コピー」され、ローカルな修正の一部となりました。この修正を再検討し、 正しく動作することを確認するのはあなたの仕事です。
別のシナリオとして、そんなにうまくはいかず、 integer.c が衝突の状態になることもあります。 標準的な方法を使って衝突を解消するか(第3章を見てください)、 結局マージが悪いアイディアだったと思ったときには、あきらめて svn revert でローカルの変更を取り消すことも できます。
しかし、マージされた変更を確認して、svn commit をかけるのが普通です。これで、変更は自分のリポジトリブランチに マージされました。バージョン管理の言い方では、このようなブランチ間の 修正点のコピーを、普通porting による変更と いいます。
ローカルな修正をコミットするときには、あるブランチから別のブランチ に対して特定の変更を移したことを示すようなログメッセージになって いることを確認してください。たとえば:
$ svn commit -m "integer.c: ported r344 (spelling fixes) from trunk." Sending integer.c Transmitting file data . Committed revision 360.
次の節で見るように、これは参考にすべき 「最善の方法」 です。非常に重要です。
注意: svn diff と svn merge は とてもよく似たコンセプトを持っていますが、いろいろな場合で 別の構文になります。関連した第9章をよく読むか、svn help を使ってください。たとえばsvn mergeは作業コピー パスを引数とします。つまりツリーの変更を適用する場所の指定が必要 になります。この指定がなければ、よく利用される以下の操作のどちらか を実行しようとしているとみなされます:
現在の作業ディレクトリ中に、ディレクトリの変更点をマージ しようとしている。
現在の作業ディレクトリ中にある同じ名前のファイルに対して、 ある特定のファイルに起きた修正をマージしようとしている。
ディレクトリをマージしようとしている場合で、目的のパスを指定しなかった 場合、svn mergeは、上にあげた第一の場合であると みなし、現在のディレクトリ中のファイルに対して適用しようとします。 もし、ファイルをマージしようとしている場合で、そのファイル(または 同じ名前のファイル)が作業コピーディレクトリに存在している場合、 svn mergeは第二の場合であるとみなし、同じ名前の ローカルファイルに対して変更を適用しようとします。
上記以外の場所に適用したい場合には そのことを明示的に指定する必要があります。たとえば作業コピーの親ディレクトリ にいて、変更を受け取るための対象ディレクトリを指定する必要がある場合なら:
$ svn merge -r 343:344 http://svn.example.com/repos/calc/trunk my-calc-branch U my-calc-branch/integer.c
ここまでのところで svn merge の例を見てきましたが、さらにいくつかの例を あげます。マージが本当のところどのように機能するかについて何か混乱 した気になるのは何もあなただけではありません。多くのユーザは(特に バージョン管理システムになじみのない人にとっては) まず最初にコマンド の構文に戸惑い、さらにどのようにして、またいつその機能をつかえば良い かということにも戸惑います。しかし怖がることは何もありません。このコマンドは 実際にはあなたが思っているよりずっと単純なものです。svn merge がどのように動作するかを正確に知るためのとても簡単な方法があります。
混乱の一番の原因はこのコマンドの名前です。「 マージ(merge)」という言葉は、何か二つのブランチが統合されたり、 データ同士が、何か神秘的な方法で混ぜ合わされてしまったりするような表現 です。しかし、そんなことがおこるわけではありません。多分このコマンドに 対するもっとふさわしい名前はsvn diff-and-apply(差分 をとってから、それを適用する)かも知れません。実際、起こることは本当に それだけなのですから: つまり、二つのリポジトリのツリーが比較され、その 差分が、作業コピーに適用されるのです。
このコマンドは三つの引数をとります:
最初の状態を示すリポジトリ・ツリー ( 比較時の左側 などとよく言われます),
最終的な状態を示すリポジトリ・ツリー (often called the 比較時の右側 などとよく言われます),
上記二つの間の差分をローカルな変更として受け入れる作業コピー (マージの ターゲットなどとよく言われます).
この三つの引数が指定されると二つのツリーが比較され、結果の 差分がターゲットの作業コピーに対して、ローカルな修正点の形で反映されま す。この結果はあなた自身が手作業でファイルを編集したり、svn add や svn deleteコマンドをいろいろと実行 したのとなんら変わるところはありません。結果の修正内容が満足のいくもの であれば、それをコミットすることができます。気に入らなければ、単に svn revertを実行しさえすればすべての変更は元に戻り ます。
svn merge の構文は必要な三つの引数をある程度 柔軟に指定できるようになっています。以下がその例です:
$ svn merge http://svn.example.com/repos/branch1@150 \
http://svn.example.com/repos/branch2@212 \
my-working-copy
$ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy
$ svn merge -r 100:200 http://svn.example.com/repos/trunk
最初の構文は三つのすべての引数を明示的に指定するもので、ツリーについては それぞれ URL@REV の形で指定し、ターゲットの作業コピー はその名前で示します。二番目の構文は、同じ URL 上にある異なるリビジョンを 比較する場合の略記法です。最後の構文は作業コピーを省略した場合の例です; デフォルトではカレントディレクトリが指定される決まりです。
変更のマージは非常に単純なことに思えますが実際には厄介な ものです。問題は、もし一つのブランチを別のブランチに対して 変更点を繰り返しマージすると、間違って同じ変更を 二度やってしまうかも知れないということです。 こういうことが起こっても、問題が起こらないこともあります。 ファイルをパッチするとき、Subversion はファイルが既に変更されている 場合にはそれに気がついて、何もしません。しかし、既に存在している 変更が何らかの方法で修正されていた場合、衝突が起こります。
理想的には、バージョン管理システムはブランチに対して変更点の重複 した適用を回避すべきです。ブランチが既に受け取った変更点を自動的に 記憶し、その一覧を表示できるようにすべきです。そしてバージョン管理システム は自動マージを支援するために可能な限りこの情報を利用すべきです。
残念ながら Subversion はそのようなシステムではありません。CVS と同様 Subversion はまだマージ操作に関するどのような情報も記録しません。 ローカルな修正をコミットしても、リポジトリはそれがsvn merge を実行したものによるのか、あるいは単に手でファイルを修正した ものによるのか区別できません。
これはユーザにとって何を意味するのでしょうか? それはSubversionにこの 機能がいつか実装されるまではマージの情報を自分で記録しておく必要が あるということです。一番良い場所はコミットログメッセージ中でしょう。 以前の例で説明したように、あなたのブランチにマージした特定のリビジョン 番号(あるいはリビジョン番号の範囲)をログメッセージ中で示しておくこと をお勧めします。あとでsvn logを実行してあなたの ブランチがどの変更点を既に含んでいるかを知ることができます。これで svn mergeコマンドを繰り返し実行する際に以前に 取り込んだ変更点を再び取り込むことがないように注意することが できます。
次の節ではこの技法の例を実際にお見せします。
マージは作業コピーを変更するだけなので、それほど危険な操作では ありません。マージに失敗しても、単にsvn revertを実行すれば元に戻せるのでもう一度 やり直すことができます。
しかし作業コピーには既にローカルな修正が加えられていることもあります。 マージによって適用された修正は既に加えていた修正と混じってしまう のでこの場合にはsvn revertは使えません。 この二つの修正の組を分離することは不可能です。
このような場合には、実際にマージする前に、マージしたとしたらどうなるか を調べておくべきです。このための一つの簡単な方法としてはsvn mergeに渡そうとしているのと同じ引数でsvn diff を実行する方法があります。それは既にマージの最初の例で見たものです。 もう一つの方法は、マージコマンドに対して --dry-run オプションを渡す方法です:
$ svn merge --dry-run -r 343:344 http://svn.example.com/repos/calc/trunk U integer.c $ svn status # nothing printed, working copy is still unchanged.
--dry-runオプションは、実際には作業コピーに対してローカルな 修正を適用しません。実際のマージで表示されるであろう 状態コードを表示するだけです。これはsvn diffではあまりにも詳細 な内容が表示されてしまうような場合に、潜在的なマージの概要を確認するための 「高度な」方法です。
svn update コマンドと同様 svn merge は変更を作業コピーに対して行うので 衝突を起こすこともあります。しかし svn mergeによっておきた衝突については様子が 違うこともあり、以下ではこの違いについて説明します。
まず、作業コピーにはローカルな修正が加えられていないとします。 特定のリビジョンに対してsvn updateを実行すると サーバから送られてきた変更点は作業コピーに対して常に 「きれいに 」適用されます。サーバは二つのツリーを比較することで差分を 生成します: 作業コピーの仮想的なスナップショットと、適用しようとして いるリビジョンとの間の差分です。前者は作業コピーと全く同じものなので この差分が作業コピーをきれいに後者に変換することは保証されています。
しかしsvn mergeの場合はそのような保証はなく、 結果はもっと混沌としたものになる可能性もあります: ユーザは全く 任意の二つのツリーの比較をするようサーバに 指示することもでき、作業コピーとは全く無関係なものであるかも知れないのです!。 これは人間の側の操作ミスが起こる潜在的な可能性が大きいことを 意味します。場合によってはユーザは間違った二つのツリーを比較し、 きれいに適用できないような差分を作ってしまうかも知れません。 svn merge はできる限りこの差分を適用しようと しますが、ある部分は不可能かも知れません。ちょうど Unix の patchコマンドが「適用できなかったハンク 」について文句を言ってくることがあるのと同じように svn mergeは「処理を飛ばしたファイル」 について文句を言うかも知れません:
$ svn merge -r 1288:1351 http://svn.example.com/repos/branch U foo.c U bar.c Skipped missing target: 'baz.c' U glub.c C glorb.h $
この例では比較対象となる二つのブランチのスナップショットの両方に baz.cが存在していたため、生成された差分もその ファイルの内容を変更しようとしますが、作業コピー中には対応するファイル が存在しなかったような場合だと考えられます。いずれにせよ 「スキップ」のメッセージはユーザが間違った二つのツリーを 比較してしまったことを意味することがほとんどです。ユーザ側のエラー を示す典型的な状況です。こうなった場合でも(svn revert --recursiveを使って)、マージによって実行されたすべての変更 点を再帰的に元に戻し、バージョン化されていないファイルやディレクトリ が残っている場合にはそれらも削除し、正しい引数でsvn mergeを再実行するのは難しいことではありません。
前の例ではglorb.hに衝突が起きたことにも注意 してください。今回の場合作業コピーに対してローカルな修正がされていない ことはすでに述べました: ではなぜ衝突が起きるのでしょうか? この場合 でもやはりユーザはsvn mergeで古い差分を作ってから 作業コピーに適用することができるので、ローカルな修正がなかったと しても、その差分が作業コピーに対してきれいに適用できないような変更を 含んでしまうことはありうるのです。
その他svn update と svn mergeの 小さな違いとしては衝突がおきたときにできるテキストファイルの名前です。 衝突の解消(他の人の変更点のマージ)の項で見たように、update の場合には filename.mine, filename.rOLDREV, filename.rNEWREVという名前のファイルができます。 これにたいしてsvn mergeの場合には filename.working, filename.left, filename.rightという名前になります。 この場合「left」 と 「right」 は、それぞれの ファイルが比較した二つのツリーのどちら側に由来するものかを示しています。 いずれにせよファイル名称の違いは、衝突が update コマンドの結果である のか merge コマンドの結果であるかを区別する助けになるでしょう。
Subversion 開発者と会話するとき系統 (ancestry)という言葉を非常によく耳にするでしょう。 この言葉はリポジトリ中の二つのオブジェクト間の関係を記述するた めに用いられるものです:もし両者が互いに関係している場合、ある オブジェクトはもう一方の祖先(ancestor)といわれます。
例えば、リビジョン100をコミットし、それが foo.cというファイルへの変更を含んでいると します。するとfoo.c@99は foo.c@100の「祖先」ということ になります。一方リビジョン 101 でfoo.cを 削除するコミットがあり、リビジョン102 で同じ名前の新しいファイ ルを追加したとしましょう。この場合 foo.c@99とfoo.c@102 は関係しているように見えます(なぜなら同じファイル名なのですか ら)が、実際にはリポジトリ中ではまったく別のオブジェクトです。 両者は履歴、あるいは「系統」を共有していないからで す。
ここでこんな話をするのは、svn diffと svn mergeの間の重要な違いを指摘したいからです。前者 は系統を無視しますが、後者は系統を非常に慎重に考慮します。例えば svn diffでリビジョン99 と102 の foo.cを比較した場合、行単位の差分を見ることになる でしょう; diff コマンドは二つのファイル名を無条件に比較するからです。 しかしsvn mergeを使っていまと同じ二つのオブジェクトを比較す るとそれらが無関係であることを検知し古いファイルをいったん削除し、それ から新しいファイルを追加しようとするでしょう; 出力は追加のあとに削除した ことを示すものとなるでしょう:
D foo.c A foo.c
ほとんどのマージはお互いに系統上関係したツリーを比較する ので、svn mergeはデフォルトで上記のような動 作になります。しかし、二つの無関係なツリーを比較するために mergeコマンドを使いたいと思うこともあるかも知れません。 たとえばあるソフトウェアプロジェクトの、異なる 二つのベンダーリリースを表すようなソースコードツリーをインポー トするかも知れません(ベンダーブランチの項参照)。 この二つのツリーをsvn mergeで比較すると最初 のツリー全体がいったん削除され、次いで後のツリー全体が追加され たように見えるでしょう!
このような場合、svn mergeは単にファイ ル名ベースの比較のみを実行し、ファイルやディレクトリの系統上の 関係を無視したいと考えるでしょう。こんなときはマージコマンドに --ignore-ancestryオプションをつければ ちょうどsvn diffと同じように振舞うようにな ります。(逆にsvn diffコマンドに --notice-ancestryオプションをつけると svn diffコマンドはmergeコマンドと同じよう に振舞うことになります。)。
ブランチの作り方とsvn mergeにはいくつもの異なったやり方があり、この節では あなたが出くわしそうな一番よくあるパターンについて説明します。
いま、考えてきた例を完結させるため、少し時間が経過したとします。何日か 経過し、たくさんの変更がtrunkにもあなたのプライベートなブランチにも 起こったとましす。そしてあなたはプライベートなブランチ上での作業を 終えたとしましょう; 機能追加、またはバグフィッックスが完了し、他の 人がその部分を使えるようにするために、あなたのブランチ上の変更点の すべてを trunk にマージしたいとします。
さて、このような状況では、どのようにしてsvn merge を使えば良いのでしょうか? このコマンドは二つのツリーを比較し、その差分を 作業コピーに適用するものであったことを思い出してください。変更点 を受け取るためには、あなたはtrunkの作業コピーを手に入れる必要があります。 ここではあなたは(完全に更新された)もともとの作業コピーをまだ持っているか、 /calc/trunkの新しい作業コピーをチェックアウトしたもの と仮定します。
しかし、どのツリーとどのツリーを比較すれば良いのでしょうか? ちょっと考えると、 その答えは明らかに思えます: 単にtrunkの最新のツリーと、あなたのブランチの最新の ツリーです。しかし、気をつけてください — この仮定は間違い です。そしてこの間違いに、たいていの初心者はやられてしまいます! svn mergeはsvn diffのように働くので 最後のトランクとブランチのツリーの比較は単にあなたが自分の ツリーに対して行った変更点のみを示すものではない のがわかります。 そのような比較は、非常にたくさんの変更を表示するでしょう: それは、あなたのブランチに対する追加点だけを表示するのではなく、 あなたのブランチでは決して起こらなかった、trunk上の変更点の 取り消しも表示してしまうことでしょう。
あなたのブランチ上に起きた変更のみをあらわすには、あなたのブランチの 初期状態と、最終的な状態を比較する必要があります。 svn logコマンドをあなたのブランチ上で使えば、 そのブランチはリビジョン341で作られたことがわかります。そして、ブランチ の最終的な状態は、単に、HEAD リビジョンを指定すればわかります。 これはブランチディレクトリのリビジョン 341 と HEAD を比較しその違いを トランクの作業コピーに適用したいと考えていることを意味します。
ブランチが作成されたリビジョンを見つけるうまい方法は (ブランチの「ベース」リビジョンのことですが)svn logで--stop-on-copyオプションを利用 することです。log サブコマンドは通常ブランチに対するすべての変更を表示 し、それはブランチが作成されたコピーよりも前にさかのぼります。このた め通常トランクの履歴も表示されてしまいます。 --stop-on-copyは、svn logがターゲットのコピーあ るいは名称変更の個所を見つけると直ちにログの出力を中止します。
それで現在の例で言うと、
$ svn log --verbose --stop-on-copy \
http://svn.example.com/repos/calc/branches/my-calc-branch
…
------------------------------------------------------------------------
r341 | user | 2002-11-03 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines
Changed paths:
A /calc/branches/my-calc-branch (from /calc/trunk:340)
$
期待したとおり、このコマンドによって表示される最後のリビ ジョンはコピーによってmy-calc-branchが作成された リビジョンになります。
結局、最終的なマージ処理は以下のようになります:
$ cd calc/trunk $ svn update At revision 405. $ svn merge -r 341:405 http://svn.example.com/repos/calc/branches/my-calc-branch U integer.c U button.c U Makefile $ svn status M integer.c M button.c M Makefile # ...examine the diffs, compile, test, etc... $ svn commit -m "Merged my-calc-branch changes r341:405 into the trunk." Sending integer.c Sending button.c Sending Makefile Transmitting file data ... Committed revision 406.
ここでもトランクにマージされた変更範囲についてコミットログメッセージは 非常に具体的に触れていることに注意してください。このことを常に憶えて おいてください。後になって必要になる非常に重要な情報だからです。
たとえば、独自の機能拡張やバグフィックスなどのために、もう一週間自分の ブランチ上で作業を続けることにしたとしましょう。リポジトリの HEAD リビジョンはいま 480 となり、あなたは自分のプライベートなブランチから トランクに対するマージの用意ができています。しかしマージの一番うまいやり方の項で議論したように既に以前マージした 変更を再びマージしたくはありません; 最後にマージしてからブランチ上に 「新しく起きた」変更だけをマージしたいのです。問題はどうやって 新しい部分を見つけるかです。
最初のステップはトランク上でsvn logを 実行し最後にブランチからマージしたときのログメッセージを見ます:
$ cd calc/trunk $ svn log … ------------------------------------------------------------------------ r406 | user | 2004-02-08 11:17:26 -0600 (Sun, 08 Feb 2004) | 1 line Merged my-calc-branch changes r341:405 into the trunk. ------------------------------------------------------------------------ …
ああ、なるほど。341 と 405 の間のリビジョンに起きたすべてのブランチ上での 変更はリビジョン 406 として既にトランクにマージされているので、それ以降にブランチ上で起きた 変更のみをマージすれば良いことがわかります— つまり、リビジョン 406 から HEAD までです。
$ cd calc/trunk $ svn update At revision 480. # 現在の HEAD が 480 であることがわかったので、以下のようにマージすれ # ばよいことになります $ svn merge -r 406:480 http://svn.example.com/repos/calc/branches/my-calc-branch U integer.c U button.c U Makefile $ svn commit -m "Merged my-calc-branch changes r406:480 into the trunk." Sending integer.c Sending button.c Sending Makefile Transmitting file data ... Committed revision 481.
これでトランクはブランチに起きた変更の第二波全体を含むことになりました。 この時点でブランチを削除する(これについては後で議論します)ことも、 ブランチ上で引き続き作業し、以降のマージについて上記の手続きを繰り返す こともできます。
svn mergeのほかのよくある使い方としては、既にコミット した変更をもとに戻したい場合です。/calc/trunkの 作業コピー上で作業中に、integer.cを修正したリビジョン 303 は完全に間違いであったことを発見したとしましょう。それはコミットすべき ではありませんでした。作業コピーの変更を「取り消す」のにsvn mergeを使い、その後リポジトリに対してローカルな変更を コミットすることができます。やらなくてはならないことは反対向きの 差分を指定することだけです:
$ svn merge -r 303:302 http://svn.example.com/repos/calc/trunk U integer.c $ svn status M integer.c $ svn diff … # verify that the change is removed … $ svn commit -m "Undoing change committed in r303." Sending integer.c Transmitting file data . Committed revision 350.
リポジトリリビジョンについてのもう一つの考え方は、それを 特定の変更のあつまりと考えることです(いくつかのバージョン管理 システムでは、これを、changesetsと 呼んでいます)。-r スイッチを使って svn merge を呼び出すことで、あるチェンジ セットを適用するか、もしくはある範囲のチェンジセット全部を作業コピーに 適用することができます。私たちの場合だとsvn merge を使ってチェンジセット#303を作業コピーに反対向きに 適用します。
このような変更の取り消しは、普通のsvn merge の操作にすぎないので、作業コピーが望む状態になったかどうかは svn status とsvn diff を 使うことができ、その後svn commit でリポジトリに 最終的なバージョンを送ることができるのだ、ということを押さえておいて ください。コミット後はこの特別なチェンジセットはもはや HEADリビジョンには反映されません。
こう思うかも知れません: とすると、それは「取り消し」じゃない じゃないか。変更はまだリビジョン303に存在しているのでは、と。 もし誰かがcalc プロジェクトのリビジョン303 と 349の間のバージョンをチェックアウトしたとしたら、間違った 変更を受け取るのではないか、違うか、と。
おっしゃる通り。私たちが、変更の「取り消し」について語るとき、 本当はHEADから取り除くことを言っています。もともとの変更は リポジトリの履歴に依然として残っています。ほとんどの状況では これで十分です。とにかくほとんどの人たちはプロジェクトの HEADを追いかける ことだけに興味があるからです。しかし、コミットに関するすべての 情報を削除したいという例外的な状況もあるでしょう。(多分、誰かが 極秘のドキュメントをコミットしてしまった、など) これはそんなに やさしいことではありません。Subversionは意図的に決して情報が 失われないように設計されているからです。履歴からのリビジョンの 削除は、連鎖的な影響を与え、すべての後続リビジョンと、多分 すべての作業コピーに混乱を起こします。 [10]
バージョン管理システムの偉大なところは情報が決して失われないという ところです。ファイルやディレクトリを削除した場合でもそれは HEAD リビジョン から消えただけであり、以前のリビジョン中には依然として存在し続けます。 新規ユーザからの一番よくある質問の一つは: 「どうやって古いファイルや ディレクトリを戻せば良いのですか?」 というものです。
最初のステップはあなたが復活させようとしているものは正確には何であるかをはっきりさせることです。 うまいたとえがあります: リポジトリ中のそれぞれのオブジェクトは 一種の二次元座標系の中に存在していると考えることができます。第一の軸 は特定のリビジョンツリーで第二の軸はそのツリー中のパスです。すると ファイルあるいはディレクトリのそれぞれのバージョンは特定の座標の組で 定義することができます。
Subversion は CVS のような Attic ディレクトリを持ちません [11] ので復活させたいと思う正確な座標ペアを見つける のにsvn logを使わなくてはなりません。うまいやり方 としては削除されたアイテムがあったディレクトリでsvn log --verbose を実行することです。--verboseオプションはそれぞれの リビジョン中でのすべての変更アイテムのリストを表示します; 必要なことは ファイルやディレクトリをどのリビジョンで削除したかを調べることです。 これはビジュアルにやることもできますし、ログ出力を解析する別のツールを 使うこともできます(grepコマンドを通じて、あるいは エディタでのインクリメンタル検索機能を使う形かも知れません)。
$ cd parent-dir $ svn log --verbose … ------------------------------------------------------------------------ r808 | joe | 2003-12-26 14:29:40 -0600 (Fri, 26 Dec 2003) | 3 lines Changed paths: D /calc/trunk/real.c M /calc/trunk/integer.c Added fast fourier transform functions to integer.c. Removed real.c because code now in double.c. …
例では削除してしまったファイル real.cを探している とします。親ディレクトリのログを見ることでこのファイルはリビジョン 808 で削除されたことを突き止めました。それでこのファイルが存在していた 最後のバージョンはそのリビジョンの直前であることになります。結論: リビジョン 807 から /calc/trunk/real.cのパスを 復活させれば良いことになります。
これが面倒な部分です — つまりファイルを見つける作業です。 これで復元したいものが何であるか突き止めました。後は二つの方法が あります。
最初のやり方はリビジョン 808 を 「逆向きに」 適用するために svn mergeを利用することです。(変更の取り消し の仕方については既に議論しました。 変更の取り消しの項 を参照してください。) これはローカルな変更としてreal.c をもう一度追加する効果があります。ファイルは追加予告され、コミット後には HEAD 上に再び存在するようになります。
しかしこの例は多分最善の方法ではないでしょう。リビジョン 808 の逆向き の適用はreal.cの追加予告だけではなく、ログメッセージ が示すように、今回必要としないinteger.cへの変更点 も取り消してしまいます。確かにリビジョン 808 を逆向きにマージした後 integer.cのローカル変更を svn revert することもできますが、この技法はファイルが多くなるとうまくスケールしません。 リビジョン 808 で 90 個のファイルが変更されていたとしたらどうなりますか?
もっと洗練された二番目の方法はsvn mergeは利用せず、 そのかわりにsvn copyコマンドを使います。正確な リビジョンとパスの 「座標の組」 を指定してリポジトリから自分の作業コピーに 単にコピーするだけです:
$ svn copy --revision 807 \
http://svn.example.com/repos/calc/trunk/real.c ./real.c
$ svn status
A + real.c
$ svn commit -m "Resurrected real.c from revision 807, /calc/trunk/real.c."
Adding real.c
Transmitting file data .
Committed revision 1390.
ステータス表示中のプラス記号はそのアイテムは単に追加予告されただけではなく 「履歴と共に」 追加予告されたことを示しています。Subversion はどこからそれが コピーされたかを記憶しています。今後このファイル上に svn logを実行するとファイルの復活についてと、リビジョン 807 以前のすべての履歴をたどることができます。言いかえるとこの新しい real.cは本当に新しいわけではありません;それは 削除されたもとのファイルの直接の子孫になっています。
私たちの例はファイルの復活でしたが、同じ技法が削除されたディレクトリ の復活についても利用できることに注意してください。
バージョン管理システムはソフトウェア開発で一番よく使われるので、ここで 何かの開発チームによって利用される典型的なブランチ化/マージのパターン をちょっと見てみましょう。Subversion をソフトウェア開発に使うのでなけ ればこの節は読み飛ばしてもかまいません。ソフトウェア開発にバージョン 管理システムを使うのが初めてなのであれば、よく読んでください。 ここでのパターンは経験を積んだ多くの開発者によって最良の方法だと考えられて いるからです。このようなやり方は Subversion に限った話ではありません; どのようなバージョン管理システムにでも応用できる考え方です。また同時に 他のシステムのユーザに対しては Subversion ではどんな言葉を使ってこの標 準的なやり方を表現するかを理解する手がかりになるでしょう。
ほとんどのソフトウェアは典型的な作業サイクルがあります: コーディング、 テスト、リリース、この繰り返しです。このようなやり方には二つの問題が あります。まず開発者は新しい機能を追加し続けなくてはならない一方で 品質保証チームはそのソフトウェアの安定版だと考えられるバージョンを テストするのに時間をついやさなくてはなりません。テスト途中だからといっ て新しい機能追加を中断することはできません。次に開発チームはほとんど の場合、すでにリリースされた古いバージョンのソフトウェアを保守しなくては なりません; もし最新のコードにバグが見つかった場合、すでにリリースしている バージョンにも同じバグが潜んでいる可能性は高く、利用者は次のリリースを 待たずにこのバグを修正して欲しいと望んでいることでしょう。
バージョン管理システムの出番です。典型的なやり方は以下のようなものです:
開発者は新規開発部分をトランクにコミットします。 日々の変更点は/trunkにコミットされます: 新しい機 バグ修正、その他もろもろです。
トランクの内容は「リリース」ブランチにコピーされます。 あるチームが、そのソフトウェアがリリースできる状態になったと考えた 時点で(つまり、1.0 のリリースのような場合)、/trunk は/branches/1.0のような名前でコピーされる ことになります。
これと並行して、他のチームが作業を続けます。 あるチームがリリースブランチの内容を徹底的なテストを開始する一方で 他のチームは新規開発分(つまり、バージョン 2.0 に向けた作業)を /trunk上で継続して行います。どちらかの場所で バグが見つかれば、必要に応じてその修正がお互いの間を行き来します。 しかしこの作業もやがては終わります。このブランチはリリース直前の 最終的なテストに向けて「凍結」されます。
ブランチはタグづけされ、リリースされます。 テストが完了したら/branches/1.0は /tags/1.0.0にコピーされ、これが参照用のスナップ ショットになります。このタグの内容はパッケージ化され、利用者に対して リリースされます。
ブランチはその後も保守されます。 バージョン 2.0に向けた作業が/trunk上で進む一方、 バグ修正個所については/trunkから /branches/1.0に引き続き反映されます。 十分なバグ修正が反映されたら、管理者は 1.0.1 をリリースする決断をする かも知れません: /branches/1.0は /tags/1.0.1にコピーされ、このタグはパッケージ 化されてからリリースされます。
このような作業の流れを繰り返すことでソフトウェアは安定していきます: 2.0 の開発が完了したら新しい 2.0 のリリースブランチが作られ、テスト され、タグがつけられ、最終的にリリースされることになります。 何年かしてリポジトリは「保守対象」の状態になったいくつかの リリースブランチと最終的にリリースされたバージョンを示すタグの集まり になるでしょう。
開発用ブランチ(feature branch) はこの章での 例として中心的な役割を果たしてきたようなタイプのブランチで、その ブランチ上であなたが作業をするのと同時に並行してSally は /trunk上で作業を継続することができるような ものでした。それは一時的なブランチで、安定している /trunkに影響を与えることなく複雑な変更をする ためのものです。リリース用ブランチ(これはずっと保守しつづけなければ ならないかも知れません)とは違って、開発用ブランチは作成されたあと ある程度の期間利用され、変更部分がトランクに反映された後で完全に 削除されてしまいます。利用されるのは、ある決まった期間の中だけです。
プロジェクトの考え方によって、開発用ブランチをいつ作るのが適切である かにはかなりの幅があります。プロジェクトによっては開発用ブランチを 全く使いません: /trunkに対するコミットは 全員に許されています。このやり方の長所はその単純さです—誰も ブランチ化やマージについて理解する必要がありません。欠点はこの方法 だとトランクのソースコードが不安定になったりまったく利用できなく なったりしやすいことです。逆に別のプロジェクトではブランチを極端な 形で使います: どんな変更もトランクに対して直接 コミットすることは認められていません。まったくささいな変更に対しても 短い生存期間をもつブランチを作り、それを注意深く検討し、 トランクに反映させます。それから、そのブランチを削除します。この方法は トランクを常に非常に安定して利用できる状態に置くことができますが それには無視できない処理効率の低下が伴います。
ほとんどのプロジェクトではこの中間のやり方をとります。普通は /trunkは常にコンパイル可能な状態であり、 一度フィックスしたバグが元に戻っていないことを保証するためのテスト もクリアした状態にあることを要求します。ある変更をするのに プログラムを不安定にするようなコミットを何度も必要とする場合に だけ開発ブランチが作られます。基本的な方針としては次のようなことを 考えてみることです: もし開発者が孤立した状態で何日も作業した後で 一度に変更点全体をコミットしたとしたら(/trunkが 不安定にならないようにするためにそうするのでしょうが)、その変更 内容が正しいかどうかを検討するには大きすぎませんか? もし答えが 「イエス」なら、その変更は開発用ブランチでやるべきで しょう。開発者はブランチに対して変更点を少しずつコミットするので 他の人たちはそれぞれの部分について簡単に内容を検証することができ ます。
最後に、開発用ブランチでの作業が進むにつれて、どうやってそれを トランクの内容に「同期」させるのがよいかについて 考えてみます。すでに注意したようにブランチ上で数週間あるいは数ヶ月 ものあいだ作業しつづけるのには大きなリスクが伴います; トランクへの 変更はその間次々と発生し、ついには二つの開発ラインはあまりにもかけ離れて しまい、ブランチの変更内容をトランクにマージによって戻すのは全く 非現実的な話になってしまうかも知れないのです。
この状況を避けるためにはトランクの内容を定期的にブランチにマージする ことです。次のようなルールを決めておきましょう: 一週間に一度、 先週トランク上におきた変更をブランチにマージすること。これは注意して 実行する必要があります; マージは手作業で実行し、繰り返してマージする のを避ける必要があります(これについては 手でマージする方法の項で説明しました)。ログメッセージ を書く時には注意して、どの範囲のリビジョンが既にマージされているか を正確に控えておきましょう(これはブランチ全体を別の場所にマージすることの項 でやってみせました)。大変な作業に思えるかも知れませんが、実際には 非常に簡単なことです。
あるところまで作業が進んだら、開発用ブランチの内容をトランクに 「同期」させるためのマージの準備が整います。これには まず、最新のトランクの変更部分をブランチに取り込む最後のマージ 処理を実行することで始めます。この処理の後では、ブランチ上の最後のリビ ジョンとトランク上の最後のリビジョンは、ブランチでの変更部分を のぞけば、完全に同じ状態になります。このような特定の状況下では ブランチとトランクの内容を比較することによってマージすることが できるはずです:
$ cd trunk-working-copy
$ svn update
At revision 1910.
$ svn merge http://svn.example.com/repos/calc/trunk@1910 \
http://svn.example.com/repos/calc/branches/mybranch@1910
U real.c
U integer.c
A newdirectory
A newdirectory/newfile
…
トランクのHEADリビジョンとブランチの HEADリビジョンを比較することで、ブランチにだけ 加えた修正点を含む差分を作ることができます; 両方の開発ラインとも トランクに起きた修正についてはすでに取り込んでいるからです。
このような作業パターンは、自分のブランチに対して 毎週トランクを同期させる処理は、作業コピーに対してsvn update を実行するのとよく似ていて、最後のマージ処理に ついては作業コピーからsvn commitを実行するのに よく似ていると考えることができます。結局、作業コピーと、ちょっと作った プライベートなブランチと、他に何が違うと言うのでしょう? 作業コピー とは、一度に一つの変更しか保存できないような単なるブランチにすぎ ません。
svn switch コマンドは存在している作業コピーを 別のブランチに変換します。このコマンドはブランチで作業するのに 常に必要というわけではありませんが、ユーザに対して便利なショートカット を用意します。前の例で、プライベートなブランチを作ったあと、その新しい リポジトリディレクトリの作業コピーをチェックアウトしました。そうする かわりに、単に/calc/trunk の作業コピーを新しい ブランチの場所のコピーに変更することができます:
$ cd calc $ svn info | grep URL URL: http://svn.example.com/repos/calc/trunk $ svn switch http://svn.example.com/repos/calc/branches/my-calc-branch U integer.c U button.c U Makefile Updated to revision 341. $ svn info | grep URL URL: http://svn.example.com/repos/calc/branches/my-calc-branch
ブランチに「スイッチ」したあとでは、作業コピーの内容はそのディレクトリ を新しくチェックアウトした場合とまったく同じものになります。そして 普通このコマンドを使うほうがより効率的です。というのは、たいてい ブランチはほんの少し内容が違うだけです。サーバはそのブランチディレクトリ を反映させるために作業コピーにしなくてはならない最小限の変更だけを 送信すれば済むのです。
svn switch は--revision (-r) オプションをとることもできるので、常に作業コピー をブランチの「最新状態」にに移す必要があるわけではありません。
もちろん、ほとんどのプロジェクトはcalcよりは もっと複雑で、複数のサブディレクトリを含んでいます。Subversionユーザは ブランチを利用するときにはよく、特定のやり方をします。:
プロジェクトの「幹(trunk)」全体を新しいブランチディレクトリ にコピーする。
幹(trunk)の作業コピーの一部のみを ブランチにミラーする。
言い換えると、ユーザが特定のサブディレクトリ上でだけブランチの作業が起きる ことを知っている場合にはsvn switchを使ってブランチに そのサブディレクトリのみを移動します。(あるいは、たった一つの作業ファイル だけをブランチに switch することさえあります!) その方法では、作業コピー のほとんどすべての更新を普通の「幹(trunk)」から従来どおり受け取ることが できますが、切り替えた部分だけは変更されることなく残ります(もしブランチ に対して誰かが変更点をコミットしさえしなければ)。この機能は 「混合作業コピー」という概念にまったく新しい次元を付け加える ことになります—作業コピーは作業リビジョンの混合を含むことができる だけではなく、リポジトリ位置の混合も含むことができます。
もし作業コピーが異なるリポジトリ位置からのスイッチされたサブツリー をいくつか含むなら、それは普通に機能し続けます。更新すると、 それぞれのサブツリーのパッチを適切に受け取るでしょう。コミットすると ローカル修正は一つの不可分の変更をリポジトリに適用するでしょう。
リポジトリ位置の混合を作業コピーに反映させることはできますが、 このようなリポジトリ位置はすべて同じ リポジトリ の中になくてはなりません。Subversionのリポジトリはまだお互いに通信 することはできません。これはSubversion1.0以降で計画されている機能です。 [12]
svn switch は本質的にはsvn update の変種なので、同じ動作を共有します。作業コピー中のどのようなローカルの 変更もリポジトリから新しいデータが届くときに保存されます。これで あらゆる利口な小技がきくようになります。
たとえば/calc/trunk の作業コピーがありそれにいくつか 変更を加えたとします。それから突然、本当はブランチにやる変更だったことに 気づきます。問題ありません。作業コピーをsvn switch でブランチにスイッチしても、ローカルの変更はそのまま残ります。で、 それをブランチに対してテストし、コミットすることができます。
別のバージョン管理の概念に、タグがあります。 タグはある時点でのプロジェクトの「スナップショット」です。Subversion ではこのアイディアは既にさまざまな場所にあるように見えます。それぞれ のリポジトリリビジョンはまさにそれです—つまり、それはコミット直後の ファイルシステムのスナップショットです。
しかし、人はしばしばタグに対して人間になじみのある名前を付けたいと 思うものです。たとえば、「release-1.0」のような。また、ファイルシステム のもっと小さなサブディレクトリのスナップショットがほしいこともあります。 結局、あるソフトの一部のrelease-1.0 がリビジョン4822の特定のサブディレクトリ であることを思い出すのは簡単ではありません。
もう一度、svn copy の助けを借ります。 もしHEADリビジョンの/calc/trunk のスナップ ショットを作りたいときには、そのコピーをとればいいのでした:
$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/tags/release-1.0 \
-m "Tagging the 1.0 release of the 'calc' project."
Committed revision 351.
この例では/calc/tags ディレクトリが既に 存在しているものとしています。(もしそうでないなら、svn mkdir を見てください)。 コピー完了後、新しい release-1.0 ディレクトリは、あなたがコピーした 時点のHEADリビジョンにおいてプロジェクトがどう見えていたかをスナップ ショットとして永遠に残すものです。 もちろん、どのリビジョンをコピーするかについてもっと正確でありたいと 思うかも知れません。他の人があなたが見ていないときにプロジェクトに 対して変更点をコミットしていたかも知れませんから。もしあなたが /calc/trunk のリビジョン350が自分のほしいスナップ ショットだと知っていればsvn copy コマンドに -r 350を指定することができます。
でもちょっと待ってください: このタグ作成の手続きはブランチを作る ために使ってきた手続きと同じじゃないの? 実はその通りです。 Subversion ではタグとブランチには違いはありません。両方とも コピーで作られた普通のディレクトリです。ちょうどブランチのように コピーされたディレクトリが「タグ」であるといわれるのは、単に 人間がそうやって扱うことに決めたから、ただ それだけです。そのディレクトリに誰もコミットしない限り、それは 永遠にスナップショットとして残ります。もし誰かがそれにコミット し始めると、それはブランチになります。
もしリポジトリを管理しているなら、タグを管理するには二通りの 方法があります。最初のアプローチは、「ユーザ任せ」です。 プロジェクトポリシーとして、あなたのタグを置く場所を決め、 すべてのユーザにそのディレクトリをコピーするときにはどうやって扱うか を知らせます。(つまり、みんながそこにコミットしないように約束します) 二番目のやり方はもっとガチガチです。Subversionが提供するアクセス 制御スクリプトのどれかを使って、タグ領域には新しいコピーを 作ることだけができて、それ以外の操作を禁止します。 (第6章 サーバの設定を参照してください。) ガチガチ方式は、普通は不要です。もしユーザが間違ってタグディレクトリ に自分の変更をコミットしてしまったら、前の章で説明した方法で その変更を取り消せばいいのですから。結局、Subversionはバージョン 管理システムなのです。
ときどき、一つのリビジョンの一つのディレクトリよりも もっと複雑な「スナップショット」がほしいことがあります。
たとえば、プロジェクトが私たちのcalc よりも もっと大きいとします。たくさんのサブディレクトリともっとたくさん のファイルがあるとします。仕事の過程で、特定の機能とバグ修正を含んだ 作業コピーが必要になったと判断します。特定のリビジョンの 以前のファイルとディレクトリを選んで(svn update -r liberally を使って), これを作ることもできますし、 特定のブランチにファイルとディレクトリをスイッチすることによっても できます。 (svn switchを使う) これをやると、あなたと作業コピーは別々のリビジョンからなる別々の リポジトリ位置のつぎはぎになります。しかしテスト後、自分がまさに必要と している組み合わせであることがわかりました。
さあスナップショットをとります。一つのURLを作業していない別の 場所にコピーします。この場合、やりたいことは特定の作業コピー状態 で、それをリポジトリに格納したいのです。幸運なことに svn copy は実際には四種類の異なる使い方が あります。(第9章を読んでください)その中には作業コピーツリー をリポジトリにコピーする、というのもあります:
$ ls my-working-copy/ $ svn copy my-working-copy http://svn.example.com/repos/calc/tags/mytag Committed revision 352.
これでリポジトリに新しいディレクトリができました。 /calc/tags/mytagです。これはあなたの作業コピー の正確なスナップショットです— 混合リビジョン、URLそして すべてです。
別のユーザはこの機能の面白い使い方を見つけました。 ときどき、自分の作業コピーにローカルな修正をした ブランチがあるが、それを他のメンバーに見せたいというような 状況です。svn diff を使ってパッチファイル (それはツリーの変更、シンボリックリンクの変更、あるいは属性の変更を取得できません) を送るかわりに、svn copyを使って、作業コピーをリポジトリの プライベートな領域に「アップロード」します。他のメンバーは 作業コピーを新しくチェックアウトするか、svn merge を使って変更点のみを受け取ることができます。
ここまでで、Subversionは非常に柔軟なシステムであることがおわかり いただけたかと思います。ディレクトリのコピーという同じ基本的な 仕組みの上にブランチもタグも実装しており、ブランチもタグも普通の ファイルシステムの空間の中にあるので、多くの人々はSubversionの 仕組みにびっくりします。それは柔軟すぎるくらい です。この節では時間経過と共にどのようにデータを配置し管理するのが 良いかについて、少し説明します。
リポジトリの編成にはある程度、標準化された、おすすめの方法が あります。ほとんどの人々はtrunkディレクトリ に開発の「主系」、ブランチのコピーがあるbranches ディレクトリ、そしてタグのコピーがあるtagsディレクトリ を入れます。リポジトリがただ一つのプロジェクトを含む場合には しばしば、この三つのディレクトリをリポジトリ最上位に作ります:
/trunk /branches /tags
リポジトリが複数プロジェクトを含む場合は、プロジェクトごとに レイアウトをインデックス化します (「プロジェクトルート」についての詳細は リポジトリレイアウトの選択の項 を読んでください):
/paint/trunk /paint/branches /paint/tags /calc/trunk /calc/branches /calc/tags
もちろん、この標準的なレイアウトを無視してもかまいません。 あなたと、チームが最も作業しやすいように、このレイアウトは どのように変化させてもかまいません。どれを選んでもそれは 永久に固定されたものではありません。いつでもリポジトリを 再編成することができます。ブランチとタグは普通のディレクトリ にすぎないのでsvn moveコマンドを使えば 好きなように移動、名称変更ができます。あるレイアウトから別の レイアウトへの切り替えは単にサーバ側での何回かの移動の話になります。 もしリポジトリ中の編成に何か気に入らないところがあるなら、ディレクトリ に関連した小技を使ってください。
しかし、ディレクトリの移動は簡単ではありますが、ユーザのこともよく 考える必要があります。この変更は既にある各自の作業コピーの場所 を再配置します。もしユーザが特定のリポジトリのディレクトリの作業 コピーを持っている場合、あなたの svn move操作 は最新リビジョンのパスを削除してしまうかも知れません。ユーザが 次にsvn updateを実行すると、作業コピーは すでに存在しないパスを示しているとされ、新しい場所に移動する ためにsvn switchの実行を強要されてしまう でしょう。
Subversionモデルの別の良い機能としては他のバージョン 管理されたアイテムと同様、ブランチとタグは有限の寿命しか持たない ようにもできることです。たとえばcalcプロジェクト の個人的なブランチ上ですべての作業が完了したとします。すべての 変更を、/calc/trunkにマージしたあとでは その個人的なブランチのディレクトリがまったく不要になります:
$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch \
-m "Removing obsolete branch of calc project."
Committed revision 375.
これでブランチはなくなってしまいました。もちろん本当に削除された わけではありません: ディレクトリは単に HEADリビジョンからなくなった だけで、誰もわずらわせることはなくなりました。 前のバージョンを調べるためにsvn checkout、 svn switch、あるいは svn list を使えば依然として古いブランチを見ることができます。
削除したディレクトリを閲覧するだけでは不十分な場合は、いつでも 戻すこともできます。データの復活はSubversionではお手のものです。 HEADに戻したい削除ディレクトリ(またはファイル)がある場合は、単に svn copy -r を使って古いリビジョンから コピーしてください:
$ svn copy -r 374 http://svn.example.com/repos/calc/branches/my-calc-branch \
http://svn.example.com/repos/calc/branches/my-calc-branch
Committed revision 376.
私たちの例では個人的なブランチは比較的短い生存時間を持ちます。バグを 直したり新しい機能を追加するのに利用したからです。作業が終われば、 ブランチの寿命もそこで終わりです。しかし、開発内容によっては 非常に長い時間にわたって二つの「主要な」ブランチが並行して生きつづけることも あります。たとえば、安定版のcalcプロジェクトを 公にリリースするときがやってきて、そのソフトのバグをとるのには 何ヶ月かかかるとします。リリースバージョンに新しい機能を追加させたくは ありませんが、すべてのメンバーに開発を中止するように言いたくもありま せん。そこでかわりに、あなたはそれほど大きな修正は発生しない 「安定版」のブランチを作ります:
$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/branches/stable-1.0 \
-m "Creating stable branch of calc project."
Committed revision 377.
これで開発者は最先端の機能(あるいは実験的な機能)を /calc/trunkに追加し続けることが でき、バグフィックスだけを/calc/branches/stable-1.0 にコミットするようなポリシーで進めることができます。 つまり、メンバーがtrunk上で作業し続けるときに、バグフィックスに ついては安定版ブランチ上に持っていくことができます。安定版ブランチ が出荷されたあとでも、そのブランチを長い時間かけて保守し続ける でしょう—つまり、顧客に対してそのリリースをサポートし続ける 限り。
この章では、いろいろな基本に触れました。タグとブランチの概念を議論し、 Subversionがsvn copyでディレクトリをコピーする ことによってこれらの概念を実装していることを説明しました。 svn mergeであるブランチから別のブランチに変更点 をコピーしたり、間違った変更を戻したりする方法をお見せしました。 svn switchを使って、混合状態の作業コピーを 作ってみせました。そして、どのようにしてリポジトリ内のブランチの 編成と寿命を管理するかについて話しました。
Subversionについて、このことだけは憶えておいてください: ブランチやタグを作る処理はとても軽いのです。好きなだけ使ってください!
[8] Subversion はリポジトリ間コピーをサポートしていません。svn copyやsvn moveで URLを指定する場合、 同じリポジトリ内でのみコピーすることができます。
[9] 将来的にはSubversionプロジェクトはツリー構造と属性の変更点を表現する ような拡張したパッチ形式を使う(あるいは開発する)計画があります。
[10] しかしながら、Subversionプロジェクトはいつの日か svnadmin obliterateコマンドを 実装する計画があります。これは情報の完全な消去を実行するコマンド です。それまでは回避策としてsvndumpfilterの項 の方法を利用してください。
[11] CVS はツリーのバージョン管理ができないので削除されたファイルを 記憶しておくためにリポジトリ用のディレクトリ中に Attic 領域を 作ります。
[12] しかし、サーバ上の URL が変更されたが、既存の作業コピー を捨てたくない場合には、--relocateスイッチ付きで svn switchを使うことはできます。 より詳しい情報と例については第9章 Subversion リファレンスの svn switchの章を見てください。
目次
Subversionのリポジトリは複数のプロジェクトのためのバージョン管理 されたデータを格納する中心的な場所です。こんなわけで、リポジトリは 管理する人間にとってはたまらない魅力のある場所になるかも知れません。 リポジトリは一般的にはそれほど複雑な管理が必要なものではありません が、正しく設定し、潜在的な問題を避け、実際に起こる問題を安全に解決する ためにはどうすれば良いかを理解することは重要です。
この章では、Subversionのリポジトリをどうやって作成し設定するかについて 議論します。リポジトリ管理についても述べますが、 これには svnlookと svnadmin (この二つはSubversionが提供するツールです)の利用も含まれています。 よくある質問と間違いをとりあげ、リポジトリ中でどのようにデータを 配置するのか良いかについてアドバイスします。
もし、Subversionのリポジトリのバージョン管理下にあるデータに ユーザとしてアクセスするだけなら、(つまり、Subversionのクライアント としてだけ利用するなら)この章は読み飛ばすことができます。しかし、 Subversionのリポジトリ管理者か、そうなろうと思っている人は [13] この章に特別の注意を払ってください。
リポジトリ管理についての広範囲な話題に飛び込む前に、リポジト リとはいったい何であるかをもう少し突っ込んで定義しておきましょう。それ はどんな風に見えるのでしょうか? いったいどんなコなんでしょう? 飲み物の好みは? ホット? アイス? 砂糖はいくつ? レモンは? 管理者としては、論理的な見え方 —つまり、リポジトリ内でデータがどのように表現されているか— から物理的な細部に到るまで—つまり Subversion 以外のツールから リポジトリはどう見え、どう振舞うか—の両方について理解している ことが期待されます。以下の節は非常に高レベルの基本的な概念のいくつか について説明します。
概念的に言うと、Subversionのリポジトリはディレクトリツリーの 並びです。それぞれのツリーはある時刻で、リポジトリ中に 管理されたファイルやディレクトリがどのように見えるか、という ことについてのスナップショットです。 このクライアントの操作によって作られるスナップショットを リビジョンといいます。
それぞれのリビジョンはトランザクションツリーとして生まれます。 コミットすると、クライアントは、自分のローカルな変更(と、クライアント のコミット処理の最初にリポジトリに起きる附加的な変更)を反映 したSubversionのトランザクションを作り、次のスナップショットとして このツリーを格納するようにリポジトリに命令します。 コミットが成功すれば、トランザクションは新しいリビジョンツリーが できたことを知らせ、新しいリビジョン番号を割り当てます。 コミットが何かの理由で失敗すれば、トランザクションは消されて、クライアント は失敗した旨の通知を受けます。
更新処理も同様に動作します。クライアントは作業コピー の状態を反映した一時的なトランザクションツリーを作ります。リポジトリは そのトランザクションツリーを要求されたリビジョンのツリー(普通は 最新の、あるいは「一番若い」ツリー)と比較し、作業コピーを リビジョンツリーの形に変形するにはどのような変更が必要であるかに ついての情報を戻します。更新が完了した後、その一時的なトランザクションは 削除されます。
トランザクションツリーの利用がリポジトリのバージョン管理された ファイルシステムに普遍的な変更を起こす唯一の方法です。しかし、 トランザクションの生存時間が完全に任意であることを理解するのは 重要です。更新の場合トランザクションはすぐに消滅する一時的なツリー です。コミットの場合は、トランザクションは普遍的なリビジョンに 変わります。(あるいはコミットが失敗したときは削除されますが) エラーやバグがあると、トランザクションはリポジトリの周辺に 取り残されてしまうかも知れません(しかしこれは領域を食うだけで、 何かに悪い影響を与えたりはしませんが)
理論的には、いつの日か、統合された作業環境をサポートする アプリケーションはトランザクションの生存期間をもっと柔軟に 管理することができるようになるかも知れません。 クライアントがリポジトリに対する修正内容の記述を終えたあとでも、 リビジョンになる候補のトランザクションが 静止した状態にとどまるようなシステムを考えることもできます。 これはそれぞれの新しいコミットを別の人、たとえば管理者やエンジニアの QAチームによって再検討することを可能にし、そのトランザクション を本当のリビジョンにしたり、取り下げたりすることができるようになるでしょう。
Subversionリポジトリでのトランザクションとリビジョンは 付随した属性を持つことができます。そのような属性は 一般的なキー・値のマッピングで、関連したツリーについての 情報を格納するのに一般的に利用されます。属性の名前と値はリポジトリの ファイルシステム中に、残りのツリーデータと一緒に格納されます。
リビジョンとトランザクションの属性はファイルやディレクトリ にそれほど強く結びついていないツリーの情報を記憶して おくのに便利です—作業コピーによって管理できないような 情報です。たとえば新しいコミットトランザクションが リポジトリに作られるとSubversionはそのトランザクションに svn:dateという名前の属性を追加します —トランザクションが作られた時刻を示すタイムスタンプです。 コミットが完了し、トランザクションが普遍的なリビジョンとなる 時点で、ツリーにはリビジョン作成者のユーザ名称(svn:author) とリビジョンに付けられたログメッセージ(svn:log)の 属性が追加されます。
リビジョンとトランザクションの属性は バージョン化されない属性です— 修正されると、それ以前の値は永久に失われてしまいます。 同様にリビジョンツリー自身は不変ですが、ツリーに付けられた 属性はそうではありません。いつでもリビジョン属性を追加、削除、修正 することができます。新しいリビジョンをコミットしたあとで、 間違った情報だったり、ログメッセージにスペルミスがあったり したことがわかったときには、単にsvn:log 属性の値を正しいログメッセージで置き換えてやるだけです。
Subversion 1.1 からは、Subversion リポジトリに二つの保存形 式が選べます。一つはすべてのデータを Berkeley DB データベースに保存 する方法です; もう一つは、独自の形式で構成した通常のフラットファイルの 形にデータを保存する方法です。Subversion 開発者はリポジトリを、 「(バージョン化された) ファイルシステム」 という名前でよく言い表すので、 この習慣に合うように後者のリポジトリをFSFS [14] と呼びますが—それは最初から OS がもっているファイルシステムを使っ てバージョン化されたファイルシステムを作る方法です。
リポジトリを作成する時には、管理者は Berkeley DB を使うか、FSFS を使うかを決めなくてはなりません。両方とも、利点と欠点がありますが、そ れについては後で少し触れます。どちらか一方がもう一方よりも「公式 のもの」であるということはありませんし、リポジトリへのアクセスは これらの実装の詳細とは分離されています。プログラムはどうやって保存して いるデータにアクセスするかを知ることはありません; リポジトリ API 全体 を通じて、抽象化されたリビジョンとトランザクションツリーが見えるだけで す。
表 5.1. 「Repository 保存形式の比較」 に Berkeley DB と FSFS リポジトリの比較表があります。 詳細についてはは次の節を見てください。
表 5.1. Repository 保存形式の比較
| 機能 | Berkeley DB | FSFS |
|---|---|---|
| リポジトリの壊れやすさ | 非常に壊れやすい; リポジトリが壊れたりパーミッショ ンの問題が起こった場合にはデータベースは「中途半 端な」状態になり、ジャーナル復帰処理が必要にな ります。 | それほどでもない |
| リードオンリーでマウントできるか | いいえ | はい |
| プラットフォームに独立した保存形式か | いいえ | はい |
| ネットワークファイルシステムでも使えるか | いいえ | はい |
| リポジトリサイズ | わずかに大きい | わずかに小さい |
| スケール性: リビジョンツリーの数が増えるとどうな るか | データベースなので問題なし | OS のファイルシステムが古い場合、一つのディ レクトリ中に数千のエントリがあるとうまく動かなくなるこ とがある。 |
| スケール性: たくさんのファイルのあるディレクトリ | 遅い | 速い |
| スピード: 最新コードのチェックアウト | 速い | 遅い |
| スピード: 大きなコミット | 遅いが、処理の負荷はコミット全体に分散する | 速いが、最終処理はクライアントのタイムアウトにつ ながるかも知れない |
| グループパーミッション制御 | umask の問題に敏感; ひとつのユーザによってアクセ スされるのが一番よい。 | umask の問題を回避できる |
| コードは枯れているか | 2001 年から使われている | 2004 年から使われている |
Subversion の最初の 設計段階で、開発者はさまざまな理由で Berkeley DB を利用することに決めました。 その理由にはそのオープンソースライセンス、トランザクションのサポート、 信頼性、パフォーマンス、API の公開、スレッドの安全性、カーソルのサポート などが含まれていました。
Berkeley DB は本当のトランザクション機能をサポートしています—おそらく 上であげた理由の中で最も強力な機能です。Subversion リポジトリにアクセスする 複数のプロセスはそれぞれ他のデータを間違って破壊することを心配する必要はありません。 トランザクションシステムによって提供されている分離機能はどんな操作においても Subversion リポジトリのコードにデータベースを静的に見せることができるように するものです—他のプロセスによってときどき変更を受けているように見えるのを 防ぐものです— そしてそのような静的な見え方に基づいて、何を実行するか を決めることができるのです。もしその決定が他のプロセスがやったことと衝突 した場合、操作全体は、それがまったく実行されなかったかのようにロールバック され、Subversion はもう一度、新しく更新された(そしてやはりまた静的に見える ような状態での)データベースに対してその処理を再実行することができます。
Berkeley DB のほかのすばらしい機能はホットバックアップ — 「オフライン」にせずにデータベース環境をバックアップできる 能力です。リポジトリのバックアップ方法についてはリポジトリのバックアップの項で議論しますが、オフラインに せずにリポジトリの完全なコピーをとることができる利点は明白でしょう。
Berkeley DB はまた非常に信頼性の高いデータベースシステムです。Subversion は Berkeley DB のログ機能を利用しますが、これはまず最初にこれからやろうと する操作内容をいったんディスク上のログファイルに書き込み、それからその 修正を実際に行うものです。これは何かまずいことが起きた場合にデータベースシステム が直前のチェックポイント— ログファイル中の最後の 問題のない地点— をバックアップすることと、データが利用可能な 状態に復元されるまでトランザクションを再実行することを保証するものです。 Berkeley DB ログファイルについての詳細はディスク領域の管理の項を 見てください。
しかしどんなバラにもトゲがあるわけであり、Berkeley DB についてわかっている 制約を記しておく必要があります。まず Berkeley DB 環境は可搬性がありません。 Unix システムで作った Subversion リポジトリをWindowsシステムに単にコピー して動作することを期待してはいけません。ほとんどの Berkeley DB データベース 形式はプラットフォーム独立ですが、環境中にはそうではない部分もあります。 次にSubversion では Berkeley DB を Windows 95/98 システム上で利用できません — ウィンドウズマシン上でリポジトリを管理しなくてはならない のであれば Windows 2000 か Windows XP 上に構築してください。また Berkeley DB リポジトリをネットワーク共有上には決して置かないでください。Berkeley DB は 仕様の一部に合致するようなネットワーク共有上での正しい動作を保証していますが、 現在実際に利用されているネットワーク共有方式で、そのすべての仕様を満たすよう なものは知られていません。
最後に、Berkeley DB は Subversion に直接リンクされたライブラリな ので典型的なリレーショナルデータベースよりも割り込みに関して敏感です。 例えばほとんどの SQL システムでは、テーブルに対するアクセス全体を取り持つサー バプロセスがあります。何かの理由でデータベースにアクセスするプログラム に異常があった場合でもデータベースデーモンは接続が中断したことを検知し て問題のある中間的な状態をきれいにします。またデータベースデーモンはテー ブルにアクセスする唯一のプロセスなのでアプリケーションはパーミッション の衝突に関して心配する必要はありません。このような性質は Berkeley DB にはありません。Subversion (と Subversion ライブラリを使うプログラム) はデータベーステーブルに直接アクセスしますが、これはプログラムで異常が あると、データベースが中間的な矛盾のある状態、アクセスできない状態のま ま残ってしまうことを意味します。このようなことがおこると、管理者は Berkeley DB に問い合わせてチェックポイントを回復する必要がありますが、 これは少し面倒な作業です。リポジトリが 「中途半端な」状態に なるのはプログラムの異常のほかにも、データベースファイルに対して与えら れたオーナーやパーミッションに関係することもあります。このように、 Berkeley DB は非常に速くスケール性にも富んでいますが、一つのサーバプロ セスを一つのユーザで実行する—たとえば Apache's httpd や svnserve (第6章 サーバの設定 を見てください。)— のが最善の利用 方法であり、file:/// や svn+ssh:// のような URL を使ってたくさん の異なるユーザがアクセスするのは避けたほうがよいでしょう。複数ユーザか ら直接 Berkeley DB をアクセスする場合には、かならず複数リポジトリアクセス方法のサポートの項を読むようにしてください。
2004年の半ばから、第二のリポジトリ保存形式が使えるようになりまし た: これはデータベースをまったく利用しないものです。FSFS リポジトリは リビジョンツリーを単一のファイルに保存し、すべてのリポジトリリビジョン は単一のサブディレクトリの下の複数ファイルになります。トランザクション は分離されたサブディレクトリに作られます。トランザクションが完了すると 単一のトランザクションファイルが作られ、それがリビジョンディレクトリに 移動されます。このためコミットの不分割性が保証されます。そして、リビジョ ンファイルは永続的なものであり、それ以上変更されないのでリポジトリは Berkeley DB リポジトリのように「ホット」バックアップするこ とができます。
リビジョンファイルの形式は、そのリビジョンのディレクトリ構造、ファ イル内容、そして他のリビジョンツリーのファイルに対する差分を表現したも のです。Berkeley DB データベースとは違いこの保存形式は異なるオペレーティ ングシステム間でもそのまま利用することができ、CPU のアーキテクチャには 依存しません。ジャーナリングのしくみや共有メモリーを使っていないので、 リポジトリはネットワークファイルシステムごしに安全にアクセスすることが でき、リードオンリーな環境を作ることもできます。またデータベース保存形式特 有のオーバーヘッドがないので、リポジトリの大きさは比較的小さくなります。
FSFS はパフォーマンスの特性にも独自の性質があります。非常にたく さんのファイルのあるディレクトリをコミットすると、FSFS は O(N) アルゴ リズムを使ってエントリーを追加しますが、Berkeley DB は O(N^2) のアルゴ リズムを使ってディレクトリ全体を書き換えます。いっぽう FSFS は以前の バージョンと、今回の最新バージョンのファイルの差分を書き込みます。 これは最新ツリーをチェックアウトする場合 Berkeley DB の HEAD リビジョンに保管されている完全な内容にアクセスするよりも少し遅くなる ことを意味しています。FSFS はコミットの最終処理でも相対的に長い遅延が 起こりますが、これは極端な場合には応答を待つ クライアントプログラムをタイムアウトさせてしまうかも知れません。
しかし一番大きな違いは FSFS では何かおかしなことが起こったときで も、「中途半端な」状態にはならないところです。Berkeley DB データベースを使ったプロセスがパーミッションの問題や突然異常終了したよ うな場合だと、データベースは管理者が復帰処理をしない限り利用できない状態に とどまります。もし同じことが FSFS リポジトリに起きても、リポジトリはまっ たく影響を受けません。せいぜいトランザクションデータが見えない場所に取 り残されてしまうだけです。
FSFS に対する唯一の問題は Berkeley DB に比較してそれほど枯れてい ないところです。 Berkeley DBほどの耐久性テストはされていないので、スピー ドとスケール性についてここで述べた多くの内容は: 妥当な推測に基づくもの です。理屈の上では FSFS は新しい管理者がとりかかる時の敷居を下げて、問 題の影響を受けにくくするはずです。実際にどうかは、いずれ時が答えてくれ るでしょう。
Subversionリポジトリの作成は非常に簡単な作業です。 Subversion付属のsvnadmin ユーティリティー にそれをやるサブコマンドがあります。新しいリポジトリを作るには 単に:
$ svnadmin create /path/to/repos
これで/path/to/reposディレクトリに 新しいリポジトリが作成されます。この新しいリポジトリはリビジョン0 で誕生しますが、これは最上位のルート(/) ファイル システムディレクトリに中身が空の状態で存在しているだけです。 初期状態でリビジョン0はリビジョン属性を一つ持っていて、 svn:dateは、リポジトリが作られた時刻が設定 されています。
Subversion 1.2 では、リポジトリはデフォルトで FSFS のバッ クエンドになります。(リポジトリの保存形式の項 を見てください)。バックエンドは--fs-typeの引数で 明示的に指定することができます:
$ svnadmin create --fs-type fsfs /path/to/repos $ svnadmin create --fs-type bdb /path/to/other/repos
ネットワーク上で共有された Berkeley DB リポジトリを作らないで ください—NFS, AFS あるいは Windows の SMB のようなリモートファイルシステム 上にリポジトリを置くことはできません。Berkeley DB は利用する ファイルシステムが POSIX のロックの方式に厳密に従っていること、そしてさらに 重要なことは、ファイルをプロセスメモリに直接マップできること、を要求します。 ネットワークファイルシステムでこの性質を持っているものはほとんどありません。 ネットワーク上で共有された場所の上で Berkeley DB を利用した結果については予測できません— すぐに正体不明のエラーが起きるかも知れませんし、自分のデータベースがわずかに 壊れてしまったことに気づくのに何ヶ月もかかるかも知れません。
もしリポジトリに対して複数のコンピュータがアクセスする必要がある なら、ネットワーク共有上に Berkeley DB リポジトリではなく、FSFS リポジ トリを作ってください。あるいはもっと良い方法として、実際の(Apache か svnserveのような)サーバプロセ スを設定し、サーバがアクセスできるようなローカルファイルシステム上にリ ポジトリを格納し、リポジトリがネットワークからも利用できるようにしてく ださい。第6章 サーバの設定でこのやり方の詳細を説明しています。
svnadmin の引数であるパスは単なるファイルシステム パスであってsvn クライアントプログラムがリポジトリ を参照するときのようなURLではないことに注意してください。 svnadmin も svnlook も、 サーバ側のユーティリティーだと考えてください— この二つはリポジトリを調べたり状態を変更するため、リポジトリがある マシン上で利用され、ネットワーク越しに実行することはできません。 Subversion初心者によくある間違いは、二つのプログラムに、URLを 渡してしまうことです。(あるいは、「local」なURLとして file: のように指定してしまうことです。)
svnadmin createコマンド実行後には、ディレクトリ にはピカピカの新しいSubversionリポジトリができます。サブディレクトリ には実際には何ができたかをちょっと見てみましょう。
$ ls repos conf/ dav/ db/ format hooks/ locks/ README.txt
README.txt ファイルと format ファイル以外は、リポジトリディレクトリは サブディレクトリの集まりです。Subversionの一般的な設計思想と同様 モジュール化に非常に配慮されています。階層化した編成は混沌とした 状態よりも望ましいものです。新しいリポジトリディレクトリについて 簡単に説明しておきます:
リポジトリ設定ファイルのあるディレクトリです。
Apacheと、内部データ管理用 mod_dav_svn の ためのディレクトリです。
すべてのバージョン化されたデータが可能されています。こ のディレクトリは Berkeley DB 環境 (DB テーブルとその他必要な全体)か、 リビジョンファイルを含む FSFS 環境になります。
一つの整数値が書いてあるファイルで、この整数はリポジトリレイアウトの バージョン番号になります。
フックスクリプトテンプレート全体が格納されたディレクトリです (また、インストールされたフックスクリプト自身も)。
Subversionリポジトリのロックされたデータのためのディレクトリで リポジトリにアクセスしている人を記録するのに使われます。
Subversionリポジトリを見る人のための情報が書かれている だけのファイルです。
一般的に、「手で」リポジトリをいじるべきではありません。 svnadmin ツールはリポジトリに対するどのような 変更にも十分対応できますし、サードパーティーのツール(たとえば Berkeley DBツールスイート)でリポジトリの関連した部分を調整する ことができます。いくつかの例外もあるので、それについては後で触れます。
hook は、新しいリビジョンの生成やバージョン化されて いない属性の修正といったリポジトリに対するイベントをきっかけに実行される プログラムです。フックのそれぞれは、どんなイベントが起こったか、 何を対象にして操作をしたのか、そのイベントを起こした人のユーザ名 などの情報を扱うことができます。フックの出力や戻り値によって フックプログラムは処理を続けたり終了したり、いくつかの方法で中断したりします。
hooks サブディレクトリには、デフォルトでは さまざまなリポジトリフックのテンプレートがあります。
$ ls repos/hooks/ post-commit.tmpl post-unlock.tmpl pre-revprop-change.tmpl post-lock.tmpl pre-commit.tmpl pre-unlock.tmpl post-revprop-change.tmpl pre-lock.tmpl start-commit.tmpl
Subversionが実装しているフックごとに一つのテンプレートがあり、 テンプレートスクリプトの内容を見ればどんなトリガーを実行し、 どんなデータがそのスクリプトに渡されるかがわかります。 またこれらたくさんのテンプレートはスクリプトを書こうとする人の例 になっていて、他のSubversion付属のプログラムと協調して、よく出くわす 作業を実行します。 動作するフックをインストールするには、何かの実行ファイルかスクリプト をrepos/hooks ディレクトリに置くだけで良く、 そのフックの名前で実行されます。(start-commit とか post-commitとかいう感じです。)
Unixでは、これは正確にフックの名前を持つスクリプトやプログラムを 置いてやる必要があるという意味です(シェルスクリプトでもいいし、 Python, コンパイルされた Cのプログラム、などなどです。) もちろんテンプレートファイルはそういう情報を与えるためだけに あるわけではありません—Unixで一番簡単にフックをインストール するにはテンプレートファイルを.tmpl の拡張子を とった新しいファイルにコピーして、内容をカスタマイズし、スクリプトに 実行権限を与えるだけです。Windowsでは、ファイルが実行できるかどうか は拡張子によって決まるので、ベース名がフックの名前で、拡張子がWindows で実行形式として認識される拡張子のどれかにしてやればOKです。 たとえば、プログラムなら.exe か .com ですし、バッチファイルなら.bat です。
セキュリティー上の理由で、Subversion リポジトリはフックスクリプト を空の環境で実行します— つまり$PATH や %PATH%を含め、環境変数は全く設定されない 状態で実行します。このため多くの管理者は手でフックスクリプトを実行 するとうまくいくのに、Subversion によって実行されたときにはうまくいかな いことに困惑します。環境変数を明示的に設定するか、実行するプログラム を絶対パスで参照していることを確認してください。
Subversionリポジトリには9種類のフックが実装されています:
これは、コミットトランザクションが作られる前に実行されます。 典型的にはユーザがコミット権限があるかどうかを決定するのに 使われます。リポジトリはこのプログラムに二つの引数を渡します: リポジトリへのパスと、コミットしようとしているユーザ名です。 もしプログラムがゼロ以外の値を返した場合、コミットは ランザクションが作られる前に中止します。フックが標準エラー出力にデータ を書き込むと、それは適切なデータ形式でクライアントに戻されます。
これは、トランザクションの完結後、実際のコミットの前に実行 されます。典型的には、コミットの内容や場所(たとえば あなたのサイトでは、すべてのコミットはバグトラッカーの管理番号 を含むようなブランチに対してしなくてはならないとか、 ログメッセージが空であってはいけないというようなポリシーが あるかも知れません) によってコミットを許可しないようにするために使われます。 リポジトリはこのプログラムに二つの引数を渡します: リポジトリのパスと、コミットされるはずのトランザクションの名前 です。もしこのプログラムがゼロ以外の値を返した場合、 コミットは中断され、トランザクションは削除されます。 フックが標準エラー出力にデータ を書き込むと、それは適切なデータ形式でクライアントに戻されます。
Subversionの配布パッケージは、アクセス制御を細かく実装するために pre-commitから呼び出すことのできるいくつかの アクセス制御スクリプトを含んでいます(Subversionソースツリーの tools/hook-scripts ディレクトリにあります)。 他の選択子はApache の httpd モジュールであるmod_authz_svn を使うもので、個別のディレクトリに対する読みこみ書き込みのアクセス制御 をすることができます(ディレクトリごとのアクセス制御の項を見てください)。 Subversion の今後のバージョンでは、ファイルシステムに直接アクセス制御リス ト(ACL)を実装する計画があります。
これはトランザクションがコミットされ、新しいリビジョンが 作られた後に実行されます。ほとんどの人はこのフックを リポジトリのコミットやバックアップに関する連絡メールを 送るのに使います。リポジトリはこのプログラムに二つの引数を 渡します:リポジトリのパスと、今回作られた新しいリビジョン番号 です。このプログラムの終了コードは無視されます。
Subversion配布パッケージはmailer.pyと commit-email.pl スクリプトを含んでいます。(Subversionソースツリーの tools/hook-scripts/ ディレクトリにあります) それは、今回のコミットに付けられた説明をメールするために使う ことができます。このメールの内容は変更されたパスの一覧、コミットに 付けたログメッセージ、コミットした人、コミットの時刻、そして、 コミットの変更部分のGNU のdiffスタイルでの表示です。
Subversionが提供するほかの役に立つツールはhot-backup.py スクリプトです。(Subversionソースツリーのtools/backup/ ディレクトリにあります)。このスクリプトはSubversionリポジトリの オンラインバックアップをとるので、(今後はBerkeleyDBデータベース のバックエンドとしてサポートする予定です)リポジトリのアーカイブ化 や緊急リカバリのためのコミットごとのスナップショットを作るのに 使うことができます。
Subversionのリビジョン属性はバージョン化されていないので、そのような 属性に対する修正は(たとえば、コミットメッセージ属性である svn:log )以前の属性値を永久に上書きしてしまいます。 データはここで失われてしまうので、Subversionはこのフック(そして この相補的な部分である post-revprop-change) を使って、必要に応じてリポジトリ管理者がこのような変更記録を残すことが 出来ます。バージョン化されていない属性データを失うことに対するあらかじめ の警告の意味で、Subversion クライアントはこのフックが自分のリポジトリに 実装されているのではない限りリビジョン属性をリモートに変更することは 決してありません。
このフックはリポジトリにそのような変更が発生する直前に実行されます。 リポジトリはこのフックに四つの引数を与えます: リポジトリのパス、修正される属性があるリビジョン、 変更しようとしている、認証の済んだユーザ名、そして属性の名前自身です。
以前に指摘したように、このフックはpre-revprop-change フックのもう片割れです。実際、神経質な人のことを考えて、このスクリプト はpre-revprop-change フックが存在しなければ 実行されません。両方のフックが存在する場合、 post-revprop-change フックはリビジョン属性が変更 された直後に実行されます。典型的には、変更された属性の新しい値を メールするのに使います。リポジトリは四つの引数をこのフックに渡します: リポジトリへのパス、属性があるリビジョン番号、変更しようと している認証済みのユーザ名称、そして属性の名前自身です。
Subversion配布パッケージは propchange-email.pl スクリプトを含んでいます。(これは、tools/hook-scripts/ ディレクトリにあります)これは、リビジョン属性の変更についての詳細を メールするために使われます。Emailはリビジョンと変更属性の名前、 変更した人、そして新しい属性値です。
このフックは誰かがファイルをロックしようとしたときには常に実行されます。 これはロックを防ぐのにも利用するこどかできますし、誰が特定のパスに対して ロックできるかという複雑なポリシーを正確に設定するのにも使えます。 フックが既にロックがかかっていることに気づいた場合にはユーザはそのロック が外れるのを 「待つ」 かどうかを決めることもできます。リポジトリはフックに 三つの引数を渡します: リポジトリへのパス、ロックされているパス、そして ロックしようとしているユーザです。プログラムが 0 ではない値で終了すると ロック処理は異常終了し、標準エラー出力へのメッセージはすべてクライアント 側に転送されます。
このロックはパスがロックされた後に実行されます。ロックされたパスはフックの 標準入力に渡されるほか、フックはまた二つの引数も受け取ります: リポジトリ へのパスと、ロックを実行したユーザです。その後フックは email 通知を送ったり 好きな方法で出来事を記録したりすることが自由にできます。ロックはすでに 実行されてしまっているのでフックの出力は無視されます。
このフックは誰かがファイルのロックを取り除こうとした時には常に 実行されます。これを使ってどのユーザがどの特定のパスに対して ロック解除できるかを決めるポリシーを作るために利用できます。 ロック解除に関するポシリーを決めることは非常に重要です。 ユーザ A がファイルをロックした場合、B はそのロックを解除できる でしょうか? ロックが一週間以上も前のものだった場合は? これらの ことはフックによって決定され、強制することができます。リポジトリ は三つの引数をフックに送ります: リポジトリのパス、ロック解除される パス、ロックを解除しようとしているユーザ。プログラムが 0 以外の 終了値を返した場合、ロック解除の処理は異常終了し標準エラーへの 出力はすべてクライアント側に転送されます。
このフックはパスがロック解除された後で実行されます。ロックが解除 されたパスはフックの標準入力に渡され、その他にも二つの引数がフック に渡されます: リポジトリのパスと、ロックを解除したユーザです。 その後フックは email 通知を送ったり、好きな方法で出来事を記録する ことができます。ロックの解除は既に起こってしまっているのでフックの 出力は無視されます。
フックスクリプトによってトランザクションを修正しようとしないで ください。このような例としてよくあるのは、コミットの途中に svn:eol-style や svn:mime-type のような属性を自動的に設定してしまうことです。いっけん良いアイディア に見えますが、問題を起こします。一番の問題はクライアントはフックスクリ プトでされた変更について知ることができないので、クライアントに対して 最新ではなくなったことを伝える方法がないことです。この矛盾した状況 が予測できないような動作の原因になることがあります。
トランザクションで修正するかわりにpre-commit フック中のトランザクションで チェックをし、 正しい要件を満たさない場合にはコミットを拒否するのがずっと良い 方法です。
SubversionはSubversionリポジトリにアクセスしているプロセス の所有者としてフックを実行しようとします。ほとんどの場合 リポジトリはApache HTTPサーバとmod_dav_svn越しにアクセスされる ので、このユーザはApacheを実行しているユーザと同じになります。 フックは、実行しようとするユーザに対するOSレベルでの実行権限 が必要です。また、これはフックが直接的に、間接的にアクセスする ファイルやディレクトリ(これにはSubversionのリポジトリ自身も 含みますが)もそうでなくてはならないことを意味します。 言い換えるとフックを実行する際に、このようなファイル権限に 関係した問題に注意してください。
Bkerlekey DB 環境は一つあるいはそれ以上のデータベース、ログファイル、 領域ファイル、設定ファイルを一つにまとめたものです。 Berkeley DB 環境には、一度にいくつのロックが許されるか、 とか、ジャーナルログファイルの一つの大きさについて、固有のデフォルト 値があります。Subversionのファイルシステムコードはこれに追加して Berkeley DB 設定値のデフォルト値を選んであります。しかし、 ときどき特定のリポジトリが特徴的なデータやアクセスパターンを 持っているために別のオプション設定値を持つのが望ましいことがあります。
Sleepycat 系のプログラム(Berkeley DBの手続き)は異なるデータベース が異なる要求を持つことを理解していて、Berkeley DB環境のいろいろな 設定値を実行時に上書きするような仕組みがあります。Berkeley は それぞれの環境ディレクトリ中のDB_CONFIG という名前のファイルの存在をチェックして、特別の Berkeley 環境 を使う場合には、その中のオプションを調べます。
あなたのリポジトリ用のBerkeley設定ファイルは repos/db/DB_CONFIGの中の、 db環境ディレクトリにあります。 Subversion自身はこのファイルをリポジトリの残りの部分を作るときに 作ります。ファイルは初期状態でいくつかのデフォルト値と、Berkeley DB のオンラインドキュメントへの場所があるので、どのオプションが 何をするかについて読んでおくことができます。もちろんどのような Berkeley DBオプションもDB_CONFIG に追加 することができます。Subversionはこのファイルの内容を読んだり 解釈したりすることはありませんし、その中にオプション値を設定 したりすることもありませんが、Subversionの残りのコードに 予測できないような影響を与える設定変更は避けてください。 同様にDB_CONFIGに対する変更はデータベース 環境を復旧するまで効果を持ちません(svnadmin recover )。
Subversion リポジトリの管理はぞっとするような仕事になること もあります。大部分はデータベースバックエンドのもつシステムから引き継い だ複雑さによります。作業をうまくこなすには、とにかくツールについて深く理解する ことです—そのようなツールがいったい何であり、いつ使えば、またど うやって使えばよいのかを知ることです。この節ではSubversionによって提供 されるリポジトリ管理用ツールを紹介し、リポジトリの移行、更新、バックアッ プ、クリーンアップのような作業でどうやって使いこなせばよいかを説明しま す。
Subversion はリポジトリの作成、調査、修正、修復に便利なユー ティリティーをいくつも提供しています。それぞれについてもっと詳し く見てみましょう。その後 Berkeley DB のディストリビューションに含 まれるユーティリティーのいくつかを簡単にためしてみます。 Berkeley DB は Subversion 自身のツールとしては提供していないリポ ジトリデータベースバックエンドに特化した機能を提供しています。
svnlookはSubversionが提供するツールで リポジトリ中のいろいろなリビジョンやトランザクションを調査 するのに使われます。このプログラムのどの部分もリポジトリを 変更することはありません—これは単なる「読み出し専用 」のツールです。 svnlookは典型的には、まさにコミットされようとして いる変更を報告したり(pre-commit フック)、 コミット直後の報告(post-commitフック)のために リポジトリフックによって使われます。リポジトリ管理者は診断のために このツールを使うこともできます。
svnlook は単純な構文です:
$ svnlook help
general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]
Note: any subcommand which takes the '--revision' and '--transaction'
options will, if invoked without one of those options, act on
the repository's youngest revision.
Type "svnlook help <subcommand>" for help on a specific subcommand.
…
ほとんどのsvnlookのサブコマンドは リビジョンかトランザクションツリーのどちらかに対して働き、 ツリー自身の情報か、以前のリポジトリのリビジョンとの違いを 表示します。--revision と --transaction オプションを使ってどの リビジョンまたはトランザクションについて調査するかを指定する ことができます。リビジョン番号は自然数として表示されますが、 トランザクション名称は英数字の文字列だということに注意してください。 ファイルシステムはコミットされていないトランザクションのみを表示 できることを憶えておいてください(新しいリビジョンを作れなかった トランザクション)。ほとんどのリポジトリにはそのようなトランザクション はありません。トランザクションは普通、コミットされる(そうすると 見えなくなります)か、中断後、削除されるからです。
--revisionも --transaction も指定しないと、 svnlook は最新の(あるいは「HEAD」) リビジョンをリポジトリの調査対象とします。それで、以下の二つの コマンドは/path/to/reposにあるリポジトリで 19が最新リビジョンである場合はまったく同じ意味になります:
$ svnlook info /path/to/repos $ svnlook info /path/to/repos --revision 19
サブコマンドに関する唯一の例外はsvnlook youngest で、これはオプションをとらず、単に、HEADリビジョンの番号を表示します。
$ svnlook youngest /path/to/repos 19
svnlook の出力は人間にも マシンにも理解できるように設計されています。 info サブコマンドを例にします:
$ svnlook info /path/to/repos sally 2002-11-04 09:29:13 -0600 (Mon, 04 Nov 2002) 27 Added the usual Greek tree.
info サブコマンドの 出力は、以下のように定義されています:
作業者、改行
日付、改行
ログメッセージの長さ、改行
ログメッセージ自身、改行
この出力は人間が読むことができます。日付のタイムスタンプ などは、何かバイナリ表現のようなものではなく、テキスト形式になっています。 しかし、これはまたマシンも解析できる形式のものです— ログメッセージは複数行にわたることができ、長さの制限がないので、 svnlook はメッセージ自身の前にその長さを 表示します。これで、このコマンドのスクリプトやほかのラッパープログラムは 賢い判断ができるようになります。たとえば、メッセージにどれだけの メモリを割り当てれば良いか、とか、イベント中で少なくとも何バイト スキップしてもデータストリームの終わりにならないか、などを知ることが できます。
よくある別の svnlook の使い方はリビジョンまたは トランザクションツリーの実際の内容を見ることです。 svnlook tree コマンドは要求されたツリー中の ディレクトリとファイルを表示します。--show-idsオプション を指定するとそれらのパスごとのファイルシステムノードリビジョン ID も 表示します(そのようなパスは一般的に言ってユーザよりも開発者に有用なもの でしょう)。
$ svnlook tree /path/to/repos --show-ids
/ <0.0.1>
A/ <2.0.1>
B/ <4.0.1>
lambda <5.0.1>
E/ <6.0.1>
alpha <7.0.1>
beta <8.0.1>
F/ <9.0.1>
mu <3.0.1>
C/ <a.0.1>
D/ <b.0.1>
gamma <c.0.1>
G/ <d.0.1>
pi <e.0.1>
rho <f.0.1>
tau <g.0.1>
H/ <h.0.1>
chi <i.0.1>
omega <k.0.1>
psi <j.0.1>
iota <1.0.1>
ツリー中のディレクトリのファイルの構成が理解できればsvnlook cat, svnlook propget, そして svnlook proplistのようなコマンドを 使ってそれらのファイルやディレクトリについてのより詳細な情報を 取得することができます。
svnlook は他にもいろいろな問い合わせをしたり、 いままで説明した情報の一部を表示したり、指定したリビジョンや トランザクションのどのパスが修正されたかを報告したり、ファイルや ディレクトリに対するテキストや属性の相違点を表示したり、などなど ができます。以下はsvnlookが理解できる現時点 でのサブコマンドの簡単な説明の一覧と、その出力です:
そのツリーの実行者です
ツリーの特定のファイルの内容を表示します。
ツリー中で変更のあったファイルとディレクトリの一覧
ツリーのタイムスタンプです
変更されたファイルの unified diffの表示
ツリー自身に変更があるか、その子供のファイルに変更が あったディレクトリの一覧表示
バージョン化されたパスの履歴中での、興味深い場所の表示 (どこで変更やコピーが起きたかを示します)。
ツリーの変更者、タイムスタンプ、ログメッセージ文字数、そして ログメッセージの表示
パスがロックされている場合にロックの属性を表示します。
ツリーのログメッセージの表示
ツリー中のパスに設定された属性値を表示します。
ツリー中のパスに対して設定された属性の名前と値を表示します。
ツリーの一覧表示をします。オプションでそれぞれのパスに 結びついたファイルシステムノードリビジョンのIDを 表示します。
リポジトリの UUID — つまり Universal Unique IDentifier(普遍的に一意な識別子) を表示します。
最新のリビジョン番号を表示します。
svnadmin プログラムはリポジトリ管理者によって 一番よく利用されます。Subversionリポジトリを作成することのほか このプログラムはリポジトリに対してさまざまな保守操作をします。 svnadmin の構文は、svnlook のものとよく似ています:
$ svnadmin help general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...] Type "svnadmin help <subcommand>" for help on a specific subcommand. Available subcommands: create deltify dump help (?, h) …
既にsvnadminのcreate サブコマンドを見てきました(リポジトリの作成と設定の項参照)。 他のサブコマンドのほとんどをこの章の後のほうで説明します。いまは 利用可能なサブコマンドの全体を軽く見ておきます。
Subversionリポジトリを新規に作成します。
リビジョン範囲を指定して実行すると、それらの リビジョンで変更されたパス上で祖先の差分を計算します。リビジョンが指定 されなければこのコマンドは単に HEAD リビジョンの差分を計算します。
指定範囲のリビジョンのリポジトリの内容をダンプ します。ポータブルダンプ形式で出力します。
リポジトリのホットコピーをとります。いつでも実行することが でき、他のプロセスがリポジトリを利用しているかどうかにかかわらず、安心して リポジトリのコピーをとることができます。
(Berkeley DB リポジトリの場合のみ)リポジトリに関係したBerkeley DBログファイルのパスを一覧表示します。 このリストはすべてのログファイルを含みます—現在Subversionが利用しているもの、もう 利用していないものも含みます。
(Berkeley DB リポジトリの場合のみ)リポジトリに関係したBerkeley DBログファイルで、既に利用していない もののパスを一覧表示します。そのようなログファイルはリポジトリレイアウトから 安全に削除することができますが、リポジトリの壊滅からの復旧には必要となる事態にそなえて アーカイブすることもできます。
データストリームから、リビジョンの集まりをリポジトリにロードします。 データストリームはdump サブコマンドで生成されたのと 同じポータブルダンプ形式です:
リポジトリに存在するロックを説明つきで一覧表示します。
現時点でリポジトリに存在しているコミットされていない Subversionトランザクションの名前を一覧表示します。
必要に応じてリポジトリの回復ステップを実行します。普通は リポジトリとの間の通信をきれいに終了できなかったプロセスによって 起きた致命的なエラーの後で実行します。
一覧されたパスからロックを無条件に取り除きます。
リポジトリからSubversionトランザクションをきれいに削除します。 (lstxns サブコマンドからの出力をこの プログラムに入力すると便利です)
リポジトリ中の指定リビジョンのsvn:log (コミットログメッセージ)属性の値を新しい値で置き換えます。
リポジトリの内容を確認します。これはリポジトリに格納されたバージョン化された データのチェックサム比較、なども含まれます。
Subversion は非公開のデータベースシステムにすべてのデータを格納 しますが、簡単には手で修正することができないようにするためです。 実際にはそれほど難しいことでもないのですが。そしてデータがリポジトリに いったん格納されてしまうと、Subversion はそのようなデータを削除する ための簡単な機能を提供してはいません。 [15] しかし、時にはどうしてもリポジトリの履歴を操作したいことがあります。 リポジトリに間違って追加してしまったすべてのファイル(あるいはどんな 理由であれとにかくそこにあるべきではないファイル)を削除したいと思う かも知れません。あるいは一つのリポジトリを共有する複数のプロジェクト があって、それぞれを固有のリポジトリに分割することに決めたのかも 知れません。このような作業のためには、管理者はリポジトリ中のデータ のより柔軟に管理可能で柔軟な表現形式が必要です—それは Subversion のリポジトリダンプフォーマットです。
Subversion のリポジトリダンプフォーマットは時間とともにバージョン化 されたデータに加えた変更点に対する可読な形の表現形式です。ダンプ データを生成するにはsvnadmin dumpを使い、 新たらしいリポジトリにそれをロードするにはsvnadmin load を使います(リポジトリの移行の項参照)。ダンプ形式が可読な形で あることの大きな利点は、注意して扱えばそれを調べたり修正したりできること です。もちろん、欠点としては、もし二年分のリポジトリ内容が一つの巨大な ダンプファイルに保存されているような場合には特定の場所を見つけて修正 するには非常に、非常に長い時間がかかるであろうことです。
管理者が自由にしたい場合に最もよく利用されるツールというわけでは ありませんが、 svndumpfilterは非常に特殊な 役に立つ機能を提供しています—パスベースのフィルタとして 実行することによってそのダンプデータをすばやく簡単に修正することが できるのです。保存したいと思うパスのリストか、保存したくないパスの リストを単に与えてこのフィルタにリポジトリのダンプデータをパイプで 入力するだけです。結果は、あなたが(明示的、あるいは暗黙に)要求した バージョン化されたパスのみを含むような修正済みダンプデータになります。
svndumpfilter の構文は以下のものです:
$ svndumpfilter help general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...] Type "svndumpfilter help <subcommand>" for help on a specific subcommand. Available subcommands: exclude include help (?, h)
興味深いサブコマンドは二つだけです。これらのサブコマンドを使って、 ストリーム中で明示的に、あるいは暗黙に取得するパスを選ぶことができます。:
ダンプデータストリームから特定のパスを排除します。
ダンプデータストリームから、指定したパスだけを 出力するようにします。
このプログラムが実際にどのように動作するか例を見てみましょう。 別の場所でリポジトリ中でどのようにレイアウトを選ぶかを決める手順について 議論しました(リポジトリレイアウトの選択の項)—プロジェクトごとの リポジトリ、あるいはそれらをまとめたものを使って、リポジトリ中で構成を 変更し、などの手法です。しかし、新しいリポジトリが運用されたあとで、 よくレイアウトを再編成していくつかの修正をしたいということもあります。 一番多いのは一つのリポジトリを共有していた複数のプロジェクトを プロジェクトごとの別々のリポジトリに分離したい、という場合です。
私たちの架空のリポジトリは三つのプロジェクトを含んでいます: calc, calendar, そして spreadsheetです。 それらは以下のようなレイアウトになっています:
/
calc/
trunk/
branches/
tags/
calendar/
trunk/
branches/
tags/
spreadsheet/
trunk/
branches/
tags/
これら三つのプロジェクトごとの固有のリポジトリを手に入れるには、 まずリポジトリ全体をダンプします:
$ svnadmin dump /path/to/repos > repos-dumpfile * Dumped revision 0. * Dumped revision 1. * Dumped revision 2. * Dumped revision 3. … $
次に結果のダンプファイルをフィルタに通しますが、各実行時で ただ一つの最上位ディレクトリを含むように指定することで、 三つの新しいダンプファイルを生成することができます:
$ cat repos-dumpfile | svndumpfilter include calc > calc-dumpfile … $ cat repos-dumpfile | svndumpfilter include calendar > cal-dumpfile … $ cat repos-dumpfile | svndumpfilter include spreadsheet > ss-dumpfile … $
この時点で、判断しなくてはなりません。上でできた三つのダンプファイルは 正しいリポジトリですが、元のリポジトリ中にあった通りのパス構成で保存されて います。これはcalcプロジェクト単独のリポジトリを 取得したにもかかわらず、リポジトリはあいかわらずcalc という名前の最上位ディレクトリ名称を持っていることを意味します。 もしtrunk, tags, そして branchesディレクトリそれぞれをリポジトリ のルートディレクトリとしたければダンプファイルを編集してNode-path と Copyfrom-pathヘッダがもうこれからは先頭に calc/というパス部分を持たないようにしなくてはなりません。 同様にcalcディレクトリを作ったダンプデータのセクションを 削除したいでしょう。それは何か以下のような感じになっています:
Node-path: calc Node-action: add Node-kind: dir Content-length: 0
もし最上位ディレクトリを削除するためにダンプファイルを手で編集しようと 考えているなら、利用するエディタが改行文字を自動的にマシン固有の形式に 変換してしまわないことを確認してください(たとえば \r\n を \n などに)。 この変換が起きるとダンプファイルの内容はメタデータと一致しなくなり、使 い物にならなくなってしまいます。
この修正後に残ったファイルを使って新しい三つのリポジトリを作成する ことができ、それぞれのダンプファイルを正しいリポジトリにロード することができます:
$ svnadmin create calc; svnadmin load calc < calc-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : button.c ... done.
…
$ svnadmin create calendar; svnadmin load calendar < cal-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : cal.c ... done.
…
$ svnadmin create spreadsheet; svnadmin load spreadsheet < ss-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : ss.c ... done.
…
$
svndumpfilterの両方のサブコマンドとも 「空の」リビジョンをどのように扱うかを決めることが できます。パスの変更のみを含んでいるようなリビジョンを除外 すれば、空のリビジョンは興味がないか、不要なものであると 考えることができます。svndumpfilterは 以下のコマンドラインオプションを用意しています:
空のリビジョンを生成しません— 単に無視します。
空のリビジョンが削除された場合に(--drop-empty-revsを 利用することによって)、残っているリビジョンのリビジョン番号を変更して リビジョン番号が飛ばないようにします。
空のリビジョンが削除されない場合に、それら空のリビジョンに関する リビジョン属性(ログメッセージ、変更者、日付、カスタム属性、など)を 保存します。そうでなければ、空のリビジョンは元のタイムスタンプと、 このリビジョンはsvndumpfilterによって空にされた ということを示す自動生成されたログメッセージのみを含むことになります。
svndumpfilter は非常に便利で、作業を省力化して くれますが、残念なことにいろいろな問題もあります。まずこのユーティリティー はパスの構文に極端に敏感です。ダンプファイル中のパスが先頭にスラッシュ を含んでいるかどうかに注意してください。Node-path と Copyfrom-pathヘッダを確認する必要がある かも知れません。
… Node-path: spreadsheet/Makefile …
パスの先頭にスラッシュがある場合、svndumpfilter include と svndumpfilter excludeに渡す パスの先頭にスラッシュを含める必要があります(そして、逆にスラッシュが ないなら含めてはいけません)。さらにダンプファイルの先頭のス ラッシュが何かの理由で矛盾している場合には [16] おそらく、すべてをスラッシュ付きにするか、その逆にするような正規化を パスに対して施す必要があります。
また、コピーされたパスは問題を起こすかも知れません。Subversionは リポジトリ中のコピー操作をサポートしていて、ここでは新しいパスは 既に存在するパスからコピーすることによって作成されます。リポジトリの 生存中のどこかでファイルあるいはディレクトリをsvndumpfilter が排除するようなどこかの場所からコピーし、svndumpfilter が含めるような場所にコピーしたかも知れません。ダンプデータに自己一貫性を 保証するためsvndumpfilterは新しいパス— コピーによって作られた任意のファイルの内容を含むような— を表示 する必要がありますが、それはダンプデータストリームから排除された 存在しないようなソースからのコピーの追加としては表示されません。 しかし Subversion のリポジトリダンプ形式はそれぞれのリビジョンで何が変更 されたかを示すだけなので、コピー元の内容は利用不可能です。もし リポジトリ中でこのようなコピーがある可能性がある場合にはもう一度 svndumpfilterに含めるパスと排除するパスを 再考する必要があるかも知れません。
Berkeley DB リポジトリを使っている場合は、バージョン化されたファイルシ ステム構造とデータ全体はリポジトリの db サブディレクトリにあるいくつかのデータベーステーブルに保存されています。 このサブディレクトリは通常の Berkeley DB 環境ディレクトリで、どのよう な Berkeley データベースツールとも組み合わせて使うことができます(これ らのツールに関するドキュメントはSleepyCat のウェブサイトhttp://www.sleepycat.com/にあります)。
通常のSubversion の利用ではこれらのツールは不要です。Subversion リポジトリ に必要なほとんどの機能はsvnadminを使って実行する ことができます。たとえばsvnadmin list-unused-dblogsと svnadmin list-dblogsは Berkeley の db_archiveで提供されている機能のサブセットであり、 svnadmin recoverは db_recover ユーティリティーの普通の状況での利用の仕方を反映したコマンドです。
それでもいくつかの Berkeley DB ユーティリティーは知っていると便利です。 db_dump と db_load プログラムはBerkeley DB データベース のキーと値を表現するカスタム形式ファイルの読み書きを実行します。 Berkeley データベースはマシンアーキテクチャをまたいだ互換性 があるので、この形式はアーキテクチャやOSの違いを意識せずに データベースマシン間で転送するのに便利な方法です。また、 db_statユーティリティーは Berkeley DB 環境の 状態についての有用な情報を表示します。これにはサブシステムの ロックやデータ保存についての詳細統計情報が含まれます。
Subversionリポジトリは一般的にいったん設定してしまえばほとんど 注意を払う必要はありません。しかし、管理者による、いくつかの 補助が必要かも知れません。svnadmin ユーティリティー には以下のような作業を助けるための機能があります。それは
コミットログメッセージの修正。
死んだトランザクションの削除。
「固まってしまった」 リポジトリの復旧。
リポジトリの内容を別のリポジトリに移すこと。
svnadminのサブコマンドで一番よく使われるのは 多分 setlogです。トランザクションがリポジトリに コミットし、リビジョンを表示したとき、新しいリビジョンに関連した ログメッセージは、そのリビジョン自体のバージョン化されない属性として 格納されます。言い換えると、リポジトリはその属性の最後の値だけを 記憶していて、以前のものは捨ててしまいます。
ときどきユーザはログメッセージに間違いを見つけます(スペルミスや 間違った情報など)。もしリポジトリが( pre-revprop-change と post-revprop-change フックを使って。 フックスクリプトの項参照) コミット完了後このログメッセージの変更を受け付けるとすると ユーザは、svn プログラムの propsetコマンドを使ってログメッセージを ネットワーク越しに「修正」 することができます。 (第9章 Subversion リファレンス参照) しかし、情報が永久に失われてしまうことを防ぐため、Subversion リポジトリはデフォルトではそれをさせません。デフォルトは、 バージョン化されない属性は、管理者のみが変更することが できます。
もしログメッセージを管理者が変更する必要がある場合、 svnadmin setlogを使います。このコマンドは リポジトリの指定したリビジョンのログメッセージ(svn:log 属性 )を、用意したファイルから新しい値を読み出し形で変更します
$ echo "Here is the new, correct log message" > newlog.txt $ svnadmin setlog myrepos newlog.txt -r 388
svnadmin setlog コマンドだけでは、リモートクライアント としてバージョン化されていない属性を修正する場合と同じ制約を 受けます—つまりpre-とpost-revprop-change フックはやはり実行され、この仕組みで変更される修正点は反映されて しまいます。しかし管理者はこのような保護機能をsvnadmin setlog コマンドに--bypass-hooksを指定することで回避できます。
しかしフックを回避すると、属性変更、バージョン化されていない属性変更 を追うためのバックアップシステム、などなどに関係した通知 メール も 回避されてしまうことに注意してください。言い換えると、何を、どのように 修正するかについて、非常に注意して実行してください。
別のよくある svnadmin の使い方は終了していない —多分死んでしまった—Subversionトランザクションに関する リポジトリへの問い合わせです。コミットが失敗したとき、普通 トランザクションはきれいに消去されます。つまりトランザクション はリポジトリから削除され、そのトランザクションに(だけに)関連した データも同様に削除されます。 しかし、しばしばトランザクションの掃除が起こらずに失敗することが あります。これにはいくつかの理由が考えられます: 多分クライアントの 操作がユーザによって乱暴に終了されたか、ネットワークの異常などが 処理の途中で起こった場合です。理由にかかわらず、死んだままの トランザクションが残ることはありえます。ディスクをわずかに食うことを のぞけば、このようなトランザクションは全く無害です。それでも 潔癖な管理者はこのようなトランザクションを削除したいと思うかも 知れません。
svnadminのlstxns コマンド を使って、その時点での未完了のトランザクションの名前の一覧表示 することができます。
$ svnadmin lstxns myrepos 19 3a1 a45 $
出力結果のそれぞれの項目はsvnlook (とその--transaction オプション)で 使うことができ、誰がトランザクションを作り、それは いつで、どのような変更がトランザクションに起きたか、を 知ることができます。 —言い換えると、そのトランザクションは削除対象として 安全な候補なのかどうか、ということをです。もしそうなら、 トランザクションの名前をsvnadmin rmtxns に渡すことができ、そのトランザクションはきれいに削除 されます。rmtxns サブコマンドは、 lstxnsの出力をそのまま入力として とることもできます!
$ svnadmin rmtxns myrepos `svnadmin lstxns myrepos` $
このような二つのサブコマンドを使う場合、リポジトリを一時的に クライアントからアクセスできなくする必要があります。 これで誰もあなたがクリーンアップを始める前に正しいトランザクション を開始できなくなります。以下は、リポジトリ内の未解決のトランザクション のそれぞれについての情報をすばやく生成するためのちょっとした スクリプトです:
例 5.1. txn-info.sh (未解決トランザクションの表示)
#!/bin/sh
### Generate informational output for all outstanding transactions in
### a Subversion repository.
REPOS="${1}"
if [ "x$REPOS" = x ] ; then
echo "usage: $0 REPOS_PATH"
exit
fi
for TXN in `svnadmin lstxns ${REPOS}`; do
echo "---[ Transaction ${TXN} ]-------------------------------------------"
svnlook info "${REPOS}" --transaction "${TXN}"
done
このスクリプトを /path/to/txn-info.sh /path/to/reposのように して実行できます。出力は基本的にはsvnlook info 出力のいろいろな断片をつないだようなものになります。 (svnlookの項参照), 以下のような 感じです:
$ txn-info.sh myrepos ---[ Transaction 19 ]------------------------------------------- sally 2001-09-04 11:57:19 -0500 (Tue, 04 Sep 2001) 0 ---[ Transaction 3a1 ]------------------------------------------- harry 2001-09-10 16:50:30 -0500 (Mon, 10 Sep 2001) 39 Trying to commit over a faulty network. ---[ Transaction a45 ]------------------------------------------- sally 2001-09-12 11:09:28 -0500 (Wed, 12 Sep 2001) 0 $
長く放置されているトランザクションは普通は何かに失敗したか、コミットを 中断されたかのどちらかです。トランザクションの日付スタンプは 役に立つ情報を与えてくれます — たとえば 9 ヵ月も前に始まった 操作がいまだに有効である可能性など、いったいどの程度あるのでしょうか?
簡単に言って、トランザクションのクリーンアップの決定は、無分別に やる必要はありません。いろいろな情報源—Apacheのエラーログや アクセスログ、成功したSubversionのコミットログ、などなど—が どうしたら良いかを決める上で役に立ちます。最後に、管理者はしばしば 死んだトランザクションの所有者と思われる人と、(メールなどで)その 死にかかったトランザクションの状態を確認することができます。
ここ数年で記憶装置のコストは非常に低くなってきた一方で、ディスクの利用方法 は、大量のデータをバージョン管理するために、システム管理者にとっては、やはり 依然として考慮すべきことです。動作中のリポジトリによって消費される追加の 領域はオフラインでバックアップすることが必要な領域でもあり、バックアップ のスケジュール管理を考えると、何倍かになるでしょう。Berkeley DB リポジ トリを使う場合には、データ保管の仕組みは複雑なデータベースシステムである ので、データのどの部分をオンラインのままに残し、どの部分にバックアップが 必要で、どの部分を安全に削除できるか、ということについて理解しておくことには 意味があります。 この節は Berkeley DB だけに関係しています。FSFS リポジトリには削除したり、 調整が必要な特殊な追加データはありません。
最近まで、Subversion リポジトリに関して最も多くディスクを消費する部分は Berkeley DB が実際にデータベースファイルを修正する前に前もっと書き込むための ログファイルの領域でした。これらのファイルはデータベースのある状態から 別の状態への変化の経緯にそったすべての操作を記録します— データベース ファイルはある特定の時刻にその状態を反映される一方でログファイルはその前後 の状態のすべての本稿を含んでいます。そのようなわけでログファイルは非常に 早いスピードでサイズを増やします。
ありがたいことに、Berkeley DB のリリース 4.2 からデータベース環境は 特に外部操作することなしに未使用のログファイルを削除する能力を持つ ようになりました。Berkeley DB バージョン 4.2 かそれ以降でコンパイルされた svnadminはこの自動的なログファイルの削除が設定 されています。この機能を有効にしたくない場合には単に svnadmin createコマンドで--bdb-log-keep を渡してください。これを忘れたり、後で変更したい場合には、単に リポジトリのdbディレクトリ中にあるDB_CONFIG を編集して、set_flags DB_LOG_AUTOREMOVEディレクティブをコメントアウトしてから この変更を強制的に有効にするためにそのリポジトリに対してsvnadmin recover を実行してください。データベースの設定についての詳細は Berkeley DB の設定の項を参照してください。
このような自動ログファイル削除の仕組みを利用しなければ、 リポジトリを利用するにつれてログファイルは蓄積されていきます。 それてこれは実際にデータベースシステムであれば当然付いている機能です— ログファイル以外に何も残っていないような状況でデータベース全体を再構成する ことができるようになっていなくてはならず、そのようなログファイルは データベースの壊滅的な破壊からの復旧で利用できなければならないからです。 しかし普通は Berkeley DB で既に利用されていないログファイルをアーカイブし、 その後ディスクから削除することで領域を広げようとするでしょう。利用していない ログファイルの一覧を見るにはsvnadmin list-unused-dblogsコマンドを使ってください:
$ svnadmin list-unused-dblogs /path/to/repos /path/to/repos/log.0000000031 /path/to/repos/log.0000000032 /path/to/repos/log.0000000033 $ svnadmin list-unused-dblogs /path/to/repos | xargs rm ## disk space reclaimed!
リポジトリのデータサイズをできるだけ小さくするために Subversion は リポジトリに対して差分化(あるいは「 差分記憶」) の処理をします。差分化は別のデータの部分に対する 差分の集まりをひとつのデータの塊として表現するものです。二つのデータ が非常に似ていればこの差分化は差分化されたデータの記憶領域を節約します —もとのデータサイズと同じだけの領域を確保するのにくらべて小さく なります。いわば、「以下の変更点をのぞけば、他の点についてはここに あるデータのままですよ」という表現に必要なだけの領域で済みます。 具体的に言うと、ファイルの新しいバージョンがリポジトリにコミットされる たび、Subversion は前のバージョン(実際には前のバージョンのいくつか)を この新しいバージョンに対する差分として表現します。その結果、大きくなり がちなリポジトリデータ—つまりバージョン化されたファイルの内容 —の大部分を、もとの「完全なテキスト」として 保存するよりはずっと小さなサイズで格納することができます。
差分化の対象となるすべての Subversion リポジトリデータは単一の Berkeley DB データベースファイルに保存されるので保存されているデータのサイズを 小さくしたからといって必ずしもデータベースファイル自身のサイズを減らす ことにはなりません。しかし Berkeley DB はデータベースファイル中の未使用 領域の内部的な記録を保存しておりデータベースファイルのサイズを拡張する 前にそのような領域をまず利用します。そのため差分化は直接に空間の節約につな がりはしなくても今後のデータベースサイズが拡大するスピードを有効に押える ことができます。
Berkeley DBの項で触れたように、Berkeley DB リポジトリは正しく閉じられなかった場合には中間的な状態に固まってしまう ことがあります。こうなった場合管理者はデータベースを以前の一貫した状態 にまで戻してやる必要があります。
リポジトリ中のデータを保護するため Berkeley DB はロックのしくみ を利用しています。このしくみはデータベースの特定のが同時に複数のデータベー スアクセスによって修正されないことを保証するためのもので、それぞれのプ ロセスから見たときには、読み込み時にはデータは正しい状態にあるように見 えます。データベース中のどこかを変更する必要ある場合にはまず対象となる データがロックされていないかどうかを確認します。もしロックされていなけ ればそのプロセスはデータをロックし、必要な修正を加え、そのデータに対す るロックを外します。他のプロセスはデータベースの内部に引き続きアクセス できるようになる前にロックファイルが削除されるまで待たされます。
Subversionリポジトリを使う上で、致命的なエラー(ディスクがいっぱいに なったり、メモリがなくなったり)や、割り込みによって、データベースに かけたロックを削除する機会をなくしてしまうことがあります。その結果 バックエンドのデータベースは「固まって」しまいます。 こうなったときには、リポジトリへのどのようなアクセスも永久に待たされる ことになってしまいます。(というのは、すべての新しいアクセスはロック が解除されるのを待ちますが、それは決してやってこないからです)
まず、そういうことがリポジトリに起こっても、悲鳴をあげないで ください。Berkeley DBのファイルシステムはデータベーストランザクション とチェックポイント、それに事前ジャーナル書き込みの仕組みをうまく 利用していて、本当に破滅的な出来事以外は [17] データベース環境を永久に葬り去ることはできないことを保証します。 十分神経質なリポジトリ管理者は何んらかの方法でリポジトリデータの オフラインバックアップをとっているかも知れませんが、バックアップテープを リストアしてくれとシステム管理者を呼ぶのはまだです。
次に、以下の手順を使って、リポジトリの「復旧」 を試してみてください:
リポジトリにアクセスしている(あるいはしようとしている)プロセスが 一つもないことを確認してください。ネットワークアクセス可能な リポジトリでは、これは Apache HTTP サーバをシャットダウンする ことも意味します。
リポジトリを所有し、管理しているユーザになってください。 これは重要ですが、実行時と同様、復旧時に間違ったユーザで作業する ことによってもリポジトリファイルのパーミッションが変更されて しまうかも知れないからです。これによって実際には「復旧」 したのにアクセス不能のままになってしまう可能性があります。
svnadmin recover /path/to/reposコマンドを 実行してください。以下のような出力が表示されると思います:
Repository lock acquired. Please wait; recovering the repository may take some time... Recovery completed. The latest repos revision is 19.
このコマンドは完了までに数分かかることもあります。
Subversionサーバの再起動
この方法はほとんどのリポジトリロックを解消します。このコマンドは 単に rootになるのではなく、データベース を所有し、管理しているユーザで実行することに注意してください。 復旧作業は、傷を負ったいろいろなデータベースファイル からの再作成の作業も含みます。(たとえば共有メモリ領域などです) root での復旧は、root が 所有しているファイルを作成することで、これはリポジトリへの 接続状況が復旧した後でも通常のユーザはこれに対してアクセスする ことができないことを意味します。
もしいま述べた作業が、何かの理由でうまくリポジトリを正常に 戻せない場合、二つのことをすべきです。まず、壊れたリポジトリを どけて、最後のバックアップをリストアします。それからSubversion のユーザリストにメールします。(これは、 <users@subversion.tigris.org>です)このとき問題点を詳しく 説明してください。データの一貫性は、Subversion開発者にとって 非常に高いプライオリティです。
Subversionファイルシステムはさまざまなデータベーステーブルに分散された データを持ちますが、これは一般的にはSubversion開発者だけが知っている (て、興味のある)ことです。しかし、すべての、あるいは一部のデータを 一つの、持ち運びに便利な単純なファイル形式にまとめたいことがあります。 Subversion はそのような仕組みをsvnadmin サブコマンド の組によって実装しています: dump と loadです。
Subversionリポジトリをダンプしたりロードしたりする一番よくある理由は Subversion自身の変更にあります。Subversion が完成に近づくにつれ、 バックエンドデータベースのスキーマ変更によっては リポジトリの前のバージョンとの互換性がなくなってしまいます。 ダンプとロードが必要になる他の理由としては、Berkeley DB を他の OS や CPU アーキテクチャに以降する場合、あるいはBerkeley DB と FSFS バックエ ンド間を切り替えて使う場合です。このために推奨されている作業ステップは比較 的簡単です:
現行 バージョンのsvnadmin を使ってリポジトリをダンプファイルにダンプしてください。
Subversionの新しいバージョンへのアップグレード。
古いリポジトリをどけて、新しい空のリポジトリをそこに作りますが、 これには新しい svnadmin を使ってください。
もう一度新しいsvnadminを 使って、ダンプファイルを、それぞれ作ったばかりのリポジトリに ロードしてください。
古いリポジトリから新しいものに必要なカスタマイズ部分を すべてコピーしてください。これにはDB_CONFIG ファイルと、フックのスクリプトが含まれます。新しいリリースの Subversionのリリースノートに注意して、最後のアップグレードでフック や設定オプションに変更がないかどうかを見てください。
もし移行によってリポジトリが別の URL からアクセスされるようになった場 合(例えば別のコンピュータに移したり、別のスキーマを経由して アクセスしたりするような場合)、おそらくユーザには既存の作業 コピー上でsvn switch --relocateを実行する ように言わなくてはならないかも知れません。 svn switchを見てください。
svnadmin dump は、リポジトリリビジョンのある 範囲を出力しますが、それはSubversionのカスタムファイルシステム ダンプ形式になっているものです。ダンプ形式は標準出力に表示され、 進行状況などのメッセージは標準エラー出力に表示されます。これで 出力をファイルにリダイレクトすることができ、その一方でステータス の出力については端末ウィンドウ上で見ることができます。たとえば:
$ svnlook youngest myrepos 26 $ svnadmin dump myrepos > dumpfile * Dumped revision 0. * Dumped revision 1. * Dumped revision 2. … * Dumped revision 25. * Dumped revision 26.
処理の最後で、指定した範囲のリポジトリリビジョンのデータすべて が保存された一つのファイル(前の例では、dumpfile) を手に入れることができます。 svnadmin dumpは他の「読み出し」プロセス(たとえばsvn checkout など)がやるのと同じような 方法でリポジトリからリビジョンツリーを読み出すことに 注意してください。そのため、このコマンドはいつでも安全に実行できます。
組になったもう一方のサブコマンドであるsvnadmin loadは、標準入力を、Subversionリポジトリのダンプファイルと して解析し、ダンプされたリビジョンを目的のリポジトリに再現します。 それはまた経過情報などを返しますが、こちらは標準出力に 表示します:
$ svnadmin load newrepos < dumpfile
<<< Started new txn, based on original revision 1
* adding path : A ... done.
* adding path : A/B ... done.
…
------- Committed new rev 1 (loaded from original rev 1) >>>
<<< Started new txn, based on original revision 2
* editing path : A/mu ... done.
* editing path : A/D/G/rho ... done.
------- Committed new rev 2 (loaded from original rev 2) >>>
…
<<< Started new txn, based on original revision 25
* editing path : A/D/gamma ... done.
------- Committed new rev 25 (loaded from original rev 25) >>>
<<< Started new txn, based on original revision 26
* adding path : A/Z/zeta ... done.
* editing path : A/mu ... done.
------- Committed new rev 26 (loaded from original rev 26) >>>
load の結果、新しいリビジョンがリポジトリに追加されます—これは 通常の Subversion クライアントからリポジトリに対してコミットをする のと同じ効果があります。またやはりコミットと同様に load 処理中に おきるそれぞれのコミットの前後で実行するフックスクリプトを使うことも できます。 svnadmin load に --use-pre-commit-hook と --use-post-commit-hook オプションを渡すことでロードされるリビジョンごとに Subversion に 対してそれぞれ pre-commit と post-commitのフックスクリプトを実行する ように指示できます。これで例えば、通常のコミット時と同様の妥当性チェック のようなステップをロードされるリビジョンごとに保障するような使い方が できます。もちろんこのようなオプションの利用には注意が必要です— post-commit フックスクリプトで新しいコミットごとにメーリングリストに対して email を送信するようになっていた場合、リビジョンがロードされるたびに リストに数え切れないくらいの email を流したいとは思わないでしょう ! フックスクリプトについては フックスクリプトの項 により詳しい情報があります。
svnadmin は標準入力と標準出力をリポジトリのダンプ とロード処理に使うので、気の利いた人は、以下のようなやり方を試す こともできます(おそらく、パイプの両側のsvnadmin は、異なるバージョンであるかも知れません):
$ svnadmin create newrepos $ svnadmin dump myrepos | svnadmin load newrepos
デフォルトではダンプファイルは非常に大きくなります—リポジトリ 自体よりもずっと大きくなるでしょう。理由はすべてのファイルのすべての バージョンは、ダンプファイル中では完全なテキストとして表現される からです。これはダンプデータをパイプ経由で他のプロセス(圧縮プログラム や、フィルタープログラム、あるいはロードプロセスのようなもの)に送る場合には もっとも早く単純な方法です。しかし長期保存用にダンプファイルを作成する のであれば --deltasスイッチを使ってディスク領域を節約 したほうが良いでしょう。このオプションを使うと引き続くリビジョン間の ファイルは圧縮された形のバイナリ差分として出力されます—これはちょ うどリポジトリ中に保存されたリビジョンファイルと同じような形になります。 このオプションを使うと処理は遅くなりますが結果のダンプファイルは 元のリポジトリにかなり近いサイズにまでなります。
前に注意したようにsvnadmin dump はリビジョンの範囲を出力します。 --revision オプションを使えば、一つの リビジョンのダンプや、リビジョン範囲のダンプができます。 このオプションを省略すれば、すべての存在するリポジトリ リビジョンがダンプされます。
$ svnadmin dump myrepos --revision 23 > rev-23.dumpfile $ svnadmin dump myrepos --revision 100:200 > revs-100-200.dumpfile
Subversionはそれぞれの新しいリビジョンをダンプするのでその出力には 後で実行されるローダが前のリビジョンを元にしてそのリビジョンを再 作成するのに必要な十分な情報があります。 言い換えると、ダンプファイル中でどのようなリビジョンが指定されても リビジョン中で変更のあったアイテムのみがダンプに現れるということ です。この規則の唯一の例外は、現在のsvnadmin dump がダンプする最初のリビジョンです。
デフォルトでは、Subversionは前のリビジョンに対する単なる差分として 最初のダンプリビジョンを表現することはありません。この理由の一つは 、ダンプファイルには直前のリビジョンがないからです! 二番目にSubversion はダンプデータがロードされるリポジトリの状態について何も知らない からです。(もしロードが起こるとすれば、ですが。) svnadmin dump の個別の実行の出力が自己充足して いるのを保証するため、最初のダンプリビジョンはデフォルトでは すべてのディレクトリ、ファイル、リポジトリにあるそのリビジョンの属性 の完全な表現になっています。
しかし、このデフォルトの振る舞いを変えることもできます。リポジトリを ダンプするときに--incremental オプションを追加すると svnadmin は最初のダンプリビジョンとリポジトリ中の 直前リビジョンとの差分をとろうとします。残りのすべてのダンプ されるリビジョンにも同じ方法で扱います。 それからダンプ範囲にある残りのリビジョンが出力するのと同じように 最初のリビジョンを—リビジョン中に起こる変更だけを 考慮して出力します。 この利点は大きな一つのダンプファイルのかわりに、ロードに成功するような 小さないくつものダンプファイルを作ることができることです。 こんな感じです :
$ svnadmin dump myrepos --revision 0:1000 > dumpfile1 $ svnadmin dump myrepos --revision 1001:2000 --incremental > dumpfile2 $ svnadmin dump myrepos --revision 2001:3000 --incremental > dumpfile3
これらのダンプファイルは以下のようなコマンドの流れで 新しいリポジトリ中にロードされます:
$ svnadmin load newrepos < dumpfile1 $ svnadmin load newrepos < dumpfile2 $ svnadmin load newrepos < dumpfile3
--incremental オプションを使った別の かっこいい方法は、既に存在しているダンプファイルに 新しいダンプリビジョン範囲を追加することです。たとえば post-commit フックがあり、それは単に フックをトリガーするような一つのリビジョンのリポジトリ ダンプを追加するものです。あるいは最後にスクリプトを実行した時点より 後にリポジトリに追加されたすべてのリビジョンに対してのダンプファイルを追加する ようなスクリプトを実行するかも知れません。このように利用することで svnadminの dump と load コマンドは 価値のある手段となりますが、これによって、リポジトリの変更を 時間をかけてバックアップして、システムクラッシュや、他の壊滅的な出来事に そなえるというわけです。
ダンプ形式はまたさまざまな異なるリポジトリの 内容を単一のリポジトリにマージするために利用することもできます。 svnadmin loadの --parent-dirオプションを使って ロードプロセス用の新たな仮想ルートディレクトリを指定することが できます。これは、もしcalc-dumpfile, cal-dumpfile, そして ss-dumpfileという三つのリポジトリ のダンプファイルがある場合、最初にそれらすべてを保持するような 新しいリポジトリを作ることができることを意味します:
$ svnadmin create /path/to/projects $
それから三つの以前のリポジトリのそれぞれの内容を含んだ 新しいディレクトリをリポジトリ中に作ります:
$ svn mkdir -m "Initial project roots" \
file:///path/to/projects/calc \
file:///path/to/projects/calendar \
file:///path/to/projects/spreadsheet
Committed revision 1.
$
最後に個々のダンプファイルを新しいリポジトリのそれぞれの 場所にロードします:
$ svnadmin load /path/to/projects --parent-dir calc < calc-dumpfile … $ svnadmin load /path/to/projects --parent-dir calendar < cal-dumpfile … $ svnadmin load /path/to/projects --parent-dir spreadsheet < ss-dumpfile … $
Subversion リポジトリダンプ形式の利用方法について最後にもう一つだけ 触れます— 異なる保存の仕組みやバージョン管理システムから データを変換する方法です。これができる理由はダンプファイル形式は ほとんどの部分が可読であるためです。 [18] このファイル形式を 使うと、比較的簡単に一般的な変更点のセットを表現することができます— それぞれの変更は新しいリビジョンとして扱われます。 事実、cvs2svnユーティリティー(CVS から Subversion へのリポジトリ変換の項参照)は、CVS リポジトリの内容を表現するのに ダンプ形式を使うので、その内容を Subversion リポジトリに取り込むことができます。
現代的なコンピュータが生まれてから技術的には非常に発展してきたものの、 残念なことに、一つのことだけは間違いなく真実です—ときどき、ものごとは まったく台無しになってしまう、ということです。停電、ネットワーク切断、 RAMの破壊、ハードディスクのクラッシュは、魔物以外の何者でもありません。 運命は最も優れた管理者にさえ降りかかるのです。それで、とても重要な トピックに行き着きます—どうやってリポジトリのバックアップを とるか、です。
一般的に、Subversionのリポジトリ管理者にとって、二つのバックアップ方法が あります—差分バックアップと、フルバックアップです。この章の前の節で どうやってsvnadmin dump --incremental を使って差分 バックアップをとるかを議論しました (リポジトリの移行の項参照)。本質的にこのアイディアは最後に バックアップをとってから起きたリポジトリの変更部分だけをバックアップ する方法です。
リポジトリのフルバックアップは文字通りリポジトリディレクトリ全体の 複製を作ることです(これはBerkeleyデータベース環境も含まれます) さて、一時的にリポジトリに対するすべてのアクセスを禁止しなければ、 単純な再帰的なディレクトリコピーの実行は、不完全なバックアップを作って しまう危険を持っています。というのは誰かが並行してデータベースに書き込んで いるかも知れないからです。
Berkeley DB の場合、Sleepycat のドキュメントは正しいバックアップコピーを 保証するようにデータベースファイルをコピーする場合の順序が書いて あります。同様の順序が FSFS データにもあります。しかしこのよう なプログラムを自分で書く必要はありません。Subversion 開発チー ムがすでにしているからです。 hot-backup.py スクリプトはSubversionのソース パッケージのtools/backup/ ディレクトリにあります。 リポジトリパスとバックアップ位置を指定すると、hot-backup.py —それは単にsvnadmin hotcopyコマンドのより賢い ラッパープログラムでしかありませんが— は、動作中のリポジトリをハックアップするのに必要なステップを実行します —あなたにリポジトリアクセスを禁止することなしに、です— ついでに、動作中のリポジトリから、死んでいる Berkeley ログファイルを きれいに削除します。
差分バックアップがあるとしても、規則的にこのプログラムを実行したく なるかも知れません。たとえばhot-backup.py を プログラムスケジューラに追加しようと考えるかも知れません (Unix であればcrond のようなもの)。 あるいは、細かい粒度のバックアップが好きなら、hot-backup.py を呼ぶような、post-commitフックスクリプトを書くこともできます。 (フックスクリプトの項参照)。これは新しいリビジョンが 作られるたひにリポジトリの新しいバックアップができる方式です。 単に、以下を動作中のリポジトリディレクトリにある hooks/post-commit に追加してください:
(cd /path/to/hook/scripts; ./hot-backup.py ${REPOS} /path/to/backups &)
結果のバックアップは、完全に機能するSubversionリポジトリで、現行の リポジトリが何かひどいことになったときには、置き換えて使うことが できるものです。
両方のバックアップ方法にはそれぞれ利点があります。一番簡単なのは フルバックアップで、それは常に現行リポジトリの完全なコピーです。 繰り返しになりますが、何かまずいことが動作中のリポジトリに起きた 時には、単純な再帰的なディレクトリコピーでこのバックアップを 復元することができます。残念なことに、もしリポジトリの複数のバックアップ を管理している場合、このようなフルコピーは、実行中のリポジトリと 同じくらい、それぞれがディスクを食うということです。
リポジトリダンプ形式を使った差分バックアップはデータベーススキーマ が引き続くSubversion自身のバージョン間で変更されるときには非常に 役に立ちます。リポジトリの完全なダンプとロードは一般的に リポジトリを新しいスキーマにアップグレードすることが必要です。 そのような作業の半分(つまり、ダンプの部分)については既に 済んでいるのでとても便利です。不幸にも、差分バックアップの 作成—そしてそのリストア—は長い時間がかかりますが、 それは、それぞれのコミットがダンプファイル、またはリポジトリの 中で、実際に再実行されるからです。
どちらのバックアップの場合も、リポジトリ管理者は どのようにしてバージョン化されない属性への変更がバックアップに影響を 与えるかに注意する必要があります。このような変更は新しいリビジョンを それ自体で作り出すわけではないので、post-commitフックを呼び出すきっかけ にはならず、pre-revprop-chage や post-revprop-change フックのきっかけ にすらならないでしょう。 [19] そして、時間の順序に沿わないでリビジョン属性を変更することができる —いつでも、どのリビジョン属性を変更することができます— ので、最後のいくつかのリビジョンの差分バックアップはそれ以前の バックアップの一部として行われたリビジョン属性の修正は取り入れる ことができません。
一般的に言って、本当に人間離れした潔癖さを持った人だけが完全なリポジトリ のバックアップを必要とするのでしょう。つまり、コミットが起こるたびに バックアップをとるわけです。しかし、そのリポジトリが相対的に細かい 粒度(コミットごとのメールなど)と共に、何か別の冗長性の仕組みを持って いるのであれば、データベースのホットバックアップはリポジトリ管理者が システム全体の日次バックアップの一環として導入したいと考えるものかも知れません。 ほとんどのリポジトリでは、コミット メール をアーカイブするだけで復旧元 データとしての十分な冗長性を持っています。少なくとも最近のいくつかのコミット についてはそうです。しかしデータはとにかくあなたのものです— 必要なだけ保護するのに越したことはありません。
しばしば、リポジトリのバックアップに対する最良の方法は、分散 させることです。フルバックアップと差分バックアップに、コミット メールのアーカイブを追加することができます。たとえばSubversion 開発者は、Subversionソースコードリポジトリを、新しいリビジョン が作られるたびにバックアップします。そして、すべてのコミットと 属性変更の通知メールをアーカイブしてとっておきます。同様の方法を とってください。ただし、必要な範囲で、便利さと安全性の微妙なバラ ンスをとってください。そして、このようなことを全部やっても、運命 の鉄拳からハードウェアを守ることはできないことに注意してください。 [20] バックアップは確かにそのような試練の時からあなたを救うはずです。
リポジトリが作られて設定されれば、後は使うだけです。もし 既にデータの集まりを持っていて、それをバージョン管理したい 場合は、きっとsvnクライアントプログラムの import サブコマンドを使いたいと思うでしょう。 しかしそうする前に、リポジトリについて長期的な視点で注意深く 考えるべきです。この節では、リポジトリのレイアウトをどのように 計画するか、そしてそのレイアウトの中にどのようにデータを配置する のが良いかについて、少しアドバイスします。
Subversionを使うと、あなたは情報を失うことなしにバージョン化されたファイルや ディレクトリをあちこちに移動することができますが、そうすることは、 データが特定の場所にあることを期待している、ときどきリポジトリにアクセス する人たちの作業を中断させてしまうかも知れません。先のこともちょっとは 考えてください。バージョン管理下にデータを置く前に、前もって計画を たててください。リポジトリの内容を、最初にうまく「レイアウト」 しておけば、あとで頭を抱えることがなくなります。
Subversionリポジトリを設定するときに考えておくと良いことがいくつか あります。あなたが、リポジトリ管理者としていくつかのプロジェクトのバージョン 管理システムのサポート責任者になったとしましょう。最初の判断は 複数プロジェクトに対して一つのリポジトリを使うか、プロジェクトごとに リポジトリを用意するか、その両者の折衷案でいくかです。
複数プロジェクトのために一つのリポジトリを使うことにはいくつか 利点があまりす。一番はっきりしているのは、重複した保守作業が不要だと いうことです。一つのリポジトリは、一組のフックスクリプト、一つの 定期バックアップ、Subversionのリリースが両立不可能な新しいバージョンに なったときの、一回のダンプとロード、しか必要ありません。 また、プロジェクト間のデータ移動は簡単ですし、履歴バージョン情報を 失うことなしにやることができます。
一つのリポジトリを使うデメリットは、異なるプロジェクトは異なる コミットメーリングリストを持っていたり、異なる認証、許可などが 必要であるかも知れないことです。 また、Subversionはリポジトリグローバルなリビジョン番号を使っている ことに注意してください。人によっては、変更が自分のプロジェクトに 何もないのに、他のプロジェクトが活発に新しいリビジョンを追加する ことによって、最新リビジョン番号がカウントアップされていくのが 好きではないかも知れません。
折衷策をとることもできます。たとえば、お互いに どの程度深く関係しているかによってプロジェクトをグループ化する ことができます。それぞれのリポジトリにいくつかのプロジェクトを持たせる ことで、少ない数のリポジトリを管理することもできます。この方法では データを共有したいプロジェクトは簡単にそうすることができますし、 新しいリビジョンがリポジトリに追加されると、開発者は そのような新しいリビジョンは、自分のプロジェクトか、少なくともそれに 関係しているプロジェクトの誰かがやったものだということがわかります。
リポジトリに関係してどのようにプロジェクトを編成するかを決めたあとは 多分、リポジトリ自身のディレクトリ構成を考えたいと思うでしょう。 Subversionは普通のディレクトリコピーをブランチ化にもタグ付けにも 使うので(第4章 ブランチとマージ参照)、Subversionのコミュニティ では、以下のようなディレクトリ構成を推奨しています; プロジェクトルート—プロジェクトに関連 したデータのある「最上位」ディレクトリのこと—ごとに リポジトリの場所を選択します; 次いでそのルートの 下に三つのサブディレクトリを作ります: trunk これはプロジェクトの主な開発が行われるディレクトリです; branches これは主な開発ラインから分岐したさまざまな名前の付いたブランチを作る ための場所です; tagsこれは作成され、削除される かも知れませんが、決して修正はされないようなブランチを入れるための ディレクトリです。 [21]
たとえば、リポジトリが以下のようであるとして:
/
calc/
trunk/
tags/
branches/
calendar/
trunk/
tags/
branches/
spreadsheet/
trunk/
tags/
branches/
…
それぞれのプロジェクトルートがリポジトリ中のどこにあるかは問題には なりません。もしリポジトリに唯一のプロジェクトがある場合は それぞれのプロジェクトルートを置くための論理的な場所はプロジェクト ごとのリポジトリのルートになります。もし複数のプロジェクトが ある場合は、リポジトリ内部のグループ中にそれを配置したいかも知れません、 おそらく同じサブディレクトリ中の似たような目標や共有するコードと 一緒にプロジェクトを置くか、あるいは名前の辞書順にグループ化するか、 などです。配置は以下のようになるでしょう:
/
utils/
calc/
trunk/
tags/
branches/
calendar/
trunk/
tags/
branches/
…
office/
spreadsheet/
trunk/
tags/
branches/
…
良いと思われる方法でリポジトリをレイアウトしてください。 Subversionはレイアウトの構成について何も仮定しません— Subversionは、ディレクトリであってディレクトリ以外の何者でも ありません。結局、リポジトリのレイアウトは、それを利用する人々の 必要に応じたふさわしい方法を選んでください。
リポジトリ中でのプロジェクトのレイアウトが決まったら、 そのレイアウトの形にリポジトリを構成して、プロジェクトの初期データを ロードしたいと思うでしょう。これにはいろいろな方法があります。 一つ一つリポジトリレイアウトに従ってディレクトリを 作るのに、svn mkdir コマンドを使うことができます ( 第9章 Subversion リファレンス参照)。 もっと手っ取り早いのは、svn import コマンドを 使うことです。(svn importの項参照) 最初にディスクの一時的な場所にレイアウトを作っておいて、その全体を 一回のコミットでリポジトリにインポートすることができます:
$ mkdir tmpdir $ cd tmpdir $ mkdir projectA $ mkdir projectA/trunk $ mkdir projectA/branches $ mkdir projectA/tags $ mkdir projectB $ mkdir projectB/trunk $ mkdir projectB/branches $ mkdir projectB/tags … $ svn import . file:///path/to/repos --message 'Initial repository layout' Adding projectA Adding projectA/trunk Adding projectA/branches Adding projectA/tags Adding projectB Adding projectB/trunk Adding projectB/branches Adding projectB/tags … Committed revision 1. $ cd .. $ rm -rf tmpdir $
svn listコマンドでインポート結果を 確認することができます:
$ svn list --verbose file:///path/to/repos
1 harry May 08 21:48 projectA/
1 harry May 08 21:48 projectB/
…
$
骨組みとなるレイアウトができて、もし既にインポートしたい データが存在しているならそれをリポジトリにインポートすることができます。 これにも、やはりいろいろな方法をとることができます。 svn importを使うかも知れません。新しいリポジトリから 作業コピーをいったんチェックアウトして、作業コピー中でデータを移動したり 編成しなおしてから、svn add とsvn commit コマンドを使うこともできます。しかし、いったんそのような 話を始めると、もう既にリポジトリ管理については議論しません。 もし、まだsvn クライアントプログラムに なじみがないのなら、 第3章 同伴ツアーを参照してください。
ここまでで、あなたは、どうやってSubversionリポジトリを作成し、設定するか についての基本的な理解ができたはずです。この作業を助けるさまざまなツール を紹介しました。そして、章全体を通じて、管理者がよくハマりそうなことをあげ、 どうやってそれを避けるかを議論しました。
あとは、リポジトリに、どのようなデータを入れ、それをネットワーク越しに 利用できる形にするかを考えるだけです。次の章ではネットワーク利用について 詳述します。
[13] こう書くと、なんだかとても高尚なことのように思えますが、 みんなのデータがある作業コピーの背後で起きている神秘の領域に 興味を持つ人なら、誰でも、という意味です。
[14] Jack Repenning が何も文句を言わないのなら「fuzz-fuzz」 と発音することになっています。
[15] ところで、そのような設計は意図したものです。バグ ではありません。
[16] svnadmin dump は先頭スラッシュに関して一貫した ポリシーがありますが— 付けないようにするというものです— データをダンプするほかのプログラムはそれほど一貫していません。
[17] たとえば: ハードディスク + 強い電磁場 = 破滅。
[18] Subversionのリポジトリダンプ形式は、RFC 822形式によく 似ていて、ほとんどのメール で利用されているのと同じ形式です。
[19] たとえばsvnadmin setlogは、 とにかくフックインターフェースを迂回するような方法で呼び出されるのでした。
[20] ご存知でしょうか— 彼女のすべての「きまぐれ」をあらわす集合名詞です。
[21] trunk, tags, branches の三つのファイルの全体を 「TTB ディレクトリ」 と呼ぶことがあります。
目次
Subversion リポジトリはfile:///方式でリポジトリのある 同じマシン上で実行されている複数のクライアントから同時にアクセスすること ができます。しかし典型的なSubversion の設定はオフィス全体—あるいは 全世界にあるコンピュータ上のクライアントからアクセスされる一台のサーバ上 で行います。
この章ではリモートクライアントを使ってホストマシンの外部にさらされる 形のSubversionリポジトリの作り方についての説明です。ここでは現在 Subversion で利用することのできるサーバの仕組みを説明し、その設定方法と 利用方法について説明します。この章を読んだ後であればどのタイプのネットワーク 設定が自分のニーズとって正しいものであるかを決め、どうやれば自分のホスト コンピュータ上でその設定が有効になるかについて理解できるはずです。
Subversion は抽象的なネットワーク層の設計を含んでいます。これはリポジトリ に対してどのようなタイプのサーバプロセスからもアクセスできるようにプログラムを 作ることができ、クライアントの「リポジトリアクセス」API を使えば、プログラマは それに関連したネットワークプロトコルで通信することのできるプラグインを書くこと ができる、ということを意味します。理論的には Subversion は無数のネットワーク 実装が可能なはずです。ただしこれを書いている現時点では実際には二つの サーバがあるだけです。
Apache は非常に有名なウェブサーバです; mod_dav_svnモジュール を使えば Apache はリポジトリにアクセスすることができ、WebDAV/DeltaV プロトコル 経由でクライアントにもリポジトリを利用させることができます。これは HTTP の 拡張の一つです。もう一つの方法は svnserveです: これは 非常に小さい、スタンドアロンのサーバプログラムでクライアントとの間で独自の プロトコルを使って通信します。Table 6-1 に二つのサーバの比較をのせまし た。
Subversion はオープンソースプロジェクトの性質上、どのようなタイプのサーバ も「最重要なもの」であるとか、「公式のもの」 である として勧めたりすることはありません。またどのようなネットワーク実装についても 副次的な価値しかないものとして扱うこともありません; それぞれのサーバは それぞれの長所と短所があります。実際、複数の異なるサーバを並行して動作させ、 それぞれの方法でリポジトリにアクセスし、お互いの邪魔をすることがないように 設定できます。(複数リポジトリアクセス方法のサポートの項 を見てください)。 表 6.1. 「ネットワークサーバの比較」 には、二つの利用可能な Subversion サーバの簡単な説明と比較があります— 管理者は、自分とそのユーザにとって最良の動作をする構成を自由に選ぶ ことができます。
表 6.1. ネットワークサーバの比較
| 機能 | Apache + mod_dav_svn | svnserve |
|---|---|---|
| 認証オプション | HTTP(S) 基本認証, X.509 認証, LDAP, NTLM, その他 Apache httpd で利用可能な方法 | CRAM-MD5 または SSH |
| ユーザアカウントオプション | 固有の 'users' ファイル | 固有の 'users' ファイルまたは既存のシステム (SSH) アカウント |
| 認可のオプション | 自由な読み書きアクセス、あるいはディレクトリごとの 読み書き制御 | 自由な読み書きアクセス、あるいはフックスクリプトに よるディレクトリごとの書き込み(読み込みは不可)アクセス制御 |
| 暗号化 | オプションのSSLを経由することで | オプションでSSH トンネルを利用することで |
| 相互運用性 | 部分的に他の WebDAVクライアントからも利用可能 | 相互運用不能 |
| ウェブによる参照 | 制限された組み込みサポート機能、あるいは ViewCVS のような サードパーティーのツール経由 | ViewCVS のようなサードパーティーツール経由 |
| スピード | やや遅い | やや速い |
| 初期設定 | やや複雑 | かなり簡単 |
この節は実際に利用する具体的なネットワーク実装にかかわらず、 Subversion クライアントがどのようにしてサーバと通信するかの一般的な議論です。 この節を読み終えた後では、クライアントの応答についての設定によって サーバがどんな風に違った形で振る舞うかについて詳しく理解していることでしょう。
Subversion クライアントはほとんどの時間を作業コピーの管理に費やします。 しかしリポジトリからの情報が必要な場合にはネットワーク要求を発行し、 これに対してサーバが適切に応答します。ネットワークプロトコルの詳細は ユーザからは隠されています; クライアントは URL にアクセスしようとし、 URL スキーマの種類によって特定のプロトコルがサーバとの通信に利用され ます(リポジトリのURL)。ユーザはsvn --version を実行してSubversion クライアントがどの URL スキーマとプロトコル を利用できるかを知ることができます。
サーバプロセスがクライアント要求を受け取ると、普通はクライアントの認証を 要求します。クライアントに対して認証確認を実行し、クライアントは 認証証明を提示することでこれに答えます。いったん 認証が成功すればサーバはクライアントがそもそも要求していた情報を返します。 このシステムは CVS のようなシステムとは異なっていることに注意してください。 CVS などではクライアントは要求を出す前に、あらかじめ認証証明を (「ログインによって」)サーバに送ります。Subversion ではサーバは適当な時点 でクライアントにチャレンジの仕組みによって認証証明を 「要求」 します。 クライアントが自発的にサーバに 「送りつける」 わけではありません。 これはある種の操作をより洗練されたものにします。たとえばもしサーバが 世界中の誰でもそのリポジトリを読めるように設定すれば、クライアントが svn checkoutを実行するときに認証確認を実行せずに 済みます。
クライアントネットワーク要求が新しいデータをリポジトリに書き込む場合 (たとえば svn commit) 、新しいリビジョンツリーが 作成されます。もしクライアント要求が認証されれば認証されたユーザ名は 新しいリビジョンのsvn:author属性の値として 格納されます(バージョン化されない属性の項参照)。もしクライアント が認証されなければ(言い換えるとサーバが認証確認に失敗すれば)、その リビジョンのsvn:author属性は空となります。 [22]
多くのサーバは要求ごとに認証を要求するように設定されます。 これはユーザにとっては大きな苦痛となることがあります。常にパスワード を入力しなくてはならないからです。
ありがたいことに、Subversion クライアントはこれに対する処方箋が あります: ディスク上での認証証明をキャッシュするための組み込みシステム があります。デフォルトではコマンドラインクライアントがサーバに対する 認証に成功したときは常にユーザの実行時環境領域にその証明を保存します — この場所はUnix 系システムでは~/.subversion/auth/、 Windows であれば %APPDATA%/Subversion/auth/に なります。(実行時領域については、実行時設定領域の項 により詳しい説明があります)。成功した証明はディスクにキャッシュされ ホスト名、ポート、認証方式の組み合わせをキーとして保存されます。
クライアントが認証確認を受けたとき、まずディスクキャッシュにある証明を探し ます; 存在しないかキャッシュされた証明が認証に失敗した場合はクライアントは ユーザに入力を求めるプロンプトを出します。
セキュリティー狂なら思うかも知れません、「パスワードをディスクにキャッシュ するだと? ひどすぎる。絶対やめろ!」 と。まあ落ち着いて。それは見かけほど 危険な状態ではありません。
auth/ のキャッシュ領域はパーミッションで保護されている ので(所有者である)ユーザだけがそのデータを読むことができ、誰でもというわけでは ありません。オペレーティングシステムのファイル所有権限はパスワードによって保護 されています。
Windows 2000 とそれ以降の場合、Subversion クライアントは標準的な Windows の 暗号サービスを利用してディスク上のパスワードを暗号化します。暗号キーはWindows によって管理されユーザ固有のログイン認証に結びついているのでそのユーザだけが キャッシュされたパスワードを復号化できます。(注意: ユーザの Windows アカウント パスワードが変更された場合、キャッシュされているすべてのパスワードは復号化不能 になります。Subversion クライアントはそれが存在していないかのような動作をし、 必要に応じてパスワードの入力をうながします。)
すべての利便性を犠牲にしてまでセキュリティーを確保したいという、本当にスジガネ入り のセキュリティー狂には、すべての認証キャッシュを完全に無効にすることも可能です。
単一のコマンド中でキャッシュ を無効にする場合は --no-auth-cacheオプションを渡して ください:
$ svn commit -F log_msg.txt --no-auth-cache Authentication realm: <svn://host.example.com:3690> example realm Username: joe Password for 'joe': Adding newfile Transmitting file data . Committed revision 2324. # password was not cached, so a second commit still prompts us $ svn delete newfile $ svn commit -F new_msg.txt Authentication realm: <svn://host.example.com:3690> example realm Username: joe …
あるいは、証明のキャッシュをずっと無効にし続けたい場合は実行時 configファイルを編集してください( auth/ディレクトリの隣にあります)。 単にstore-auth-credsをnoに 設定すればディスク上に証明書をキャッシュしなくなります。
[auth] store-auth-creds = no
ときどき特定の証明をディスクキャッシュから削除したくなることがあります。 これにはauth/領域を調べて適当なキャッシュファイルを 手で削除してください。証明は個別のファイルにキャッシュされています; それぞれ のファイルの中にはキーとその値があります。 svn:realmstring キーはファイルが関係している特定のサーバの認証範囲を記録しています:
$ ls ~/.subversion/auth/svn.simple/ 5671adf2865e267db74f09ba6f872c28 3893ed123b39500bca8a0b382839198e 5c3c22968347b390f349ff340196ed39 $ cat ~/.subversion/auth/svn.simple/5671adf2865e267db74f09ba6f872c28 K 8 username V 3 joe K 8 password V 4 blah K 15 svn:realmstring V 45 <https://svn.domain.com:443> Joe's repository END
適切なキャッシュファイルを特定し、それを削除してください。
クライアント認証について最後に一つ: --username と --passwordオプションについての説明が少し 必要です。たくさんのクライアントサブコマンドはこれらのオプションを 受け付けます; しかしこのようなオプションはサーバに自動的に証明を 送るのではないことに注意してください。既に 説明したようにサーバは必要に応じてクライアントに証明の提示を「要求」 します; クライアントから「自発的に提示する」ことはできないのです。 もしユーザ名とパスワードがオプションとして渡された場合でも、それは サーバが要求したときにのみ提示されるのです。 [23] 典型的にはこれのようなオプションは以下のような場合に利用されます:
ユーザは自分のログイン名称とは違うユーザで認証を 受けたいか、
あるスクリプトがキャッシュされている証明なしに 認証を受けたい。
最後にどのようにしてSubversion クライアントが認証確認を受けたときに 振る舞うかをまとめておきます:
ユーザがコマンドラインオプション中で--username または--passwordを通じて何らかの証明を指定しているか どうかを確認します。指定していないか、オプションによる認証が失敗した 場合には、
実行時 auth/領域中でサーバの認証範囲を探して ユーザが既に適切な証明をキャッシュしているかどうかを調べます。 もしそうでないかキャッシュされた証明が認証に失敗した場合はさらに、
ユーザに対して証明の入力をうながします。
クライアントが上記のどれかの方法で認証に成功した場合はディスク上にその 証明をキャッシュしようとします (既に述べたように、ユーザがこの動作 を無効にしない限り、そうします)。
svnserve プログラムは軽量なサーバで専用の状態プロトコル によって TCP/IP 上でクライアントと通信することができます。クライアントは svn://またはsvn+ssh:// で始まる URL によって svnserveサーバと通信します。この節 では svnserveを実行する別の方法を説明しクライアントが どうやってサーバに認証するか、またリポジトリに適切なアクセス制御を設定 するにはどうしたら良いかについて説明します。
svnserveプログラムの起動にはいくつかの異なる方法が あります。オプションなしで起動した場合は何もせずヘルプメッセージを 表示するだけです。しかしinetd経由で起動するなら -i(--inetd) オプションを指定すること ができます:
$ svnserve -i ( success ( 1 2 ( ANONYMOUS ) ( edit-pipeline ) ) )
--inetd オプション付きで起動すると svnserve はSubversion クライアントとの間 で、専用のプロトコルを使い、stdinと stdoutチャンネル経由で通信しようとします。 これはinetdを経由して 実行されるプログラムの標準的な振る舞い方です。IANAはポート3690 をSubversion プロトコルのために予約しているため Unix風のシステム上 なら/etc/servicesファイルに(もしまだ追加されていない のなら)以下の行を追加することができます:
svn 3690/tcp # Subversion svn 3690/udp # Subversion
そしてもし伝統的な Unix風のinetdデーモンを使って いるのなら /etc/inetd.confに以下のような行を 追加することができます:
svn stream tcp nowait svnowner /usr/bin/svnserve svnserve -i
「svnowner」はリポジトリにアクセスするのに適切な パーミッションをもったユーザであることを確認してください。 これでクライアントがサーバのポート3690に接続しにきた時点で inetdはsvnserve プロセスを起動し、処理を任せます。
Windows システムではsvnserveをサービスとして 起動するためのサードパーティーのツールがあります。このようなツールの 一覧については Subversion のウェブサイトを見てください。
第二の方法はsvnserveを単独の「デーモン」プロセスとして 起動する方法です。これには-dオプションを使って ください:
$ svnserve -d $ # svnserve is now running, listening on port 3690
デーモンモードでsvnserveを実行するときには --listen-port=と --listen-host=オプションで待ち受けポートとホスト 名を「指定」することができます。
さらにsvnserveを起動する第三の方法があり、 それは「トンネルモード」と呼ばれますが、-tオプション を付けて起動します。このモードは RSHや SSHのようなリモートサービスプログラムがユーザを 正しく認証しそのユーザでプライベートな svnserve サーバを起動している状況を仮定しています。 svnserve プログラムは普通に振る舞い(stdinとstdout を通じて)、通信データは自動的にクライアントの背後にいる何らかのトンネル にリダイレクトされると仮定しています。svnserveが このようなトンネルエージェントによって起動された場合は認証ユーザは リポジトリデータベースファイルに完全な読み書きアクセスを持つことに注意 してください。(サーバとパーミッション: 留意点を参照してください)。 これは本質的には file:///URLを使ってリポジトリにアクセス するローカルユーザと同じになります。
一度svnserveプログラムが実行されるとネットワーク越しに システム上のすべてのリポジトリが利用可能になります。クライアントは リポジトリ URL の絶対パスを指定する必要があります。 たとえば、リポジトリが/usr/local/repositories/project1 にあるならクライアントは svn://host.example.com/usr/local/repositories/project1 によってそこにアクセスするでしょう。セキュリティー を高めるため svnserveに -r オプションを渡すこともできますが、これはそのパス以下のリポジトリだけを公開 するように制限します:
$ svnserve -d -r /usr/local/repositories …
-rオプションの利用は リモートファイルシステム空間のルートとしてプログラムが扱う場所 を効果的に変更することができます。この場合クライアントはそのルート までの部分を除いたパスを指定することになり、もっと短い(そしてより情報制限された) URL を利用できます:
$ svn checkout svn://host.example.com/project1 …
クライアントがsvnserveプロセスに接続する とき、以下のことが起こります:
クライアントは特定のリポジトリを選択します。
サーバはリポジトリの conf/svnserve.conf ファイルを処理しその中に定義されている認証と認可の方式に強制的に 従います。
そのときの状況と認可の方式により、以下のどれかになります。
クライアントは要求を匿名で行うことができ、どのような認証確認も 要求されないか、
クライアントは常に認証許可を求められるか、
もし"トンネルモード"で実行されている場合であれば、クライアント は既に外部的に認証されたことを宣言するか、です。
これを書いている時点では、サーバはCRAM-MD5 [24] 認証確認の方法だけを知っています。本質的にサーバはクライアント に対して少しのデータを送ります。 クライアントはMD5ハッシュのアルゴリズムを使ってデータとパスワードを一 緒にしたデータについてのフィンガープリントを作成し、これを応答メッセー ジとして送信します。サーバは同じ計算を保存してあるパスワードについて おこない結果が同じであることを確認します。いかなる場合でも ネットワーク上に実際のパスワードが流れることはありません。
もちろんクライアントはトンネルエージェント、たとえば SSHのようなものを経由して外部的に 認証することもできます。この場合サーバは単に実行している ユーザを確認し、それを認証されたユーザ名であるとして 利用します。より詳しくは SSH 認証と認可の項を見てください。
もうおわかりだと思いますが、リポジトリのsvnserve.conf ファイルは認証と認可の方式を制御する中心的な仕組みです。このファイルは他の 設定ファイルと同じ形式をしています。(実行時設定領域の項 参照): セクション名は角かっこ([ and ]) で示され、コメントはハッシュ文字(#)で始まり、セクションの それぞれには設定可能な特定の変数が含まれています。(variable = value)。 このファイルを見てどのように利用されているか理解してください。
ここでは svnserve.confの [general] セクションに必要な変数のすべてがあります。 ユーザ名とパスワードを含むファイルの定義で始まり、認証範囲を設定 しています:
[general] password-db = userfile realm = example realm
realm は自分で定義できる名前です。 それはクライアントに接続先の「認証用の名前空間」の種別を伝えます; Subversion クライアントは認証プロンプトでそれを表示し、ディスク上の キャッシュされた証明のキーとして(サーバのホスト名、ポートと共に) 利用します。(クライアント証明のキャッシュの項参照。) password-db変数はユーザ名称とパスワードのリスト を含む個別のファイルを指す変数で、やはり同じ形式が利用されます。 たとえば:
[users] harry = foopassword sally = barpassword
password-db の値はユーザファイルの相対または 絶対パスです。多くの管理者にとって、svnserve.conf に従ったリポジトリの conf/領域にファイルを 正しく保つのは容易なことです。一方、同じユーザファイルを共有するような 二つ以上のリポジトリがほしいこともあります; そのような場合は ファイルは多分もっと公開された場所に移動すべきでしょう。ユーザファイルを共有 するリポジトリは同じ認証範囲を持つよう設定されていなくてはならず、それは ユーザ全員が本質的にただ一つの認証範囲を定義するためです。 ファイルがある場所であればどこでもファイルの読み書きパーミッション を正しく設定してください。もし svnserveを どのユーザが実行しているかわかるのであれば、必要に応じて ユーザファイルに対する読み出しアクセス制限をかけてください。
svnserve.confファイル中に、さらに二つの変数を 設定できます: それは認証されていない(匿名の)ユーザと、認証された ユーザに何を許すかを決めるものです。その変数anon-access と auth-accessはnone、read、 あるいはwriteに設定できます。 noneはどのようなタイプのアクセスも制限します。 readはそのリポジトリに読み出し許可のみを与え、 writeはリポジトリに完全な読み書きアクセスを許します。 たとえば:
[general] password-db = userfile realm = example realm # anonymous users can only read the repository anon-access = read # authenticated users can both read and write auth-access = write
この例としての設定は、実際にはこれらの変数のデフォルト値なので定義 しなくても問題ありません。もしさらに保守的に設定したいのなら、 匿名のアクセスを完全に遮断することもできます:
[general] password-db = userfile realm = example realm # anonymous users aren't allowed anon-access = none # authenticated users can both read and write auth-access = write
svnserveは単に「無制限の」アクセスコントロールのみ を理解することに注意してください。ユーザは完全な読み書きアクセス、 完全な読み出しアクセス、あるいは、まったくアクセスできない、のいずれか です。リポジトリ中の特定のパスに対する詳細なアクセス制御は 存在しません。多くのプロジェクトとサイトではこのレベルのアクセス制御 は十分すぎるものです。しかしもしディレクトリごとのアクセス制御が必要 なら、Apache をmod_authz_svn と一緒に使うか (ディレクトリごとのアクセス制御の項を見てください)、書き込み制御を 行う pre-commit フックスクリプトを使う必要があります (フックスクリプトの項を見てください)。Subversion の ディストリビューション中には commit-access-control.pl と、さらに洗練された svnperms.py スクリプトがあって、 pre-commit スクリプトの中で利用することができます。
svnserveの組み込み認証は非常に使いやすいものですが、 それは本当のシステム上のアカウントを作る必要がないからです。一方 管理者によっては既に確立された SSH 認証の仕組みを運用しているかも 知れません。そのような場合、プロジェクトユーザのすべてはシステムアカウント を持っており、サーバマシンに対して 「SSH による」 アクセスが可能なはず です。
SSH とsvnserveの組み合わせは簡単なものです。クライアントは 単にsvn+ssh://URLスキーマを使って接続することができます:
$ whoami harry $ svn list svn+ssh://host.example.com/repos/project harry@host.example.com's password: ***** foo bar baz …
この例では、Subversionクライアントはローカルなssh プロセスを起動し host.example.comに接続し、ユーザ harryとして認証し、そのあとプライベートな svnserveプロセスをリモートマシン上で、ユーザ harryとして実行する、というものです。 svnserveコマンドはトンネルモード(-t) 起動され、そのネットワークプロトコルはトンネルエージェントであるssh によって暗号化された接続上で「トンネル」された形で動作します。 svnserveはユーザharryで実行されていることを知って いるのでクライアントがコミットしようとすると、その認証済みのユーザ名は新しい リビジョンの変更者として利用されます。
ここで重要なのは Subversion クライアントはsvnserve デーモンに接続するわけではないということです。 このアクセス方法はデーモンは不要で、存在しているかどうかを知る必要も ありません。実際には sshが一時的に起動する svnserve プロセスにだけ依存していて、ネットワーク接続が閉じるとそのプロセスも終了します。
svn+ssh://の URL を使ってリポジトリにアクセスする 場合、認証を要求するのはsshプログラムであり svnクライアントプログラムではないことを思い出して ください。これは自動的なパスワードのキャッシュが起きないことを 意味します(クライアント証明のキャッシュの項を見てください)。 Subversion クライアントはリポジトリに複数の接続を張ることもよくあります がユーザはパスワードキャッシュの仕組みによって通常そのことに気づくことは ありません。しかし svn+ssh:// URL を使う場合には ユーザは接続ごとに sshが繰り返しパスワードをうながす ことに悩ませられるかも知れません。解決策は Unix 風のシステムなら ssh-agent、Windows なら pageant のような独立した SSH パスワードキャッシュツールを利用することです。
トンネル上で実行する場合、認可は基本的にはリポジトリデータベースファイルに 対するオペレーティングシステムのパーミッションによって一義的には制御 されます; それはちょうど harry が直接file:/// URLで リポジトリにアクセスした場合と同じことになります。 複数のシステムユーザがリポジトリに対して直接アクセスしようとしている場合 そのようなユーザを一つのグループにまとめ、umask を注意して設定する必要が あるでしょう。(複数リポジトリアクセス方法のサポートの項をぜひ読んでください)。 しかしトンネルモードを利用する場合でもauth-access = read またはauth-access = noneと設定すれば、 svnserve.confファイルはやはりアクセス遮断のために 利用できます。
SSH トンネルの話はこれで終わりかと思うかも知れませんが、そうではありません。 Subversion では実行時configファイル中に専用のトンネル モードに関する動作設定をすることができます。(実行時設定領域の項 を見てください)。たとえば SSH のかわりに RSH を使いたいとします。config ファイルの[tunnels]セクションに以下のように指定してください:
[tunnels] rsh = rsh
これで新しい変数の名前にマッチする URL スキーマを使って この新しいトンネル定義を利用することができます: svn+rsh://host/pathとなります。 新しい URL スキーマを利用すると Subversion クライアントは実際には裏で rsh host svnserve -tコマンドを実行します。もしURL にユーザ名が含まれている場合(たとえば svn+rsh://username@host/path) クライアントはやはりそのコマンドに含めます(rsh username@host svnserve -t.) しかし、以下のようにもっと賢いトンネルスキーマを定義することもできます:
[tunnels] joessh = $JOESSH /opt/alternate/ssh -p 29934
この例はいろいろなことの参考になります。まずそれはどのようにして Subversion クライアントが非常に特殊なトンネリングのためのプログラムを 特定のオプション付きで起動するかを示しています(この場合それは /opt/alternate/sshにあります)。 この場合svn+joessh:// URLにアクセスすると 引数として-p 29934の付いた特定のSSHプログラム が起動されるでしょう— もし標準ではないポートにトンネルプログラム を接続したいと考えているならこれは便利です。
次にそれはどのように してトンネルプログラムの名前を上書きする環境変数を定義してやれば 良いかを示しています。 SVN_SSH環境変数を設定するのはデフォルトの SSH トンネル エージェントを上書きする便利な方法です。 しかしもし異なるサーバ上でいくつもの異なる上書きが必要で、それぞれが 異なるポートや異なるオプションを SSH に渡しているような場合には、この例で 示すような仕組みを利用することができます。 もしJOESSH環境変数を設定 してあれば、その値はトンネル変数全体を上書きします— $JOESSHは /opt/alternate/ssh -p 29934のかわりに実行 されるでしょう。
クライアントが sshを起動する方法を制御できるだけ ではなく、サーバマシン上の sshdの動作の仕方も 制御することができます。この節では sshd によって起動される svnserveコマンドを正しく制御する 方法を示して、複数のユーザが単一システムアカウントをどのように共有すれば 良いかについて説明します。
まずsvnserveを起動するのに使うアカウントの ホームディレクトリを用意します。そのアカウントに SSH の公開鍵/秘密鍵 がインストールされていて、ユーザがその公開鍵でログインできることを 確認してください。パスワード認証は動作しなくなりますが、それは以下の SSH の技法を使うと、すべての処理に SSH authorized_keysファイル を使うためです。
まだ存在していなければauthorized_keysファイルを 作ってください(Unix では普通 ~/.ssh/authorized_keys になります)。このファイルの各行には接続を許す相手先の公開鍵の記述があります。 各行は普通以下のような形をしています:
ssh-dsa AAAABtce9euch.... user@example.com
最初のフィールドはキーの型で、二番目のフィールドは uuencode された鍵そのもの であり、三番目のフィールドはコメントです。あまり知られていませんが、実は 行全体を command フィールドの後におくこともできます。
command="program" ssh-dsa AAAABtce9euch.... user@example.com
commandフィールドが設定されると 通常の svnserve -t のかわりに SSH デーモンがその名前のプログラムを実行します。このプログラムが Subversion クライアントの接続先になります。これがサーバ上でのいろいろな技法を 可能にする鍵です。以下の例では、ファイル中で次のように行を省略して説明します:
command="program" TYPE KEY COMMENT
実行されるサーバ側コマンドを指定することができるので、特定の svnserveバイナリを指定したり、追加の引数を 指定して実行することが簡単にできます:
command="/path/to/svnserve -t -r /virtual/root" TYPE KEY COMMENT
この例では /path/to/svnserveは svnserve に対するカスタマイズされたラッパースクリプ トで、umask を設定するようなものかも知れません(複数リポジトリアクセス方法のサポートの項を見てください)。それはまた svnserve用の仮想ルートディレクトリをどのように設定 するかも示しています。これはデーモンプロセスとして svnserveする場合によく起こることです。たとえばシス テムの特定の部分にアクセス制限する場合や、単に svn+ssh:// URL の絶対パス名を入力する手間を省くため であったりします。
複数のユーザが単一アカウントを共有するようにもできます。それにはまずユー ザごとに独立したシステムアカウントを作るかわりに、メンバーごとに公開鍵 /秘密鍵のペアを生成します。つぎに一行に公開鍵をひとつづつ authorized_users ファイルにおきます。そして --tunnel-user オプションを使うとうまくいきます。
command="svnserve -t --tunnel-user=harry" TYPE1 KEY1 harry@example.com command="svnserve -t --tunnel-user=sally" TYPE2 KEY2 sally@example.com
この例では Harry も Sally も公開鍵認証方式によって同じアカウントで接続する ように設定しています。どちらもそれぞれにカスタマイズされたコマンドが実行されます; --tunnel-userオプションは svnserve -t が名前つき引数が認証されたユーザであることを認めるように指示しています。 --tunnel-user がなければ、すべてのコミットはひとつの共有された システムアカウントから発行したように見えるようになります。
最後の注意です: 共有アカウントにある公開鍵を経由してユーザにアクセス権を 与えても、他の形の SSH アクセスを禁止したことにはなりません。これは authorized_keys にcommandの 形の設定をした場合でもそうです。たとえば、ユーザは依然として SSH 経由で シェルを使ったアクセスができますし、あなたのサーバ経由で X11 や、より 一般的なポートフォワードを実行することもできます。ユーザにできるかぎり わずかな権限しか与えないようにするには commandの すぐ後にそれぞれの制限オプションを指定する必要があります:
command="svnserve -t --tunnel-user=harry",no-port-forwarding,\
no-agent-forwarding,no-X11-forwarding,no-pty \
TYPE1 KEY1 harry@example.com
Apache HTTP Server は 「非常にいろいろなことをしてくれる」 ネットワークサーバ でSubversionの機能も上げることができます。カスタムモジュールを使って httpdはSubversion リポジトリをWebDAV/DeltaVプロトコル 経由でクライアントから利用可能にします。WebDAV/deltaVプロトコルは HTTP 1.1 の拡張です(http://www.webdav.org/ により詳しい情報が あります)。このプロトコルはワールドワイドウェブの核心である、 広く利用可能なHTTP プロトコルに対して、書き込み—特にバージョン化された 書き込み—機能を付け加えます。結果は標準化された、堅牢なシステム を構成することができ、それは Apache 2.0 の一部としてパッケージ化されて います。また Apache 2.0 はさまざまなオペレーティングシステムとサード パーティー性製品によってサポートされており、それを利用すればネットワーク 管理者は新たなカスタムポートを開く必要がありません。 [25] Apache-Subversion サーバはsvnserveよりも多くの 機能を持っていますが、セットアップは少し難しくなります。柔軟性には しばしば複雑さがともなうものです。
以下の議論の多くはApacheの設定ディレクティブへの参照を含んでいます。 いくつかの例はそのようなディレクティブの利用方法になっていますが、 その完全な説明はこの章の範囲外です。Apache チームは非常にすばらしい ドキュメントを管理していてhttp://httpd.apache.org から自由に参照可能です。たとえば設定ディレクティブの一般的な リファレンスはhttp://httpd.apache.org/docs-2.0/mod/directives.html にあります。
また、Apache の設定を変更する場合、しばしば間違いが起こります。 Apache のログシステムにまだなじみがないのであれば、それに注意すると 良いでしょう。httpd.confファイルにはApache に よって生成されるアクセスログとエラーログのディスク上での場所を指定する ディレクティブがあります。(それぞれCustomLogと ErrorLogという名前です)。Subversion の mod_dav_svn も Apache のエラーログインターフェースを利用しています。これらのファイルは 情報取得のために常に閲覧することができ、ほかの方法でははっきりしない 問題の原因を明らかにするかも知れません。
HTTP 越しにリポジトリにアクセスする場合、基本的には二つのパッケージで利用 可能な四つの部品が必要になります。Apache httpd 2.0、 それに付属しているmod_dav DAVモジュール、Subversion、 そしてそれに付属しているmod_dav_svnファイルシステム 提供モジュールです。すべての部品を手に入れてしまえばリポジトリのネットワーク 対応は以下のように簡単です:
httpd 2.0 を起動し、mod_dav モジュール付きで実行する。
mod_dav_svn プラグインを mod_dav にインストールする。mod_dav_svn はリポジトリにアクセスするために Subversion のライブラリを利用 します。そして、
httpd.confファイルを設定してリポジトリを 公開する。
最初の二つについてはhttpdとSubversion を ソースコードからコンパイルするか、自分のシステム用の既にコンパイル 済みのバイナリパッケージをインストールすることによって取得できます。 どのようにして Apache HTTP サーバと共に Subversion をコンパイルするか、 そしてこの目的のために Apache 自身をどのように設定すれば良いかに ついての最新情報は Subversion ソースコードツリーの最上位にある INSTALLファイルを参照してください。
システム上に必要なすべての部品をインストールしたあとは、httpd.conf によって Apache の設定をすることだけが残っています。 LoadModule ディレクティブを使って mod_dav_svn モジュールを Apache にロードしてください。この ディレクティブはほかの Subversion 関連の設定項目に先立って指定しなくてはなりません。 Apache がデフォルトレイアウトを使ってインストールされているなら、mod_dav_svn モジュールは Apache インストールディレクトリのmodulesサブディレクトリ 中になければなりません(たいていの場合、/usr/local/apache2の ようなディレクトリになります)。LoadModuleディレクティブは単純な 構文を持ち、名前の付いたモジュールをディスク上の共有ライブラリの場所に対応付けます:
LoadModule dav_svn_module modules/mod_dav_svn.so
mod_davが ( httpdプログラムに直接静的にリンクされるのではなく) 共有オブジェクトとしてコンパイルされた場合、それに対しても同様の LoadModule行が必要になります。 mod_dav_svn 行の前に設定することに注意してください:
LoadModule dav_module modules/mod_dav.so LoadModule dav_svn_module modules/mod_dav_svn.so
次に、設定ファイルの後の場所のどこかで Subversion リポジトリをどこに 置くかを Apache に伝える必要があります。Location ディレクティブは XML風の記述で、開始タグで始まり、終了タグで終わる間 にさまざまなほかの設定ディレクティブを書きます。 Locationディレクティブの目的は Apache に、指定した URL かそのサブディレクトリである特定の処理をするように指示するためにあります。 Subversion の場合、DAV層で管理するバージョン化された資源のある URL で 処理を単に引き渡すように Apache に指示するだけです。Apache に対して /repos/で始まる部分(つまり、URL のサーバ名と 場合によって付随するポート番号文字列の後に続く部分)を持ったすべての URL について、/absolute/path/to/repositoryにある リポジトリを管理する DAV 提供モジュールに引き渡すように指示することが できます。それには以下のようなhttpd.conf構文を使い ます:
<Location /repos> DAV svn SVNPath /absolute/path/to/repository </Location>
ローカルディスク上の同じ親ディレクトリにある複数の Subversion リポジトリ を提供する計画がある場合は、別のディレクティブ、 SVNParentPath を使って共通の親ディレクトリを示すこともできます。たとえばhttp://my.server.com/svn/repos1とか、 http://my.server.com/svn/repos2のような URL を経由してアクセスされる/usr/local/svnディレクトリ 中に複数の Subversion リポジトリを作る場合であれば、以下の例の中にある httpd.confの設定構文を使うことができます:
<Location /svn> DAV svn # any "/svn/foo" URL will map to a repository /usr/local/svn/foo SVNParentPath /usr/local/svn </Location>
この構文を使うと Apache は/svn/で始まるパス部分 を持つすべての URL を Subversion DAV モジュールに渡しますが、すると このモジュールはSVNParentPathによって指定される ディレクトリ中のすべてのアイテムは実際のSubversion リポジトリであると 仮定します。これはSVNPathディレクティブを利用 するのとは違って新しいネットワーク公開用リポジトリを作るたびに Apache を再起動する必要がないのでとても便利です。
新しいLocationを定義する場合は、他の公開された Location と重ならないように注意してください。たとえばメインの DocumentRootが/wwwに 設定されている場合、Subversion リポジトリを<Location /www/repos>の中で公開しないでください。 URI /www/repos/foo.cが要求されても Apache は DocumentRoot中にあるrepos/foo.c を探せば良いのか、Subversion リポジトリから foo.c を返すためにmod_dav_svnに取り次げば良いのか 判断できなくなります。
この時点で、パーミッションがどうなるかというについて十分考慮する ことが必要になります。Apache をある程度の期間にわたって通常利用する Webサーバとしてきた場合、おそらく既にいろいろなコンテンツがある ことでしょう—ウェブページ、スクリプト、などなど。これらの アイテムは既に Apache と協調動作するようなパーミッションの組が設定 されている、あるいはもっと正確には、Apache にそのようなファイルを 扱うことを許可する設定になっています。Subversion サーバとして Apache が利用される場合も、Subversion リポジトリに対して正しい 読み書きのパーミッションを設定する必要があります。(詳しくは サーバとパーミッション: 留意点を見てください)。
既に存在しているウェブページやスクリプトの設定に問題を起こさない ように Subversion の要求を満足させるためのパーミッションを決定しなくて なりません。これは Subversion リポジトリを、Apache が既に あなたに対して提供しているほかのサービスと協調するようなパーミッション に変更するか、あるいはhttpd.confの中で UserやGroupディレクティブを 使ってSubversion リポジトリを所有しているユーザ・グループで Apache が実行されるべき状態に変更することを意味します。 このための唯一の正しい解法といったものはありませんし、個々の 管理者は正しいやり方をするための異なる理由を持っているはずです。 パーミッションに関連した問題はおそらく Apache を利用した Subversion リポジトリの設定時に一番よく見落とされることであるのに注意してください。
この時点で httpd.confを以下のような 感じで設定している場合
<Location /svn> DAV svn SVNParentPath /usr/local/svn </Location>
あなたのリポジトリは「匿名で」 世界中からアクセス可能となります。何らかの 認証と認可の仕組みを設定するまで、あなたの作ったSubversion リポジトリは Locationディレクティブによって一般的に誰からも アクセスすることができてしまいます。言い換えると、
誰でもリポジトリ URL(とその任意のサブディレクトリ) の作業コピーをチェックアウト するために Subversion クライアントを利用することができます,
誰でもリポジトリ URL をブラウザで指定することによってリポジトリの最新 リビジョンを閲覧することができます。そして
誰でもそのリポジトリにコミットすることができます。
クライアントを認証する一番簡単な方法は HTTP の基本認証の仕組みを使うことで、 それは単純にユーザ名とパスワードを使って、ある人間が自分がその当人である と言っているのを確認します。Apache はhtpasswdユーティリティー を用意して、受け入れることのできるユーザ名とパスワードの一覧を管理 しますが、その人たちにだけあなたの Subversion リポジトリにアクセスする権利を 与えることができます。 Sarry と Harry にだけコミット権限を与えてみましょう。 まず彼らをパスワードファイルに追加する必要があります。
$ ### First time: use -c to create the file $ ### Use -m to use MD5 encryption of the password, which is more secure $ htpasswd -cm /etc/svn-auth-file harry New password: ***** Re-type new password: ***** Adding password for user harry $ htpasswd -m /etc/svn-auth-file sally New password: ******* Re-type new password: ******* Adding password for user sally $
次に新しいパスワードファイルを何に利用するかというのを Apache に伝える ため、 Locationブロック内部で追加の httpd.conf ディレクティブが必要になります。 AuthTypeディレクティブは 利用する認証システムのタイプを指定します。今回はBasic 認証システムを指定したいと思います。AuthNameは 任意の名前で認証ドメインを与えるためのものです。ほとんどのブラウザは ユーザに名前とパスワードを問い合わせるときにこの名前をポップアップダイアログ ボックス中に表示します。最後にAuthUserFileディレクティブは htpasswdで作ったパスワードファイルの場所を指定します。
三つのディレクティブを追加した後では、あなたの <Location> ブロックは以下のような感じになっていることでしょう:
<Location /svn> DAV svn SVNParentPath /usr/local/svn AuthType Basic AuthName "Subversion repository" AuthUserFile /etc/svn-auth-file </Location>
この <Location> ブロックはまだ完成しておらず 役に立つことは何もしません。単に Apache に対して、認証が要求されるとき には常にSubversion クライアントからユーザ名とパスワードを取得するように 言うだけです。しかしここで欠けているのは Apache に対してどのような 種類のクライアント要求が認証で必要とされるのかを言うための ディレクティブです。これをやるのに最も簡単な方法はすべてのリクエストを保護 するこです。Require valid-user の追加は Apache に対して すべてのリクエストは認証されたユーザであることを伝えます:
<Location /svn> DAV svn SVNParentPath /usr/local/svn AuthType Basic AuthName "Subversion repository" AuthUserFile /etc/svn-auth-file Require valid-user </Location>
認可のポリシーを設定する Requireディレクティブと、その他の方法についての 詳細については次の節(認可のオプションの項) を読んでください。
一点注意があります: HTTP の基本認証パスワードはほとんど平文のままネットワーク を流れるため、セキュリティー上は非常に弱いものです。もしパスワードの 盗聴が心配なら、SSL 暗号化のような仕組みを使うのが最良でしょう。これで クライアント認証はhttp://のかわりに https://を使って認証することになります; 最低限度の 処置として Apache に自己サイン付きサーバ証明書を設定することができます。 [26] どうすれば良いかについては Apache のドキュメント(と、OpenSSL の ドキュメント)を見てください。
リポジトリを自社ファイアウォールの外にさらす必要のあるビジネス は認可されていない他人が自分たちのネットワークデータを「盗聴」 しているかも知れないということを意識すべきです。 SSL はこの手の望ましくない意図が重要なデータの流出に帰結する可能性を 小さなものにします。
Subversion クライアントが OpenSSL を使ってコンパイルされた場合、https:// URL を使って Apache サーバと通信する能力を得ます。Subversion クライアントで 利用される Neon ライブラリはサーバ証明書を検証することができるだけ ではなく、確認要求を受けた場合には自分の証明書を提示する能力も持って います。クライアントとサーバが SSL 証明書を交換しお互いの認証に成功 すれば、その後のすべての通信はセッションキーによって暗号化されます。
どのようにしてクライアントとサーバ証明書を生成するか、またその証明書を 利用するようにどうやって Apache を設定するかについてはこの本の範囲外です。 Apache 自身のドキュメントを含め、さまざまな本でこの方法を説明しています。 ここでは通常の Subversion クライアントでのサーバとクライアント証明書を どのように管理するかについて説明します。
https://経由で Apache と通信する場合、Subversion クライアントは二つの異なるタイプの情報を受け取ることができます:
サーバ証明書
クライアント証明書の提示要求
クライアントがサーバ証明書を受け取った場合、それが信頼できるものであるか どうかの検証が必要になります: サーバは本当に名乗っているそのサーバなの でしょうか? OpenSSL ライブラリはサーバ証明書にサインした者、あるいは 認証期間 (CA)を調べることでこれを確認します。もし OpenSSL が自動的に CA を信用することができないか、他の問題が起きた 場合(たとえば、証明書の有効期間が過ぎていたり、ホスト名が一致していない 場合など)、Subversion コマンドラインクライアントはそのサーバ証明書を とにかく信用するかどうかをユーザに聞いてきます:
$ svn list https://host.example.com/repos/project Error validating server certificate for 'https://host.example.com:443': - The certificate is not issued by a trusted authority. Use the fingerprint to validate the certificate manually! Certificate information: - Hostname: host.example.com - Valid: from Jan 30 19:23:56 2004 GMT until Jan 30 19:23:56 2006 GMT - Issuer: CA, example.com, Sometown, California, US - Fingerprint: 7d:e1:a9:34:33:39:ba:6a:e9:a5:c4:22:98:7b:76:5c:92:a0:9c:7b (R)eject, accept (t)emporarily or accept (p)ermanently?
このダイアログはなじみ深いものだと思います; 本質的にはウェブブラウザで 見ることのできるのと同じ質問になっています(ブラウザは Subversion と 同じような HTTP クライアントの一種なんです!)。もし (p)ermanent、常に信用 する、というオプションを選ぶと、サーバはあなたのユーザ名とパスワードを キャッシュしたのとちょうど同じ方法であなたの実行時auth/ 領域にそのサーバ証明書をキャッシュします。(クライアント証明のキャッシュの項 を参照してください)。キャッシュされてしまえば、Subversion はそれ以降の やり取りについては自動的にこの証明書を信用します。
実行時serversファイルもSubversion クライアントが自動的に 特定の CA を信頼するように設定することができます。すべてのものについて そうすることもできますし、ホストごとにすることもできます。 単にssl-authority-filesを、 PEM で暗号化された CA 証明書をセミコロンで区切ったリストに設定してください:
[global] ssl-authority-files = /path/to/CAcert1.pem;/path/to/CAcert2.pem
多くの OpenSSL の設定ではほとんど無制限に信頼する 「default」 CA が、あらかじめ設定されています。Subversion クライアントにそのような 標準的な認証機関を信用させるためにはssl-trust-default-ca 変数をtrueに設定してください。
Apache と通信する際、Subversion クライアントはクライアント証明書の 確認要求を受けるかも知れません。Apache はクライアントに対して自分自身を 証明するようにたずねます: あんたは本当にあんたなのか? もしすべてが正し ければ Subversion クライアントはApache が信用している CA によってサイン されたプライベート証明書を送り返します。クライアント証明書は通常 暗号化された形式でディスク中に保管され、ローカルパスワードによって 保護されています。Subversion がこの確認要求を受けた場合、あなたは 証明書のパスと、それを保護しているパスワードについて聞かれます:
$ svn list https://host.example.com/repos/project Authentication realm: https://host.example.com:443 Client certificate filename: /path/to/my/cert.p12 Passphrase for '/path/to/my/cert.p12': ******** …
クライアント証明書は 「p12」形式のファイル であることに注意してください。クライアント証明書を Subversion で利用する場合、それは標準的な PKCS#12 フォーマットでなければなりません。 ほとんどのウェブブラウザは既にその形式の証明書をインポートしたり エクスポートしたりすることができます。他の方法としては既存の証明書 をOpenSSLのコマンドラインツールによってPKCS#12形式に変換するという ものです:
ここでも実行時serversファイルはホスト単位で この確認要求を自動化することを認めています。そのような情報は 実行時変数で指定できます:
[groups] examplehost = host.example.com [examplehost] ssl-client-cert-file = /path/to/my/cert.p12 ssl-client-cert-password = somepassword
いったんssl-client-cert-fileと ssl-client-cert-password変数を 設定すれば、Subversion クライアントはユーザに問い合わせることなしに 自動的にクライアント証明書の確認要求に応答することができるようになります。 [27]
ここまでのところで、すでに認証についての設定は完了しました が認可はまだです。Apache はクライアントを試し、本当のクライアン トであることを確認することができますが、これらの認証済みクライアントそれ ぞれにどのようなアクセスを許し、また制限するかについてはまだ説明 していません。この節ではリポジトリに対してアクセス制御するための 二つの方法について説明します。
アクセス制御の一番簡単な方法は特定のユーザをリポジトリに対して読み出し 専用、あるいは読み書き可能として認可することです。
<Location>ブロックにRequire valid-user ディレクティブを追加することによってすべてのリポジトリ操作にアクセス制限を 設けることができます。前の例を使うと、これはharry、 sally、あるいはユーザごとの正しいパスワードを入力した 人だけに、Subversion リポジトリに対する任意の操作を許すというものです:
<Location /svn> DAV svn SVNParentPath /usr/local/svn # how to authenticate a user AuthType Basic AuthName "Subversion repository" AuthUserFile /path/to/users/file # only authenticated users may access the repository Require valid-user </Location>
しばしばそのような厳しい設定は不要です。たとえば Subversion 自身の ソースコードリポジトリはhttp://svn.collab.net/repos/svn にありますが、世界中の誰でも読み出しアクセスすることが可能です( それはチェックアウトしたり、ウェブブラウザでリポジトリを閲覧する ような操作です)が、書き込み操作は認証されたユーザにのみ許されています。 この手の制限を付与するには Limitと LimitExcept設定ディレクティブを使うことができます。 Locationディレクティブのように、この二つの ブロックは開始タグと終了タグがあり、 <Location> ブロック中でネストすることができます。
LimitとLimitExceptディレクティブ に現れるパラメータは HTTP 要求タイプで、そのブロック全体に影響を 与えます。たとえば、現在サポートされている読み出しのみの操作を 除くすべてのリポジトリアクセスを禁止したい場合、LimitExcept ディレクティブが、GET、PROPFIND、 OPTIONS、そしてREPORT要求タイプ パラメータを渡す形で利用できます。そして既に触れたRequire valid-user ディレクティブを、単に<Location>ブロックの中に 置くかわりに、<LimitExcept>ブロックの中に置く形に なります。
<Location /svn>
DAV svn
SVNParentPath /usr/local/svn
# how to authenticate a user
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /path/to/users/file
# For any operations other than these, require an authenticated user.
<LimitExcept GET PROPFIND OPTIONS REPORT>
Require valid-user
</LimitExcept>
</Location>
このようなことは単純な例にすぎません。Apache のアクセス制御と Requireディレクティブについてのさらに詳しい情報 はhttp://httpd.apache.org/docs-2.0/misc/tutorials.html にある Apache ドキュメントチュートリアルのSecurity セクションを見てください。
第二の Apache httpd モジュールである mod_authz_svn を使うと、より詳細なパーミッションの設定が可能です。このモジュールは クライアントからサーバに送信されるさまざまな裸の URL を取得し、 mod_dav_svnにそれを解析するように要求し、設定ファイル で定義されたアクセス方式に基づき必要に応じてアクセスを拒否します。
Subversion をソースコードから構築した場合はmod_authz_svn は自動的にmod_dav_svnのそばにインストールされます。 多くのバイナリ配布でもやはり自動的にインストールします。正しくインストール されているかどうかを確認するにはhttpd.conf にある、mod_dav_svnのLoadModule ディレクティブのすぐ後に来ていることを確認してください:
LoadModule dav_module modules/mod_dav.so LoadModule dav_svn_module modules/mod_dav_svn.so LoadModule authz_svn_module modules/mod_authz_svn.so
このモジュールを有効にするには AuthzSVNAccessFile ディレクティブを使うためにLocationブロックを 設定する必要があります。このディレクティブはリポジトリにある パスのパーミッションが書かれたファイルを指定します。(すぐあとで このファイルの形式について議論します。)
Apache は柔軟なので三つの一般的なパターンのどれかにブロックを 設定することができます。まず基本的な設定パターンの一つを選びます。 (以下の例は非常に単純です; Apache の認証と認可の設定の詳細に ついては Apache 自身のドキュメントを参照してください。)
最も単純なブロックはすべての人に対して自由にアクセスすることを 許すものです。このやり方では Apache は認証要求を送信することは ないのですべてのユーザは「匿名」として扱われます。
例 6.1. 匿名アクセスの設定例。
<Location /repos>
DAV svn
SVNParentPath /usr/local/svn
# our access control policy
AuthzSVNAccessFile /path/to/access/file
</Location>
この対極にある設定方法として、すべての人にたいして認証要求するための ブロックを設定することもできます。すべてのクライアントは自身を特定 するための証明を送る必要があります。ブロックはRequire valid-user ディレクティブによって無条件に認証を要求し、またその方法を定義します。
例 6.2. 認証つきアクセスの設定例。
<Location /repos>
DAV svn
SVNParentPath /usr/local/svn
# our access control policy
AuthzSVNAccessFile /path/to/access/file
# only authenticated users may access the repository
Require valid-user
# how to authenticate a user
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /path/to/users/file
</Location>
三番目の非常に一般的な方法は認証つきアクセスと匿名アクセスの組合せ によるものです。たとえば多くの管理者はあるリポジトリのディレクトリを 誰でも読めるようにしたいが、もっと重要な場所については認証された ユーザのみが読めるように(あるいは書き込めるように)したいと考えます。 このような設定ではすべてのユーザはまずは匿名でリポジトリにアクセスし ます。ある時点で本当のユーザ名を要求しなくてはならないアクセスが 発生すると、Apache はクライアントから認証を要求します。このためには Satisfy Anyディレクティブと Require valid-userディレクティブの両方を使います。
例 6.3. 認証つき/匿名の両方でアクセスする場合の設定例。
<Location /repos>
DAV svn
SVNParentPath /usr/local/svn
# our access control policy
AuthzSVNAccessFile /path/to/access/file
# try anonymous access first, resort to real
# authentication if necessary.
Satisfy Any
Require valid-user
# how to authenticate a user
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /path/to/users/file
</Location>
いったん基本的な Locationブロックが設定されて しまえばその中にアクセスファイルを作り、認証の規則を定義すること ができます。 アクセスファイルの形式はsvnserve.confや 実行時設定ファイルで利用されるのと同じです。 ハッシュ文字(#)で始まる行は無視されます。 一番単純な形では、それぞれのセクションはリポジトリ とのその中にあるパスの名前を決め、認証用のユーザ名はセクションごと の中にくるオプション名になります。それぞれのオプションの値は リポジトリパスにアクセスするユーザレベルを記述します; r (読み込み専用)か、 rw(読み書き可能)のどちらか になります。ユーザがまったく含まれていなければ、アクセスは全面的 に禁止されます。
もっと具体的に言うと; セクション名は[repos-name:path] か、[path]の形になります。SVNParentPath ディレクティブを使っている場合はセクション中でリポジトリ名を指定するのが 重要です。それを省略すると[/some/dir]のようなセクション は すべてのリポジトリのパス /some/dir にマッチしてしまいます。しかしSVNPathディレクティブ を使っている場合はセクションで唯一のパスを定義するのが良い方法です— 結局そこには唯一のリポジトリしか無いのですから。
[calc:/branches/calc/bug-142] harry = rw sally = r
この最初の例ではユーザ harry はcalcリポジトリ中の /branches/calc/bug-142 ディレクトリに対して完全な読み書きアクセスが可能ですが、 sally は読み出し アクセスのみです。それ以外のユーザにはこのディレクトリのアクセスは 禁止されます。
もちろんパーミッションは親ディレクトリから子ディレクトリに継承 されます。これは Sally のために、サブディレクトリ中では異なる アクセス方式を指定することができるという意味です:
[calc:/branches/calc/bug-142] harry = rw sally = r # give sally write access only to the 'testing' subdir [calc:/branches/calc/bug-142/testing] sally = rw
これで Sally はブランチのtestingサブディレクトリ では書き込みができますが、ディレクトリのほかの部分では依然として読み出し のみが可能です。一方 Harry はブランチ全体に対して依然として 完全な読み書きアクセスが可能です。
ユーザ名変数を設定しなければ、他の人を継承の規則に従って許可する のを明示的に拒否することもできます:
[calc:/branches/calc/bug-142] harry = rw sally = r [calc:/branches/calc/bug-142/secret] harry =
この例では Harry はbug-142のツリーに 対して完全な読み書きアクセスが可能ですが、その中のサブディレクトリ secretにはまったくアクセスできません。
留意しておくことは、一番詳しく指定したパスが常に最初にマッ チするということです。mod_authz_svnモジュー ルはまず最初にパス自身にマッチするかどうかを調べ、次にその親 ディレクトリ、さらにその親ディレクトリ、と調べていきます。結 果はアクセスファイル中の具体的なパスが有効になると、親ディレ クトリから引き継いでいるパーミッション情報は常に上書きされて しまいます。
デフォルトでは、誰であれリポジトリに対するすべてのアクセスは禁止 されます。これは、もし空のファイルで始めた場合、リポジトリのルート ですべてのユーザに対して少なくとも読み出しパーミッションを与えたい だろうということを意味します。これはアスタリスク変数(*) を使って、「すべてのユーザ」をあらわすことで可能です。 :
[/] * = r
これはよくある設定です; セクション名の中にリポジトリ名が存在しないことに 注意してください。これはSVNPathを使っていようが SVNParentPathを使っていようが、すべてのリポジトリが すべてのユーザによってどこからでも読み込めるようにします。 すべてのユーザがリポジトリに読み込みアクセスできるようになってしまえば 特定のリポジトリの特定のサブディレクトリに特定のユーザが読み書き可能と するため、明示的に rwの許可を与えることができます。
アスタリスク変数 (*) も特に注意しておく価値があります: それは匿名ユーザにマッチするような 唯一のパターン です。Locationブロックで匿名と認証されたアクセスの 組合せを許すように設定した場合、すべてのユーザは Apache に対して匿名で アクセスするところから話が始まります。 mod_authz_svn はアクセスするパスのために 定義された * の値を探します; みつからなければ Apache はクライアントに対して実際に認証要求を出します。
アクセスファイルでもユーザのグループ全体を定義することが てきます。これは Unix の/etc/groupファイルと 良く似た形式です:
[groups] calc-developers = harry, sally, joe paint-developers = frank, sally, jane everyone = harry, sally, joe, frank, sally, jane
グループを使ってユーザと同じようにアクセス制御することができ、この場合 グループであることを示す「アットマーク」(@)を先頭に付けます:
[calc:/projects/calc] @calc-developers = rw [calc:/projects/paint] @paint-developers = rw jane = r
グループは他のグループを含むように定義することもできます:
[groups] calc-developers = harry, sally, joe paint-developers = frank, sally, jane everyone = @calc-developers, @paint-developers
...これでほとんどすべてです。
mod_dav_svnモジュールには、「読み込み禁止」 のしるし がついたデータが間違って外部に漏れないようにいろいろな工夫がしてありま す。これは、 svn checkout や svn update のようなコマンドからの戻り値となるすべてのパス名とファイルの内容を綿密に チェックする必要があることを意味します。このようなコマンドが認可のポリシーに よって読み込むべきではないファイルパス名に出会うと、通常は完全にそれを 無視します。履歴や名称変更を追うようなコマンドの場合— 例えばずっと 昔に名称変更されたファイルに対して svn cat -r OLD foo.c のようなコマンドを実行するような場合など— 名称変更の履歴は、その ようなファイルの以前の名前に読み込み制約がある場合には、単に異常終了して しまいます。
このようなすべてのパス名に対するチェックは場合によっては非常に効率の悪 いものになり、特にsvn logコマンドではそうです。リビ ジョンの一覧を取得する場合、サーバはすべてのリビジョンのすべての変更さ れたパスを見てそれらが読み込み許可されているかどうかを調べます。許可さ れていないパスが見つかるとリビジョンの変更のあったパスの一覧からは除外 され(これは通常--verboseオプションで見ることのできる ものです)、ログメッセージ全体が表示されなくなります。言うまでもありま せんが、たくさんのファイルのあるリビジョンでは多くの時間を消費します。 しかしこれはセキュリティーを保つための代償です: mod_authz_svnのようなモジュールをまったく設定してい ない場合でも、やはり mod_dav_svnモジュールがApache の httpd に対してすべてのパスについての認可チェック をするように要求します。mod_dav_svnモジュールは具体 的にどんな認可モジュールがインストールされているかは知らないので、単に Apache に対して、もしそのようなものがあるなら実行するようにと依頼する だけです。
一方、これに関する逃げ道もやはりあって、セキュリティーよりも効率を重視 するようにも設定できます。ディレクトリごとの認可の仕組みをまったく利用 しないのなら(たとえば mod_authz_svn やそれに類似 のモジュールを使わないのなら)、このパス名に対するチェックを完全に無効に することもできます。httpd.confファイルで、 SVNPathAuthzディレクティブを使ってください:
例 6.4. Disabling path checks altogether
<Location /repos>
DAV svn
SVNParentPath /usr/local/svn
SVNPathAuthz off
</Location>
SVNPathAuthz ディレクティブはデフォルトでは 「on」 です。 「off」 に設定するとすべてのパス名にもとづいた認可のチェックが禁止されます; mod_dav_svnはすべてのパスについて認可のチェックをしなく なります。
ここまで Apache と mod_dav_svn のための認証と認可のオプショ ンの大部分を説明してきました。しかし Apache が用意している、さらに いくつかのすばらしい機能があります。
Subversion リポジトリでApache/WebDAVの設定による一番の恩恵は バージョン化されたファイルやディレクトリの最新リビジョンがウェブブラウザ から直接参照可能だということです。Subversion は URL をバージョン化された 資源を特定するために利用するので、そのような HTTPベースのリポジトリアクセス に利用される URL はウェブブラウザから直接入力することが可能です。ブラウザは その URLに対してGET要求を発行し、その URL がバージョン化 されたディレクトリであるかファイルであるかに応じてmod_dav_svnはディレクトリ の一覧またはファイルの内容を表示します。
URL は見たいと思うリソースのバージョンについての情報は含まれていない ので mod_dav_svn は常に最新のバージョンで答えます。この機能は Subversion URL をドキュメントの参照先として渡すことができ、その URL は常にドキュメントの最新を指すことになる、というすばらしい効果も あります。もちろん他のウェブサイトからのハイパーリンクとして URL を 利用することもできます。
一般的には、(ディレクトリへのURLに比べて) バージョン化されたファイルへ のURLのほうをよく使うことになるでしょう—結局のところ、関心のある内容 がありそうな場所は、そこなのですから。 しかし、Subversion のディレクトリ一覧を参照する機会はあるかも知れず、 その場合すぐに一覧表示で生成される HTML は非常に基本的なもので、美の追求 (や、何か面白いことをやらかそうということ)を目的としているわけではないの に気づくでしょう。このディレクトリ一覧をカスタマイズするために、 Subversion は XML インデックス機能を用意しています。 httpd.conf中で、リポジトリのLocation ブロック中で単一のSVNIndexXSLTディレクティブを使うと mod_dav_svn に対して、ディレクトリ一覧表示時に XML 出力を生成し、 好きな XSLT スタイルシートを参照するように設定することができます:
<Location /svn> DAV svn SVNParentPath /usr/local/svn SVNIndexXSLT "/svnindex.xsl" … </Location>
SVNIndexXSLTディレクティブとクールな XSLT スタイル シートを使ってディレクトリ一覧をウェブサイトのほかの部分で利用されている 色スキーマや画像に一致させることができます。あるいは、もし望むなら Subversion ソース配布中のtools/xslt/ディレクトリ にあるサンプルスタイルシートを使うこともできます。 SVNIndexXSLT ディレクトリで指定されるパスは実際の URL パスであることに注意してください — ブラウザはそれを利用するためにはスタイルシートが読める場所に なければなりません!
堅牢なウェブサーバとして Apache で既に提供されている機能のいくつかは Subversion においても機能とセキュリティーの向上につながります。 Subversion は Neon というSSL(安全なソケット層。既に述べました)や 圧縮(gzipや PKZIPと同じような アルゴリズムを使ってファイルより小さなデータの塊に「縮める」 こと)のような仕組みをサポートした一般的なHTTP/WebDAV ライブラリを使って Apache と通信します。やらなくてはならないことは 単にSubversionとApacheで必要な機能をコンパイルし、そのような機能を使える ように正しくプログラムを設定することだけです。
圧縮の仕組みは、実際のサイズを減らすためのネットワーク転送データの圧縮と 解凍処理でクライアントとサーバに少し負荷をかけます。ネットワークの帯域 が細い場合この圧縮はサーバとクライアント間の転送スピードを非常に大きく することができます。極端な場合このデータ転送量の現象は、操作がタイムアウト してしまうか、完了するかの違いになることさえあります。
それほどは面白くありませんが、やはり有用なことは Apache と Subversion に関係したその他の機能で、たとえば特定のポートを指定する機能( デフォルト HTTP ポートである 80 のかわりに)や、Subversion リポジトリが アクセスする仮想ドメイン名の機能や、プロキシ経由でリポジトリにアクセス する能力などがあります。これらはすべて Neon でサポートされているので、 Subversion は自由にその機能を利用することができます。
最後にmod_dav_svnは、ほぼ完全なWebDAV/DeltaV の方言を利用することができるのでサードパーティー製の DAV クライアント を経由してリポジトリにアクセスすることもできます。ほとんどのモダンな オペレーティングシステム(Win32, OS X そして Linux)では標準ネットワーク を「共有」することによって DAV サーバをマウントする組み込みの機能があります。 これは複雑な話題です; 詳細は付録 B. WebDAV と、自動バージョン化を読んでください。
いろいろな異なる方法でどのようにしてリポジトリにアクセスするかを見て きました。しかし、複数の方法で同時にあるリポジトリにアクセスすることは 可能—あるいは安全に—なのでしょうか? 答えはイエスです。少し ばかり慎重になる必要はありますが。
ある時刻において、以下のようなプロセスがあなたのリポジトリに対して 読み出しアクセス、あるいは書き込みアクセス要求を出しているかも知れません :
Subversion クライアントを使って (自分自身の名のもとに)file:///URLを 指定することで直接リポジトリにアクセスする通常のユーザによって:
リポジトリにアクセスするプライベートに SSH-起動されたsvnserve プロセスに接続する通常のシステムユーザによって:
デーモンとして、またはinetdによって起動されたsvnserve プロセスが、特定の固定されたユーザとして実行されることによって:
Apache httpd プロセスが、特定の固定されたユーザとして実行 されることによって
管理者が直面する一番よくある問題はリポジトリの所有権とパーミッションです。 上のリストにあるすべてのプロセス(あるいはユーザ)は Berkeley DB ファイルに対する 読み書きの権限を持っていますか? あなたが Unix風のオペレーティングシステム を使っているとして、一番素直な解決方法は、すべての潜在的なリポジトリユーザ を新しいsvnグループに入れてしまい、リポジトリをその グループによって完全に所有されている形にしてしまうことです。しかし それだけでは十分ではありません。プロセスはデータベースファイルに、排他的な umask で書き込むかも知れません—それは他のユーザがアクセスするのを 妨害してしまいます。
それでリポジトリユーザの共通グループ設定の後の次のステップはすべてのリポジトリ アクセスプロセスで、正しい umask を使うことです。リポジトリに直接アクセスする ユーザの場合はsvnプログラムをくるんでしまうスクリプトを 作り、その先頭でumask 002を設定してから実際のsvn クライアントプログラムを実行すれば良いでしょう。同様のラッパースクリプトを svnserveについても書き、Apache の起動スクリプト apachectlの先頭にもumask 002コマンドを 追加しましょう。たとえば:
$ cat /usr/bin/svn #!/bin/sh umask 002 /usr/bin/svn-real "$@"
また別のよくある問題が Unix風システムではよく起こります。 リポジトリが利用されると BerkeleyDB は必要におうじて新しい ログファイルを作り動作を記録します。リポジトリ自体は 完全にsvnグループによって所有されていても このようにして新規に作られたファイルが同じグループによって 所有される必要はありません。これが理由でユーザにとっては また別のパーミッションの問題がおこります。うまい回避策としては リポジトリのあるdbディレクトリにたいして グループ SUID ビットを立てることです。これによって すべての新たに作成されるログファイルは親ディレクトリと 同じ所有グループになります。
いったんこの問題を乗り越えてしまえば、あなたのリポジトリは すべての必要なプロセスからアクセスすることができるようになって いるはずです。少し面倒で複雑ですが、複数のユーザが書き込みアクセス を共有することで起こる問題は、しばしばきれいに解くことができない 古典的な問題です。
ありがたいことに、ほとんどのリポジトリ管理者はそのような複雑な 設定をする必要はないでしょう。 同じマシン上にあるリポジトリにアクセスしたいと思うユーザは file://アクセス URL を利用することに制限されて いるわけではありません— http://や svn://URL中にサーバ名として localhost を指定する形でApache HTTP サーバやsvnserve を使うこともできるのです。そして複数のサーバプロセスを Subversion リポジトリのために管理することは頭痛の種を増やすだけのことです。 自分のニーズに本当に合ったサーバ構成を選択し、そこにしがみついて 離れない、これがおすすめの方法です!
[22] この問題は実際によくある質問で、サーバの設定に間違いがあると起こります。
[23] ここでもよくある間違いは認証確認を決して要求しないようにサーバを 間違って設定してしまうというものです。この場合ユーザが --usernameと --passwordオプション をクライアントに渡しているのにそれが利用されないという状況に驚くでしょう。 つまり新しいリビジョンは依然として匿名でコミットされているように見える わけです!
[24] RFC 2195を参照してください
[25] 彼らはそういう作業を本当に嫌います。
[26] 自己サイン付きサーバ証明書は、「中間偽装」攻撃に対しては やはり脆弱ですが、そのような攻撃は、暗号化されていないパスワードを 盗聴するタイプのものに比べてはるかに困難です。
[27] セキュリティーにもっと神経質な人はクライアント証明書用パスワード を実行時serversファイルに格納するのを嫌がる でしょう。
もし、この本を章ごとに、最初から最後まで読んでいるのなら、 もうあなたは、ほとんどのバージョン管理操作を実行するために Subversionクライアントを使うための十分な知識を持っているはずです。 どのようにして、Subversionリポジトリから作業コピーをチェックアウト するかを理解しているはずです。svn commit や svn updateを使った変更点の送受信になじんでいる はずです。そして多分、ほとんど無意識にsvn status を実行してしまうような反射神経さえ身についているかも知れません。 どんな意図や目的に対しても、典型的な環境でSubversionを使う用意が できているはずです。
しかし、Subversionの機能セットは、「普通のバージョン管理操作」 で終わるわけではありません。
この章ではいくつかのSubversionの機能で、それほど頻繁には利用されない ようなものをとりあげます。その中で、Subversionの属性(あるいは 「メタデータ」)のサポートについて議論し、どのようにして Subversionのデフォルトの振る舞いを実行時設定領域の調整によって変更 することができるかを見ます。また、どのように外部定義を使って、 複数のリポジトリからデータを引っぱってくるようにSubversionに命令 するかを説明します。そして、Subversionのパッケージの一部である、 追加のクライアント側、サーバ側のツールのいくつかの詳細にも触れます。
この章を読む前に、Subversionで基本的なファイルとディレクトリに関する バージョン管理の能力についてなじんでいるべきです。もしまだそれについて は読んでいないか、復習が必要なら、第2章 基本概念 と 第3章 同伴ツアー を読むことをお勧めします。 一度基本をマスターしてからこの章を消化すれば、あなたはもうSubversion のパワーユーザです。
Subversionはたくさんのオプションの振る舞いを用意していて、それはユーザに よって制御することができます。そのようなオプションの多くはユーザがすべての Subversion操作に適用したいと思うようなことです。それで、このようなオプション を指定するためにユーザにコマンドライン引数を思い出させるように強いるよりも また、実行しようとするすべての操作に対してそれらを使うよりも、Subversionは 定義ファイルを使います。それはSubversionの定義領域に分離されているものです。
Subversionの設定領域 は二層に分かれたオプション名と 値の階層です。普通、これは定義ファイル (最初の 層)を含む特別なディレクトリに要約してあり、それは標準的なINI形式の テキストファイルにすぎません。(そこには「sections」 があり、 それが第二層になります)これらのファイルは好きなテキストエディタを 使って簡単に編集することができます。(emacs とか vi とか) そして、クライアント によって読み出される命令を含んでいて、ユーザが好むさまざまなオプションの 振る舞いをどうするかを決定します。
svnコマンドラインクライアントが最初に実行されると、 それはユーザごとの構成領域を作ります。Unix風のシステムなら、この領域は ユーザのホームディレクトリに、.subversion という名前の ディレクトリとして用意されます。Win32システムでは、Subversionは Subversionという名前のフォルダを作ります。普通には ユーザプロファイルディレクトリ(これは通常は隠れたディレクトリになりますが) のApplication Data 領域 の内部になります。しかし、このプラットフォームでは、完全な場所はシステム ごとに違っていて、本当の場所はWindowsレジストリ [28] に設定されています。 ユーザごとの設定領域は、Unix での名前である.subversion を使って参照することにします。
ユーザごとの設定領域に加えて、Subversionはシステム全体の設定領域も 理解することができます。これはシステム管理者にあるマシン上での すべてのユーザに対するデフォルトを設定する力を与えます。システム全体 の設定領域は必須のポリシーがあるわけではありません—ユーザ ごとの設定領域は、システム全体の領域を上書きし、svn プログラムに与えるコマンドライン引数は振る舞いを決める最後の場所に なります。Unix風のプラットフォームでは、システム全体の設定領域は /etc/subversion ディレクトリにあると期待されて います。Windows マシンの場合は共通アプリケーション データ 領域の内部にある Subversionディレクトリを見に行きます (このディレクトリもWindows レジストリによって指定されます)。 ユーザごとの場合と違って、svn プログラムは システム全体の設定領域を作ろうとはしません。
.subversion ディレクトリは現在のところ 三つのファイルを含んでいます—二つの設定ファイル (config と serversです)、それに README.txt ファイルで、これはINI形式を 説明するものです。それらの生成時には、ファイルはSubversion がサポートするそれぞれのオプションのデフォルト値が入っており、 ほとんどがコメントアウトされていて、さらに、どのようにキーに対する 値がSubversionの振る舞いに影響するかということについて、テキストの 説明付きでグループ化されています。何かの振る舞いを変えるためには 関連する設定ファイルをテキストエディタで開き、必要なオプション値で 修正することだけが必要です。 もし設定をデフォルトに戻したい場合は、いつでも単にその設定ディレクトリを 削除し、何か無害なsvnコマンド、たとえば svn --versionのようなものを実行することができます。 新しい設定用ディレクトリがデフォルト値を含む形で生成されます。
ユーザごとの設定領域は認証データのキャッシュも含みます。auth ディレクトリは Subversionでサポートされているさまざまな認証方法で利用される キャッシュ情報の要素を含むサブディレクトリの集まりを保持します。 このディレクトリはユーザ自身だけがその内容を読むことができるような形に 作成されます。
普通の INIベースの設定領域に加えて、Windowsプラットフォーム上で実行されて いるSubversionクライアントはWindowsのレジストリも設定データを格納する場所 として利用することができます。オプション名とその値は INIファイル中と同じ です。「file/section」 の階層関係も保存されます。わずかに異なる 方法によりますが—この方法では、ファイルとセクションは単にレジストリ キーのツリーの階層にしかすぎません。
Subversionはシステム全体の設定値を HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversionキー の元で検索します。たとえばglobal-ignores オプション、 これはconfig ファイルのmiscellany セクションにありますが、HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Config\Miscellany\global-ignoresに見つけることができます。 ユーザごとの設定値は HKEY_CURRENT_USER\Software\Tigris.org\Subversion. の下に格納されるはずです。
レジストリベースの設定オプションは、ファイルベースの残りの部分を 検索する前に 検索されます。それで、 このようなオプションは、設定ファイル中で見つかった値によって上書き されます。言い換えると、設定のプライオリティは Windowsシステムの 場合、以下の順序となることが保証されています:
コマンドラインオプション
ユーザごとのINIファイル
ユーザごとのレジストリ値
システム全体のINIファイル
システム全体のレジストリ値
また、Windowsレジストリは「コメントアウト」 のような概念をサポートしていません。しかし、Subversionは、 キーの名前がハッシュ文字(#) で始まるような すべてのオプションを無視します。これで実際にはSubversionのオプション を、レジストリから完全にキーを消さずにコメントアウトすることができます。 明らかに、そのオプションの設定作業は簡単にしています。
svn コマンドラインクライアントはWindowsの レジストリに書き込むことは決してありませんし、そこに デフォルトの設定値を作ろうともしません。必要なキーは REGEDITプログラムで作ることができます。他の方法 としては、.reg ファイルを作り、エクスプローラ シェルからそのファイルをダブルクリックすると、そのデータが レジストリにマージされます。
例 7.1. レジストリエントリ(.reg) ファイルの例
REGEDIT4 [HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Servers\groups] [HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Servers\global] "#http-proxy-host"="" "#http-proxy-port"="" "#http-proxy-username"="" "#http-proxy-password"="" "#http-proxy-exceptions"="" "#http-timeout"="0" "#http-compression"="yes" "#neon-debug-mask"="" "#ssl-authority-files"="" "#ssl-trust-default-ca"="" "#ssl-client-cert-file"="" "#ssl-client-cert-password"="" [HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\auth] "#store-auth-creds"="no" [HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\helpers] "#editor-cmd"="notepad" "#diff-cmd"="" "#diff3-cmd"="" "#diff3-has-program-arg"="" [HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\miscellany] "#global