denoでgitを呼び出してみた2

#はじめに

[前回](https://qiita.com/b2ox/items/20935c65efd9d6021702)は1ファイルにベタ書きしてしまったので、今回はgit呼び出し関数をモジュールに切り分けて別スクリプトからも利用できるようにしてみた。

#コード

そのままモジュール化するのも芸がないので、各関数の中身もベタ書きしてたのを共通処理`runCmd: string[] -> Promise<string | null>`として括り出してみた。
runCmdはコマンドを呼び出して、失敗したならnullを、成功したなら標準出力を文字列として返す。

```git.ts
export async function branches(): Promise<string[]> {
  const ret = await runCmd(['git', 'rev-parse', '--abbrev-ref', '--branches'])
  if (ret === null) return []
  return ret.split(/\r\n|\n/)
}

export const currentBranch = () => runCmd(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
export const checkoutBranch = async (branch: string) => null !== await runCmd(['git', 'checkout', '-b', branch])
export const checkout = async (branch: string) =>  null !== await runCmd(['git', 'checkout', branch])
export const merge = async (branch: string) => null !== await runCmd(['git', 'merge', '--no-ff', branch])
export const deleteBranch = async (branch: string) => null !== await runCmd(['git', 'branch', '-D', branch])

async function runCmd(cmds: string[]): Promise<string | null> {
  const p = Deno.run({ cmd: cmds, stdout: 'piped', stderr: 'piped' })
  const { code } = await p.status()
  return code !== 0 ? null : (new TextDecoder().decode(await p.output())).trim()
}
```

あとはgit.tsをインポートする形にgit-wip.tsを書き換えて終了。

```git-wip.ts
// Usage: deno run --allow-run git-wip.ts
// 現在のブランチがworkなら wip/YYYYMMDD_hh ブランチを作成し移動
// wip/～ ブランチならworkブランチにマージ
//
// Script install: deno install --allow-run ~/denoscripts/git-wip.ts
// 上記でインストールすると ~/.deno/bin に ~/denoscripts/git-wip.ts を呼び出すスクリプトが作られる
// ~/denoscripts/git-wip.ts自体は~/.deno/binにコピーされない

import * as git from './git.ts'

async function main(): Promise<number> {
  const work = 'work'
  const twoDigits = (n: number): string => (n > 9 ? '' : '0') + n

  const current = await git.currentBranch()
  if (current === null) {
    console.error('not a git repository')
    return -1
  } else if (current === work) {
    const now = new Date()
    const wip = `wip/${now.getFullYear()}${twoDigits(now.getMonth() + 1)}${twoDigits(now.getDate())}_${twoDigits(now.getHours())}`
    if (await git.checkoutBranch(wip)) {
      console.log(`branching "${wip}"`)
    } else {
      console.error(`do not branching "${wip}"`)
      return -1
    }
  } else if (current.startsWith('wip/')) {
    if (!(await git.branches()).includes(work)) {
      console.error('no exists "work" branch')
      return -1
    } else {
      console.log(`${current} is merging to work`)
      await git.checkout(work)
      await git.merge(current)
      await git.deleteBranch(current)
    }
  } else {
    console.error('not a work or wip branch')
    return -1
  }
  return 0
}

Deno.exit(await main())
```

せっかくなのでgit.tsを利用したマージ処理も作る。
処理の流れは、現在のブランチを覚えておいて、toブランチに切り替え、fromブランチをマージ、覚えておいた元のブランチに戻るというもの。

```git-mgft.ts
// Usage: deno run --allow-run git-mgft.ts from to
// fromをtoにマージする

import * as git from './git.ts'

async function main(): Promise<number> {
  if (Deno.args.length < 2) {
    console.error('Usage: git-mgft from to');
    return -1
  }
  const [branchFrom, branchTo] = Deno.args
  const current = await git.currentBranch()
  if (current === null) {
    console.error('not a git repository')
    return -1
  }
  const info = await git.branches()
  if (info.indexOf(branchFrom) < 0 || info.indexOf(branchTo) < 0) {
    console.error('no branch: ' + branchTo)
    return -1
  }
  console.log(`git merge: ${branchFrom} -> ${branchTo}`)
  await git.checkout(branchTo)
  await git.merge(branchFrom)
  await git.checkout(current)
  return 0
}

Deno.exit(await main())
```