denoでgitを呼び出してみた

#はじめに

nodeでgitブランチをちょっと弄るやつを書いて使ってたんだけど、せっかくだしdenoで書き直してnodeからdenoに完全に乗り換えようかなと思った。

とりあえずgithubからモジュールを読み込めるみたいだし、元々nodeでやってたのと同じようにnpmパッケージのsimple-gitを使えば良いかなとインポートしてみたら、インポート文だけなら問題なかったけど、中身を利用しようとすると文句言われたので結局Deno.runを使って自前でgitを呼び出すことに…

#コード

workブランチにwip/日時ブランチを作って作業して、終わったらworkにマージという定形作業をやるスクリプトです。
deno installでスクリプトインストールすれば作業開始も終了もgit-wipを実行するだけというお手軽仕様。

gitの標準出力を取得するためにはDeno.runに{stdout: 'piped'}を渡して、new TextDecoder().decode(await p.output())でstringにしないといけない。

```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にコピーされない

async function branches(): Promise<string[]> {
  const p = Deno.run({ cmd: ['git', 'rev-parse', '--abbrev-ref', '--branches'], stdout: 'piped', stderr: 'piped' })
  const { code } = await p.status()
  if (code !== 0) return []
  return (new TextDecoder().decode(await p.output())).trim().split(/\r\n|\n/)
}

async function currentBranch(): Promise<string | null> {
  const p = Deno.run({ cmd: ['git', 'rev-parse', '--abbrev-ref', 'HEAD'], stdout: 'piped', stderr: 'piped' })
  const { code } = await p.status()
  return code !== 0 ? null : (new TextDecoder().decode(await p.output())).trim()
}

async function checkoutBranch(branch: string): Promise<boolean> {
  const p = Deno.run({ cmd: ['git', 'checkout', '-b', branch], stdout: 'piped', stderr: 'piped' })
  const { code } = await p.status()
  return code === 0
}

async function checkout(branch: string): Promise<boolean> {
  const p = Deno.run({ cmd: ['git', 'checkout', branch], stdout: 'piped', stderr: 'piped' })
  const { code } = await p.status()
  return code === 0
}

async function merge(branch: string): Promise<boolean> {
  const p = Deno.run({ cmd: ['git', 'merge', '--no-ff', branch], stdout: 'piped', stderr: 'piped' })
  const { code } = await p.status()
  return code === 0
}

async function deleteBranch(branch: string): Promise<boolean> {
  const p = Deno.run({ cmd: ['git', 'branch', '-D', branch], stdout: 'piped', stderr: 'piped' })
  const { code } = await p.status()
  return code === 0
}

const work = 'work'
const twoDigits = (n : number): string => (n > 9 ? '' : '0') + n

const current = await currentBranch()
if (current === null) {
  console.error('not a git repository')
  Deno.exit(-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 checkoutBranch(wip)) {
    console.log(`branching "${wip}"`)
    Deno.exit(0)
  } else {
    console.error(`do not branching "${wip}"`)
    Deno.exit(-1)
  }
} else if (current.startsWith('wip/')) {
  if (!(await branches()).includes(work)) {
    console.error('no exists "work" branch')
    Deno.exit(-1)
  } else {
    console.log(`${current} is merging to work`)
    await checkout(work)
    await merge(current)
    await deleteBranch(current)
    Deno.exit(0)
  }
} else {
  console.error('not a work or wip branch')
  Deno.exit(-1)
}
```