Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ jobs:
> prs.json
python3 -c "import json;print('collected',len(json.load(open('prs.json'))),'open Dependabot PRs')"

# Required contexts, so a check that never reported can be named. A
# green rollup is not the same as a satisfied ruleset: on
# appeler/pranaam#10 all seven reported checks passed while the
# required `build` context never ran at all -- its workflow had been
# cancelled by a concurrency collision -- and the PR sat BLOCKED for
# weeks looking entirely green. Counting reported checks cannot see
# that; comparing against the requirement can.
gh api "repos/${GH_REPO}/rulesets" --jq '.[].id' 2>/dev/null \
| while read -r id; do
gh api "repos/${GH_REPO}/rulesets/${id}" --jq \
'.rules[]? | select(.type=="required_status_checks")
| .parameters.required_status_checks[].context' 2>/dev/null
Comment on lines +163 to +165

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter required checks to rulesets that apply to the PR

When a repository has disabled rulesets or separate rulesets targeting branches such as release/*, this loop unions their required contexts with those for the PR's base branch because it ignores each ruleset's enforcement and conditions. A blocked Dependabot PR to main can therefore be reported as missing checks that were never required or expected to run on it, defeating the diagnostic this change adds; collect only active rulesets whose conditions match the PR's base ref.

Useful? React with 👍 / 👎.

done | sort -u > required.txt || true
echo "required contexts: $(tr '\n' ' ' < required.txt)"

# Decide per PR, print one line for every one of them, and act. Check
# state is read from statusCheckRollup rather than from mergeStateStatus
# alone: CLEAN is GitHub's opinion about mergeability, and this job needs
Expand Down Expand Up @@ -185,6 +200,17 @@ jobs:
return "running"
return "green" if all(s in TERMINAL_OK for s in states) else "failing"

def never_reported(pr):
"""Required contexts with no check run at all on this PR."""
seen = {c.get("name") or c.get("context") for c in
(pr.get("statusCheckRollup") or [])}
return sorted(required - seen)

try:
required = {ln.strip() for ln in open("required.txt") if ln.strip()}
except OSError:
required = set()

for pr in json.load(open("prs.json")):
n = pr["number"]
names = {l["name"] for l in pr.get("labels") or []}
Expand All @@ -194,7 +220,13 @@ jobs:
elif pr.get("autoMergeRequest"):
verdict, act = "already armed", "none"
elif pr["mergeStateStatus"] in {"DIRTY", "BLOCKED", "DRAFT"}:
verdict, act = f"not mergeable ({pr['mergeStateStatus']})", "none"
# Name the missing requirement rather than only its symptom:
# "BLOCKED" sends a reader looking for a failing check that
# does not exist.
absent = never_reported(pr)
reason = (f"required never ran: {','.join(absent)}" if absent
else pr["mergeStateStatus"])
Comment on lines +226 to +228

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve DIRTY and DRAFT as the primary reason

When a PR is conflicted or still a draft and any required context is absent, this branch replaces DIRTY or DRAFT with required never ran: .... In those states the conflict or draft status is the immediate reason merging is impossible, and checks may intentionally not have run, so the scheduled sweep now directs operators toward the wrong problem; apply the missing-context substitution only when mergeStateStatus is BLOCKED.

Useful? React with 👍 / 👎.

verdict, act = f"not mergeable ({reason})", "none"
else:
state = check_state(pr)
verdict, act = {
Expand Down
Loading