Node.jsでSVNからcheckoutするためにsvn-spawnを使う

# コード

```checkout.js
const Client = require('svn-spawn');
const client = new Client({
    // ディレクトリがなければ作成される
    cwd: __dirname + '/../target',
    // 実行時に手動入力できるのでコードに載せなくても良い。
    //username: 'username', // optional if authentication not required or is already saved
    //password: 'password', // optional if authentication not required or is already saved
    noAuthCache: true, // optional, if true, username does not become the logged in user on the machine
});

// targetディレクトリは作成されないので、cwdの方に新規ディレクトリを指定しないと混ざるので注意
client.checkout('http://repo/～/target');
```

実行すると、

```
> node .\checkout.js
Authentication realm: <http://repo:80> Kerberos Login
Password for 'khsk': ********
A    hogehoge.txt
～～

Checked out revision xxxx.
```

とパスワードを聞いてくるので入力してやる。
ユーザー名はたぶん普段遣いのSVNの記録から読み込んでいると思われる。パスワードを間違えればUsernameからの入力になるので、違うユーザーでチェックアウトしたいときも焦らない。

# 実行環境

* Windows7
* node v9.3.0


# ライブラリ

* [svn-spawn - npm](https://www.npmjs.com/package/svn-spawn)
* [ddliu/node-svn-spawn: Easy way to access svn repository with node.js.](https://github.com/ddliu/node-svn-spawn)

Readmeには`checkout`がなくて、`client.cmd`からか…と凹んだが、念の為コードを検索すると

[node-svn-spawn/svn.js at 59c4295e33403023f4e8a0e1fe34447a1cef9b5b · ddliu/node-svn-spawn](https://github.com/ddliu/node-svn-spawn/blob/59c4295e33403023f4e8a0e1fe34447a1cef9b5b/lib/svn.js#L38)

ちゃんと生えていた。


([peteward44/node-svn-ultimate: The ultimate SVN wrapper for node. Contains all the basic methods checkout, update, info, etc, and includes svnmucc support](https://github.com/peteward44/node-svn-ultimate)を先に試したんですが、失敗したんですよね)


# Promise化したい

`await`大好きマンなので、コールバックはちょっと…

[コールバック関数をPromise化して使う - Qiita](https://qiita.com/akameco/items/14ab12fe81bbffb03dca)
いいのがあった。

```promise1.js
const Client = require('svn-spawn');
const {promisify} = require('util');

const client = new Client({
    // ディレクトリがなければ作成される
    cwd: __dirname + '/../target',
    // 実行時に手動入力できるのでコードに載せなくても良い。
    //username: 'username', // optional if authentication not required or is already saved
    //password: 'password', // optional if authentication not required or is already saved
    noAuthCache: true, // optional, if true, username does not become the logged in user on the machine
});

// targetディレクトリは作成されないので、cwdの方に新規ディレクトリを指定しないと混ざるので注意
(async () => {
    await promisify(client.checkout)('http://repo/～/target');
})();
```

```:promise1実行結果
(node:7396) UnhandledPromiseRejectionWarning:TypeError: Cannot read property 'getOption' of undefin
ed    at Client.checkout (~\node_modules\svn-spawn\lib\svn.js:4
0:20)

…
```

4行目ではないけれど
[node-svn-spawn/svn.js at 59c4295e33403023f4e8a0e1fe34447a1cef9b5b · ddliu/node-svn-spawn](https://github.com/ddliu/node-svn-spawn/blob/59c4295e33403023f4e8a0e1fe34447a1cef9b5b/lib/svn.js#L22)
この`this.getOption`が問題な気がする。(`util.inherits`初めてみた。継承らしい。)
`this`の値が変わっちゃったのかな？

`this`を`client`に戻してやればいいのかなーと`.bind(client)`の位置をあれこれいじり、



```promise2.js
const Client = require('svn-spawn');
const {promisify} = require('util');

const client = new Client({
    // ディレクトリがなければ作成される
    cwd: __dirname + '/../target',
    // 実行時に手動入力できるのでコードに載せなくても良い。
    //username: 'username', // optional if authentication not required or is already saved
    //password: 'password', // optional if authentication not required or is already saved
    noAuthCache: true, // optional, if true, username does not become the logged in user on the machine
});

// targetディレクトリは作成されないので、cwdの方に新規ディレクトリを指定しないと混ざるので注意
(async () => {
    await promisify(client.checkout).bind(client)('http://repo/～/target');
})();
```

とした。
が、どうやらコールバックのときから、チェックアウトには成功するものの失敗扱いらしく、Rejectされるので`try/catch`してやらないといけない。[^1]
[^1]: 普段から真面目に`try/catch`してる人なら出ない発言

ついでにakamecoさん風なら`await/catch`。
というか今回は

* 同期処理(`await`)が目的
* 成功がエラーなので握りつぶす(邪悪)

なので、`await/catch`の方が簡潔に握りつぶせると気づいた。

[async関数においてtry/catchではなくawait/catchパターンを活用する - Qiita](https://qiita.com/akameco/items/cc73afcdb5ac5d0774bc)
(ちよ父すき)


つまり

```js
(async () => {
    try {
        await promisify(client.checkout).bind(client)('http://repo/～/target');
    } catch () {}
})();

// よりも

(async () => {
    await promisify(client.checkout).bind(client)('http://repo/～/target').catch(() => {});
})();

```

ネストも行数も減るし見やすい。
(ちゃんとした`try/catch`の場合は上下どちらにももう一段`try/catch`が必要かな。上の`try/catch`は握りつぶすための`try/catch`なので)

これでひとまず、チェックアウト後の処理をコールバックの中に書かずに済むようになった。


# やっぱりexportも

同じフォルダにSVNから別のファイルを参照用に置きたくなった。
となると同一ディレクトリに別リポジトリはおけないので、エクスポートすることに。
exportは残念ながら実装されていないみたい
[Issues · ddliu/node-svn-spawn](https://github.com/ddliu/node-svn-spawn/issues?utf8=%E2%9C%93&q=export)
なので、結局コマンドで実行することに。

```js
await promisify(client.cmd).bind(client)(['export','http://repo/～/targetfile']).catch(() => {});
```

と続けて書いてエクスポートした。
`client`を使いまわしているので、ダウンロード先は同じ`cwd`になる。