はじめに
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)
}