Skip to content

Instantly share code, notes, and snippets.

@nate-double-u
Last active February 22, 2025 00:15
Show Gist options
  • Save nate-double-u/b31ef0642091dbb465672fdaa15bf3e8 to your computer and use it in GitHub Desktop.
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
#!/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
@nate-double-u
Copy link
Author

nate-double-u commented Feb 2, 2024

This script is written to run on a mac with gh, Firefox and jq installed.

@nate-double-u
Copy link
Author

Added check for CLA

@nate-double-u
Copy link
Author

Written with the help of ChatGPT.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment