09

intra-mart Accel Platfom 上でKotlin(じゃなくてもいいけど)でアプリ書きたい

GWをつぶしてあれこれ考えてみたけど結局ダメそう、と言う時間の無駄をシェアさせていただきます。

  • intra-mart Accel Platform 上で開発するアプリケーションには開発言語として Java(フレームワークいろいろ)とJavascript(JavaScript Server Pages : JSSPと言われている)が使えるんだけど、そろそろScalaとかClojureとかKotlinとか使えたほうがいいんじゃないの、と思ったりしてみた。まあ個人がいろいろ考えても本家のほうでサポートしてくれなきゃ意味ないんだけど。
  • とりあえずユーザーの乗り換え障壁が少なそうなKotlinを考える
  • Javaとのインターオペラビリティはそれなりに高そうだから、JSを実行している実行エンジンの部分で同様に切り替えるような仕組みを作ればいいんじゃないのかな
  • とりあえずそれができそうなインタフェースとして公開されているのは BaseAuthzUserAction (公開されてない)あたりかなあ。
  • とはいえ、KotlinでJavaのアノテーションがそのまま使えるのであれば、WEB API MakerでWeb APIを作るのであれば問題なくやれるんじゃないのか。
  • ということで開発環境にKotlinの適用を考える。
  • Accel Platform の 開発環境といえば e-Builder。
  • e-Builderといえばeclipse。
    • Kotlinのeclipseプラグインてあるのか?
      • 公式サイトに一応リンク載ってる
    • インストールして試してみるが、サジェスト以外のサポートは受けられない模様
    • 外部のクラス参照しようとしても(サジェストされるのに)自動的にimportを書いたりはしてくれない
      • たぶんリファクタリング操作もそんなに期待できないのでは・・・。
    • やはり IntelliJ IDEA でなければならんのか?
  • IntelliJ IDEA の Ultimate には最初からResinプラグインが入っているから、IDEからResinをコントロールするのはそれほど問題がない。(別にResinじゃなくてもいいんだけどまあ一応)
  • IntelliJ IDEA はビルドの処理をあれこれ設定ベースでコントロールできるので、im-Juggling で作ったwarを元に、そのソースを展開して、Kotlinで書いたコードを混ぜこんでJava EE 形式のWebアプリケーションとしてResinに実行させるというようなことはいろいろ設定を頑張ればできなくはない。
    • 正直、いちいち手で設定するのはかなり面倒。この辺初期セットアップをプラグインなんかで補助できればいいんではないかと思っているけどその辺はよく知らないのでとりあえずパス
  • デバッグ実行にしておけばKotlinのコードをコンパイルしたタイミングでクラスはホットスワップされるっぽい
    • なんか挙動が妙(たまにResinが丸ごと再起動する)な気がしているのだがあまり深く検証していない
  • まあそんなこんなでもとりあえずWEB-API makerの手順にあるような構造のコードをアノテーションばりばり書きつつKotlinで書いてみて、Swaggerから呼び出したりホットスワップで挙動が修正されるのは確認できた
    • まあクラスファイルにコンパイルされるのは同じなわけだから当たり前なのか
  • しかしそれでWeb-API書けたとして、クライアントどうやって書くんだ?
  • 素のHTMLだとテーマとかメニューとか認証認可とかなんだかんだいろいろ抜け落ちるわけで、なんかRouterを介したエントリーポイントとなるViewを吐き出す必要がある。
    • ここもKotlinでやろうとすると上記の通りBaseAuthzUserAction とか要するにRouter周辺の拡張を書かなきゃならない
      • だったら最初からそっちに取り掛かったほうが良かったじゃん
    • JSSPで書いてもいいんだけどなんかそれも負けた感じ
  • それはそれとしてもクライアントサイドはやはり今はやりのJSフレームワークとかAlt JS的なツールチェインを使いたいけど
    • Java EE的なビルドプロセスの中でNodeJSのツールチェイン混ぜこむのどうしたらいいわけ
      • mavenからnode.js実行したりnode.jsからmaven実行したりgradleからnode実行したりいろいろあるようだ。が、どれもあまりスマートには見えない。まあ追加でNode.jsを要求する時点ですでにいろいろ難しいのだが。
    • クライアントサイドとWeb-APIで別なプロジェクトにしてしまえばあるいは
    • プロジェクト構成もどうするのがいいのか悩ましい。e-Builderが用意するデフォルトのpublicディレクトリには直接ソースをおかず、別なディレクトリでTypeScriptとかES2015とかで書いたものをコンパイルしてpublicにもっていけばいいのか?
    • その場合そのコンパイルを行うwebpackとかbrowserifyとかの基準ディレクトリはどこに置いたらいいのか?個人的にはプロジェクトルートに置きたい感じがすごいする(npmをプロジェクトルートディレクトリで実行すればいいから)けど、そうしてしまうとそれはそれでルートにいろいろできてうざい気はする
    • それに普通にpublicに置きたいリソースがあった場合(imgとか)どうなるのか。
    • ていうかwebpack初めて触ったけど設定の書き方がものすごく不安。いろいろ書きすぎなんじゃないの
      • また半年とかしたらオワコンとか言われたりするのかしら
        • 忘れもしない2013年前半、これからはAngular!Angular!みたいなことをあちこちで言いまわっていたくせに俺がAngularつかったシステムリリースしたとたん手のひら返したようにオワコンオワコン言いだしやがったフロントエンド界隈の連中絶対に許さんからな
    • こういうpolyglotというか複数のエコシステムが絡み合う問題は少し前から気になってはいたけど、なんかうまくやる方法あるんだろうか

