Skip to content

Commit f58f698

Browse files
committed
Merge branch 'develop' of https://github.com/stdlib-js/stdlib into develop
2 parents 727917a + 5b71a7c commit f58f698

File tree

2 files changed

+153
-33
lines changed

2 files changed

+153
-33
lines changed

.github/workflows/check_duplicate_prs.yml

+14-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ on:
2727

2828
# Allow the workflow to be manually run:
2929
workflow_dispatch:
30+
inputs:
31+
debug:
32+
description: 'Enable debug output'
33+
required: false
34+
default: 'false'
35+
type: choice
36+
options:
37+
- 'true'
38+
- 'false'
3039

3140
# Global permissions:
3241
permissions:
@@ -55,20 +64,17 @@ jobs:
5564
# Pin action to full length commit SHA
5665
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
5766
with:
58-
# Specify whether to remove untracked files before checking out the repository:
59-
clean: false
60-
61-
# Limit clone depth to the most recent commit:
62-
fetch-depth: 1
63-
64-
# Specify whether to download Git-LFS files:
65-
lfs: false
67+
# Ensure we have access to the scripts directory:
68+
sparse-checkout: |
69+
.github/workflows/scripts
70+
sparse-checkout-cone-mode: false
6671
timeout-minutes: 10
6772

6873
# Check for duplicate PRs:
6974
- name: 'Check for duplicate PRs'
7075
env:
7176
GITHUB_TOKEN: ${{ secrets.STDLIB_BOT_PAT_REPO_WRITE }}
77+
DEBUG: ${{ inputs.debug || 'false' }}
7278
run: |
7379
. "$GITHUB_WORKSPACE/.github/workflows/scripts/check_duplicate_prs"
7480
timeout-minutes: 15

.github/workflows/scripts/check_duplicate_prs

+139-25
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,26 @@ repo_name="stdlib"
4242
# Label to add/remove for duplicate PRs:
4343
duplicate_label="Potential Duplicate"
4444

45+
# Debug mode controlled by environment variable (defaults to false if not set)
46+
debug="${DEBUG:-false}"
47+
48+
# Configure retries for API calls
49+
max_retries=3
50+
retry_delay=2
51+
4552

4653
# FUNCTIONS #
4754

