[このドキュメントの単一ページ版もご覧いただけます。]
Subversionのコードとヘッダーファイルは、ライブラリ固有かライブラリ間か、パブリックかプライベートかといったいくつかの重要な線に沿って分離されています。この分離は、主に適切なモジュール性とコード編成に焦点を当てているためですが、広く採用されているパブリックAPIのプロバイダーおよびメンテナーとしての約束のためでもあります。Subversionで新しい関数を作成する際には、これらのことを慎重に考慮し、進めながら自分自身にいくつかの質問をする必要があります。
「私の新しいコードの利用者は、単一のライブラリ内の特定のソースコードファイルにローカルですか?」 その場合、おそらく同じソースファイルに静的関数が必要です。
「私の新しい関数は、このライブラリ内の他のソースコードが使用する必要があるものの、ライブラリ*外*では使用しない類のものですか?」 その場合、非静的な、二重アンダースコア付きの名前の関数(例えばsvn_foo__do_something)を使用し、適切なライブラリ固有のヘッダーファイルにそのプロトタイプを記述します。
「私のコードは、別のライブラリからアクセスされる必要がありますか?」 ここでは、「私のコードは、元々置こうとしていたライブラリに置くべきか、それともlibsvn_subrのようなより一般的なユーティリティライブラリに置くべきか?」など、追加で答えるべき質問がいくつかあります。どちらにしても、ライブラリ間のヘッダーファイルを使用することを検討しています。しかし、どちらにするか決める前に、次の質問をご覧ください...
「私のコードは、永続的に合理的に維持でき、SubversionパブリックAPIの提供に価値をもたらす、クリーンで保守可能なAPIを備えていますか?」 もしそうなら、パブリックAPIにプロトタイプを追加します。すぐ内側にsubversion/include/があります。そうでない場合は、計画を再確認してください。おそらく機能の抽象化に最適な方法を選択していない可能性があります。しかし、Subversion自体以外には他のソフトウェアには役に立たないような、ライブラリが共有する必要がある関数が場合によっては発生します。そのような場合は、subversion/include/private/.
コーディングスタイル ¶
SubversionはANSI Cを使用し、GNUコーディング標準に従っています。ただし、関数の名前とパラメータリストの開き括弧の間にスペースを入れない点が異なります。Emacsユーザーは、svn-dev.elをロードするだけで適切なインデント動作を得ることができます(ここにあるほとんどのソースファイルは、`enable-local-eval'が適切に設定されていれば、自動的にロードします)。
char * /* func type on own line */ argblarg(char *arg1, int arg2) /* func name on own line */ { /* first brace on own line */ if ((some_very_long_condition && arg2) /* indent 2 cols */ || remaining_condition) /* new line before operator */ { /* brace on own line, indent 2 */ arg1 = some_func(arg1, arg2); /* NO SPACE BEFORE PAREN */ } /* close brace on own line */ else { do /* format do-while like this */ { arg1 = another_func(arg1); } while (*arg1); } }
GNUコーディング標準の完全な説明については、https://www.gnu.org/prep/standards.html を参照してください。以下は、最も重要な書式設定ガイドラインを示す簡単な例であり、パラメータリストの括弧の前にスペースを入れないという例外も含まれています。
改ページの使用 ¶
コードとプレーンテキストの両方のファイルで、セクションの区切りに改ページ(Ctrl-L文字、ASCII 12)を使用しています。各セクションは改ページで始まり、改ページの直後にセクションのタイトルが続きます。
エラーメッセージの規約 ¶
エラーメッセージには、次の規約が適用されます。
subversion/include/svn_error_codes.hにある一般的なエラーメッセージに追加する情報がある場合にのみ、具体的なエラーメッセージを提供してください。
メッセージは大文字で始まります。
メッセージを70文字未満に保つようにしてください。
エラーメッセージをピリオド(".")で終わらせないでください。
エラーメッセージに改行文字を含めないでください。
情報の引用は、単一引用符(例:"'some info'")を使用して行います。
エラーが発生した関数の名前をエラーメッセージに含めないでください。Subversionが「--enable-maintainer-mode」設定フラグを使用してコンパイルされている場合、Subversion自体がこの情報を提供します。
エラー文字列にパスまたはファイル名を含める場合は、必ずそれらを引用符で囲んでください(例:"'/path/to/repos/userfile'が見つかりません")。
エラー文字列にパスまたはファイル名を含める場合は、必ず含める前にsvn_dirent_local_style()を使用して変換してください(Subversion APIとの間で渡されるパスは、標準形式であると想定されているため)。
Subversion固有の省略形を使用しないでください(例:"repo"の代わりに"repository"、"wc"の代わりに"working copy"を使用してください)。
"Invalid " SVN_PROP_EXTERNALS " property on '%s': " "target involves '.' or '..'".
エラーに説明を追加する場合は、次のようにコロンと説明を続けて報告してください。
"Can't write to '%s': object of same name already exists; remove " "before retrying".
提案やその他の追加は、次のようにセミコロンの後に加えることができます。
これらの規約の範囲内にとどまるようにしてください。したがって、エラーメッセージの異なる部分を「--」などの他の区切り文字で区切ることは避けてください。
APRプール使用の規約 ¶
(これは、あなたがAPRプールがどのように機能するかを基本的に理解していることを前提としています。詳細についてはapr_pools.hを参照してください。)
Subversionライブラリを使用するアプリケーションは、Subversion関数を呼び出す前にapr_initialize()を呼び出す必要があります。
Subversionの一般的なプール使用戦略は、次の2つの原則にまとめることができます。
プールを作成したコールレベルは、そのプールをクリアまたは破棄する唯一の場所です。
apr_pool_t *iterpool = svn_pool_create(scratch_pool); for (i = 0; i < n; ++i) { svn_pool_clear(iterpool); do_operation(..., iterpool); } svn_pool_destroy(iterpool);
無制限の回数を反復処理する場合は、反復処理に入る前にサブプールを作成し、ループ内で使用し、各反復処理の開始時にクリアしてから、ループが完了した後に破棄します。次のようにします。
上記ルールをサポートするために、さまざまなプールの寿命を表す規約として、次のプール名を使用します。
result_pool
:関数の出力が割り当てられるプール。result_poolの宣言は、関数の引数リストに常にあり、ローカルブロック内には決してないようにする必要があります。(ただし、すべての関数にresult_poolが必要である、または持っているとは限りません。)
scratch_pool
:関数ローカルデータがすべて割り当てられるプール。このプールは呼び出し元によっても提供され、呼び出し元は制御が戻ったときにすぐにこのプールをクリアすることを選択できます。
iterpool
:上記の例のように、ループ内で使用される反復処理プール。
(注:一部のレガシーコードでは、単一のpool
関数引数を使用しており、これは結果プールとスクラッチプールの両方として機能します。)
ループ境界のあるデータにiterpoolを使用することにより、関数がループ内から突然戻った場合(例えばエラーのため)、O(N)のメモリリークではなくO(1)のメモリリークを保証します。そのため、関数全体で持続するデータのサブプールを作成するのではなく、呼び出し元から渡されたプールを使用する必要があります。そのメモリは、呼び出し元のプールがクリアまたは破棄されるときに再利用されます。呼び出し元がループ内で呼び出し先を呼び出している場合は、各反復処理でプールをクリアする責任を呼び出し元に委ねます。同じロジックがコールスタック全体に伝播されます。
apr_hash_t *persistent_objects = apr_hash_make(result_pool); apr_pool_t *iterpool = svn_pool_create(scratch_pool); for (i = 0; i < n; ++i) { const char *intermediate_result; const char *key, *val; svn_pool_clear(iterpool); SVN_ERR(do_something(&intermediate_result, ..., iterpool)); SVN_ERR(get_result(intermediate_result, &key, &val, ..., result_pool)); apr_hash_set(persistent_objects, key, APR_HASH_KEY_STRING, val); } svn_pool_destroy(iterpool); return persistent_objects;
使用するプールは、コードを読む人がオブジェクトの寿命を理解するのにも役立ちます。特定のオブジェクトはループの1回の反復処理中にのみ使用されるのか、それともループの終わりを超えて持続する必要があるのか?たとえば、プールの選択は、このコードで何が起こっているかについて多くのことを示しています。
これらの原則が十分に理解される前に作成されたいくつかのレガシーコードを除いて、Subversionでの事実上すべてのプールの使用は、上記のガイドラインに従っています。
そのようなレガシーパターンの1つに、プール内にオブジェクトを割り当て、プールをオブジェクトに格納し、そのプールを(直接的またはclose_foo()関数を介して)解放してオブジェクトを破棄する傾向があります。
/*** Example of how NOT to use pools. Don't be like this. ***/
static foo_t *
make_foo_object(arg1, arg2, apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
foo_t *foo = apr_palloc(subpool, sizeof(*foo));
foo->field1 = arg1;
foo->field2 = arg2;
foo->pool = subpool;
}
[...]
[Now some function calls make_foo_object() and returns, passing
back a new foo object.]
[...]
[Now someone, at some random call level, decides that the foo's
lifetime is over, and calls svn_pool_destroy(foo->pool).]
例えば
これは魅力的に思えるかもしれませんが、プールの使用の目的を損なっています。プールの使用の目的は、個々の割り当てについてあまり心配するのではなく、全体的なパフォーマンスと寿命グループについて心配することです。代わりに、一般的にfoo_tに`pool`フィールドを含めるべきではありません。必要な数のfooオブジェクトを現在のプールに割り当てるだけです。そのプールがクリアまたは破棄されると、それらはすべて同時に消えます。
プールの破棄時に、プールに関連付けられたリソースがどのようにクリーンアップされるかの詳細については、例外処理セクションも参照してください。
要約すると
オブジェクトは独自のプールを持つべきではありません。オブジェクトは、コンストラクターの呼び出し元によって定義されたプールに割り当てられます。呼び出し元はオブジェクトの寿命を把握しており、プールを介してオブジェクトを管理します。
関数は、その操作のためにプールを作成/破棄するべきではありません。呼び出し元によって提供されたプールを使用する必要があります。繰り返しになりますが、呼び出し元は関数がどのように使用されるか、頻度、回数などについてより多くのことを知っています。したがって、関数のメモリ使用量を担当する必要があります。
たとえば、タイトなループで複数回呼び出されている関数を考えてみてください。呼び出し元は、各反復処理でスクラッチプールをクリアします。したがって、内部サブプールの作成は不要であり、大幅なオーバーヘッドになる可能性があります。代わりに、関数は渡されたプールを使用する必要があります。
上記すべてを考慮すると、すべての関数にプールを渡すことはほぼ必須です。オブジェクトはそれ自身のプールを記録せず、呼び出し側は常にメモリを管理することになっているため、各関数は隠れたマジックプールに依存するのではなく、プールを必要とします。限定的なケースでは、オブジェクトは自身の構築に使用されたプールを記録してサブパートを構築できますが、これらのケースは慎重に検討する必要があります。
プールの使用に関する問題の診断のヒントについては、メモリリークの追跡も参照してください。
APRステータスコード(APR_SUCCESSを除く)は、直接比較ではなく、常にAPR_STATUS_IS_...()マクロで確認してください。これは、Unix以外のプラットフォームへの移植性を確保するために必要です。
Subversionで例外を使用する方法を説明します。
例外はsvn_error_t構造体に格納されます
typedef struct svn_error_t { apr_status_t apr_err; /* APR error value, possibly SVN_ custom err */ const char *message; /* details from producer of error */ struct svn_error_t *child; /* ptr to the error we "wrap" */ apr_pool_t *pool; /* place to generate message strings from */ const char *file; /* Only used iff SVN_DEBUG */ long line; /* Only used iff SVN_DEBUG */ } svn_error_t;
エラーの最初の作成者である場合、次のようなことを行うでしょう。
return svn_error_create(SVN_ERR_FOO, NULL, "User not permitted to write file");
NULLフィールドに注目してください...これは、このエラーに子がない、つまり最下位のエラーであることを示しています。
エラーメッセージの記述に関するセクションも参照してください。
Subversionは内部的にUTF-8を使用してデータを格納します。これは'message'文字列にも当てはまります。APRは現在のロケールでデータを返すことが想定されているため、APRから返されたテキストはメッセージ文字列に含める前にUTF-8に変換する必要があります。
エラーを受け取った場合、3つの選択肢があります。
エラーを自分で処理します。独自のコードを使用するか、プリミティブなsvn_handle_error(err)を呼び出すだけです。(このルーチンは、エラーのスタックを巻き戻し、メッセージをUTF-8から現在のロケールに変換して出力します。)
ルーチンが無視または自分で処理する予定のエラーを受け取った場合は、必ずsvn_error_clear()を使用してクリーンアップしてください。このようなエラーがクリアされない場合は、メモリリークが発生します。
エラーを返す関数は、出力パラメータを初期化する必要はありません。
エラーを修正せずに上位にスローします
error = some_routine(foo); if (error) return svn_error_trace(error);
実際には、これを行うより良い方法はSVN_ERR()マクロを使用することです。これは同じことを行います
SVN_ERR(some_routine(foo));
エラーを上位にスローし、それを「子」引数として含めることで、新しいエラー構造体でラップします
error = some_routine(foo); if (error) { svn_error_t *wrapper = svn_error_create(SVN_ERR_FOO, error, "Authorization failed"); return wrapper; }
もちろん、子と同じフィールドを持つラッパーエラーを、カスタムメッセージを除いて作成する便利なルーチンがあります
error = some_routine(foo); if (error) { return svn_error_quick_wrap(error, "Authorization failed"); }
SVN_ERR_W()マクロを使用しても同じことができます(また、そうするべきです)
SVN_ERR_W(some_routine(foo), "Authorization failed");
(b)および(c)の場合、ルーチンによって割り当てられ、プールに関連付けられているリソースは、プールが破棄されると自動的にクリーンアップされることを知っておくことが重要です。これは、エラーを渡す前にこれらのリソースをクリーンアップする必要がないことを意味します。したがって、SVN_ERR()およびSVN_ERR_W()マクロを使用しない理由はありません。プールに関連付けられたリソースは次のとおりです。
メモリ
ファイル
apr_file_openで開かれたすべてのファイルは、プールのクリーンアップ時に閉じられます。Subversionはsvn_io_file_* APIでこの関数を使用します。つまり、svn_io_file_*またはapr_file_openで開かれたファイルは、プールのクリーンアップ時に閉じられます。
一部のファイル(たとえば、ロックファイル)は、操作が完了したときに削除する必要があります。APRにはこの目的のためのAPR_DELONCLOSEフラグがあります。次の関数は、プールのクリーンアップ時に削除されるファイルを作成します。
apr_file_openおよびsvn_io_file_open(APR_DELONCLOSEフラグが渡された場合)
svn_io_open_unique_file(delete_on_closeでTRUEが渡された場合)
ロックされたファイルは、svn_io_file_lockを使用してロックされていた場合はロック解除されます。
SVN_ERR()
マクロは、SVN_ERR__TRACING
が定義されている場合、ラップされたエラーを作成します。これは、開発者がエラーの原因を特定するのに役立ち、configure
に--enable-maintainer-mode
オプションを指定することで有効にできます。
場合によっては、呼び出された関数が返すものをそのまま返したい場合があります。通常は、独自の関数の最後にです。結果を直接返すという誘惑を避けてください
/* Don't do this! */ return some_routine(foo);
代わりに、svn_error_traceメタ関数を使用して値を返します。これにより、有効になっているときにスタックトレースが正しく実行されるようになります。
return svn_error_trace(some_routine(foo));
ほとんどすべてのプログラミング言語と同様に、Cには、攻撃者が予測可能な方法でプログラムを失敗させることができる望ましくない機能があります。多くの場合、攻撃者の利益になります。これらのガイドラインの目標は、Subversionプロジェクトに適用されるCの落とし穴を認識させることです。コードをレビューするときは、これらの落とし穴を念頭に置くことをお勧めします。熟練したパラノイアなプログラマーでも時折間違いを犯すからです。
入力検証とは、有効な入力を定義し、それ以外のすべてを拒否する行為です。コードは、信頼できないすべての入力に対して入力検証を実行する必要があります。
セキュリティ境界
Subversionサーバーコードのセキュリティ境界は、監査人が境界の品質を迅速に判断できるように、そのように識別する必要があります。セキュリティ境界は、実行中のコードがユーザーが持っていない情報にアクセスできる場合、またはコードがリクエストを行うユーザーよりも高い権限で実行される場合に存在します。このような典型的な例は、アクセス制御を行うコードや、SUIDビットが設定されたアプリケーションです。
セキュリティ境界を呼び出す関数は、渡された引数の検証チェックを含める必要があります。セキュリティ境界自体である関数は、受信した入力を監査し、不適切な値で呼び出された場合に警告する必要があります。
[### todo: ここにSubversionの例が必要...]
文字列操作
文字列に書き込む標準Cライブラリ関数ではなく、apr_strings.hで提供されている文字列関数を使用してください。APR関数は、境界チェックと宛先割り当てを自動的に行うため、より安全です。プレーンC文字列関数を使用しても理論的に安全な状況(たとえば、ソースと宛先の長さが既にわかっている場合)があるかもしれませんが、コードの堅牢性を高めてレビューしやすくするため、とにかくAPR関数を使用してください。
パスワードストレージ
ユーザーがパスワードを秘密に保つのを支援します。クライアントがローカルでパスワードを読み書きする場合は、ファイルがモード0600であることを確認する必要があります。ファイルが他のユーザーに読み取り可能な場合、クライアントは、露出の危険性があるため、ユーザーにファイルモードを変更するように指示するメッセージを表示して終了する必要があります。
アプリケーションの正しい機能を保証するために破棄が必要なリソースがいくつかあります。このようなリソースには、ファイルが含まれます。特に、開いているファイルはWindowsで削除できないためです。
バックグラウンドでファイルまたは他のストリームにスタックされている可能性のあるストリームを作成して返すAPIを作成する場合、リソースの正しい破棄を確実にするために、ストリームが構築されるストリーム(が所有する)のデストラクタを正しく呼び出す必要があります。
最初に https://svn.haxx.se/dev/archive-2005-12/0487.shtmlで、次に https://svn.haxx.se/dev/archive-2005-12/0633.shtmlで、ファイル、ストリーム、エディター、ウィンドウハンドラーについてより一般的な用語で議論されました。
グレッグ・ハドソンが述べたように
検討の結果、ここで目標とするのは次のとおりです。
- 基になるオブジェクトから読み取りまたは書き込みを行うストリームは、そのオブジェクトを所有します。つまり、ストリームを閉じると、該当する場合は基になるオブジェクトが閉じられます。
- ストリームを作成したレイヤー(関数またはデータ型)は、上記の規則が適用される場合を除き、ストリームを閉じる責任があります。
- ウィンドウハンドラーは奇妙な種類のストリームと見なされ、最後のNULLウィンドウを渡すとストリームを閉じると見なされます。
apply_textdeltaをウィンドウハンドラーの作成と考えると、それほど間違っていません。svn_stream_from_aprfileは子ファイルの所有権を持っておらず、svn_txdelta_applyは渡されたウィンドウストリームを閉じる責任を誤って引き受けており、他にもいくつかの逸脱がある可能性があります。
ただし、上記の規則には1つの例外があります。ストリームが引数として関数に渡される場合(例:svn_client_cat2()の'out'パラメーター)、そのルーチンはそのリソースを作成しなかったため、ストリームのデストラクタを呼び出すことはできません。
svn_client_cat2()がストリームを作成する場合は、そのストリームのデストラクタも呼び出す必要があります。上記のモデルでは、そのストリームは'out'パラメーターのデストラクタを呼び出します。ただし、'out'パラメーターを破棄する責任は別の場所にあるため、これは間違っています。
この問題を解決するために、少なくともストリームの場合には、svn_stream_disown()が導入されました。この関数はストリームをラップし、その上にスタックされたストリームが破棄しようとしても、破棄されないようにします。
可変数の引数を受け入れ、リストがnullポインター定数で終了することを期待する関数を呼び出す場合(そのような関数の例はapr_pstrcat)、リストを終了するためにNULLシンボルを使用しないでください。コンパイラとプラットフォームによっては、NULLポインターサイズの定数である場合とそうでない場合があります。そうでない場合、関数は引数リストの末尾を超えてデータを読み取ってしまう可能性があります。
代わりに、SVN_VA_NULL(1.9以降で定義されています。svn_types.h)を使用してください。これはnullポインター定数であることが保証されています。たとえば、
return apr_pstrcat(cmd->temp_pool, "Cannot parse expression '", arg2, "' in SVNPath: ", expr_err, SVN_VA_NULL);
GNU標準に加えて、Subversionではこれらの規約を使用しています。
ほとんどのSubversion APIへの入力としてパスまたはファイル名を使用する場合は、svn_dirent_internal_style() APIを使用してSubversionの内部/正規形式に変換してください。あるいは、Subversion APIからパスまたはファイル名を出力として受け取る場合は、svn_dirent_local_style() APIを使用して、プラットフォームで期待される形式に変換してください。
コードのインデントには、タブではなくスペースのみを使用してください。タブの表示幅は十分に標準化されておらず、スペースを使用したインデントを手動で調整する方が簡単です。
コードが最小限の標準表示ウィンドウで適切に表示されるように、行を79列に制限してください。(インデント、引用符などで追加の数列を占める80列のテキストのブロックを宣言する場合など、各行を2つに分割すると不合理に面倒になる場合など、例外が存在する可能性があります。)
公開されているすべての関数、変数、構造体は、対応するライブラリ名で示されている必要があります。たとえば、libsvn_wcのsvn_wc_adm_openなどです。ライブラリプライベートヘッダーファイル(たとえば、libsvn_wc/wc.h)で作成されたすべてのライブラリ内部宣言は、ライブラリプレフィックスの後に2つのアンダースコアで示されている必要があります(たとえば、svn_wc__ensure_directoryなど)。単一のファイル(たとえば、libsvn_wc/update_editor.c内の静的関数get_entry_urlなど)にプライベートなすべての宣言には、追加の名前空間装飾は必要ありません。ライブラリの外部で使用する必要があるが、公開されていないシンボルは、include/private/ディレクトリに共有ヘッダーファイルに配置され、二重アンダースコア表記を使用します。このようなシンボルは、Subversionコアコードのみで使用できます。
要約すると、
/* Part of published API: subversion/include/svn_wc.h */ svn_wc_adm_open() #define SVN_WC_ADM_DIR_NAME ... typedef enum svn_wc_schedule_t ... /* For use within one library only: subversion/libsvn_wc/wc.h */ svn_wc__ensure_directory() #define SVN_WC__BASE_EXT ... typedef struct svn_wc__compat_notify_baton_t ... /* For use within one file: subversion/libsvn_wc/update_editor.c */ get_entry_url() struct handler_baton { /* For internal use in svn core code only: subversion/include/private/svn_wc_private.h */ svn_wc__entry_versioned()
Subversion 1.5より前では、ライブラリの外部で使用する必要があるプライベートシンボルは、二重アンダースコア表記を使用して、公開ヘッダーファイルに配置されていました。この慣行は廃止され、このようなシンボルはすべてレガシーであり、下位互換性を維持するために保守されています。
ユーザーに出力される可能性のあるテキスト文字列では、パスやその他の引用可能なものの周りに順方向の引用符のみを使用してください。例えば
$ svn revert foo svn: warning: svn_wc_is_wc_root: 'foo' is not a versioned resource $
最初の引用符にバックティック('foo'の代わりに`foo')を使用した文字列がたくさんありましたが、一部のフォントでは見栄えが悪く、一部の人の自動強調表示も台無しにしたため、順方向の引用符を常に使用するという規則に落ち着きました。
Emacsを使用する場合は、必要に応じてsvn-dev.elとsvnbook.elを取得できるように、.emacsファイルに次のようなものを配置してください。
;;; Begin Subversion development section (defun my-find-file-hook () (let ((svn-tree-path (expand-file-name "~/projects/subversion")) (book-tree-path (expand-file-name "~/projects/svnbook"))) (cond ((string-match svn-tree-path buffer-file-name) (load (concat svn-tree-path "/tools/dev/svn-dev"))) ((string-match book-tree-path buffer-file-name) ;; Handle load exception for svnbook.el, because it tries to ;; load psgml, and not everyone has that available. (condition-case nil (load (concat book-tree-path "/src/tools/svnbook")) (error (message "(Ignored problem loading svnbook.el.)"))))))) (add-hook 'find-file-hooks 'my-find-file-hook) ;;; End Subversion development section
もちろん、セットアップに合わせてパスをカスタマイズする必要があります。たとえば、ある開発者は、正規表現をより選択的に文字列照合させることもできます。
> Here's the regexp I'm using: > > "src/svn/[^/]*/\\(subversion\\|tools\\|build\\)/" > > Two things to notice there: (1) I sometimes have several > working copies checked out under ...src/svn, and I want the > regexp to match all of them; (2) I want the hook to catch only > in "our" directories within the working copy, so I match > "subversion", "tools" and "build" explicitly; I don't want to > use GNU style in the APR that's checked out into my repo. :-)
私たちは、ファイルに個々の作者の名前をマークしないという伝統を持っています(つまり、ソースファイルの先頭に「Author: foo」や「@author foo」のような行を特別な場所に置かないということです)。これは、縄張り意識を助長しないためです。たとえファイルに作者が一人しかいなくても、他の人が自由に修正できるようにしたいのです。誰かがそのファイルに個人的な主張を立てているように見えると、人々は不必要に躊躇するかもしれません。
文末と次の文頭の間には2つのスペースを入れます。これにより、可読性が向上し、エディターの文移動や操作コマンドを使用できるようになります。
コード全体には、他にも多くの暗黙の規約があり、誰かが意図せずにそれらに従わなかった場合にのみ気づかれます。物事の進め方に敏感な目を持ち、疑問がある場合は質問してください。
すべてのコミットにはログメッセージが必要です。
ログメッセージの対象読者は、Subversionにはすでに精通しているが、必ずしもこの特定のコミットに精通しているわけではない開発者です。通常、誰かが変更を振り返って読むとき、彼はその変更に関するすべてのコンテキストを頭に持っていません。たとえ彼がその変更の作者であっても、それは当てはまります!すべての議論やメーリングリストのスレッドなどは忘れられている可能性があり、その変更が何に関するものなのかを知る唯一の手がかりは、ログメッセージと差分自体から得られます。また、人々は驚くほど頻繁に変更を再訪します。たとえば、元のコミットから数か月後、その変更がメンテナンスブランチに移植される可能性があります。
ログメッセージは、変更の紹介です。
ブランチで作業している場合は、ログメッセージの先頭に以下を付加します。
On the 'name-of-branch' branch: (Start of your log message)
ログメッセージは、変更の一般的な性質を示す1行で始め、必要に応じて説明的な段落を続けます。
これにより、開発者がログメッセージの残りを読むための正しい心構えになるのに役立つだけでなく、各コミットの最初の行をIRCのようなリアルタイムフォーラムにエコーする「ASFBot」ボットともうまく連携します。(詳細については、https://wilderness.apache.org/ を参照してください。)
コミットが1つのファイルへの単純な変更である場合は、一般的な説明を省略して、以下に示す標準のファイル名、記号形式で詳細な説明に直接進むことができます。
ログメッセージ全体で、文の断片ではなく完全な文を使用してください。断片は曖昧であることが多く、何を意味するのかを書き出すのに数秒しかかかりません。「Doc fix」、「New file」、「New function」のような特定の断片は標準的なイディオムであるため許容されますが、詳細についてはすべてソースコードに記載する必要があります。
ログメッセージには、このコミットで削除される記号の名前を含め、影響を受けるすべての関数、変数、マクロ、makefileターゲット、文法規則などの名前を記載する必要があります。これにより、後でログを検索するのに役立ちます。ワイルドカードで名前を隠さないでください。グロブされた部分は、後で誰かが検索する可能性があるからです。たとえば、これは良くありません。
* subversion/libsvn_ra_pigeons/twirl.c (twirling_baton_*): Removed these obsolete structures. (handle_parser_warning): Pass data directly to callees, instead of storing in twirling_baton_*. * subversion/libsvn_ra_pigeons/twirl.h: Fix indentation.
後で、誰かが`twirling_baton_fast`に何が起こったのかを把握しようとしたとき、「_fast」だけで検索すると見つからない可能性があります。より良いエントリは次のようになります。
* subversion/libsvn_ra_pigeons/twirl.c (twirling_baton_fast, twirling_baton_slow): Removed these obsolete structures. (handle_parser_warning): Pass data directly to callees, instead of storing in twirling_baton_*. * subversion/libsvn_ra_pigeons/twirl.h: Fix indentation.
ワイルドカードは`handle_parser_warning`の説明では問題ありませんが、2つの構造がログエントリの他の場所でフルネームで言及されている場合に限ります。
ログメッセージには、プロパティの変更も記載する必要があります。たとえば、トランクの「svn:ignore」プロパティを変更する場合は、ログに次のような内容を記載することができます。
* trunk/ (svn:ignore): Ignore 'build'.
上記は、サブバージョンによって管理される「svn:mergeinfo」のようなプロパティではなく、自分が管理するプロパティにのみ適用されます。
各ファイルには「*」で始まる独自のエントリがあり、ファイル内の変更は記号ごとにグループ化されており、記号は括弧で囲んでコロンが続き、その後に変更を説明するテキストが続くことに注意してください。たとえ変更されたファイルが1つだけであっても、この形式に従ってください。一貫性が可読性を助けるだけでなく、ソフトウェアがログエントリを自動的に色分けできるようになります。
上記に対する例外として、複数のファイルでまったく同じ変更を加える場合は、変更されたすべてのファイルを1つのエントリにリストします。たとえば、次のようになります。
* subversion/libsvn_ra_pigeons/twirl.c, subversion/libsvn_ra_pigeons/roost.c: Include svn_private_config.h.
変更されたすべてのファイルがソースツリーの奥深くにある場合は、変更エントリの前に共通のプレフィックスを記述することで、ファイル名エントリを短くすることができます。
[in subversion/bindings/swig/birdsong] * dialects/nightingale.c (get_base_pitch): Allow 3/4-tone pitch variation to account for trait variability amongst isolated populations Erithacus megarhynchos. * dialects/gallus_domesticus.c: Remove. Unreliable due to extremely low brain-to-body mass ratio.
変更が課題追跡システムの特定の課題に関連している場合は、ログメッセージに「issue #N」のような文字列を含めますが、変更内容を要約するようにしてください。たとえば、パッチが課題#1729を解決する場合は、ログメッセージは次のようになります。
Fix issue #1729: Don't crash because of a missing file. * subversion/libsvn_ra_ansible/get_editor.c (frobnicate_file): Check that file exists before frobnicating.
関連する変更をまとめてください。たとえば、svn_ra_get_ansible()を非推奨にしてsvn_ra_get_ansible2()を作成する場合は、それらの2つはログメッセージ内で互いに近くにある必要があります。
* subversion/include/svn_ra.h (svn_ra_get_ansible2): New prototype, obsoletes svn_ra_get_ansible. (svn_ra_get_ansible): Deprecate.
大規模な変更または変更グループの場合は、ログエントリを空白行で区切られた段落にグループ化します。各段落は、単一の目標を達成する一連の変更である必要があり、各グループは変更を要約する1つまたは2つの文で始める必要があります。当然、真に独立した変更は別々のコミットで行う必要があります。
他の人が書いたパッチをコミットする場合や、提案された変更をコミットする場合は、 クレジット を参照して、クレジットを付与する方法を確認してください。
現在のコードを理解するためにログエントリが必要になることは決してあってはなりません。ログに重要な説明を書いている場合は、そのテキストが実際に説明しているコードと一緒にコメントに属するのではないかを慎重に検討する必要があります。正しい実行方法の例を次に示します。
(consume_count): If `count' is unreasonable, return 0 and don't advance input pointer.
そして、`cplus-dem.c`の`consume_count`に。
while (isdigit((unsigned char)**type)) { count *= 10; count += **type - '0'; /* A sanity check. Otherwise a symbol like `_Utf390_1__1_9223372036854775807__9223372036854775' can cause this function to return a negative value. In this case we just consume until the end of the string. */ if (count > strlen(*type)) { *type = save; return 0; }
これが、たとえば、新しい関数に必要なログエントリが「新しい関数」のみである理由です。すべての詳細はソースに記載する必要があります。
変更されたすべての名前を記述する必要性には、常識的な例外を設けることができます。たとえば、プログラム全体で些細な変更が必要になる変更(変数の名前変更など)を行った場合、影響を受けるすべての関数を名前を付ける必要はありません。「すべての呼び出し元が変更されました」と言うだけで済みます。記号の名前を変更する場合は、追跡のために、古い名前と新しい名前の両方を必ず記述してください。例については、r861020を参照してください。
一般に、識別子を検索することでエントリを簡単に見つけられるようにすることと、時間を浪費したり、網羅的になることで読みにくいエントリを作成することの間には緊張関係があります。上記のガイドラインと最善の判断を使用し、仲間の開発者に配慮してください。(また、「svn log」を使用して、他の人がどのようにログエントリを書いているかを確認してください。)
ドキュメントまたは翻訳のログメッセージには、やや緩やかなガイドラインがあります。すべての記号に名前を付ける必要性は明らかに適用されず、変更が翻訳などの継続的なプロセスのもう1つの増分にすぎない場合は、すべてのファイルに名前を付ける必要さえありません。変更を簡単に要約するだけで済みます。たとえば、「マダガスカル語の翻訳に関する作業の追加」。プロジェクトに関与するすべての人が行った変更を理解できるように、ログメッセージは英語で記述してください。
ブランチを使用してコードを「チェックポイント」している場合で、レビューの準備ができていないと感じる場合は、ログメッセージの上部に、'On the 'xxx' branch notice'の後などに、何らかの通知を記述してください。
*** checkpoint commit -- please don't waste your time reviewing it ***
また、そのブランチでの後のコミットをレビューする必要がある場合は、ログメッセージに適切な「svn diff」コマンドを提供してください。差分には、そのブランチの隣接していない2つのコミットが含まれる可能性があり、レビュー担当者はどれがどれであるかを把握するために時間を費やす必要がないからです。
コードの貢献を首尾一貫した解析可能な方法で記録することは非常に重要です。これにより、誰が積極的に貢献しているか、そして彼らが何に貢献しているかを把握するスクリプトを作成できるため、潜在的な新しいコミッターを迅速に見つけることができます。Subversionプロジェクトでは、これを実現するために、ログメッセージで人間が読めるが機械で解析可能なフィールドを使用しています。
他の人が書いたパッチをコミットする場合は、行の先頭に「Patch by: 」を使用して、作者を示します。
Fix issue #1729: Don't crash because of a missing file. * subversion/libsvn_ra_ansible/get_editor.c (frobnicate_file): Check that file exists before frobnicating. Patch by: J. Random <jrandom@example.com>
複数の個人がパッチを書いた場合は、各継続行を必ず空白で開始して、それぞれを別の行にリストします。コミッターでない場合は、名前(わかる場合)とメールでリストする必要があります。完全および部分コミッターの場合は、COMMITTERS (そのファイルの左端の列) からの正規のユーザー名でリストする必要があります。さらに、「me」は、実際に変更をコミットしている人の略語として使用できます。
Fix issue #1729: Don't crash because of a missing file. * subversion/libsvn_ra_ansible/get_editor.c (frobnicate_file): Check that file exists before frobnicating. Patch by: J. Random <jrandom@example.com> Enrico Caruso <codingtenor@codingtenor.com> jcommitter me
誰かがバグを見つけたか、問題を指摘したが、パッチを書かなかった場合は、「Found by: 」(または「Reported by: 」)でその貢献を示します。
Fix issue #1729: Don't crash because of a missing file. * subversion/libsvn_ra_ansible/get_editor.c (frobnicate_file): Check that file exists before frobnicating. Found by: J. Random <jrandom@example.com>
誰かが何か役立つことを提案したが、パッチを書かなかった場合は、「Suggested by: 」でその貢献を示します。
Extend the Contribulyzer syntax to distinguish finds from ideas. * www/hacking.html (crediting): Adjust accordingly. Suggested by: dlr
誰かがパッチをテスト駆動した場合は、「Tested by: 」を使用します。
Fix issue #23: random crashes on FreeBSD 3.14. Tested by: Old Platformer (I couldn't reproduce the problem, but Old hasn't seen any crashes since he applied the patch.) * subversion/libsvn_fs_sieve/obliterate.c (cover_up): Account for sieve(2) returning 6.
誰かが変更をレビューした場合は、「Review by: 」(または、必要に応じて「Reviewed by: 」)を使用します。
Fix issue #1729: Don't crash because of a missing file. * subversion/libsvn_ra_ansible/get_editor.c (frobnicate_file): Check that file exists before frobnicating. Review by: Eagle Eyes <eeyes@example.com>
フィールドには複数の行を含めることができ、ログメッセージにはフィールドの任意の組み合わせを含めることができます。
Fix issue #1729: Don't crash because of a missing file. * subversion/libsvn_ra_ansible/get_editor.c (frobnicate_file): Check that file exists before frobnicating. Patch by: J. Random <jrandom@example.com> Enrico Caruso <codingtenor@codingtenor.com> me Found by: J. Random <jrandom@example.com> Review by: Eagle Eyes <eeyes@example.com> jcommitter
貢献に関する詳細については、対応するフィールドの直後に括弧付きで記載する必要があります。そのような括弧内の注釈は常に、すぐ上のフィールドに適用されます。次の例では、可読性を高めるためにフィールドの間隔が空けられていますが、間隔はオプションであり、解析可能性のために必須ではありません。
Fix issue #1729: Don't crash because of a missing file. * subversion/libsvn_ra_ansible/get_editor.c (frobnicate_file): Check that file exists before frobnicating. Patch by: J. Random <jrandom@example.com> (Tweaked by me.) Review by: Eagle Eyes <eeyes@example.com> jcommitter (Eagle Eyes caught an off-by-one-error in the basename extraction.)
現在、これらのフィールド
Patch by: Suggested by: Found by: Review by: Tested by:
が、正式にサポートされている唯一のクレジットフィールドです(「サポートされている」とは、スクリプトがそれらを検索することを意味します)。これらはSubversionのログメッセージで広く使用されています。今後のフィールドはおそらく「VERB by: 」の形式になり、時々、誰かが正式に聞こえるが実際にはそうではないフィールドを使用する可能性があります。たとえば、「Inspired by: 」のいくつかのインスタンスがあります。これらは問題ありませんが、自分で作成するよりも、公式フィールドまたは括弧内の注釈を使用するようにしてください。また、報告者がすでに課題に記録されている場合は、「Reported by: 」を使用しないでください。代わりに、単に課題を参照してください。
Subversionの既存のログメッセージを調べて、これらのフィールドを実際にどのように使用するかを確認してください。トランクのワーキングコピーの先頭からこのコマンドが役立ちます。
svn log | contrib/client-side/search-svnlog.pl "(Patch|Review|Suggested) by: "
注意: 一部のコミットメッセージに見られる「Approved by: 」フィールドは、これらのクレジットフィールドとはまったく関係がなく、通常はスクリプトによって解析されません。これは、部分コミッターのコミットが通常範囲外で承認された人物を示す標準的な構文、または(リリースブランチへのマージの場合)変更のマージに投票した人物を示す標準的な構文にすぎません。
Subversionリポジトリは、GitHubのhttps://github.com/apache/subversion/ にミラーリングされています。
一部のユーザーはGitHubでプルリクエストを作成する場合があります。コードがSubversionリポジトリにコミットされた場合は、ログメッセージにテキストを含めて、プルリクエストを自動的にクローズするようにしてください。
This fixes #NNN in GitHub
コードをコミットせずにプルリクエストを管理するには、ASF IDに接続されたGitHubアカウントを持っている必要があり、ASF Infraによってアカウントにトリアージ担当ロールが割り当てられている必要があります。