Skip to content

Check PRs

Check PRs #2528

Workflow file for this run

name: Check PRs
on:
workflow_dispatch:
schedule:
- cron: '0 */2 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
validate-prs:
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
pull-requests: write
steps:
- name: Install deps
run: |
pip install 'publicsuffixlist==1.0.2.20250802'
- name: Fetch recent PR numbers
run: |
CUTOFF_DATE="2025-05-25T00:00:00Z"
prs_raw=$(gh pr list --base "new-pr" -L 50 --state open --json number,createdAt,updatedAt,isDraft,labels \
| jq -r --arg cutoff "$CUTOFF_DATE" '[.
[]
| select(
.isDraft == false
and .createdAt >= $cutoff
and .updatedAt >= (now - (2 * 24 * 60 * 60) | todate)
and (all(.labels[].name; . != "Stale") or (.labels == []))
)
| .number
] | .[]')
prs_list=$(printf '%s\n' $prs_raw | head -30 | paste -sd "," -)
echo "PR_LIST=$prs_list" >> "$GITHUB_ENV"
draft_prs_raw=$(gh pr list --base "new-pr" -L 50 --state open --json number,createdAt,updatedAt,isDraft,labels \
| jq -r --arg cutoff "$CUTOFF_DATE" '[.
[]
| select(
.isDraft == true
and .createdAt >= $cutoff
and .updatedAt >= (now - (2 * 24 * 60 * 60) | todate)
and (all(.labels[].name; . != "Stale") or (.labels == []))
)
| .number
] | .[]')
draft_prs_list=$(printf '%s\n' $draft_prs_raw | head -30 | paste -sd "," -)
echo "DRAFT_PR_LIST=$draft_prs_list" >> "$GITHUB_ENV"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
- name: Label draft PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
IFS=',' read -ra PR_NUMBERS <<< "${{ env.DRAFT_PR_LIST }}"
if [ ${#PR_NUMBERS[@]} -eq 0 ] || [ -z "${PR_NUMBERS[0]}" ]; then
echo "No PRs found, exiting."
exit 0
fi
for PR_NUM in "${PR_NUMBERS[@]}"; do
echo "Checking PR https://github.com/flathub/flathub/pull/$PR_NUM"
gh pr edit "$PR_NUM" --add-label "work-in-progress" || true
done
- name: Validate PRs
run: |
IFS=',' read -ra PR_NUMBERS <<< "${{ env.PR_LIST }}"
if [ ${#PR_NUMBERS[@]} -eq 0 ] || [ -z "${PR_NUMBERS[0]}" ]; then
echo "No PRs found, exiting."
exit 0
fi
for PR_NUM in "${PR_NUMBERS[@]}"; do
echo "Checking PR https://github.com/flathub/flathub/pull/$PR_NUM"
json=$(gh pr view "$PR_NUM" --json body,title -q '{body: .body, title: .title}')
PR_BODY=$(echo "$json" | jq -r .body)
PR_TITLE=$(echo "$json" | jq -r .title)
NORMALIZED_PR_BODY=$(printf "%s\n" "$PR_BODY" | tr -d '\r')
unset "$PR_TITLE"
export PR_TITLE="$PR_TITLE"
EXIT_CODE=$(curl -sSL "$SCRIPT_URL" | python3 | grep 'EXIT_CODE=' | cut -d= -f2)
echo "EXIT_CODE is $EXIT_CODE"
unset "$PR_TITLE"
PR_COMMENTS="$(gh pr view "$PR_NUM" --json comments -q '.comments[].body')"
PR_LABELS="$(gh pr view "$PR_NUM" --json labels -q '.labels[].name')"
PR_THREAD_COUNT=$(gh api repos/$GH_REPO/pulls/$PR_NUM/comments \
--jq '[.[] | select(.position != null)] | length')
PR_FILES=$(gh pr view "$PR_NUM" --json files --jq '.files[].path')
COMMIT_COUNT="(gh api repos/$GH_REPO/pulls/$PR_NUM --jq '.commits')"
CONTAINS_MASTER=$(gh api repos/$GH_REPO/pulls/$PR_NUM/commits --jq '.[1] | (.commit.author.email == "[email protected]" and .commit.message == "Add some instructions")')
BLOCKED=0
CHECKLIST_FAILED=0
REVIEW_COMMENT_LINES=()
gh pr view $PR_NUM --json isDraft --jq '.isDraft' | grep -q false && \
gh pr edit $PR_NUM --remove-label "work-in-progress" || true
comment_exists() {
local comment="$1"
echo "$PR_COMMENTS" | grep -Fq "$comment"
}
comment_exists_full() {
local comment="$1"
echo "$PR_COMMENTS" | grep -Fxq "$comment"
}
comment_exists_any() {
for comment in "$@"; do
if echo "$PR_COMMENTS" | grep -Fq "$comment"; then
return 0
fi
done
return 1
}
label_exists_any() {
for label in "$@"; do
if echo "$PR_LABELS" | grep -Fxq "$label"; then
return 0
fi
done
return 1
}
start_build() {
if ! label_exists_any "pr-check-blocked" "blocked"; then
if ! comment_exists_any "$BUILD_START_COMMENT_PARTIAL" "$BUILD_SUCCESS_COMMENT"; then
echo "PR is not marked as blocked and none of the build comments exist. Starting a build"
gh pr comment "$PR_NUM" --body "$BUILD_START_COMMENT" || true
fi
fi
}
if [ "$CONTAINS_MASTER" = "true" ]; then
echo "PR contains commits from master branch"
REVIEW_COMMENT_LINES+=("- PR does not contain commits from the [master branch](https://github.com/flathub/flathub/commits/master/)")
BLOCKED=1
fi
if [ "$EXIT_CODE" -ne 0 ]; then
echo "PR title validation failed"
REVIEW_COMMENT_LINES+=('- PR title is "Add $FLATPAK_ID"')
BLOCKED=1
fi
if echo "$PR_FILES" | grep -qE '.*/flathub\.json$'; then
echo "flathub.json not at toplevel"
BLOCKED=1
REVIEW_COMMENT_LINES+=("- flathub.json file is at toplevel")
fi
if ! echo "$PR_FILES" | grep -qE '^[^/]+\.(ya?ml|json)$'; then
echo "No JSON/YAML files at toplevel"
BLOCKED=1
REVIEW_COMMENT_LINES+=("- Flatpak manifest is at toplevel")
fi
CHECKLIST_PRESENT=$(printf "%s\n" "$PR_BODY" | grep -cE '^- ?\[[xX]\] [A-Za-z]+' || true)
echo "Total checklists found: $CHECKLIST_PRESENT"
UNCHECKED=$(printf "%s\n" "$PR_BODY" | grep -Ec '^- \[ \] [A-Za-z]+' || true)
echo "Unchecked checklists found: $UNCHECKED"
if [ "$CHECKLIST_PRESENT" -eq 0 ]; then
echo "No checklist present in PR body"
CHECKLIST_FAILED=1
BLOCKED=1
elif [ "$UNCHECKED" -gt 0 ]; then
echo "Checklist incomplete in PR body"
CHECKLIST_FAILED=1
BLOCKED=1
fi
REQUIRED_ITEM="I have read and followed all the [Submission requirements]"
REQUIRED_CHECKED=$(printf "%s\n" "$NORMALIZED_PR_BODY" | grep -Fi "[x] $REQUIRED_ITEM" || true)
if [ -z "$REQUIRED_CHECKED" ]; then
echo "Required checklist not found in PR body"
CHECKLIST_FAILED=1
BLOCKED=1
fi
if [ "$CHECKLIST_FAILED" -eq 1 ]; then
REVIEW_COMMENT_LINES+=("- All [checklists](https://github.com/flathub/flathub/blob/master/.github/pull_request_template.md?plain=1) are present in PR body and are completed")
fi
if [ "$BLOCKED" -eq 1 ]; then
REVIEW_COMMENT_LINES+=("- The [requirements](https://docs.flathub.org/docs/for-app-authors/requirements) and [submission process](https://docs.flathub.org/docs/for-app-authors/submission) have been followed")
fi
echo "BLOCKED is set to $BLOCKED"
if [ "$BLOCKED" -eq 0 ]; then
if ! label_exists_any "blocked"; then
DOMAIN=$(curl -sSL "$DOMAIN_SCRIPT_URL" | python3 - "$PR_TITLE")
if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "None" ]; then
echo "Domain is: $DOMAIN"
VERIF_URL="https://$DOMAIN/.well-known/org.flathub.VerifiedApps.txt"
VERIF_COMMENT="If you intend to verify this app, please confirm that you can upload $VERIF_URL. Otherwise, ignore this"
DOMAIN_COMMENT="$DOMAIN_COMMENT_PARTIAL $DOMAIN. $VERIF_COMMENT. Please comment if this incorrect."
if ! comment_exists_full "$DOMAIN_COMMENT"; then
echo "Did not find domain comment, commenting"
gh pr comment "$PR_NUM" --body "$DOMAIN_COMMENT" || true
fi
fi
fi
if label_exists_any "pr-check-blocked"; then
echo "Removing pr-check-blocked label"
gh pr edit "$PR_NUM" --remove-label "pr-check-blocked" || true
fi
if ! label_exists_any "awaiting-changes" "awaiting-upstream" "blocked" "reviewed-waiting"; then
echo "Marking as awaiting-review"
gh pr edit "$PR_NUM" --add-label "awaiting-review" --remove-label "pr-check-blocked" || true
fi
start_build
elif [ "$BLOCKED" -eq 1 ]; then
echo "Marking as blocked"
gh pr edit "$PR_NUM" --add-label "pr-check-blocked" --remove-label "awaiting-review" || true
if ! comment_exists "$REVIEW_COMMENT_PARTIAL"; then
echo "Did not find comment, commenting"
REVIEW_COMMENT="$BASE_REVIEW_COMMENT"
for line in "${REVIEW_COMMENT_LINES[@]}"; do
REVIEW_COMMENT="$REVIEW_COMMENT"$'\n'"$line"
done
gh pr comment "$PR_NUM" --body "$REVIEW_COMMENT" || true
else
echo "Found comment, skipping commenting"
fi
else
echo "Nothing to do"
start_build
fi
if label_exists_any "pr-check-blocked" "blocked" \
&& ! comment_exists_any "$BUILD_SUCCESS_COMMENT" \
&& [ "$(echo "$PR_COMMENTS" | grep -E -c 'Test build.*failed')" -gt 5 ]; then
echo "PR is blocked and too many failing builds. Locking"
gh pr comment "$PR_NUM" --body "$LOCKED_COMMENT" || true
gh pr lock "$PR_NUM" || true
fi
if label_exists_any "awaiting-review" && [ "$PR_THREAD_COUNT" -gt 0 ]; then
echo "Has awaiting-review label and review comments, marking as awaiting-changes"
gh pr edit "$PR_NUM" --add-label "awaiting-changes" --remove-label "awaiting-review" || true
fi
done
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
SCRIPT_URL: https://raw.githubusercontent.com/flathub/flathub/refs/heads/master/.github/scripts/validate.py
DOMAIN_SCRIPT_URL: https://raw.githubusercontent.com/flathub/flathub/refs/heads/master/.github/scripts/domain_from_appid.py
BUILD_SUCCESS_COMMENT: "[Test build succeeded]"
BUILD_START_COMMENT: |
Starting a test build of the submission. Please fix any
issues reported in the build log. You can restart the build
once the issue is fixed by commenting the phrase below.
bot, build
BUILD_START_COMMENT_PARTIAL: "Starting a test build of the submission"
BASE_REVIEW_COMMENT: |
This pull request is temporarily marked as blocked as some
automated checks failed on it. Please make sure the
following items are done:
LOCKED_COMMENT: >
This pull request is marked as blocked and has too many
builds failing. Locking this PR temporarily. Please build
it locally following the instructions and push the relevant
changes first.
REVIEW_COMMENT_PARTIAL: "This pull request is temporarily marked as blocked as some"
DOMAIN_COMMENT_PARTIAL: "The domain to be used for verification is"