|
21 | 21 | permissions: |
22 | 22 | contents: read |
23 | 23 | actions: read |
| 24 | + checks: read |
24 | 25 |
|
25 | 26 | env: |
26 | 27 | GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.53.0.vfs.0.7' }} |
|
67 | 68 | const head_sha = run.head_sha; |
68 | 69 | const tree_id = run.head_commit.tree_id; |
69 | 70 |
|
| 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 | +
|
70 | 154 | // See whether there is a successful run for that commit or tree |
71 | 155 | const { data: runs } = await github.rest.actions.listWorkflowRuns({ |
72 | 156 | owner: context.repo.owner, |
@@ -101,8 +185,10 @@ jobs: |
101 | 185 | } |
102 | 186 |
|
103 | 187 | 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 |
106 | 192 | } |
107 | 193 | } |
108 | 194 | return '' |
|
0 commit comments