svn commit時に英語でメッセージ書くためのヒントを表示する

Qiitaに次のような投稿がありました。

qiita.com

git commitをするときに英語の例文をコメントとして表示するようにして、英語コミット力を上げようという内容です。これはいいなと、同じようなことがSubversionでも出来ないかなと調べてみました。結果としてうまくできたので、今回はそのときの調査メモと、実現方法を記します。

調査編

テンプレートが指定できない?

svnのオプションなどを調べ始めてすぐに気付いたのですが、Subversionにはそもそもテンプレートを表示する設定・外部変数のような概念は存在していないようです。環境変数SVN_EDITOR」を使い、コミットで使うエディタを指定できる程度で、それ以上のカスタマイズはできません。ネット上の情報にも当たってみましたが、ほとんどがエディタのコマンドだけを指定しているだけで、特殊変数のようなものは使っていませんでした。

とはいえ、コミット時のエディタ上には対象ファイル/ディレクトリの一覧と、入力欄とを区切る一文が表示されています。これはどのようにして渡ってきているのかと、以下のようなシェルスクリプトを作成して引数を調べてみました。

#!/bin/sh

echo "num args: $#"
for arg in $@; do
        echo "-----"
        echo $arg
done
env
exit 0

コマンド引数の数と、その内容、そして環境変数を出力するだけのスクリプトです。これをSVN_EDITORに指定してsvn commitを実行してみると…

$ SVN_EDITOR=/Users/user/bin/commit.sh svn commit
num args: 1
-----
svn-commit.tmp
  :
…環境変数の一覧…

というように、ファイル名として「svn-commit.tmp」が指定したプログラムに渡されていて、それ以外は環境変数も含めて何もしていないことがわかりました。ちなみにsvn-commit.tmpはコミット作業中にしか存在しない一時ファイルで、中身はコミット時に出ているメッセージそのものでした。

% cat ~/tmp/foo/svn-commit.tmp

--This line, and those below, will be ignored--

…更新ファイルの一覧…

