miauのブログ

はてなダイアリー「miauの避難所」をはてなブログに移行しました

pre-commit hook で Hoge.txt と hoge.txt の重複を防ぐ設定

Subversion でファイルを管理していると、プロジェクト期間中に一度くらいは「大文字/小文字が異なる同名ファイルへのリネーム」・・・たとえば Hogeutil.java を HogeUtil.java にリネームするとか・・・で悩まされることがあったりします。こういう操作を行ったリポジトリから最新ファイルを取得しようとすると、エラーになって抜け出せなくなったりするんですよね。

ちゃんと原因は調べていないんですが、SVN 上のリネーム(ファイル移動)はコピー&元ファイルの削除で行われるので、

  • Hogeutil.java を HogeUtil.java にリネームしようとする→Windows 上だと同名ファイルなので処理に失敗する
  • もし同名ファイルの処理をスキップしても次の削除の段階で意図せずに消されてしまい、次のリビジョン取得に失敗する

というのが原因だと思われます。今回はまじめに対策してみようと思って case-insensitive.py を導入してみましたので、そのお話。

残念ながらリネームの防止には成功していないんですが、ファイルの重複は防止できるので設定しておいたほうがいいとは思います。

check-case-insensitive.py の概要

ちょっと古いページでリンクも切れてしまってますが。

大文字と小文字を区別しないために生じるファイル名の衝突を検出する pre-commit フック。

このスクリプトは、check-case-insensitive.py よりも *遥かに* 効率的ですが、Subversion 1.3.0 以降を必要とします。

というように紹介されています。

check-case-insensitive.py の導入方法

導入は簡単です。

hooks ディレクトリに check-case-insensitive.py を配置&実行権限付与
cd hooks
wget http://svn.apache.org/repos/asf/subversion/trunk/contrib/hook-scripts/case-insensitive.py
chmod +x case-insensitive.py
# svn で管理している場合は chmod ではなく以下の 2 行で
#svn add case-insensitive.py
#svn propset 'svn:executable' '*' case-insensitive.py
pre-commit hook からの呼び出しを追加

pre-commit に以下の 2 行(実質 1 行)を追加します。

 # Detect case-insensitive filename clashes
 $REPOS/hooks/case-insensitive.py "$REPOS" "$TXN" || exit 1

動作確認

大文字/小文字の異なる同名ファイルの追加

hoge.txt がある状態で Hoge.txt を追加、という操作を Linux 上で行って実験してみます。

# svn status -v
                 3        3 root         .
                 7        7 root         hoge.txt
A                0       ?   ?           Hoge.txt

# svn commit -m 'Add Hoge.txt'
Adding         Hoge.txt
Transmitting file data .svn: Commit failed (details follow):
svn: Commit blocked by pre-commit hook (exit code 1) with output:
Clash: '/hoge.txt' '/Hoge.txt'

うまく衝突を検出してくれました。

大文字/小文字の異なる同名ファイルへのリネーム

hoge.txt を Hoge.txt にリネームしてみます。

# svn status
D       hoge.txt
A  +    Hoge.txt

# svn commit -m 'Rename hoge.txt to Hoge.txt'
Adding         Hoge.txt
Deleting       hoge.txt

Committed revision 8.

これもエラーになってくれることを期待していたんですが、残念ながらコミットが成功してしまいました・・・。

リネームの検出できない?→挫折

※うまくいっていないんですが、調べたことをいろいろメモしておきます。

ちょっとソースを読んでみました。

fs.dir_entries (実体は libsvn の svn_fs_dir_entries)でファイル名の一覧を取得しているけど、これで取得しているのは更新後のファイル一覧で、

    if (name_pair[0] == canonical and name_pair[1] != name):

のように「小文字にすると同じ名前で、自分自身ではないもの」をリストアップしている形と。

これを更新前のファイル一覧に変えてやって、判定処理を少し変えてやればリネームの検出もできそうですけど・・・SubversionAPI がよくわからないのと、今回のリポジトリはすでに複数の会社で共用していてあまり大きな変更は加えたくなかったので、この方法はパス。元の処理は残したまま「リネームされたファイルの一覧を取得して変更前/後のファイル名をチェックする」処理を追加する方針でやってみます。

変更ファイル一覧を取得している fs.paths_changed(実体は svn_fs_paths_changed)だと変更前の一覧は取れないようなので fs.paths_changed2(実体は svn_fs_paths_changed2)のほうを使って・・・あれ?なんか copyfrom_path って属性がないって言われる。なぜか svn_fs_path_change2_t が取得されるはずなのに svn_fs_path_change_t が取得されてる??

調べて見ると、どうもバグがあったようで。

で修正されている感じなんですが、yum で取得した 1.6.11-7.el5_6.3 にはこの修正はまだ適用されていない(?)ようで。

じゃあ repos.ChangeCollector でも変更の一覧が取得できるらしいので、こっちを使えばいいかな?と、

を参考にファイル一覧を取得してみたんですが、なぜか get_changes() の戻り値が空。使い方間違ってるかもしれませんけど、このあたりで時間切れ。

本当にリネームの対策は必要?

ここの「ファイル名の大文字小文字の変換」では

  • 解決法 A) リポジトリブラウザ上でリネームする ※こちらが「お勧め」とのこと
  • 解決法 B) 一旦別名にリネーム→コミットしてから本来のファイル名にリネームする

の二種類の対策が載っています。確かに A) でリネームすると取得のタイミングでエラーにはなりませんでした。以前この操作でハマった気がするんだけど、リポジトリブラウザ上でリネームすればいいということ?それとも TortoiseSVN のバージョンが上がって挙動が変わったとか?

もしかしたら Subversion の各種クライアントがちゃんと対応していれば、同名ファイルのリネームも正しく操作できる(=サーバ側のでの対策は不要)のかもしれません。でもクライアントによってはちゃんと動作するとも限らないし、個人的には同名ファイルへのリネームはサーバ側で禁止して、B) の「いったん別名にリネームする」運用にしてしまいなと思っています。