SVNKit(4)
前回まででとりあえず最新のコンテンツの取得はなんとかなりそうかと考えている。
個人的にあと押さえておきたい動きとしては削除・移動・名前の変更がある。
これらの操作の動きを確認するために、リポジトリにいくつか操作をして、履歴を以下のようにした。
- r1 : プロジェクト構成(trunk/branches/tags)のディレクトリを作成
- r2 : file1.txt, .project を追加
- r3 : file1.txtの内容を更新、file2.txt を追加
- r4 : folder1, folder1/file3.txt, folder2,folder2/file4.txt を追加
- r5 : folder1/file3.txt を file3.txt に移動
- r6 : 空になった folder1 と、folder2/file4.txt を削除
- r7 : file2.txt の名前を file5.txt に変更
移動
r4 -> r5 の 「folder1/file3.txt を file3.txt に移動」を見てみる。
SVNRepository#update の呼び出しは以下のようにリビジョンに5を指定
@Test public void repositoryUpdate() throws Exception { setUpRepo(); final SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url)); final ISVNAuthenticationManager authenticationManager = SVNWCUtil.createDefaultAuthenticationManager(user, password.toCharArray()); repository.setAuthenticationManager(authenticationManager); repository.update(5, "trunk", true, new ExpReporter(), new ExpEditor()); }
ワーキングコピーの状態を報告するISVNReporterBaton
の実装ではワーキングコピーのトップで リビジョンが4であると報告
public class ExpReporter implements ISVNReporterBaton { @Override public void report(final ISVNReporter reporter) throws SVNException { reporter.setPath("", null, 4, SVNDepth.INFINITY, false); reporter.finishReport(); return; } }
以下のような出力が得られた
#targetRevision start > [rev.5] #openRoot start > [rev.4] #openDir start > trunk[rev.4] #changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/5/trunk #changeDirProperty start > svn:entry:committed-rev 5 #changeDirProperty start > svn:entry:committed-date 2016-03-12T13:00:44.576740Z #changeDirProperty start > svn:entry:last-author test #changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #addFile start > trunk/file3.txt #changeFileProperty start > trunk/file3.txt svnkit:entry:sha1-checksum 6b3c45f2d43d16c028ef18e38cb1e516f653463d #changeFileProperty start > trunk/file3.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/5/trunk/file3.txt #changeFileProperty start > trunk/file3.txt svn:entry:committed-rev 5 #changeFileProperty start > trunk/file3.txt svn:entry:committed-date 2016-03-12T13:00:44.576740Z #changeFileProperty start > trunk/file3.txt svn:entry:last-author test #changeFileProperty start > trunk/file3.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #changeFileProperty start > trunk/file3.txt svn:mime-type text/plain #applyTextDelta start > trunk/file3.txt #textDeltaChunk start > trunk/file3.txt 0:0:7:1:7:8:0 commit4 [MD5] trunk/file3.txt -> A47E3E5340D0E65435F9D3DAB044561F #textDeltaEnd start > trunk/file3.txt #closeFile start > trunk/file3.txt [MD5] match for trunk/file3.txt / a47e3e5340d0e65435f9d3dab044561f == A47E3E5340D0E65435F9D3DAB044561F #openDir start > trunk/folder1[rev.4] #changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/5/trunk/folder1 #changeDirProperty start > svn:entry:committed-rev 5 #changeDirProperty start > svn:entry:committed-date 2016-03-12T13:00:44.576740Z #changeDirProperty start > svn:entry:last-author test #changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #deleteEntry start > trunk/folder1/file3.txt[rev.-1] #closeDir start > #closeDir start > #closeDir start > #closeEdit start >
まあ普段からSubversionを使っている人ならなんとなく知っているだろうが、単純に新パスの追加と旧パスの削除になっている。addFile
には引数にコピー元のファイルパスとリビジョンが与えられる場合があり、個人的には移動の際には新パスのメタ情報として旧パスからコピーしたよといった情報が入るかと思っていたが、上記に表れていないのは渡されていないということで、これは若干意外。もう少し使い方を調べてみる必要があるかもしれない。
削除
次に r5 -> r6 の「空になった folder1 と、folder2/file4.txt を削除」を見てみる。
移動の時同様にコードの指すリビジョンを修正して実行してみた。
#targetRevision start > [rev.6] #openRoot start > [rev.5] #openDir start > trunk[rev.5] #changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/6/trunk #changeDirProperty start > svn:entry:committed-rev 6 #changeDirProperty start > svn:entry:committed-date 2016-03-12T13:02:02.887978Z #changeDirProperty start > svn:entry:last-author test #changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #deleteEntry start > trunk/folder1[rev.-1] #openDir start > trunk/folder2[rev.5] #changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/6/trunk/folder2 #changeDirProperty start > svn:entry:committed-rev 6 #changeDirProperty start > svn:entry:committed-date 2016-03-12T13:02:02.887978Z #changeDirProperty start > svn:entry:last-author test #changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #deleteEntry start > trunk/folder2/file4.txt[rev.-1] #closeDir start > #closeDir start > #closeDir start > #closeEdit start >
移動の時でも同様だが、openDir
した後でdeleteEntryされている。ただそれだけ。
名前の変更
移動の例からほぼ結果が見えているが、r6 -> r7 「file2.txt の名前を file5.txt に変更」も同様に実行してみる。
#targetRevision start > [rev.7] #openRoot start > [rev.6] #openDir start > trunk[rev.6] #changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/7/trunk #changeDirProperty start > svn:entry:committed-rev 7 #changeDirProperty start > svn:entry:committed-date 2016-03-12T13:02:42.772197Z #changeDirProperty start > svn:entry:last-author test #changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #deleteEntry start > trunk/file2.txt[rev.-1] #addFile start > trunk/file5.txt #changeFileProperty start > trunk/file5.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3 #changeFileProperty start > trunk/file5.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/7/trunk/file5.txt #changeFileProperty start > trunk/file5.txt svn:entry:committed-rev 7 #changeFileProperty start > trunk/file5.txt svn:entry:committed-date 2016-03-12T13:02:42.772197Z #changeFileProperty start > trunk/file5.txt svn:entry:last-author test #changeFileProperty start > trunk/file5.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #changeFileProperty start > trunk/file5.txt svn:mime-type text/plain #applyTextDelta start > trunk/file5.txt #textDeltaChunk start > trunk/file5.txt 0:0:7:1:7:8:0 commit2 [MD5] trunk/file5.txt -> 8AFDB541A011CC47D258C4962EC19D90 #textDeltaEnd start > trunk/file5.txt #closeFile start > trunk/file5.txt [MD5] match for trunk/file5.txt / 8afdb541a011cc47d258c4962ec19d90 == 8AFDB541A011CC47D258C4962EC19D90 #closeDir start > #closeDir start > #closeEdit start >
これも追加・削除の操作になっている。
Overall
r3 -> r7 の差分取得をした場合、削除されてワーキングコピーに影響のないファイル(folder1, file4.txt)については得に知らされない。
#targetRevision start > [rev.7] #openRoot start > [rev.3] #openDir start > trunk[rev.3] #changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/7/trunk #changeDirProperty start > svn:entry:committed-rev 7 #changeDirProperty start > svn:entry:committed-date 2016-03-12T13:02:42.772197Z #changeDirProperty start > svn:entry:last-author test #changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #deleteEntry start > trunk/file2.txt[rev.-1] #addFile start > trunk/file3.txt #changeFileProperty start > trunk/file3.txt svnkit:entry:sha1-checksum 6b3c45f2d43d16c028ef18e38cb1e516f653463d #changeFileProperty start > trunk/file3.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/5/trunk/file3.txt #changeFileProperty start > trunk/file3.txt svn:entry:committed-rev 5 #changeFileProperty start > trunk/file3.txt svn:entry:committed-date 2016-03-12T13:00:44.576740Z #changeFileProperty start > trunk/file3.txt svn:entry:last-author test #changeFileProperty start > trunk/file3.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #changeFileProperty start > trunk/file3.txt svn:mime-type text/plain #applyTextDelta start > trunk/file3.txt #textDeltaChunk start > trunk/file3.txt 0:0:7:1:7:8:0 commit4 [MD5] trunk/file3.txt -> A47E3E5340D0E65435F9D3DAB044561F #textDeltaEnd start > trunk/file3.txt #closeFile start > trunk/file3.txt [MD5] match for trunk/file3.txt / a47e3e5340d0e65435f9d3dab044561f == A47E3E5340D0E65435F9D3DAB044561F #addFile start > trunk/file5.txt #changeFileProperty start > trunk/file5.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3 #changeFileProperty start > trunk/file5.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/7/trunk/file5.txt #changeFileProperty start > trunk/file5.txt svn:entry:committed-rev 7 #changeFileProperty start > trunk/file5.txt svn:entry:committed-date 2016-03-12T13:02:42.772197Z #changeFileProperty start > trunk/file5.txt svn:entry:last-author test #changeFileProperty start > trunk/file5.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #changeFileProperty start > trunk/file5.txt svn:mime-type text/plain #applyTextDelta start > trunk/file5.txt #textDeltaChunk start > trunk/file5.txt 0:0:7:1:7:8:0 commit2 [MD5] trunk/file5.txt -> 8AFDB541A011CC47D258C4962EC19D90 #textDeltaEnd start > trunk/file5.txt #closeFile start > trunk/file5.txt [MD5] match for trunk/file5.txt / 8afdb541a011cc47d258c4962ec19d90 == 8AFDB541A011CC47D258C4962EC19D90 #addDir start > trunk/folder2 #changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/6/trunk/folder2 #changeDirProperty start > svn:entry:committed-rev 6 #changeDirProperty start > svn:entry:committed-date 2016-03-12T13:02:02.887978Z #changeDirProperty start > svn:entry:last-author test #changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #closeDir start > #closeDir start > #closeDir start > #closeEdit start >
さて、個人的に知りたいこととしてはここまでで一通り知ることができたので、このシリーズはここでおしまい。
carrotsword 先生の次回作にご期待ください!
SVNKit(3)
さて、前回は雑にSVNRepository#update
してみたわけだけど、どうもコンテンツのDiffが流れてくるので、ワーキングコピーに対してpatchしなければならなさそう、というところまでだった。
このDiffのコンテンツはISVNEditor
のtextDaltaChunk
というメソッドの引数に渡される 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 >
どうやら問題なさそうだ。
SVNKit(2)
前回の続き。
前回はとりあえずISVNRepository
とISVNEditor
とISVNDiffWindow
を使ってリポジトリの内容にアクセスできそう、というところまでやった。
一応注記しておくと、前回取得できた情報を何とかすればSVNクライアントとして動けるのかもしれないが、前回の内容ではいわゆる .svn
フォルダ配下の情報を作ることについては何も気にしていないので、まっとうなSVNクライアントとして動作させるにはいろいろと足りないかもしれない。
ここの記事では今後もまっとうなSVNクライアントとして動かすことは考えていないことはここでお断りしておく。たぶん、SVNクライアントを実装したいのなら、high-level APIを使うべきだろう。
で、今回は定期または不定期的にリポジトリから最新の情報を取得することを考える。その場合、前回取得した内容から現時点での最新までの差分のみを取得したい。前回はcheckout
を呼んでみたので、差分となるとそこからのupdate
ということになるだろう。diff
というメソッドもあるようなのでリビジョンを指定してDiffを取るのでもいいのかもしれないが、とりあえず今回はupdate
を調べてみる。
ISVNRepository#update
セットアップと接続までは前回と同じ。update
メソッドには(にも)いくつかオーバライドがあるが、以下のように呼びだしてみる。
repository.update(-1, "trunk", true, new ExpReporter(), new ExpEditor());
前回は端折ったが、引数について順に。
-1
- アップデートする先のリビジョン。APIドキュメントによるとデフォルトはHEADだ、と書いてあるが、型が
long
なのでデフォルトって何のことだと思ったが、どうも-1
(というかたぶん0より小さい値だろう)っぽい。
- アップデートする先のリビジョン。APIドキュメントによるとデフォルトはHEADだ、と書いてあるが、型が
"trunk"
- APIドキュメントではこれはリポジトリのインスタンスを生成する際に指定するURLの次の1レベルのパス、見たいな説明がされている。今回、trunkの上までを指定してリポジトリのインスタンスを生成しているので、こんな感じになる。optionalと書かれているので、リポジトリオブジェクトを生成する際に trunk を含めている場合、なくてもいいのかもしれない。
- ブランチを指定したい場合にどうなるのかよくわからないが
branches/branch-name
的な書き方でいいんだろうか
true
- 再帰的に処理するかどうかフラグ。SVNクライアントの画面なんかでもチェックボックスを見ることがある
new ExpReporter()
ISVNReporterBaton
をimplementsした適当実装。後述。
new ExpEditor()
- 前回若干説明したやつ
- 今回も呼ばれたメソッド名と引数を適当に結合して出力する適当実装
ISVNReporterBaton
前回のISVNEditor
に加えてISVNReporterBatonというinterfaceの実装を要求されている。
APIドキュメントに加えて若干の説明がここにあるが、要はワーキングコピーに存在するディレクトリやファイルについて個別にどんな状態か(チェックアウトされたものか、どのリビジョンか、削除するつもりかどうかなど)個別のパス情報としてリポジトリに伝えてやる必要があるらしい。
- 上記のインタフェースを実装すると引数に
ISVNReporter
インスタンスを引数に受け取れるので、それに対してsetPath
というメソッドを呼ぶことで個別のディレクトリ、ファイルについての状態を伝えていけということのようだ。 - ユーザの操作によってチェックアウト時とurlが変わってしまう場合(ファイルの移動などだろうか)は代わりに
linkPath
を呼べ、とか同様にしてファイルをリポジトリから削除するつもりなら、deletePath
を呼べとも書かれている。 - そして一通りの報告処理が終わったら
finishReport
を呼ぶ必要があるようだ。なんでそんな必要があるのかよくわからないが、個別にパスを調べる処理はそれなりに時間がかかることが想像されるのでマルチスレッド化されて処理されることを見越してのことかな?
ということで、ワーキングコピーに何もないことにして以下のような報告をするコードを書いてみる。
public class ExpReporter implements ISVNReporterBaton { @Override public void report(final ISVNReporter reporter) throws SVNException { reporter.setPath("", null, 0, SVNDepth.INFINITY, false); reporter.finishReport(); return; } }
reporter.setPath
の引数について
- ワーキングコピーには
""
しか存在してない - 特にロックは使用していないので ロックトークンは
null
""
のリビジョンは0
(最初)- 対象にするサブエントリ(配下のファイルとかフォルダ)の階層は無限(
SVNDepth.INFINITY
) - このパス(この場合
""
)がディレクトリでかつ前回のupdate
がエラーで失敗した場合はtrue
である必要があるが、そうでないのでfalse
setPathの第一引数は"/"
ではないかと思ったのだが、それだとよくわからないエラーが返ってきた。というかapacheのSVNモジュールがassertで死んでた。ドキュメントでは絶対パスで書かれているように見えるが…。
で以下のような出力を得た
#targetRevision start > [rev.3] #openRoot start > [rev.0] #addDir start > trunk #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 #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 #textDeltaEnd start > trunk/file2.txt #closeFile start > trunk/file2.txt #addFile start > trunk/.project #changeFileProperty start > trunk/.project svnkit:entry:sha1-checksum 4d0a7da34b1a6f2e1416d746bf268c1137453e14 #changeFileProperty start > trunk/.project svn:wc:ra_dav:version-url /svn/test/!svn/ver/2/trunk/.project #changeFileProperty start > trunk/.project svn:entry:committed-rev 2 #changeFileProperty start > trunk/.project svn:entry:committed-date 2016-03-03T18:14:34.687289Z #changeFileProperty start > trunk/.project svn:entry:last-author test #changeFileProperty start > trunk/.project svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #changeFileProperty start > trunk/.project svn:mime-type text/plain #applyTextDelta start > trunk/.project #textDeltaChunk start > trunk/.project 0:0:208:3:208:211:0 #textDeltaEnd start > trunk/.project #closeFile start > trunk/.project #addFile start > trunk/file1.txt #changeFileProperty start > trunk/file1.txt svnkit:entry:sha1-checksum 994c650e0954fd76b6859dae6d87caf2fad19e35 #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 #changeFileProperty start > trunk/file1.txt svn:mime-type text/plain #applyTextDelta start > trunk/file1.txt #textDeltaChunk start > trunk/file1.txt 0:0:16:1:16:17:0 #textDeltaEnd start > trunk/file1.txt #closeFile start > trunk/file1.txt #closeDir start > #closeDir start > #closeEdit start >
ファイルを順に追加しているのがわかる。
リポジトリの詳細な内容は前回 svn log --diff
した結果を貼っているが、ざっくりおさらいすると
- rev.0 : このリポジトリの最初
- rev.1 : プロジェクト構造(trunkとかbranchesとか)つくった
- rev.2 : /.projectと /file1.txtを追加
- rev.3 : /file1.txtを更新、/file2.txtを追加
といった履歴になっている。
この状態でワーキングコピー""
のリビジョンが2とだけ報告したらどうなるのだろうか。
つまり本来ワーキングコピー全体がリビジョン2であるならば/.projectと /file1.txtが存在するはずなのでそれらについても報告する必要があるが、意図的にそれをしない。
public class ExpReporter implements ISVNReporterBaton { @Override public void report(final ISVNReporter reporter) throws SVNException { reporter.setPath("", null, 2, SVNDepth.INFINITY, false); reporter.finishReport(); return; } }
得られた出力は以下
#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 #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 #textDeltaEnd start > trunk/file2.txt #closeFile start > trunk/file2.txt #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 #textDeltaEnd start > trunk/file1.txt #closeFile start > trunk/file1.txt #closeDir start > #closeDir start > #closeEdit start >
/file2.txt がaddFile
で /file1.txt はopenFile
から始まっている。単純に差分になるようだ。
別途ISVNEditor#textDeltaChunk
で受け取れるSVNDiffWindow
オブジェクトのインスタンスから、ファイルの内容としてどういったものが取得できるのか見てみると、rev2->HEADでファイルの追加になる /file2.txt はファイルの内容すべて、内容の変更のみだった /file1.txt は変更差分のみ取得できた。
となると、ワーキングコピーとこれらの内容からどうにかして本来(というか、最新の全体)の内容を得るためには、ワーキングコピーに対してpatchしなければならない?
続きはまた次回。
SVNKit(1)
今時SVNのAPI触ってるなんて人に聞かれたら袋叩きにされるのだろうか。 とにかくRepositoryにある内容を取得したい。
ドキュメントを見るとAPIが2段階あるらしいことがわかる。
- high-level api (working copy api)
org.tmatesoft.svn.core.wc
およびorg.tmatesoft.svn.core.wc2
パッケージ- たぶん、実際SVNを使ったツールを開発するにあたっては当然必要になるワーキングコピーをベースにしたユースケースをカバーするAPI
- low-level api
org.tmatesoft.svn.core.io
パッケージ- リポジトリとプロトコルを直接的にインタフェースするとある
- 上記を構成するために使用される低レイヤーのAPIなんだろうか?
- 個人的にはこのレベルでもだいぶユースケースよりな設計の印象を受ける
SVNRepository
APIドキュメントを眺めているとどうやらリポジトリオブジェクトを作ってそこから操作をするらしい。
SVNRepository
オブジェクトを作るにはSVNRespositoryFactory
というファクトリクラスを使うらしいが、その前に接続先のプロトコル(http:
か、svn:
またはsvn+hoge:
か、file:
か)によってセットアップをしてやる必要があるらしい。
if (url.toLowerCase().startsWith("http")) { DAVRepositoryFactory.setup(); } else if (url.toLowerCase().startsWith("svn")) { SVNRepositoryFactoryImpl.setup(); } else if (url.toLowerCase().startsWith("file")) { FSRepositoryFactory.setup(); } else { fail(); }
こんな感じか。url
は普通にsubversionの接続先として使用するURLの文字列。
なんでsvn
の場合だけImpl
がついているのか。ってSVNRepositoryFactory
にそれをさせるわけにいかなかったってことか。しかしファクトリのセットアップを別のクラスに切り出して、そっちを呼ばせるというのも、なんだか妙な設計に思えるが・・・まあ大した実害はないかもしれない。
続いてリポジトリオブジェクトを作成
final SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url)); final ISVNAuthenticationManager authenticationManager = SVNWCUtil.createDefaultAuthenticationManager(user, password.toCharArray()); repository.setAuthenticationManager(authenticationManager);
ISVNAuthenticationManager
はその名のとおり接続時の認証情報を管理してくれるものらしい。この場合は単純にhttpのベーシック認証に渡すユーザ名とパスワードをSVNWUtil
というユーティリティに渡してインスタンスを作ってもらっている。なんでパスワードがchar
配列なのかよくわからないが、これは単にパスワードの平文文字列。たぶんSSH経由のSVNとかだとこの辺のやり方は認証方式に応じて変わるんだろう。
ISVNEditor
でとりあえずチェックアウトはこんな感じになるが
repository.checkout(-1, "trunk", true, new ExpEditor());
最後の引数に要求されているExpEditor
はISVNEditorを実装したオブジェクトで、どうもチェックアウト実行中にはリポジトリ中の変更情報が順に流れてくるのでそれを処理するハンドラというか、Visitorのようなものを実装する必要があるようだ。ExpEditor
自体は今回動きを確認するために適当に作った実装。
とりあえずどんな動きをするのか、ということでこんな構造のリポジトリを作った
[carrotsword trunk]$ ls -la total 20 drwxrwxr-x 2 carrotsword carrotsword 4096 Mar 6 01:55 . drwxrwxr-x 6 carrotsword carrotsword 4096 Mar 6 01:55 .. -rw-rw-r-- 1 carrotsword carrotsword 208 Mar 6 01:55 .project -rw-rw-r-- 1 carrotsword carrotsword 16 Mar 6 01:55 file1.txt -rw-rw-r-- 1 carrotsword carrotsword 7 Mar 6 01:55 file2.txt
一応コミットは何度かに分けてある
[carrotsword trunk]$ svn log --diff ... 略 ... ------------------------------------------------------------------------ r3 | test | 2016-03-04 03:15:19 +0900 (Fri, 04 Mar 2016) | 1 line commit2 Index: file2.txt =================================================================== --- file2.txt (revision 0) +++ file2.txt (revision 3) @@ -0,0 +1 @@ +commit2 \ No newline at end of file Property changes on: file2.txt ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: file1.txt =================================================================== --- file1.txt (revision 2) +++ file1.txt (revision 3) @@ -1 +1,2 @@ -commit1 \ No newline at end of file +commit1 +commit2 \ No newline at end of file ------------------------------------------------------------------------ r2 | test | 2016-03-04 03:14:34 +0900 (Fri, 04 Mar 2016) | 1 line commit1 Index: .project =================================================================== --- .project (revision 0) +++ .project (revision 2) @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>test</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + </buildSpec> + <natures> + </natures> +</projectDescription> Property changes on: .project ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: file1.txt =================================================================== --- file1.txt (revision 0) +++ file1.txt (revision 2) @@ -0,0 +1 @@ +commit1 \ No newline at end of file Property changes on: file1.txt ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property ------------------------------------------------------------------------ r1 | test | 2016-03-04 03:12:54 +0900 (Fri, 04 Mar 2016) | 1 line ------------------------------------------------------------------------
これに対して、ISVNEditorがどんな呼ばれ方をするのか。 各メソッドに呼ばれたメソッド名とパラメータを簡単に出力するだけの実装をして実行してみたところ、以下のような出力が得られた。
#targetRevision start > [rev.3] #openRoot start > [rev.3] #openDir start > trunk[rev.3] #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 #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 #textDeltaEnd start > trunk/file2.txt #closeFile start > trunk/file2.txt #addFile start > trunk/.project #changeFileProperty start > trunk/.project svnkit:entry:sha1-checksum 4d0a7da34b1a6f2e1416d746bf268c1137453e14 #changeFileProperty start > trunk/.project svn:wc:ra_dav:version-url /svn/test/!svn/ver/2/trunk/.project #changeFileProperty start > trunk/.project svn:entry:committed-rev 2 #changeFileProperty start > trunk/.project svn:entry:committed-date 2016-03-03T18:14:34.687289Z #changeFileProperty start > trunk/.project svn:entry:last-author test #changeFileProperty start > trunk/.project svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122 #changeFileProperty start > trunk/.project svn:mime-type text/plain #applyTextDelta start > trunk/.project #textDeltaChunk start > trunk/.project 0:0:208:3:208:211:0 #textDeltaEnd start > trunk/.project #closeFile start > trunk/.project #addFile start > trunk/file1.txt #changeFileProperty start > trunk/file1.txt svnkit:entry:sha1-checksum 994c650e0954fd76b6859dae6d87caf2fad19e35 #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 #changeFileProperty start > trunk/file1.txt svn:mime-type text/plain #applyTextDelta start > trunk/file1.txt #textDeltaChunk start > trunk/file1.txt 0:0:16:1:16:17:0 #textDeltaEnd start > trunk/file1.txt #closeFile start > trunk/file1.txt #closeDir start > #closeDir start > #closeEdit start >
これは面倒そう!
ざっと見る限り、コミットされているファイルの内容が見えない。
これはtextDeltaChunk
の引数に渡されるSVNDiffWindow
オブジェクトがその情報を持っている。
適当に吐き出してみたところSVNヘッダ+ファイルの内容のバイナリストリームを持っているっぽい。
ということは、その内容を指定のパスに吐き出してやれば(checkoutだけど)export相当のことはできるようになるのかも。
今回はこの辺で。
teslogger-server
少し前に以下で発表されたやつ
www.slideshare.net
試してみたかったけどClojure力が足りない
なんとか動かしたメモ
Leiningen はあらかじめ入れておく。公式ではbatファイルをお勧めされるけど、Windows10ではなんだったか忘れたがエラーになったと思うので、インストーラ使った気がする。
- teslogger-server を
git clone
して持ってくる。 - axebomber-clj が必要だが Clojars にないようなのでこれも
git clone
して持ってくるlein uberjar
して axebomber-clj-0.1.1-standalone.jar をビルド
- teslogger-server/lib/ に axebomber-clj-0.1.1-standalone.jar を配置
- teslogger-server/project.clj を修正した
:dependencies
[net.unit8/axebomber-clj "0.1.0-SNAPSHOT"]
をコメントアウト[net.unit8/ulon-colon "0.2.3"]
はSNAPSHOTじゃない
:resource-paths ["lib/axebomber-clj-0.1.1-standalone.jar" "resources"]
を追加
- clojure script をコンパイルする
lein cljsbuild once
- サーバを起動する
lein ring server
- ブラウザが起動して 【START TO EVIDENCE】が表示されたら teslogger を起動すればいい。
日付のパース
iAP上で日付を固定書式でパースしたいのだが。
DateTimeFormatter: intra-mart Accel Platform SSJS API Documentation
とある既存コードを眺めていて気になったので試してみた
Debug.browse( DateTimeFormatter.parseToDate('yyyy/M/d', '2015/9/9'), DateTimeFormatter.parseToDate('yyyy/MM/dd', '2015/9/9'), DateTimeFormatter.parseToDate('yyyy/M/d', '2015/09/09'), DateTimeFormatter.parseToDate('yyyy/MM/dd', '2015/09/09') );
結果は
1 Wed Sep 09 00:00:00 JST 2015 Date 2 Wed Sep 09 00:00:00 JST 2015 Date 3 Wed Sep 09 00:00:00 JST 2015 Date 4 Wed Sep 09 00:00:00 JST 2015 Date
そうだよねえ。
ちなみにパースに失敗すると例外が飛ぶ。javacript 側の try-catch で捕まえられるから、それで何とかするしかないねえ。
嵌り実況
- ファイルをアップロードする画面が必要になった。intra-mart Accel Platform 上なので当然
imuiFileUpload
を使うことを考える。 - ただアップロードするだけじゃなく、アップロードに際していくつか入力する項目が付属するので、ファイルは
imuiFileUpload
に任せて、imuiFileUpload
がアップロードしたファイルについての情報と付属項目を一緒に<form>
にいれてimuiAjaxSend
で送信するのが楽そう、と考える。 - 画面全体を
<form>
で囲ったらimuiFileUpload
のスタイルが剥がれて、おそらくまともに動作していない状態になった。- ファイルをアップロードする性質上フォームとは相性が悪いのか?
- 選択肢としてありそうなのは
imuiFileUpload
のouterFormId
属性の指定。- でもこれって単にマルチパートとして送信してしまうのでは?
- 別にそれでもいいんだけど、
imuiFileUpload
がサーバサイドで Session Scope Storage に自動的に保存してくれる機能を活かしたい
- とりあえず、全ての値を
hidden
で保持するフォームを作成して、入力された値はそこに転記してimuiAjaxSend
することにしようと考える。 - でも入力値はバリデーションしたいので、上記の転記先のフォームに対して
imuiValidate
のルールを定義して呼び出してみる。- エラーメッセージ表示は関数を与えることでコントロールできるので、バリデーション対象が
hidden
でもいけると踏んだ
- エラーメッセージ表示は関数を与えることでコントロールできるので、バリデーション対象が
- 何を入力してもまったくバリデーションエラーにならない(入力値に何を入れても
imuiValidate
の戻り値がtrue
) jQuery validation plugin hidden
でGoogle検索したらデフォルトではhidden
項目はバリデーションの対象にしないらしいことが分かる。これを無効にするにはバリデーションを無視する項目を指定する引数に[]
(空配列)を渡せばよいらしいことが書いてある。- 与えてみるが何も変わらない。
- DOMエレメントツリーを見ていると上記の転記先の
<form>
にnovalidate
なる属性が付与されていることを発見する。 - 調べてみると html5 の機能でバリデーションを無効にする指定らしい。StackOverFlowにてjQueryのvalidation pluginも何らかの条件下でこの属性を付与するようで困るというようなポストを見つけた。
- StackOverFlowの同スレッドにて
$("#form").removeAttr("novalidate")
せよとのポストが見つかったので試してみるもnovalidate
はなくなるが(あたりまえ)動作には変化ない。 - ここまで書いたところでふと思い当たってバリデーション無視対象を
[]
でなく''
にしてみる
・・・やっと imuiValidate
がエラーを返してきた。[-_-]
敗因は?
ファイルアップロードが中途半端に混ざってる画面設計か、もしくは4で outerFormId
使うほうを選んでたらもっと簡単に解決したのだろうか(試してない)。 あとはバリデーション無視対象はString型だよ、と一応公式ドキュメントには書いてあるので、それを見逃した点かな…。