09

SVNKit(3)

さて、前回は雑にSVNRepository#updateしてみたわけだけど、どうもコンテンツのDiffが流れてくるので、ワーキングコピーに対してpatchしなければならなさそう、というところまでだった。

このDiffのコンテンツはISVNEditortextDaltaChunkというメソッドの引数に渡される SVNDiffWindow 型のオブジェクトの内部に格納されている。

diffをいわゆるdiffテキスト的なものから自分でpatch処理をする必要があるのかというと、Javadocを見るにSVNDiffWindowがよろしくやってくれるためのメソッドを持っているように見える。

apply(byte[] sourceBuffer, byte[] targetBuffer) 

これはいかにもsourceBufferがワーキングコピーで、targetBufferが適用後のバッファとなるようだ。 もう一つこんなのがあるが

apply(SVNDiffWindowApplyBaton applyBaton) 

SVNDiffWindowApplyBaton インスタンスを作るためにはDiff適用後のファイルのMD5をチェックサムとして渡す必要があるようだが、このMD5をどこから取得すればよいのかわからない。ISVNEditorの一連の呼び出しの中でチェックサムが受け取れるが、それは closeFileのタイミングなので、ここでは間に合わない。別途リポジトリに問い合わせるのだろうか。

仕方ないので、とりあえず前者でやってみることにする。

現在テストのために使用しているリポジトリの内容は前回前々回に詳細を記載しているのでそちらを参照していただきたい。今回とりあえずrevision2からrevision3(最新)でfile1.txtが変更になっているので、その差分を適用することを考えていく。

とりあえずrevision2の時点でfile1.txtの内容は以下のようになっている。

commit1

末尾には改行がない。

これがrevision3(最新)では以下のように変更なっている。

commit1
commit2

なので差分は

(改行)
commit2

が来ることになる。

この差分を適用するために、textDaltaChunk に以下のような内容を書いた。

    @Override
    public OutputStream textDeltaChunk(final String path, final SVNDiffWindow diffWindow) throws SVNException {
        logStart(path + " " + diffWindow.toString());

        final Map<String, String> pathContentsMap = new HashMap<>();
        pathContentsMap.put("trunk/file1.txt", "commit1");

        final String content = pathContentsMap.get(path);
        byte[] souceBuffer = new byte[0];
        if (content != null) {
            souceBuffer = content.getBytes();
        }

        final byte[] targetBuffer = new byte[diffWindow.getTargetViewLength()];
        diffWindow.apply(souceBuffer, targetBuffer);

        System.out.println(new String(targetBuffer));

        logEnd();
        return null;
    }

file1.txtに対して呼び出されたときのみ、"commit1"という内容をワーキングコピーのソースとして与えて、それ以外の場合はソースは空のバッファにしている。 で、ターゲットの長さ分をtargetBufferとして用意して、その内容をapply実行後に標準出力へ出力してみている。

logStartはメソッド名と適当なメッセージを出力している。 logEndは最初終了のメッセージを出力していたのだけど、対して意味なかったので現状何もしていない。

戻りがnullになっているのはJavadocの説明にそうしろとあったため。戻りがOutputStreamになっているのは過去の互換性のためらしい。ここで出力ストリームを戻すと戻した先で閉じられてしまう。

さて、これを実行した結果は以下の通り。

#targetRevision start > [rev.3]
#openRoot start > [rev.2]
#openDir start > trunk[rev.2]
#changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk
#changeDirProperty start > svn:entry:committed-rev 3
#changeDirProperty start > svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeDirProperty start > svn:entry:last-author test
#changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#openFile start > trunk/file1.txt[rev.2]
#changeFileProperty start > trunk/file1.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file1.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file1.txt svn:entry:last-author test
#changeFileProperty start > trunk/file1.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#applyTextDelta start > trunk/file1.txt
#textDeltaChunk start > trunk/file1.txt 0:7:16:3:9:12:0
commit1
commit2
#textDeltaEnd start > trunk/file1.txt
#closeFile start > trunk/file1.txt
#addFile start > trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3
#changeFileProperty start > trunk/file2.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file2.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file2.txt svn:entry:last-author test
#changeFileProperty start > trunk/file2.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file2.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file2.txt
#textDeltaChunk start > trunk/file2.txt 0:0:7:1:7:8:0
commit2
#textDeltaEnd start > trunk/file2.txt
#closeFile start > trunk/file2.txt
#closeDir start > 
#closeDir start > 
#closeEdit start > 