というわけで、まあクライアントサイドのビルド関係の話を除くなら開発環境のサポート具合と結局Routingで各言語対応しなきゃならなさそう、というあたりがとりあえずの障壁?

KotlinでServlet書いてもいいけど、それだと認可のためにrouting-servlet-configを書く必要があるな。今は亡き?SAStrutsサポートとかTERASOLUNAフレームワークサポートでやってるような方式がこっちにも応用できるようならそのほうがいいだろうけど。どうなんだろ。

resin.propertiesの残像に惑わされる

e-Builder からresinを起動してhttp://localhost:8080/imart/loginをたたくと以下のような例外。

net.sf.ehcache.config.InvalidConfigurationException: There is one error in your configuration: 
    * CacheManager configuration: You've assigned more memory to the on-heap than the VM can sustain, please adjust your -Xmx setting accordingly


    at net.sf.ehcache.config.CacheConfiguration.setupFor(CacheConfiguration.java:1555) ~[ehcache-core-2.6.0.jar:na]
    at net.sf.ehcache.CacheManager.initializeEhcache(CacheManager.java:1277) ~[ehcache-core-2.6.0.jar:na]
    at net.sf.ehcache.CacheManager.addCacheNoCheck(CacheManager.java:1329) ~[ehcache-core-2.6.0.jar:na]
    at net.sf.ehcache.CacheManager.addConfiguredCaches(CacheManager.java:750) ~[ehcache-core-2.6.0.jar:na]
    at net.sf.ehcache.CacheManager.doInit(CacheManager.java:451) ~[ehcache-core-2.6.0.jar:na]
    at net.sf.ehcache.CacheManager.init(CacheManager.java:374) ~[ehcache-core-2.6.0.jar:na]
    at net.sf.ehcache.CacheManager.<init>(CacheManager.java:259) ~[ehcache-core-2.6.0.jar:na]
    at net.sf.ehcache.CacheManager.newInstance(CacheManager.java:1029) ~[ehcache-core-2.6.0.jar:na]
    at net.sf.ehcache.CacheManager.newInstance(CacheManager.java:1005) ~[ehcache-core-2.6.0.jar:na]
    at jp.co.intra_mart.system.cache.ehcache.EhcacheManagerProvider.getCacheManager(EhcacheManagerProvider.java:58) ~[im_cache_impl-8.0.12-main.jar:na]
    at jp.co.intra_mart.foundation.cache.CacheManagerFactory.getCacheManager(CacheManagerFactory.java:96) ~[im_cache_base-8.0.12-main.jar:na]
    at jp.co.intra_mart.foundation.cache.CacheManagerFactory.getCacheManager(CacheManagerFactory.java:62) ~[im_cache_base-8.0.12-main.jar:na]
    at jp.co.intra_mart.foundation.authz.services.admin.impl.dao.ImazSubjectDAO.getCache(ImazSubjectDAO.java:173) ~[im_authz_impl-8.0.13-main.jar:na]
    at jp.co.intra_mart.foundation.authz.services.admin.impl.dao.ImazSubjectDAO.find(ImazSubjectDAO.java:66) ~[im_authz_impl-8.0.13-main.jar:na]
    at jp.co.intra_mart.foundation.authz.services.admin.impl.InHouseSubjectManager.mergeSubject(InHouseSubjectManager.java:1135) ~[im_authz_impl-8.0.13-main.jar:na]
    at jp.co.intra_mart.foundation.authz.services.admin.impl.InHouseSubjectManager.registerSubject(InHouseSubjectManager.java:1176) ~[im_authz_impl-8.0.13-main.jar:na]
    at jp.co.intra_mart.foundation.authz.services.admin.impl.InHouseSubjectManager.registerAsSubject(InHouseSubjectManager.java:650) ~[im_authz_impl-8.0.13-main.jar:na]
    at jp.co.intra_mart.foundation.authz.services.admin.impl.InHouseSubjectManager.registerAsSubject(InHouseSubjectManager.java:89) ~[im_authz_impl-8.0.13-main.jar:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_65]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_65]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_65]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_65]

