@@ -97,31 +97,48 @@ for cmd in jq gh; do
9797done
9898
9999# ── Mode: branch ──
100+ #
101+ # Walk the branch's commit history from HEAD and return the first commit
102+ # whose workflow has a successful run matching any required artifact
103+ # patterns. The one Actions-API filter used is ?head_sha=<SHA>, which is
104+ # not on the list of filters flagged as returning stale/cached slices in
105+ # https://github.com/orgs/community/discussions/24626.
100106if [[ " ${MODE} " == " branch" ]]; then
101107 echo " Looking up latest successful '${WORKFLOW_NAME} ' run on branch: ${REF} " >&2
102108
103- RUN_DATA=$( gh run list \
104- --repo " ${REPOSITORY} " \
105- --branch " ${REF} " \
106- --workflow " ${WORKFLOW_NAME} " \
107- --json databaseId,workflowName,status,conclusion,headSha,headBranch,createdAt,url \
108- --limit 100)
109-
110- CANDIDATE_RUNS=$( echo " ${RUN_DATA} " | jq -r \
111- --arg branch " ${REF} " '
112- map(select(
113- .headBranch == $branch
114- and .conclusion == "success"
115- ))
116- | sort_by(.createdAt, .databaseId)
117- | reverse
118- | .[]
119- | [.databaseId, .headSha, .createdAt]
120- | @tsv
121- ' )
122-
123- if [[ -z " ${CANDIDATE_RUNS} " ]]; then
124- echo " Error: No successful '${WORKFLOW_NAME} ' run found on branch '${REF} '" >&2
109+ # Resolve workflow selector to a numeric workflow ID.
110+ WORKFLOW_ID=" "
111+ if [[ " ${WORKFLOW_NAME} " =~ ^[0-9]+$ || " ${WORKFLOW_NAME} " =~ \. (yml| yaml)$ ]]; then
112+ if ! WORKFLOW_ID=$( gh api " repos/${REPOSITORY} /actions/workflows/${WORKFLOW_NAME} " \
113+ --jq ' .id' 2> /dev/null) ; then
114+ WORKFLOW_ID=" "
115+ fi
116+ fi
117+ if [[ -z " ${WORKFLOW_ID} " ]]; then
118+ WORKFLOW_ID=$( gh api --paginate \
119+ " repos/${REPOSITORY} /actions/workflows?per_page=100" \
120+ | jq -sr --arg name " ${WORKFLOW_NAME} " '
121+ [.[] | .workflows[]]
122+ | map(select(
123+ .name == $name
124+ or .path == $name
125+ or ((.path // "") | split("/") | last) == $name
126+ ))
127+ | .[0].id // empty
128+ ' )
129+ fi
130+ if [[ -z " ${WORKFLOW_ID} " ]]; then
131+ echo " Error: Could not resolve workflow '${WORKFLOW_NAME} ' in '${REPOSITORY} '" >&2
132+ exit 1
133+ fi
134+
135+ # Resolve branch HEAD SHA.
136+ if ! BRANCH_HEAD_SHA=$( gh api " repos/${REPOSITORY} /branches/${REF} " \
137+ --jq ' .commit.sha' 2> /dev/null) ; then
138+ BRANCH_HEAD_SHA=" "
139+ fi
140+ if [[ -z " ${BRANCH_HEAD_SHA} " ]]; then
141+ echo " Error: Could not resolve branch '${REF} ' in '${REPOSITORY} '" >&2
125142 exit 1
126143 fi
127144
@@ -130,59 +147,96 @@ if [[ "${MODE}" == "branch" ]]; then
130147 printf ' - %s\n' " ${ARTIFACT_PATTERNS[@]} " >&2
131148 fi
132149
150+ # Walk branch commits newest-first; accept the first commit with a
151+ # successful run that passes the artifact check.
152+ COMMIT_SCAN_LIMIT=" ${LOOKUP_RUN_ID_COMMIT_SCAN_LIMIT:- 200} "
133153 RUN_ID=" "
134154 HEAD_SHA=" "
135- while IFS= read -r candidate; do
136- IFS=$' \t ' read -r candidate_id candidate_sha candidate_created_at <<< " ${candidate}"
137-
138- missing_patterns=()
139- if [[ ${# ARTIFACT_PATTERNS[@]} -gt 0 ]]; then
140- if ! ARTIFACT_NAMES=$( gh api --paginate \
141- " repos/${REPOSITORY} /actions/runs/${candidate_id} /artifacts?per_page=100" \
142- --jq ' .artifacts[] | select(.expired == false) | .name' ) ; then
143- echo " Error: Failed to list artifacts for run ${candidate_id} " >&2
155+ commits_scanned=0
156+ page=1
157+
158+ while [[ ${commits_scanned} -lt ${COMMIT_SCAN_LIMIT} ]]; do
159+ if ! COMMIT_PAGE=$( gh api --method GET \
160+ " repos/${REPOSITORY} /commits" \
161+ -f " sha=${BRANCH_HEAD_SHA} " \
162+ -F " per_page=100" \
163+ -F " page=${page} " \
164+ --jq ' .[].sha' 2> /dev/null) ; then
165+ echo " Error: Failed to list commits for branch '${REF} ' (page ${page} )" >&2
166+ exit 1
167+ fi
168+ [[ -n " ${COMMIT_PAGE} " ]] || break
169+
170+ while IFS= read -r commit_sha; do
171+ [[ -n " ${commit_sha} " ]] || continue
172+ [[ ${commits_scanned} -lt ${COMMIT_SCAN_LIMIT} ]] || break
173+ commits_scanned=$(( commits_scanned + 1 ))
174+
175+ if ! SHA_RUNS=$( gh api --method GET \
176+ " repos/${REPOSITORY} /actions/workflows/${WORKFLOW_ID} /runs" \
177+ -f " head_sha=${commit_sha} " \
178+ -F " per_page=100" \
179+ --jq ' [.workflow_runs[] | select(.conclusion == "success") | {id, head_sha, created_at}] | sort_by(.created_at) | reverse' ) ; then
180+ echo " Error: Failed to list runs for commit ${commit_sha} " >&2
144181 exit 1
145182 fi
146-
147- for pattern in " ${ARTIFACT_PATTERNS[@]} " ; do
148- pattern_matched=0
149- while IFS= read -r artifact_name; do
150- # The caller supplies a shell glob, so the RHS must remain unquoted.
151- # shellcheck disable=SC2053
152- if [[ -n " ${artifact_name} " && " ${artifact_name} " == ${pattern} ]]; then
153- pattern_matched=1
154- break
183+ [[ " ${SHA_RUNS} " != " []" ]] || continue
184+
185+ count=$( echo " ${SHA_RUNS} " | jq ' length' )
186+ for (( i= 0 ; i< count; i++ )) ; do
187+ candidate_id=$( echo " ${SHA_RUNS} " | jq -r " .[${i} ].id" )
188+ candidate_sha=$( echo " ${SHA_RUNS} " | jq -r " .[${i} ].head_sha" )
189+ candidate_created_at=$( echo " ${SHA_RUNS} " | jq -r " .[${i} ].created_at" )
190+
191+ missing_patterns=()
192+ if [[ ${# ARTIFACT_PATTERNS[@]} -gt 0 ]]; then
193+ if ! ARTIFACT_NAMES=$( gh api --paginate \
194+ " repos/${REPOSITORY} /actions/runs/${candidate_id} /artifacts?per_page=100" \
195+ --jq ' .artifacts[] | select(.expired == false) | .name' ) ; then
196+ echo " Error: Failed to list artifacts for run ${candidate_id} " >&2
197+ exit 1
155198 fi
156- done <<< " ${ARTIFACT_NAMES}"
157- if [[ ${pattern_matched} == 0 ]]; then
158- missing_patterns+=(" ${pattern} " )
199+
200+ for pattern in " ${ARTIFACT_PATTERNS[@]} " ; do
201+ pattern_matched=0
202+ while IFS= read -r artifact_name; do
203+ # The caller supplies a shell glob; RHS must stay unquoted.
204+ # shellcheck disable=SC2053
205+ if [[ -n " ${artifact_name} " && " ${artifact_name} " == ${pattern} ]]; then
206+ pattern_matched=1
207+ break
208+ fi
209+ done <<< " ${ARTIFACT_NAMES}"
210+ if [[ ${pattern_matched} == 0 ]]; then
211+ missing_patterns+=(" ${pattern} " )
212+ fi
213+ done
159214 fi
160- done
161- fi
162215
163- if [[ ${# missing_patterns[@]} -gt 0 ]]; then
164- echo " Skipping run ${candidate_id} (${candidate_created_at} ); missing unexpired artifact(s): ${missing_patterns[*]} " >&2
165- continue
166- fi
216+ if [[ ${# missing_patterns[@]} -gt 0 ]]; then
217+ echo " Skipping run ${candidate_id} (sha ${candidate_sha: 0: 12} , ${candidate_created_at} ); missing unexpired artifact(s): ${missing_patterns[*]} " >&2
218+ continue
219+ fi
220+
221+ RUN_ID=" ${candidate_id} "
222+ HEAD_SHA=" ${candidate_sha} "
223+ break 3
224+ done
225+ done <<< " ${COMMIT_PAGE}"
167226
168- RUN_ID=" ${candidate_id} "
169- HEAD_SHA=" ${candidate_sha} "
170- break
171- done <<< " ${CANDIDATE_RUNS}"
227+ page=$(( page + 1 ))
228+ done
172229
173230 if [[ -z " ${RUN_ID} " ]]; then
174- echo " Error: No successful '${WORKFLOW_NAME} ' run on branch '${REF} ' has all required artifacts" >&2
231+ echo " Error: No successful '${WORKFLOW_NAME} ' run with required artifacts found in the ${commits_scanned} most-recent commits of branch '${REF} '" >&2
232+ echo " Branch HEAD: ${BRANCH_HEAD_SHA} " >&2
175233 exit 1
176234 fi
177235
178- echo " Found run ID: ${RUN_ID} " >&2
236+ echo " Found run ID: ${RUN_ID} (sha ${HEAD_SHA : 0 : 12} ) " >&2
179237 echo " ${RUN_ID} "
180238
181239 if [[ " ${HEAD_SHA_FLAG} " == 1 ]]; then
182- if [[ -z " ${HEAD_SHA} " ]]; then
183- echo " Error: Run ${RUN_ID} has no head SHA" >&2
184- exit 1
185- fi
186240 echo " Head SHA: ${HEAD_SHA} " >&2
187241 echo " ${HEAD_SHA} "
188242 fi
0 commit comments