今回のコードが出力している部分を抜粋すると

#textDeltaChunk start > trunk/file1.txt 0:7:16:3:9:12:0
commit1
commit2

ちゃんと最新版と一致しているようだ。 まあ目視の上ではこれでいいんだけど、一応データのチェックサムを確認するべきではある。 上記で少し触れているけど、ISVNEditor#closeFileの引数にチェックサムが与えられている。 型はただのString。Javadocによれば、これはMD5らしい。ExpEditorのインスタンス変数にファイルパスとMD5の結果を持つMap型の変数を定義して、textDeltaChunkの処理後にMD5を記録し、closeFileで渡される値を比較してみることにしてみる。

textDeltaChunkを以下のように修正。上記までと同様の処理の後に、MD5を計算する処理を追加している。

    @Override
    public OutputStream textDeltaChunk(final String path, final SVNDiffWindow diffWindow) throws SVNException {
        logStart(path + " " + diffWindow.toString());

        try {
            final Map<String, String> pathContentsMap = new HashMap<>();
            pathContentsMap.put("trunk/file1.txt", "commit1");

            final String content = pathContentsMap.get(path);
            byte[] souceBuffer = new byte[0];
            if (content != null) {
                souceBuffer = content.getBytes();
            }

            final byte[] targetBuffer = new byte[diffWindow.getTargetViewLength()];
            diffWindow.apply(souceBuffer, targetBuffer);

            System.out.println(new String(targetBuffer));

            final MessageDigest instance = MessageDigest.getInstance("MD5");
            final byte[] digest = instance.digest(targetBuffer);
            final String hexBinary = DatatypeConverter.printHexBinary(digest);
            pathMD5Map.put(path, hexBinary);
            System.out.println("[MD5] " + path + " -> " + hexBinary);
        } catch (final NoSuchAlgorithmException e) {
            throw new SVNException(SVNErrorMessage.UNKNOWN_ERROR_MESSAGE, e);
        }

        logEnd();
        return null;
    }

加えて、closeFileで以下のように検証してみる。

    @Override
    public void closeFile(final String path, final String textChecksum) throws SVNException {
        logStart(path);
        // do nothing
        if (!textChecksum.equalsIgnoreCase(pathMD5Map.get(path))) {
            System.err.println("[MD5] UNmatch for " + path + " / " + textChecksum + " != " + String.valueOf(pathMD5Map.get(path)));
        } else {
            System.out.println("[MD5] match for " + path + " / " + textChecksum + " == " + pathMD5Map.get(path));
        }

        logEnd();
    }

すると以下のような出力が得られた。

#targetRevision start > [rev.3]
#openRoot start > [rev.2]
#openDir start > trunk[rev.2]
#changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk
#changeDirProperty start > svn:entry:committed-rev 3
#changeDirProperty start > svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeDirProperty start > svn:entry:last-author test
#changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#openFile start > trunk/file1.txt[rev.2]
#changeFileProperty start > trunk/file1.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file1.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file1.txt svn:entry:last-author test
#changeFileProperty start > trunk/file1.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#applyTextDelta start > trunk/file1.txt
#textDeltaChunk start > trunk/file1.txt 0:7:16:3:9:12:0
commit1
commit2
[MD5] trunk/file1.txt -> CAE91EA5B6E3F99FE23E6C4F5089A91D
#textDeltaEnd start > trunk/file1.txt
#closeFile start > trunk/file1.txt
[MD5] match for trunk/file1.txt / cae91ea5b6e3f99fe23e6c4f5089a91d == CAE91EA5B6E3F99FE23E6C4F5089A91D
#addFile start > trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3
#changeFileProperty start > trunk/file2.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file2.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file2.txt svn:entry:last-author test
#changeFileProperty start > trunk/file2.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file2.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file2.txt
#textDeltaChunk start > trunk/file2.txt 0:0:7:1:7:8:0
commit2
[MD5] trunk/file2.txt -> 8AFDB541A011CC47D258C4962EC19D90
#textDeltaEnd start > trunk/file2.txt
#closeFile start > trunk/file2.txt
[MD5] match for trunk/file2.txt / 8afdb541a011cc47d258c4962ec19d90 == 8AFDB541A011CC47D258C4962EC19D90
#closeDir start > 
#closeDir start > 
#closeEdit start > 

どうやら問題なさそうだ。