55+
# Debug logging function
56+
#
57+
# $1 - debug message
58+
debug_log() {
59+
# Only print debug messages if DEBUG environment variable is set to "true"
60+
if [ "$debug" = true ]; then
61+
echo "[DEBUG] $1" >&2
62+
fi
63+
}
64+
4865
# Error handler.
4966
#
5067
# $1 - error status
@@ -67,6 +84,10 @@ github_api() {
6784
local method="$1"
6885
local endpoint="$2"
6986
local data="$3"
87+
local retry_count=0
88+
local response=""
89+
local status_code
90+
local success=false
7091

7192
# Initialize an array to hold curl headers:
7293
local headers=()
@@ -76,26 +97,80 @@ github_api() {
7697
headers+=("-H" "Authorization: token ${GITHUB_TOKEN}")
7798
fi
7899

100+
debug_log "Making API request: ${method} ${endpoint}"
101+
79102
# For POST/PATCH requests, always set the Content-Type header:
80103
if [ "$method" != "GET" ]; then
81104
headers+=("-H" "Content-Type: application/json")
82105
fi
83106

84-
# Make the API request:
85-
if [ -n "${data}" ]; then
86-
curl -s -X "${method}" "${headers[@]}" -d "${data}" "${github_api_url}${endpoint}"
87-
else
88-
curl -s -X "${method}" "${headers[@]}" "${github_api_url}${endpoint}"
107+
# Add retry logic
108+
while [ $retry_count -lt $max_retries ] && [ "$success" = false ]; do
109+
if [ $retry_count -gt 0 ]; then
110+
echo "Retrying request (attempt $((retry_count+1))/${max_retries})..."
111+
sleep $retry_delay
112+
fi
113+
114+
# Make the API request:
115+
if [ -n "${data}" ]; then
116+
response=$(curl -s -w "%{http_code}" -X "${method}" "${headers[@]}" -d "${data}" "${github_api_url}${endpoint}")
117+
else
118+
response=$(curl -s -w "%{http_code}" -X "${method}" "${headers[@]}" "${github_api_url}${endpoint}")
119+
fi
120+
121+
# Extract status code (last 3 digits) and actual response (everything before)
122+
status_code="${response: -3}"
123+
response="${response:0:${#response}-3}"
124+
125+
debug_log "Status code: $status_code"
126+
127+
# Check if we got a successful response
128+
if [[ $status_code -ge 200 && $status_code -lt 300 ]]; then
129+
success=true
130+
else
131+
echo "API request failed with status $status_code: $response" >&2
132+
retry_count=$((retry_count+1))
133+
fi
134+
done
135+
136+
if [ "$success" = false ]; then
137+
echo "Failed to complete API request after $max_retries attempts" >&2
138+
return 1
139+
fi
140+
141+
# Validate that response is valid JSON if expected
142+
if ! echo "$response" | jq -e '.' > /dev/null 2>&1; then
143+
echo "Warning: Response is not valid JSON: ${response}" >&2
144+
# Return empty JSON object as fallback
145+
echo "{}"
146+
return 0
89147
fi
148+
149+
# Return the actual response data (without status code)
150+
echo "$response"
151+
return 0
90152
}
91153

92154
# Extracts issue numbers resolved/closed in PRs for stdlib-js/stdlib.
93155
#
94156
# $1 - PR body text
95157
extract_resolved_issues() {
96158
local body="$1"
97-
echo "$body" | grep -Eio "(resolves|closes|close|fix|fixes|fixed|resolve)[[:space:]]*(#[0-9]+|https?://github\.com/stdlib-js/stdlib/issues/[0-9]+)" |
98-
grep -Eo "([0-9]+)$" | sort -u
159+
160+
debug_log "Extracting resolved issues from PR body of length ${#body} chars"
161+
162+
# Handle empty body case
163+
if [ -z "$body" ]; then
164+
debug_log "PR body is empty, no issues to extract"
165+
return 0
166+
fi
167+
168+
local issues
169+
issues=$(echo "$body" | grep -Eio "(resolves|closes|close|fix|fixes|fixed|resolve)[[:space:]]*(#[0-9]+|https?://github\.com/${repo_owner}/${repo_name}/issues/[0-9]+)" |
170+
grep -Eo "([0-9]+)$" | sort -u)
171+
172+
debug_log "Extracted issues: $issues"
173+
echo "$issues"
99174
}
100175

101176
# Removes a label from a PR.
@@ -106,7 +181,11 @@ remove_label() {
106181
local pr_number="$1"
107182
local label="$2"
108183

109-
github_api "DELETE" "/repos/${repo_owner}/${repo_name}/issues/${pr_number}/labels/${label}" || true
184+
# URL encode the label name by replacing spaces with %20:
185+
local encoded_label="${label// /%20}"
186+
187+
debug_log "Removing label '${label}' from PR #${pr_number}"
188+
github_api "DELETE" "/repos/${repo_owner}/${repo_name}/issues/${pr_number}/labels/${encoded_label}"
110189
}
111190

112191
# Main execution sequence.
@@ -119,13 +198,19 @@ main() {
119198

120199
while true; do
121200
# Fetch current page of PRs:
122-
page_data=$(github_api "GET" "/repos/${repo_owner}/${repo_name}/pulls?state=open&per_page=100&page=${page}")
201+
debug_log "Fetching page $page of PRs"
202+
if ! page_data=$(github_api "GET" "/repos/${repo_owner}/${repo_name}/pulls?state=open&per_page=100&page=${page}"); then
203+
echo "Error fetching PRs on page $page, aborting" >&2
204+
exit 1
205+
fi
123206

124207
# Check if we got any results:
125208
page_count=$(echo "$page_data" | jq length)
209+
debug_log "Got $page_count PRs on page $page"
126210

127211
if [ "$page_count" -eq 0 ]; then
128212
# No more results, break the loop
213+
debug_log "No more PRs, breaking pagination loop"
129214
break
130215
fi
131216

@@ -137,14 +222,14 @@ main() {
137222
done
138223

139224
# Check if we found any PRs:
140-
pr_count=$(echo "$open_prs" | jq length)
141-
if [ "$pr_count" -eq 0 ]; then
225+
total_prs=$(echo "$open_prs" | jq length)
226+
if [ "$total_prs" -eq 0 ]; then
142227
echo "No open pull requests found."
143228
print_success
144229
exit 0
145230
fi
146231

147-
echo "Found ${pr_count} open pull requests."
232+
echo "Found ${total_prs} open pull requests."
148233

149234
# Create arrays to store mappings and track labeled PRs:
150235
declare -a issue_prs_keys
@@ -158,65 +243,93 @@ main() {
158243

159244
if ! echo "$labeled_prs_data" | jq -e 'if type=="array" then true else false end' > /dev/null 2>&1; then
160245
echo "Warning: Invalid response when fetching labeled PRs: ${labeled_prs_data}" >&2
246+
debug_log "Full labeled PRs response: $labeled_prs_data"
161247
elif [ -n "$labeled_prs_data" ]; then
162248
while IFS= read -r labeled_pr; do
163249
pr_number=$(echo "$labeled_pr" | jq -r '.number')
164250
labeled_prs_list+=("$pr_number")
251+
debug_log "Found PR #$pr_number with duplicate label"
165252
done < <(echo "$labeled_prs_data" | jq -c '.[]')
166253
fi
167254
echo "Found ${#labeled_prs_list[@]} PRs with duplicate label"
168255

169256
# Process each PR to build issue mappings:
170257
echo "Processing PRs for issue references..."
171-
pr_count=0
172-
while IFS= read -r pr; do
258+
debug_log "Starting to process $total_prs PRs for issue references"
259+
260+
# Process PRs one by one...
261+
processed_count=0
262+
263+
for ((i=0; i<total_prs; i++)); do
264+
pr=$(echo "$open_prs" | jq -c ".[$i]")
265+
266+
if [ -z "$pr" ] || [ "$pr" = "null" ]; then
267+
debug_log "Warning: Empty PR data at index $i"
268+
continue
269+
fi
270+
173271
pr_number=$(echo "$pr" | jq -r '.number')
174-
pr_body=$(echo "$pr" | jq -r '.body')
272+
if [ -z "$pr_number" ] || [ "$pr_number" = "null" ]; then
273+
debug_log "Warning: Could not extract PR number"
274+
continue
275+
fi
276+
277+
debug_log "Processing PR #$pr_number"
278+
pr_body=$(echo "$pr" | jq -r '.body // ""')
175279
resolved_issues=$(extract_resolved_issues "$pr_body")
176280

177-
pr_count=$((pr_count + 1))
178-
if [ $((pr_count % 50)) -eq 0 ]; then
179-
echo "Processed ${pr_count} PRs..."
281+
processed_count=$((processed_count + 1))
282+
if [ $((processed_count % 50)) -eq 0 ]; then
283+
echo "Processed ${processed_count} PRs..."
180284
fi
181285

182286
for issue in $resolved_issues; do
287+
debug_log "PR #$pr_number references issue #$issue"
183288
# Find existing issue index
184289
index=-1
185-
for i in "${!issue_prs_keys[@]}"; do
186-
if [ "${issue_prs_keys[$i]}" = "$issue" ]; then
187-
index=$i
290+
for j in "${!issue_prs_keys[@]}"; do
291+
if [ "${issue_prs_keys[$j]}" = "$issue" ]; then
292+
index=$j
188293
break
189294
fi
190295
done
191296
if [ "$index" -eq -1 ]; then
297+
debug_log "Creating new entry for issue #$issue with PR #$pr_number"
192298
issue_prs_keys+=("$issue")
193299
issue_prs_values+=("$pr_number")
194300
else
301+
debug_log "Adding PR #$pr_number to existing issue #$issue"
195302
issue_prs_values[index]="${issue_prs_values[index]} $pr_number"
196303
fi
197304
done
198-
done < <(echo "${open_prs}" | jq -c '.[]')
305+
done
306+
307+
debug_log "Finished processing all PRs for issue references"
308+
debug_log "Found ${#issue_prs_keys[@]} unique issues referenced in PRs"
199309

200310
# Process the mappings to find duplicates:
201311
declare -a should_be_labeled_list
202312

203313
for i in "${!issue_prs_keys[@]}"; do
204314
read -r -a prs <<< "${issue_prs_values[$i]}"
205315
if [ ${#prs[@]} -gt 1 ]; then
316+
debug_log "Issue #${issue_prs_keys[$i]} has ${#prs[@]} PRs: ${prs[*]}"
206317
for pr in "${prs[@]}"; do
207318
should_be_labeled_list+=("$pr")
208319
done
209320
fi
210321
done
211322

212-
echo "PRs that should have label: ${should_be_labeled_list[*]}"
213-
echo "PRs that currently have label: ${labeled_prs_list[*]}"
323+
debug_log "PRs that should have label: ${should_be_labeled_list[*]:-none}"
324+
debug_log "PRs that currently have label: ${labeled_prs_list[*]:-none}"
214325

215326
for pr in "${labeled_prs_list[@]}"; do
216327
echo "Checking if PR #${pr} should still have label..."
217328
if ! printf '%s\n' "${should_be_labeled_list[@]}" | grep -q "^${pr}$"; then
218329
echo "Removing duplicate label from PR #${pr}..."
219330
remove_label "$pr" "$duplicate_label"
331+
else
332+
debug_log "PR #${pr} should keep its label"
220333
fi
221334
done
222335

@@ -227,10 +340,11 @@ main() {
227340
github_api "POST" "/repos/${repo_owner}/${repo_name}/issues/${pr}/labels" \
228341
"{\"labels\":[\"${duplicate_label}\"]}"
229342
else
230-
echo "PR #${pr} already has label, skipping..."
343+
debug_log "PR #${pr} already has label, skipping..."
231344
fi
232345
done
233346

347+
debug_log "Script completed successfully"
234348
print_success
235349
exit 0
236350
}

0 commit comments

Comments
 (0)