Last active
February 22, 2025 00:15
-
-
Save nate-double-u/b31ef0642091dbb465672fdaa15bf3e8 to your computer and use it in GitHub Desktop.
Find stale PRs where the author hasn't commented in 2 weeks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Function to display help information. | |
help_message() { | |
cat <<EOF | |
Usage: $0 [-r <repository>] [-d <inactivity days>] [-v] | |
-r Repository in the format owner/repo (default: kubernetes/website) | |
-d Inactivity period in days (default: 14) | |
-v Enable verbose output for troubleshooting. | |
EOF | |
} | |
# Default values | |
REPO="kubernetes/website" | |
INACTIVE_DAYS=14 | |
VERBOSE=0 | |
# If no arguments provided, show help and note defaults, then continue. | |
if [ $# -eq 0 ]; then | |
help_message | |
echo "No arguments provided. Using default values." | |
fi | |
# Parse command-line options. | |
while getopts "r:d:vh" opt; do | |
case $opt in | |
r) REPO="$OPTARG" ;; | |
d) INACTIVE_DAYS="$OPTARG" ;; | |
v) VERBOSE=1 ;; | |
h) help_message; exit 0 ;; | |
*) help_message; exit 1 ;; | |
esac | |
done | |
[ $VERBOSE -eq 1 ] && echo "Repository: $REPO" | |
[ $VERBOSE -eq 1 ] && echo "Inactivity period (days): $INACTIVE_DAYS" | |
# Calculate the inactive threshold date in ISO8601 format and seconds since epoch. | |
INACTIVE_DATE=$(date -v-"${INACTIVE_DAYS}"d +%Y-%m-%dT%H:%M:%SZ) | |
INACTIVE_DATE_SEC=$(date -jf "%Y-%m-%dT%H:%M:%SZ" "$INACTIVE_DATE" +%s) | |
[ $VERBOSE -eq 1 ] && echo "Inactive threshold date: $INACTIVE_DATE" | |
[ $VERBOSE -eq 1 ] && echo "Inactive threshold (seconds): $INACTIVE_DATE_SEC" | |
# Array to store flagged PR details. | |
flagged_prs=() | |
# Process each PR JSON object. | |
process_pr() { | |
local pr="$1" | |
local pr_number pr_url author pr_created pr_created_sec | |
pr_number=$(echo "$pr" | jq '.number') | |
pr_url=$(echo "$pr" | jq -r '.url') | |
author=$(echo "$pr" | jq -r '.author.login') | |
pr_created=$(echo "$pr" | jq -r '.createdAt') | |
pr_created_sec=$(date -jf "%Y-%m-%dT%H:%M:%SZ" "$pr_created" +%s) | |
[ $VERBOSE -eq 1 ] && echo "Processing PR #$pr_number: created at $pr_created ($pr_created_sec)" | |
# Skip PRs that are too recent. | |
if (( pr_created_sec > INACTIVE_DATE_SEC )); then | |
[ $VERBOSE -eq 1 ] && echo "Skipping PR #$pr_number: too recent (created at $pr_created)" | |
return | |
fi | |
# Filter: only process PRs with the "language/en" label using jq. | |
if [ "$(echo "$pr" | jq '[.labels[] | select(.name=="language/en")] | length')" -eq 0 ]; then | |
[ $VERBOSE -eq 1 ] && echo "Skipping PR #$pr_number: not labeled as language/en." | |
return | |
fi | |
# Check if the PR is marked as work in progress. | |
if echo "$pr" | jq -r '.labels[].name' | grep -q "do-not-merge/work-in-progress"; then | |
[ $VERBOSE -eq 1 ] && echo "Skipping PR #$pr_number: marked as work in progress." | |
return | |
fi | |
local should_flag=0 | |
# Condition 1: Check for the "cncf-cla: no" label. | |
if echo "$pr" | jq -r '.labels[].name' | grep -q "cncf-cla: no"; then | |
echo "PR #$pr_number: $pr_url - Author ($author) hasn't signed the CLA and is older than ${INACTIVE_DAYS} days" | |
should_flag=1 | |
fi | |
# Condition 2: Check the author's comment/review activity. | |
local issue_comments review_comments review_reviews all_comments last_comment last_comment_sec | |
issue_comments=$(gh api -X GET repos/"$REPO"/issues/"$pr_number"/comments --paginate | \ | |
jq -r --arg AUTHOR "$author" '.[] | select(.user.login == $AUTHOR) | .created_at') | |
review_comments=$(gh api -X GET repos/"$REPO"/pulls/"$pr_number"/comments --paginate | \ | |
jq -r --arg AUTHOR "$author" '.[] | select(.user.login == $AUTHOR) | .created_at') | |
review_reviews=$(gh api -X GET repos/"$REPO"/pulls/"$pr_number"/reviews --paginate | \ | |
jq -r --arg AUTHOR "$author" '.[] | select(.user.login == $AUTHOR) | .submitted_at') | |
all_comments=$(echo -e "$issue_comments\n$review_comments\n$review_reviews" | grep .) | |
if [ $VERBOSE -eq 1 ]; then | |
echo "Issue comments for PR #$pr_number:" | |
echo "$issue_comments" | |
echo "Review comments for PR #$pr_number:" | |
echo "$review_comments" | |
echo "Review submissions for PR #$pr_number:" | |
echo "$review_reviews" | |
fi | |
if [ -z "$all_comments" ]; then | |
echo "PR #$pr_number: $pr_url - Author ($author) has not commented since creation ($pr_created)" | |
should_flag=1 | |
else | |
last_comment=$(echo "$all_comments" | sort -r | head -n 1) | |
last_comment_sec=$(date -jf "%Y-%m-%dT%H:%M:%SZ" "$last_comment" +%s) | |
[ $VERBOSE -eq 1 ] && echo "Last comment for PR #$pr_number: $last_comment ($last_comment_sec)" | |
if (( last_comment_sec < INACTIVE_DATE_SEC )); then | |
echo "PR #$pr_number: $pr_url - Author ($author) last commented on $last_comment, which is over ${INACTIVE_DAYS} days ago" | |
should_flag=1 | |
fi | |
fi | |
if (( should_flag == 1 )); then | |
flagged_prs+=( "PR #$pr_number: $pr_url" ) | |
open -a /Applications/Firefox.app "$pr_url" | |
fi | |
} | |
# Fetch open PRs with required fields—here we increase the limit. | |
PRs=$(gh pr list -R "$REPO" --json number,url,author,createdAt,labels --state open --limit 1000) | |
[ $VERBOSE -eq 1 ] && echo "Fetched PRs:" && echo "$PRs" | jq . | |
# Process each PR via a here-string. | |
while IFS= read -r pr; do | |
process_pr "$pr" | |
done <<< "$(echo "$PRs" | jq -c '.[]')" | |
# Summarize flagged PRs. | |
if [ ${#flagged_prs[@]} -eq 0 ]; then | |
echo "No PRs found meeting the criteria." | |
else | |
echo -e "\nFlagged PRs to be checked:" | |
for pr in "${flagged_prs[@]}"; do | |
echo "$pr" | |
done | |
fi |
Added check for CLA
Written with the help of ChatGPT.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This script is written to run on a mac with
gh
, Firefox andjq
installed.