しかもこの「svn-commit[.XX].tmp」という名前はSubversionのソースにハードコーディングされていて(!)、気軽に変更できるようなものではありませんでした。

 if (! lmb->non_interactive)
   {
     err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
                                               lmb->editor_cmd,
                                               lmb->base_dir ? lmb->base_dir : "",
                                               msg_string, "svn-commit",  // ←ココ!
                                               lmb->config, TRUE,
                                               lmb->message_encoding,
                                               pool);
   }
 else /* non_interactive flag says we can't pop up an editor, so error */
   {

…番号で重複を避けているとはいえ、このカスタマイズを一切許さない設計はどうなんだろう。

ともかく、gitと同じ方法では実現できなさそうです。

どうやってコミット内容だけを取り出しているか

それではと、次にコミットファイル「svn-commit.tmp」の内容に注目し、このファイルからどうやってコミットメッセージだけを取り出しているかを調べてみることにしました。ファイル内には「--This line, and those below, will be ignored--」から始まる文字列が含まれていますが、コミット後のログメッセージにはその内容が一切含まれていません。ということは、コミット処理のときに何かしらの方法で取り除いているはずです。

それが分かれば、gitの方法#で始まる行が無視されることを利用して例文を挿入しているのと同じことが実現できるはず、という魂胆です。

本ファイル作成処理は先ほど述べたようにSubversionソースコードとして書かれていたので、どうせその近辺に処理があるだろうと読んでみます。

すると、trunk/subversion/svn/util.cの337行目にて、svn-commit.tmpに書かれている「--This line, and those below, will be ignored--」そのものがマクロで定義されていました。当然のようにハードコーディングされています。ローカライズの余地すらありません。

#define EDITOR_EOF_PREFIX  _("--This line, and those below, will be ignored--")

さらに読んでみると、この行より後ろに書かれた内容はすべて切り捨てていて、一切無視していることがわかりました。最初に現れるこの行以降はすべて無視される仕様です。

なるほど。

調査のまとめ

ここまでの調査をまとめます。

  • subversionではgitにあるようなテンプレートを指定する設定はない
  • エディタは環境変数SVN_EDITORで指定できる。…が、第1引数にファイル名svn-commit.tmpが渡されるだけ
    • ファイルの中身は、どのファイルが更新されるかが書かれた、いつものメッセージ
  • ファイル名、ファイルの内容はソースコードにハードコーディングされていて、再コンパイルしなければ変更できない
  • コミットメッセージとコメントは、コミットメッセージの最初に現れる「--This line, and those below, will be ignored--」行よりも前か後ろかで判断する

ということで、ファイル名と魔法の文字列とがキーになることがわかりました。ここまで分かれば色々とできそうです。

実現編:例文コメントの 実現

というわけで、調査結果をふまえると、次のような方針であれば実現出来そうだと期待できます。それは、

環境変数SVN_EDITORにsvn-commit.tmpを書き換えた上でエディタを起動するコマンドをいれ、コメントにしたい文は"--This line, and those below, will be ignored--"の後に入れておく」

という方法です。svnプログラムが把握しているのはコミットメッセージの対象ファイルのみですから、エディタが終了するまでの間に適当なファイルないように書き換えてしまえば、コメントだろうとテンプレートだろうと、こちらの狙ったメッセージを入れ込むことができるだろうと考えました。

さっそくそんな感じの処理を実現する以下のスクリプトを書いてみました。

#!/bin/sh

mv "$1" "$1".orig
echo "\n--This line, and those below, will be ignored--" > "$1"
echo "Hello, world!" >> "$1"
cat "$1".orig >> "$1"
rm "$1".orig
vi "$1"

exit 0

まず元のファイルを.origと付けて退避しておきます。そして、魔法の言葉「--This line, and those below, will be ignored--」を挟んでからコメント(ここではとりあえず"Hello, world!")を入れ、最後に退避しておいた元のデータを書き加え、退避データは削除します。

・・・さあ、上記のファイルを~/tmp/commit.shに保存して、うまく動くかどうか試してみましょう。

(SVN_EDITOR=~/tmp/commit.sh svn commitの実行画面)
コミットメッセージを書いてみる

--This line, and those below, will be ignored--
Hello, world!

--This line, and those below, will be ignored--

M    myfile

おお。

% svn up
Updating '.':
At revision 4.
% svn log
------------------------------------------------------------------------
r4 | XXXXX | 2016-01-23 14:02:30 +0900 (土, 23  1 2016) | 2 lines

コミットメッセージを書いてみる
------------------------------------------------------------------------
:

おおお。うまくいきました!

まとめ&出来上がったスクリプト

当初はsvnでは環境変数がないため実現できないかも、と思った今回の調査でしたが、SVN_EDITORに渡すコマンドとコメント判別ルールを利用することで、git版と同様の機能を実現することができました。

最後に、svngit版の記事と同じようなコミットコメントを実現するスクリプトを記します。先ほどのテストでは「-- This line, …」が2回も出てきてうっとうしかったので、それを消すような処理も加えてあります。

#!/bin/sh

echo "num args: $#"
for arg in $@; do
    echo "-----"
    echo $arg
done

mv "$1" "$1".orig

# コミット時のコメントを作成
cat <<'_EOT_' > "$1"

--This line, and those below, will be ignored--

fix, add, changeといった事実ではなく、このcommitで実現する要件や仕様を書きましょう。(リファクタなどは除く)

 例文)
 - Fix typo in docs
 - Remove unused code
 - Remove use of deprecated method
 - Update Modernizr to v1.6
 - Make it possible to have IDs per request
 - Make sure to reset default_url_options(必ずリセットする)
 - Don't use "assert_not_nil"
 - Allow the user to drag faster
 - Remove methods to avoid warnings.
---
_EOT_

# 元コメントの3行目以降(=変更ファイル一覧)を追加
tail -n +3 "$1".orig >> "$1"
rm "$1".orig

# エディタの起動
vi "$1"
exit 0

これでsvnしか使えないプロジェクトでも幸せになれますね!