... あまりにも長いので省略 ...

どうやらメッセージを読む限りメモリが足りないってことらしいのだけど、もともと普通にコンソールから起動した使っていたインスタンスで、その時は問題なく使えていた。その後e-Builder側でデバッグサーバとして設定したんだけど、e-Builderから起動すると、起動時は問題ないんだけどブラウザからアクセスしたときに上記スタックトレースが流れて、画面も壊れた表示になる。

起動方法を変えたとはいえ、別にアプリケーションの構成変えたりはしていない。

普通メモリの設定は${resin.home}/conf/resin.propertiesにコメントアウトで記載されている

# Arg passed directly to the JVM
# jvm_args  : -Xmx2048m -XX:MaxPermSize=512m

このあたりを有効にして大きくしてやればいいんだけど、e-Builderからデバッグサーバを設定するとワークスペースにServerってプロジェクトができて、サーバ構成毎のフォルダが作られ、その下にもresin.properties が作成される。なのでつまりこれを直してやればよい。・・・かというとそうでもなくて、e-Builder のサーバペインで構成済みのサーバをダブルクリックすると、コードペインでサーバ構成の内容が表示されるんだけど、その中にある「起動構成を開く」というリンクをクリックすると何やらresin起動時に指定していると思われるオプションをみることができる。

設定の仕方がまずいのか何なのか、ここでresinの引数に-Xmx512mなんて書かれていたのでメモリが足りなくなってて上記のエラーになる、ということみたい。

IntramartDialectAutoSelector が NullPointerException

だいたいこんなような例外が出る

java.lang.NullPointerException
    at jp.co.intra_mart.mirage.ext.dialect.IntramartDialectAutoSelector.getDialect(IntramartDialectAutoSelector.java:25)
    at jp.co.intra_mart.mirage.ext.dialect.IntramartDialectAutoSelector.getTenantDatabaseDialect(IntramartDialectAutoSelector.java:13)
    at jp.co.intra_mart.mirage.ext.session.IntramartDatabaseManagerSession.getTenantDatabaseSqlManager(IntramartDatabaseManagerSession.java:173)
    at jp.co.intra_mart.mirage.ext.dao.DAOFactory.getTenantDatabaseDAO(DAOFactory.java:96)
    at jp.co.intra_mart.system.service.provider.updater.TenantSetupInformationDelegateImpl.getTenantImUpdateSchemaDAO(TenantSetupInformationDelegateImpl.java:586)
    at jp.co.intra_mart.system.service.provider.updater.TenantSetupInformationDelegateImpl.existTable(TenantSetupInformationDelegateImpl.java:454)
    at jp.co.intra_mart.system.service.provider.updater.TenantSetupInformationDelegateImpl.getUpdatedSchemaVersion(TenantSetupInformationDelegateImpl.java:598)
    at jp.co.intra_mart.system.service.provider.updater.TenantSetupInformationDelegateImpl.getDeployedSchemaVersion(TenantSetupInformationDelegateImpl.java:488)
    at jp.co.intra_mart.system.service.provider.updater.TenantSetupInformationDelegateImpl.completedSetup(TenantSetupInformationDelegateImpl.java:445)
    at jp.co.intra_mart.system.service.provider.updater.TenantSetupInformationDelegateImpl.needSetup(TenantSetupInformationDelegateImpl.java:364)
    at jp.co.intra_mart.system.service.provider.updater.TenantSetupInformation.needSetup(TenantSetupInformation.java:76)
    at jp.co.intra_mart.system.authz.context.impl.StandardAuthzSubjectContextBuilder.needSetup(StandardAuthzSubjectContextBuilder.java:158)
    at jp.co.intra_mart.system.authz.context.impl.StandardAuthzSubjectContextBuilder.build(StandardAuthzSubjectContextBuilder.java:45)
    at jp.co.intra_mart.system.context.impl.command.LifecycleBeginOperation.buildContext(LifecycleBeginOperation.java:96)
    at jp.co.intra_mart.system.context.impl.command.LifecycleBeginOperation.execute(LifecycleBeginOperation.java:64)
    at jp.co.intra_mart.system.context.impl.LifecycleImpl.begin(LifecycleImpl.java:82)
    at jp.co.intra_mart.system.context.web.impl.ContextFilter.doContextFilter(ContextFilter.java:114)
    at jp.co.intra_mart.system.context.web.impl.PreContextFilterChain.doFilter(PreContextFilterChain.java:47)
    at jp.co.intra_mart.system.context.web.impl.ContextFilter.doFilter(ContextFilter.java:78)
    at com.caucho.server.dispatch.FilterFilterChain.doFilter(FilterFilterChain.java:89)
    at jp.co.intra_mart.foundation.security.filter.ResponseCharacterEncodingFilter.doFilter(ResponseCharacterEncodingFilter.java:90)
    at com.caucho.server.dispatch.FilterFilterChain.doFilter(FilterFilterChain.java:89)
    at jp.co.intra_mart.foundation.security.filter.RequestCharacterEncodingFilter.doFilter(RequestCharacterEncodingFilter.java:47)
    at com.caucho.server.dispatch.FilterFilterChain.doFilter(FilterFilterChain.java:89)
    at org.seasar.framework.container.filter.S2ContainerFilter.doFilter(S2ContainerFilter.java:79)
    at com.caucho.server.dispatch.FilterFilterChain.doFilter(FilterFilterChain.java:89)
    at org.seasar.framework.container.hotdeploy.HotdeployFilter.doHotdeployFilter(HotdeployFilter.java:99)
    at org.seasar.framework.container.hotdeploy.HotdeployFilter.doFilter(HotdeployFilter.java:67)
    at com.caucho.server.dispatch.FilterFilterChain.doFilter(FilterFilterChain.java:89)
    at com.caucho.server.httpcache.ProxyCacheFilterChain.doRequestCacheable(ProxyCacheFilterChain.java:252)
    at com.caucho.server.httpcache.ProxyCacheFilterChain.doFilter(ProxyCacheFilterChain.java:193)
    at com.caucho.server.webapp.WebAppFilterChain.doFilter(WebAppFilterChain.java:156)
    at com.caucho.server.webapp.AccessLogFilterChain.doFilter(AccessLogFilterChain.java:95)
    at com.caucho.server.dispatch.ServletInvocation.service(ServletInvocation.java:289)
    at com.caucho.server.http.HttpRequest.handleRequest(HttpRequest.java:838)
    at com.caucho.network.listen.TcpSocketLink.dispatchRequest(TcpSocketLink.java:1349)
    at com.caucho.network.listen.TcpSocketLink.handleRequest(TcpSocketLink.java:1305)
    at com.caucho.network.listen.TcpSocketLink.handleRequestsImpl(TcpSocketLink.java:1289)
    at com.caucho.network.listen.TcpSocketLink.handleRequests(TcpSocketLink.java:1197)
    at com.caucho.network.listen.TcpSocketLink.handleAcceptTaskImpl(TcpSocketLink.java:993)
    at com.caucho.network.listen.ConnectionTask.runThread(ConnectionTask.java:117)
    at com.caucho.network.listen.ConnectionTask.run(ConnectionTask.java:93)
    at com.caucho.network.listen.SocketLinkThreadLauncher.handleTasks(SocketLinkThreadLauncher.java:169)
    at com.caucho.network.listen.TcpSocketAcceptThread.run(TcpSocketAcceptThread.java:61)
    at com.caucho.env.thread2.ResinThread2.runTasks(ResinThread2.java:173)
    at com.caucho.env.thread2.ResinThread2.run(ResinThread2.java:118)

