Skip to content

Commit 7e2fd01

Browse files
authored
Merge pull request microsoft#1973 from microsoft/skip-flatten
build: collapse chains of skipped workflow runs
2 parents e4016c8 + 6198b4e commit 7e2fd01

1 file changed

Lines changed: 88 additions & 2 deletions

File tree

.github/workflows/build.yaml

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ on:
2121
permissions:
2222
contents: read
2323
actions: read
24+
checks: read
2425

2526
env:
2627
GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.53.0.vfs.0.7' }}
@@ -67,6 +68,89 @@ jobs:
6768
const head_sha = run.head_sha;
6869
const tree_id = run.head_commit.tree_id;
6970
71+
/*
72+
* If the given workflow run was itself a "skip" run that references another
73+
* run, follow the chain to find the deepest *actual* successful, non-skipped
74+
* run, so we don't end up pointing at a chain of skip-runs.
75+
*/
76+
const SKIP_NOTICE_RE = /^Skipping: There already is a successful run: (.+)$/
77+
const RUN_URL_RE = /\/actions\/runs\/(\d+)/
78+
const MAX_CHAIN_LENGTH = 10
79+
async function resolveSkipChain(initialRunId, initialRunUrl) {
80+
let currentRunId = initialRunId
81+
let currentRunUrl = initialRunUrl
82+
console.log(`Resolving skip chain starting at ${currentRunUrl}`)
83+
for (let i = 0; i < MAX_CHAIN_LENGTH; i++) {
84+
let referencedUrl = null
85+
try {
86+
const { data: jobsData } = await github.rest.actions.listJobsForWorkflowRun({
87+
owner: context.repo.owner,
88+
repo: context.repo.repo,
89+
run_id: currentRunId,
90+
})
91+
const validationJob = jobsData.jobs.find((j) => j.name === 'Validation')
92+
if (!validationJob) {
93+
console.log(`No 'Validation' job found on ${currentRunUrl}; treating as the real run`)
94+
return currentRunUrl
95+
}
96+
97+
const { data: annotations } = await github.rest.checks.listAnnotations({
98+
owner: context.repo.owner,
99+
repo: context.repo.repo,
100+
check_run_id: validationJob.id,
101+
})
102+
for (const annotation of annotations) {
103+
const match = annotation.message && annotation.message.match(SKIP_NOTICE_RE)
104+
if (match) {
105+
referencedUrl = match[1]
106+
break
107+
}
108+
}
109+
} catch (e) {
110+
// If we cannot inspect this run, fall back to the URL we already have
111+
console.log(`Failed to inspect ${currentRunUrl}: ${e.message}; stopping chain resolution`)
112+
return currentRunUrl
113+
}
114+
115+
if (!referencedUrl) {
116+
console.log(`${currentRunUrl} is not a skip-run; using it as the resolved run`)
117+
return currentRunUrl
118+
}
119+
120+
console.log(`${currentRunUrl} is a skip-run referencing ${referencedUrl}; following the chain`)
121+
122+
const idMatch = referencedUrl.match(RUN_URL_RE)
123+
if (!idMatch) {
124+
console.log(`Could not parse a run ID from ${referencedUrl}; stopping chain resolution`)
125+
return referencedUrl
126+
}
127+
128+
const referencedId = Number(idMatch[1])
129+
let referencedRun
130+
try {
131+
const response = await github.rest.actions.getWorkflowRun({
132+
owner: context.repo.owner,
133+
repo: context.repo.repo,
134+
run_id: referencedId,
135+
})
136+
referencedRun = response.data
137+
} catch (e) {
138+
console.log(`Could not fetch referenced run ${referencedUrl}: ${e.message}; stopping chain resolution`)
139+
return currentRunUrl
140+
}
141+
142+
if (referencedRun.status !== 'completed' || referencedRun.conclusion !== 'success') {
143+
console.log(`Referenced run ${referencedUrl} is no longer usable (status=${referencedRun.status}, conclusion=${referencedRun.conclusion}); stopping chain resolution`)
144+
return currentRunUrl
145+
}
146+
147+
currentRunId = referencedRun.id
148+
currentRunUrl = referencedRun.html_url
149+
}
150+
console.log(`Reached MAX_CHAIN_LENGTH (${MAX_CHAIN_LENGTH}); stopping at ${currentRunUrl}`)
151+
return currentRunUrl
152+
}
153+
70154
// See whether there is a successful run for that commit or tree
71155
const { data: runs } = await github.rest.actions.listWorkflowRuns({
72156
owner: context.repo.owner,
@@ -101,8 +185,10 @@ jobs:
101185
}
102186
103187
if (run.status === 'completed' && run.conclusion === 'success') {
104-
core.notice(`Skipping: There already is a successful run: ${run.html_url}`)
105-
return run.html_url
188+
console.log(`Found previous successful run at ${run.html_url} (head_sha=${run.head_sha}, tree_id=${run.head_commit?.tree_id})`)
189+
const resolvedUrl = await resolveSkipChain(run.id, run.html_url)
190+
core.notice(`Skipping: There already is a successful run: ${resolvedUrl}`)
191+
return resolvedUrl
106192
}
107193
}
108194
return ''

0 commit comments

Comments
 (0)