Subversionで変更されているファイルのみのdiffを表示させる

ちょっと色々気になったので書いてみた。

やりたいこと

「ローカルで修正されたもの」つまり"svn status"で行頭に"M"が表示されるファイルだけを選び、そのdiffを表示させる。"A"や"D"のものは無視する。

簡単に考えると

さすがに"svn diff"だけだと"A"や"D"のものまで表示してしまうので、対象パスを指定する必要がある。
となると、"svn status"の出力結果をフィルタリングして、対象パスを抽出する?

$ svn st
M      hoge
M      fuga
M      piyo

まぁ、こうすれば出てくるからあとは適当に

$ svn diff $(svn status | grep ^M | awk '{ print $2 }')

とでもやってしまいたいところだけど。
この"svn status"の出力結果からファイル名だけを取得するのは結構むずかしい気がする。

イヤらしい例

例えばこんなの。

$ svn st
A      piyo
M    K fuga
A      hoge
M      a b c
A        space
M      ab'`"!
M      ab'cd"ef
  • 単純に出力を区切った2番目がファイル名になるとも限らない
    • 例えば修正してさらにlockをかけると左側の状態表示が"M K fuga"となる
  • " "(半角スペース)から始まるファイル名
    • 単純に出力を区切った2番目の文字列におそらく先頭のスペースは含まれない
  • 記号の混じったファイル名
    • 得たファイル名を"svn diff"の引数にそのまま渡せない

解決策

"svn help status"を見る限り、出力は先頭6文字で状態を表し、8文字目から先がパスを表すようなので、8文字目以降を取得するのが良いのかもしれない。
それを"svn diff"に渡すときには記号は自力でエスケープするしかないのかな…

せっかくなのでPerl

プログラムでやるなら出来る限りその中で完結させられるよう、SubversionPerlバインディングモジュールを使って書いてみた。

$ sudo port install subversion-perlbindings
#!/opt/local/bin/perl
use strict;
use warnings;

use SVN::Client;

my $ctx = new SVN::Client();
$ctx->status(
    '/Users/sugyan/temp/hoge',
    'HEAD',
    sub {
        my ($path, $status) = @_;
        if ($status->text_status() == $SVN::Wc::Status::modified) {
            $path =~ s|([^\w/])|\\$1|g;
            print $path, "\n";
            system "svn diff $path";
        };
    },
    1, 0, 1, 0);

SVN::Clientのperldocは読んでみたのだけど、どうやらdiffはリポジトリにあるものを比較することしか出来ないようで、ローカルでの変更をみることはできないっぽい。何かいい方法あるのかなぁ。
結局こんなことしなくてもやっぱり"svn status"の出力をとって先頭が"M"で始まるものの8文字目以降をpathとして使えば良いのかもしれない。