Skip to content

Commit e9e31db

Browse files
committed
/git-artifacts: if there is already a tag-git run, use it
We only need to trigger a new `tag-git` run if there is no existing, successful one yet. If there _is_ one, we can trigger the `git-artifacts` runs right away. This commit is best viewed with `--color-moved --color-moved-ws=allow-indentation-change`. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent d6560d2 commit e9e31db

4 files changed

Lines changed: 189 additions & 97 deletions

File tree

Lines changed: 106 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,127 @@
1-
module.exports = async (context, req) => {
2-
const action = req.body.action
3-
const checkRunOwner = req.body.repository.owner.login
4-
const checkRunRepo = req.body.repository.name
5-
const checkRun = req.body.check_run
1+
const getToken = (() => {
2+
const tokens = {}
3+
4+
const get = async (context, owner, repo) => {
5+
const getInstallationIdForRepo = require('./get-installation-id-for-repo')
6+
const installationId = await getInstallationIdForRepo(context, owner, repo)
7+
const getInstallationAccessToken = require('./get-installation-access-token')
8+
return await getInstallationAccessToken(context, installationId)
9+
}
10+
11+
return async (context, owner, repo) => tokens[[owner, repo]] || (tokens[[owner, repo]] = await get(context, owner, repo))
12+
})()
13+
14+
const triggerGitArtifactsRuns = async (context, checkRunOwner, checkRunRepo, checkRun) => {
615
const commitSHA = checkRun.head_sha
7-
const name = checkRun.name
816
const conclusion = checkRun.conclusion
917
const text = checkRun.output.text
1018

11-
const getToken = (() => {
12-
const tokens = {}
19+
if (conclusion !== 'success') {
20+
throw new Error(`tag-git run ${checkRun.id} completed with ${conclusion}: ${checkRun.html_url}`)
21+
}
1322

14-
const get = async (owner, repo) => {
15-
const getInstallationIdForRepo = require('./get-installation-id-for-repo')
16-
const installationId = await getInstallationIdForRepo(context, owner, repo)
17-
const getInstallationAccessToken = require('./get-installation-access-token')
18-
return await getInstallationAccessToken(context, installationId)
19-
}
23+
const match = text.match(/For details, see \[this run\]\(https:\/\/github.com\/([^/]+)\/([^/]+)\/actions\/runs\/(\d+)\)/)
24+
if (!match) throw new Error(`Unhandled 'text' attribute of tag-git run ${checkRun.id}: ${checkRun.url}`)
25+
const owner = match[1]
26+
const repo = match[2]
27+
const workflowRunId = Number(match[3])
28+
if (owner !== 'git-for-windows' || repo !== 'git-for-windows-automation') {
29+
throw new Error(`Unexpected repository ${owner}/${repo} for tag-git run ${checkRun.id}: ${checkRun.url}`)
30+
}
2031

21-
return async (owner, repo) => tokens[[owner, repo]] || (tokens[[owner, repo]] = await get(owner, repo))
22-
})()
32+
const gitVersionMatch = checkRun.output.summary.match(/^Tag Git (\S+) @([0-9a-f]+)$/)
33+
if (!gitVersionMatch) {
34+
throw new Error(`Could not parse Git version from summary '${checkRun.output.summary}' of tag-git run ${checkRun.id}: ${checkRun.url}`)
35+
}
36+
if (commitSHA !== gitVersionMatch[2]) {
37+
throw new Error(`Expected ${commitSHA} in summary '${checkRun.output.summary}' of tag-git run ${checkRun.id}: ${checkRun.url}`)
38+
}
39+
const gitVersion = gitVersionMatch[1]
2340

24-
if (action === 'completed') {
25-
if (name === 'tag-git') {
26-
if (checkRunOwner !== 'git-for-windows' || checkRunRepo !== 'git') {
27-
throw new Error(`Refusing to handle cascading run in ${checkRunOwner}/${checkRunRepo}`)
28-
}
41+
let res = ''
2942

30-
if (conclusion !== 'success') {
31-
throw new Error(`tag-git run ${checkRun.id} completed with ${conclusion}: ${checkRun.html_url}`)
32-
}
43+
const architecturesToTrigger = []
44+
const { listCheckRunsForCommit, queueCheckRun } = require('./check-runs')
45+
for (const architecture of ['x86_64', 'i686']) {
46+
const workflowName = `git-artifacts-${architecture}`
47+
const runs = await listCheckRunsForCommit(
48+
context,
49+
await getToken(context, checkRunOwner, checkRunRepo),
50+
checkRunOwner,
51+
checkRunRepo,
52+
commitSHA,
53+
workflowName
54+
)
55+
const latest = runs
56+
.filter(run => run.output.summary.endsWith(`(tag-git run #${workflowRunId})`))
57+
.sort((a, b) => a.id - b.id)
58+
.pop()
59+
if (latest && (latest.status !== 'completed' || latest.conclusion === 'success')) {
60+
// It either succeeded or is still running
61+
res = `${res}${workflowName} run already exists at ${latest.html_url}.\n`
62+
} else {
63+
architecturesToTrigger.push(architecture)
64+
}
65+
}
3366

34-
const match = text.match(/For details, see \[this run\]\(https:\/\/github.com\/([^/]+)\/([^/]+)\/actions\/runs\/(\d+)\)/)
35-
if (!match) throw new Error(`Unhandled 'text' attribute of tag-git run ${checkRun.id}: ${checkRun.url}`)
36-
const owner = match[1]
37-
const repo = match[2]
38-
const workflowRunId = Number(match[3])
39-
if (owner !== 'git-for-windows' || repo !== 'git-for-windows-automation') {
40-
throw new Error(`Unexpected repository ${owner}/${repo} for tag-git run ${checkRun.id}: ${checkRun.url}`)
41-
}
67+
if (architecturesToTrigger.length === 0) return `${res}No workflows need to be run!\n`
68+
69+
for (const architecture of architecturesToTrigger) {
70+
const workflowName = `git-artifacts-${architecture}`
71+
const title = `Build Git ${gitVersion} artifacts`
72+
const summary = `Build Git ${gitVersion} artifacts from commit ${commitSHA} (tag-git run #${workflowRunId})`
73+
await queueCheckRun(
74+
context,
75+
await getToken(context, checkRunOwner, checkRunRepo),
76+
checkRunOwner,
77+
checkRunRepo,
78+
commitSHA,
79+
workflowName,
80+
title,
81+
summary
82+
)
83+
}
4284

43-
let res = ''
44-
45-
const architecturesToTrigger = []
46-
const { listCheckRunsForCommit, queueCheckRun } = require('./check-runs')
47-
for (const architecture of ['x86_64', 'i686']) {
48-
const workflowName = `git-artifacts-${architecture}`
49-
const runs = await listCheckRunsForCommit(
50-
context,
51-
await getToken(checkRunOwner, checkRunRepo),
52-
checkRunOwner,
53-
checkRunRepo,
54-
commitSHA,
55-
workflowName
56-
)
57-
const latest = runs
58-
.filter(run => run.output.summary.endsWith(`(tag-git run #${workflowRunId})`))
59-
.sort((a, b) => a.id - b.id)
60-
.pop()
61-
if (latest && (latest.status !== 'completed' || latest.conclusion === 'success')) {
62-
// It either succeeded or is still running
63-
res = `${res}${workflowName} run already exists at ${latest.html_url}.\n`
64-
} else {
65-
architecturesToTrigger.push(architecture)
66-
}
85+
const triggerWorkflowDispatch = require('./trigger-workflow-dispatch')
86+
for (const architecture of architecturesToTrigger) {
87+
const run = await triggerWorkflowDispatch(
88+
context,
89+
await getToken(context, owner, repo),
90+
owner,
91+
repo,
92+
'git-artifacts.yml',
93+
'main', {
94+
architecture,
95+
tag_git_workflow_run_id: workflowRunId
6796
}
97+
)
98+
res = `${res}The \`git-artifacts-${architecture}\` workflow run [was started](${run.html_url}).\n`
99+
}
68100

69-
if (architecturesToTrigger.length === 0) return `${res}No workflows need to be run!\n`
101+
return res
102+
}
70103

71-
const gitVersionMatch = checkRun.output.summary.match(/^Tag Git (\S+) @([0-9a-f]+)$/)
72-
if (!gitVersionMatch) {
73-
throw new Error(`Could not parse Git version from summary '${checkRun.output.summary}' of tag-git run ${checkRun.id}: ${checkRun.url}`)
74-
}
75-
if (commitSHA !== gitVersionMatch[2]) {
76-
throw new Error(`Expected ${commitSHA} in summary '${checkRun.output.summary}' of tag-git run ${checkRun.id}: ${checkRun.url}`)
77-
}
78-
const gitVersion = gitVersionMatch[1]
79-
80-
for (const architecture of architecturesToTrigger) {
81-
const workflowName = `git-artifacts-${architecture}`
82-
const title = `Build Git ${gitVersion} artifacts`
83-
const summary = `Build Git ${gitVersion} artifacts from commit ${commitSHA} (tag-git run #${workflowRunId})`
84-
await queueCheckRun(
85-
context,
86-
await getToken(checkRunOwner, checkRunRepo),
87-
checkRunOwner,
88-
checkRunRepo,
89-
commitSHA,
90-
workflowName,
91-
title,
92-
summary
93-
)
94-
}
104+
const cascadingRuns = async (context, req) => {
105+
const action = req.body.action
106+
const checkRunOwner = req.body.repository.owner.login
107+
const checkRunRepo = req.body.repository.name
108+
const checkRun = req.body.check_run
109+
const name = checkRun.name
95110

96-
const triggerWorkflowDispatch = require('./trigger-workflow-dispatch')
97-
for (const architecture of architecturesToTrigger) {
98-
const run = await triggerWorkflowDispatch(
99-
context,
100-
await getToken(owner, repo),
101-
owner,
102-
repo,
103-
'git-artifacts.yml',
104-
'main', {
105-
architecture,
106-
tag_git_workflow_run_id: workflowRunId
107-
}
108-
)
109-
res = `${res}The \`git-artifacts-${architecture}\` workflow run [was started](${run.html_url}).\n`
111+
if (action === 'completed') {
112+
if (name === 'tag-git') {
113+
if (checkRunOwner !== 'git-for-windows' || checkRunRepo !== 'git') {
114+
throw new Error(`Refusing to handle cascading run in ${checkRunOwner}/${checkRunRepo}`)
110115
}
111116

112-
return res
117+
return await triggerGitArtifactsRuns(context, checkRunOwner, checkRunRepo, checkRun)
113118
}
114119
return `Not a cascading run: ${name}; Doing nothing.`
115120
}
116121
return `Unhandled action: ${action}`
122+
}
123+
124+
module.exports = {
125+
triggerGitArtifactsRuns,
126+
cascadingRuns
117127
}

GitForWindowsHelper/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ module.exports = async function (context, req) {
4545
}
4646

4747
try {
48-
const cascadingRuns = require('./cascading-runs.js')
48+
const { cascadingRuns } = require('./cascading-runs.js')
4949
if (req.headers['x-github-event'] === 'check_run'
5050
&& req.body.repository.full_name === 'git-for-windows/git'
5151
&& req.body.action === 'completed') return ok(await cascadingRuns(context, req))

GitForWindowsHelper/slash-commands.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,36 @@ module.exports = async (context, req) => {
270270
const { getPRCommitSHA } = require('./issues')
271271
const rev = await getPRCommitSHA(context, await getToken(), owner, repo, issueNumber)
272272

273+
const { listCheckRunsForCommit } = require('./check-runs')
274+
const runs = await listCheckRunsForCommit(
275+
context,
276+
await getToken(owner, repo),
277+
owner,
278+
repo,
279+
rev,
280+
'tag-git'
281+
)
282+
const latest = runs
283+
.sort((a, b) => a.id - b.id)
284+
.pop()
285+
if (latest && latest.status === 'completed' && latest.conclusion === 'success') {
286+
// There is already a `tag-git` workflow run; Trigger the `git-artifacts` runs directly
287+
if (!latest.head_sha) latest.head_sha = rev
288+
const { triggerGitArtifactsRuns } = require('./cascading-runs')
289+
const res = await triggerGitArtifactsRuns(context, owner, repo, latest)
290+
291+
const { appendToIssueComment } = require('./issues')
292+
const answer2 = await appendToIssueComment(
293+
context,
294+
await getToken(),
295+
owner,
296+
repo,
297+
commentId,
298+
res
299+
)
300+
return `I edited the comment: ${answer2.html_url}`
301+
}
302+
273303
const triggerWorkflowDispatch = require('./trigger-workflow-dispatch')
274304
const answer = await triggerWorkflowDispatch(
275305
context,

__tests__/index.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ let mockGitHubApiRequest = jest.fn((_context, _token, method, requestPath, paylo
109109
if (method === 'GET' && requestPath.endsWith('/pulls/4328')) return {
110110
head: { sha: 'this-will-be-rc2' }
111111
}
112+
if (method === 'GET' && requestPath.endsWith('/pulls/4323')) return {
113+
head: { sha: 'dee501d15' }
114+
}
112115
throw new Error(`Unhandled ${method}-${requestPath}-${JSON.stringify(payload)}`)
113116
})
114117
jest.mock('../GitForWindowsHelper/github-api-request', () => {
@@ -274,6 +277,19 @@ let mockListCheckRunsForCommit = jest.fn((_context, _token, _owner, _repo, rev,
274277
if (checkRunName === 'git-artifacts-x86_64') return [{ id: 8664, status: 'completed', conclusion: 'success', output }]
275278
if (checkRunName === 'git-artifacts-i686') return [{ id: 686, status: 'completed', conclusion: 'success', output }]
276279
}
280+
if (rev === 'dee501d15') {
281+
if (checkRunName === 'tag-git') return [{
282+
status: 'completed',
283+
conclusion: 'success',
284+
html_url: '<url-to-tag-git',
285+
output: {
286+
title: 'Tag Git -rc1½',
287+
summary: `Tag Git -rc1½ @${rev}`,
288+
text: 'For details, see [this run](https://github.com/git-for-windows/git-for-windows-automation/actions/runs/341).'
289+
}
290+
}]
291+
return []
292+
}
277293
if (checkRunName === 'git-artifacts-x86_64') return [{
278294
status: 'completed',
279295
conclusion: 'success',
@@ -432,6 +448,42 @@ The \`tag-git\` workflow run [was started](dispatched-workflow-tag-git.yml)`,
432448
rev: 'c8edb521bdabec14b07e9142e48cab77a40ba339',
433449
snapshot: false
434450
})
451+
452+
jest.clearAllMocks()
453+
dispatchedWorkflows.splice(0, dispatchedWorkflows.length) // empty the array
454+
455+
// with existing `tag-git` run
456+
context.req.body.issue = {
457+
number: 4323,
458+
title: 'Rebase to v2.40.0-rc1½',
459+
pull_request: {
460+
html_url: 'https://github.com/git-for-windows/git/pull/4323'
461+
}
462+
}
463+
464+
expect(await index(context, context.req)).toBeUndefined()
465+
expect(context.res).toEqual({
466+
body: `I edited the comment: appended-comment-body-existing comment body
467+
468+
The \`git-artifacts-x86_64\` workflow run [was started](dispatched-workflow-git-artifacts.yml).
469+
The \`git-artifacts-i686\` workflow run [was started](dispatched-workflow-git-artifacts.yml).
470+
`,
471+
headers: undefined,
472+
status: undefined
473+
})
474+
expect(mockGetInstallationAccessToken).toHaveBeenCalled()
475+
expect(mockGitHubApiRequestAsApp).not.toHaveBeenCalled()
476+
expect(dispatchedWorkflows).toHaveLength(2)
477+
expect(dispatchedWorkflows[0].html_url).toEqual('dispatched-workflow-git-artifacts.yml')
478+
expect(dispatchedWorkflows[0].payload.inputs).toEqual({
479+
architecture: 'i686',
480+
tag_git_workflow_run_id: 341
481+
})
482+
expect(dispatchedWorkflows[1].html_url).toEqual('dispatched-workflow-git-artifacts.yml')
483+
expect(dispatchedWorkflows[1].payload.inputs).toEqual({
484+
architecture: 'x86_64',
485+
tag_git_workflow_run_id: 341
486+
})
435487
})
436488

437489
testIssueComment('/release', {

0 commit comments

Comments
 (0)