Skip to content

Commit 0878bcc

Browse files
authored
tools: refactor bump omicron script, use worktree (#2668)
use git worktree to bump omicron
1 parent dacb102 commit 0878bcc

File tree

5 files changed

+137
-92
lines changed

5 files changed

+137
-92
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ deno.lock
1010
*.launch
1111
.settings/
1212
*.sublime-workspace
13+
.helix
1314

1415
# IDE - VSCode
1516
.vscode/*

.licenserc.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
header:
22
# default is 80, need to make it slightly longer for a long shebang
3-
license-location-threshold: 100
3+
license-location-threshold: 120
44
license:
55
spdx-id: MPL-2.0
66
content: |

.prettierrc.js

+2
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ export default {
2424
'^[./]',
2525
],
2626
importOrderTypeScriptVersion: '5.2.2',
27+
// ts and jsx are the default, last is needed for `await using` in bump-omicron.ts
28+
importOrderParserPlugins: ['typescript', 'jsx', 'explicitResourceManagement'],
2729
}

tools/deno/bump-omicron.ts

+132-91
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#! /usr/bin/env -S deno run --allow-run=gh,git --allow-net --allow-read --allow-write --allow-env
1+
#! /usr/bin/env -S deno run --allow-run=gh,git,mktemp --allow-net --allow-read --allow-write --allow-env
22

33
/*
44
* This Source Code Form is subject to the terms of the Mozilla Public
@@ -10,29 +10,28 @@
1010
import * as flags from 'https://deno.land/[email protected]/flags/mod.ts'
1111
import * as path from 'https://deno.land/[email protected]/path/mod.ts'
1212
import $ from 'https://deno.land/x/[email protected]/mod.ts'
13+
import { existsSync } from 'jsr:@std/[email protected]'
1314

1415
const HELP = `
15-
Update tools/console_version in ../omicron with current console commit
16-
hash and tarball hash and create PR in Omicron with that change.
16+
Update tools/console_version in ../omicron to the specified console
17+
commit and create PR in Omicron with that change. We use a git worktree
18+
to avoid touching your Omicron clone.
1719
1820
Requirements:
1921
- GitHub CLI installed
2022
- Omicron is a sibling dir to console
2123
2224
Usage:
23-
./tools/deno/bump-omicron.ts [options]
25+
./tools/deno/bump-omicron.ts [commit-ish=main] [options]
2426
2527
Options:
2628
-d, --dry-run Dry run, showing changes without creating PR
2729
-h, --help Show this help message
2830
-m, --message <msg> Add message to PR title: 'Bump web console (<msg>)'
2931
`
3032

31-
const OMICRON_DIR = '../omicron'
32-
const VERSION_FILE = path.join(OMICRON_DIR, 'tools/console_version')
33-
33+
const OMICRON_DIR = path.resolve('../omicron')
3434
const GH_MISSING = 'GitHub CLI not found. Please install it and try again.'
35-
const VERSION_FILE_MISSING = `Omicron console version file at '${VERSION_FILE}' not found. This script assumes Omicron is cloned in a sibling directory next to Console.`
3635

3736
/**
3837
* These lines get printed in an Omicron PR, so any references to commits or
@@ -56,29 +55,28 @@ function linkifyGitLog(line: string): string {
5655
return `* ${shaLink} ${rest}`
5756
}
5857

59-
// script starts here
58+
async function makeOmicronWorktree() {
59+
const tmpDir = await $`mktemp -d`.text()
60+
await $`git worktree add ${tmpDir} origin/main`.cwd(OMICRON_DIR).quiet('stdout')
6061

61-
const args = flags.parse(Deno.args, {
62-
alias: { dryRun: ['d', 'dry-run'], h: 'help', m: 'message' },
63-
boolean: ['dryRun', 'help'],
64-
string: ['message'],
65-
})
66-
67-
if (args.help) {
68-
console.info(HELP)
69-
Deno.exit()
62+
return {
63+
dir: tmpDir,
64+
[Symbol.asyncDispose]: async function () {
65+
console.log('Cleaning up worktree')
66+
await $`git worktree remove ${tmpDir}`.cwd(OMICRON_DIR).quiet('stdout')
67+
},
68+
}
7069
}
7170

72-
const newCommit = await $`git rev-parse HEAD`.text()
73-
74-
const shaUrl = `https://dl.oxide.computer/releases/console/${newCommit}.sha256.txt`
75-
const shaResp = await fetch(shaUrl)
71+
async function fetchTarballSha(commit: string) {
72+
const shaUrl = `https://dl.oxide.computer/releases/console/${commit}.sha256.txt`
73+
const shaResp = await fetch(shaUrl)
7674

77-
if (!shaResp.ok) {
78-
const workflowId =
79-
await $`gh run list -L 1 -w 'Upload assets to dl.oxide.computer' --json databaseId --jq '.[0].databaseId'`.text()
80-
console.error(
81-
`
75+
if (!shaResp.ok) {
76+
const workflowId =
77+
await $`gh run list -L 1 -w 'Upload assets to dl.oxide.computer' --json databaseId --jq '.[0].databaseId'`.text()
78+
console.error(
79+
`
8280
Failed to fetch console tarball SHA. Either the current commit is not on origin/main or the asset upload job is still running.
8381
8482
Status: ${shaResp.status}
@@ -87,90 +85,133 @@ Body: ${await shaResp.text()}
8785
8886
Running 'gh run watch ${workflowId}' to watch the current upload action.
8987
`
90-
)
91-
await $`gh run watch ${workflowId}`
92-
Deno.exit(1)
93-
}
94-
95-
const newSha2 = (await shaResp.text()).trim()
96-
const newVersionFile = `COMMIT="${newCommit}"\nSHA2="${newSha2}"\n`
88+
)
89+
await $`gh run watch ${workflowId}`
90+
return Deno.exit(1)
91+
}
9792

98-
const oldVersionFile = await Deno.readTextFile(VERSION_FILE).catch(() => {
99-
throw Error(VERSION_FILE_MISSING)
100-
})
93+
return (await shaResp.text()).trim()
94+
}
10195

102-
const oldCommit = /COMMIT="?([a-f0-9]+)"?/.exec(oldVersionFile)?.[1]
103-
if (!oldCommit) throw Error('Could not parse existing version file')
96+
async function getOldCommit() {
97+
const oldVersionFile = await $`git show origin/main:tools/console_version`
98+
.cwd(OMICRON_DIR)
99+
.text()
104100

105-
if (oldCommit === newCommit) {
106-
console.info('Nothing to update: Omicron already has the current commit pinned')
107-
Deno.exit()
101+
const oldCommit = /COMMIT="?([a-f0-9]+)"?/.exec(oldVersionFile)?.[1]
102+
if (!oldCommit) throw new Error('Could not parse existing version file')
103+
return oldCommit
108104
}
109105

110-
const commitRange = `${oldCommit.slice(0, 8)}...${newCommit.slice(0, 8)}`
106+
async function makeOmicronPR(
107+
consoleCommit: string,
108+
prTitle: string,
109+
changesLink: string,
110+
commits: string
111+
) {
112+
const branchName = 'bump-console-' + consoleCommit.slice(0, 8)
113+
114+
{
115+
// create git worktree for latest main in temp dir. `using` ensures this gets
116+
// cleaned up at the end of the block
117+
await using worktree = await makeOmicronWorktree()
118+
119+
const newSha2 = await fetchTarballSha(consoleCommit)
120+
const newVersionFile = `COMMIT="${consoleCommit}"\nSHA2="${newSha2}"\n`
121+
122+
const versionFileAbsPath = path.resolve(worktree.dir, 'tools/console_version')
123+
await Deno.writeTextFile(versionFileAbsPath, newVersionFile)
124+
console.info('Updated ', versionFileAbsPath)
125+
126+
// cd to omicron, pull main, create new branch, commit changes, push, PR it, go back to
127+
// main, delete branch
128+
Deno.chdir(worktree.dir)
129+
await $`git checkout -b ${branchName}`
130+
console.info('Created branch', branchName)
131+
132+
await $`git add tools/console_version`
133+
134+
// commits are console commits, so they won't auto-link in omicron
135+
const commitsMarkdown = commits.split('\n').map(linkifyGitLog).join('\n')
136+
const prBody = `${changesLink}\n\n${commitsMarkdown}`
137+
await $`git commit -m ${prTitle} -m ${prBody}`
138+
139+
await $`git push --set-upstream origin ${branchName}`
140+
console.info('Committed changes and pushed')
141+
142+
// create PR
143+
const prUrl = await $`gh pr create --title ${prTitle} --body ${prBody}`.text()
144+
console.info('PR created:', prUrl)
145+
146+
// set it to auto merge
147+
const prNum = prUrl.match(/\d+$/)![0]
148+
await $`gh pr merge ${prNum} --auto --squash`
149+
console.info('PR set to auto-merge when CI passes')
150+
}
111151

112-
const commits = await $`git log --graph --oneline ${commitRange}`.text()
113-
// commits are console commits, so they won't auto-link in omicron
114-
const commitsMarkdown = commits.split('\n').map(linkifyGitLog).join('\n')
152+
// worktree has been cleaned up, so branch delete is allowed
153+
await $`git branch -D ${branchName}`.cwd(OMICRON_DIR)
154+
}
115155

116-
const changesLine = `https://github.com/oxidecomputer/console/compare/${commitRange}`
156+
// wrapped in a function so we can do early returns rather than early
157+
// Deno.exits, which mess up the worktree cleanup
158+
async function run(commitIsh: string, dryRun: boolean, messageArg: string | undefined) {
159+
await $`git fetch`.cwd(OMICRON_DIR)
117160

118-
const branchName = 'bump-console-' + newCommit.slice(0, 8)
119-
const prBody = `${changesLine}\n\n${commitsMarkdown}`
161+
const oldConsoleCommit = await getOldCommit()
162+
const newConsoleCommit = await $`git rev-parse ${commitIsh}`.text()
120163

121-
console.info(`\n${changesLine}\n\n${commits}\n`)
164+
if (oldConsoleCommit === newConsoleCommit) {
165+
console.info(`Nothing to update: Omicron already has ${newConsoleCommit} pinned`)
166+
return
167+
}
122168

123-
if (args.dryRun) Deno.exit()
169+
const commitRange = `${oldConsoleCommit.slice(0, 8)}...${newConsoleCommit.slice(0, 8)}`
170+
const commits = await $`git log --graph --oneline ${commitRange}`.text()
171+
const changesLink = `https://github.com/oxidecomputer/console/compare/${commitRange}`
124172

125-
const message =
126-
args.message ||
127-
(await $.prompt({
128-
message: 'Description? (enter to skip)',
129-
noClear: true,
130-
}))
173+
console.info(`\n${changesLink}\n\n${commits}\n`)
131174

132-
const prTitle = 'Bump web console' + (message ? ` (${message})` : '')
175+
if (dryRun) return
133176

134-
console.info(`\nPR title: ${prTitle}\n`)
177+
const message =
178+
messageArg ||
179+
(await $.prompt({ message: 'Description? (enter to skip)', noClear: true }))
180+
const prTitle = 'Bump web console' + (message ? ` (${message})` : '')
181+
console.info(`\nPR title: ${prTitle}\n`)
135182

136-
const go = await $.confirm({ message: 'Make Omicron PR?', noClear: true })
137-
if (!go) Deno.exit()
183+
const go = await $.confirm({ message: 'Make Omicron PR?', noClear: true })
184+
if (!go) return
138185

139-
if (!$.commandExistsSync('gh')) throw Error(GH_MISSING)
186+
if (!$.commandExistsSync('gh')) throw new Error(GH_MISSING)
140187

141-
await Deno.writeTextFile(VERSION_FILE, newVersionFile)
142-
console.info('Updated ', VERSION_FILE)
188+
const consoleDir = Deno.cwd() // save it so we can change back
143189

144-
const consoleDir = Deno.cwd()
190+
await makeOmicronPR(newConsoleCommit, prTitle, changesLink, commits)
145191

146-
// cd to omicron, pull main, create new branch, commit changes, push, PR it, go back to
147-
// main, delete branch
148-
Deno.chdir(OMICRON_DIR)
149-
await $`git checkout main`
150-
await $`git pull`
151-
await $`git checkout -b ${branchName}`
152-
console.info('Created branch', branchName)
192+
// bump omicron tag in console to current commit
193+
Deno.chdir(consoleDir)
194+
console.info('Bumping omicron tag in console')
195+
await $`git tag -f -a omicron -m 'pinned commit on omicron main' ${commitIsh}`
196+
await $`git push -f origin tag omicron`
197+
}
153198

154-
await $`git add tools/console_version`
155-
await $`git commit -m ${prTitle} -m ${prBody}`
156-
await $`git push --set-upstream origin ${branchName}`
157-
console.info('Committed changes and pushed')
199+
// script starts here
158200

159-
// create PR
160-
const prUrl = await $`gh pr create --title ${prTitle} --body ${prBody}`.text()
161-
console.info('PR created:', prUrl)
201+
const args = flags.parse(Deno.args, {
202+
alias: { dryRun: ['d', 'dry-run'], h: 'help', m: 'message' },
203+
boolean: ['dryRun', 'help'],
204+
string: ['message'],
205+
})
162206

163-
// set it to auto merge
164-
const prNum = prUrl.match(/\d+$/)![0]
165-
await $`gh pr merge ${prNum} --auto --squash`
166-
console.info('PR set to auto-merge when CI passes')
207+
if (args.help) {
208+
console.info(HELP)
209+
Deno.exit()
210+
}
167211

168-
await $`git checkout main`
169-
await $`git branch -D ${branchName}`
170-
console.info('Checked out omicron main, deleted branch', branchName)
212+
if (!existsSync(OMICRON_DIR)) {
213+
throw new Error(`Omicron repo not found at ${OMICRON_DIR}`)
214+
}
171215

172-
// bump omicron tag in console to current commit
173-
Deno.chdir(consoleDir)
174-
console.info('Bumping omicron tag in console')
175-
await $`git tag -f -a omicron -m 'pinned commit on omicron main'`
176-
await $`git push -f origin tag omicron`
216+
const commitIsh = args._[0]?.toString() || 'main'
217+
await run(commitIsh, args.dryRun, args.message)

tools/deno/deno.jsonc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

0 commit comments

Comments
 (0)