多分 data-source-mapping-config.xml に定義されているデータソースに指定したtenant-idが実際使おうとしているテナントのIDと一致していない。

設定の誤りだけど、テナントIDがdefault以外でかつ、データベースやらストレージの情報を使いまわしているような状況でWarだけ入れ替えたとかいうときに起こりそう。

intramart Accel platformのログをkibanaで概観したい。

パフォーマンスが時系列的にどう変化しているのかを知りたくて、リクエストログ(レスポンスタイムが載ってる)を概観したい。

elasticsearch + kibana でやれそうだが、ログの収集をどうするか。

サーバがWindowsなのでfluentdでやるとするとLinuxの分追加リソースが必要になりそう。とりあえず一括で取り込んでみることを考えてEmbulkでもいいか、と思ってやってみた。同等の目的でLogstashを使う場合もあるらしい。とりあえず設定するだけでなんだかうまくいかないな~という感じで時間使ってしまったので最終的にできた手順をGistに書いた。

Embulk

iAPのリクエストログをembulkでelasticsearchに読み込んでみる

gemの扱いが若干微妙・・・というのは、インターネットから断絶された環境で使う必要があったので。 まあつながってる環境で構築してからそのまま持っていけばいいんだろうけど。

これ自身は単にコマンドなのでデータを転送し終わるとプロセスが終了する。

Logstash

Logstash で IMのログをelasticsearchに転送してみた

こっちはこっちで基本バルクロードというよりはfluentdのtd-agentのようにログを監視して変化があれば転送するという感じのようだ。 だから単に起動するとプロセスは終了しない。

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のコンテンツは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 > 

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

SVNKit(2)

前回の続き。

前回はとりあえずISVNRepositoryISVNEditorISVNDiffWindowを使ってリポジトリの内容にアクセスできそう、というところまでやった。

一応注記しておくと、前回取得できた情報を何とかすれば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より小さい値だろう)っぽい。
  • "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しなければならない?

続きはまた次回。