You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by ja...@apache.org on 2026/10/22 08:13:16 UTC
(mynewt-nimble) 15/19: ci: Make workflows-update label informative
This is an automated email from the ASF dual-hosted git repository.
sjanc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-nimble.git
commit e818469aa6ccc509005ed55d1eb17756827a3d95
Author: Szymon Janc <sz...@codecoup.pl>
AuthorDate: Wed Oct 21 16:28:14 2026 +0200
ci: Make workflows-update label informative
Workflow runs of contributors without write access need approval
anyway, so 'Check workflows' status adds little. Remove it and set
'workflows-update' label on pull requests modifying .github compared
to their merge base instead, same as other PR maintenance labels.
Assisted-by: Claude:claude-opus-5-5
---
.github/scripts/pr_maintenance.py | 139 ++++++++---------------------------
.github/workflows/pr_maintenance.yml | 18 +----
2 files changed, 34 insertions(+), 123 deletions(-)
diff --git a/.github/scripts/pr_maintenance.py b/.github/scripts/pr_maintenance.py
index 896996c23..8b6f07a6d 100644
--- a/.github/scripts/pr_maintenance.py
+++ b/.github/scripts/pr_maintenance.py
@@ -25,25 +25,18 @@ is done through GitHub API using gh tool.
- 'needs-ci-approval' label is set on pull requests with workflow runs
waiting for approval on their head commit, and removed otherwise.
- 'workflows-outdated' label is set on pull requests if .github of
- base branch changed since merge base, and removed otherwise. It is
- informative only.
-- 'Check workflows' commit status reports whether .github of pull
- request head matches base branch ('workflows-update' label skips the
- check).
+ base branch changed since merge base, and removed otherwise.
+- 'workflows-update' label is set on pull requests modifying .github
+ (compared to merge base), and removed otherwise.
- All pull requests are reported for path based labeling, which is
done by actions/labeler in the workflow.
+Workflows labels are informative only, workflow runs of contributors
+without write access need approval anyway.
+
Pull requests without activity for MAX_AGE_DAYS are skipped, also
when marked with 'stale' label (set by stale workflow) until new commit
is pushed.
-
-'Check workflows' status is tied to head commit and its description
-contains base branch .github tree it was checked against, so it is
-reported only for new or changed pull requests.
-
-Usage:
- pr_maintenance.py check - update labels, write step outputs
- pr_maintenance.py report - report statuses from STATUSES variable
"""
from datetime import date, timedelta
@@ -53,11 +46,9 @@ import subprocess
import sys
CI_LABEL = "needs-ci-approval"
-WORKFLOWS_LABEL = "workflows-update"
+UPDATE_LABEL = "workflows-update"
OUTDATED_LABEL = "workflows-outdated"
-STATUS_CONTEXT = "Check workflows"
STALE_LABEL = "stale"
-SKIPPED = f"Skipped, '{WORKFLOWS_LABEL}' label present"
# Keep in sync with days-before-pr-stale in stale.yml
MAX_AGE_DAYS = 365
@@ -65,9 +56,6 @@ MAX_AGE_DAYS = 365
# Runs never approved stay waiting forever, skip old ones
CI_RUNS_MAX_AGE_DAYS = 30
-# Limit API usage, remaining pull requests are handled by next runs
-MAX_PULLS_PER_RUN = 20
-
# .github trees are queried with object() which returns null if path
# doesn't exist, Commit.file() fails whole query (and gh pagination) instead
TREES_PER_QUERY = 100
@@ -78,7 +66,6 @@ PULLS_QUERY = """
query (
$owner: String!
$repo: String!
- $context: String!
$endCursor: String
) {
repository(owner: $owner, name: $repo) {
@@ -96,7 +83,6 @@ query (
number
updatedAt
headRefOid
- baseRefName
labels(first: 100) {
nodes {
name
@@ -112,12 +98,6 @@ query (
commit {
oid
committedDate
- status {
- context(name: $context) {
- state
- description
- }
- }
}
}
}
@@ -177,8 +157,7 @@ def list_pulls():
owner, repo = REPO.split("/")
pages = json.loads(gh("api", "graphql", "--paginate", "--slurp",
"-f", f"query={PULLS_QUERY}",
- "-f", f"owner={owner}", "-f", f"repo={repo}",
- "-f", f"context={STATUS_CONTEXT}"))
+ "-f", f"owner={owner}", "-f", f"repo={repo}"))
since = (date.today() - timedelta(days=MAX_AGE_DAYS)).isoformat()
pulls = []
skipped = 0
@@ -204,8 +183,6 @@ def list_pulls():
"number": node["number"],
"head": commit["oid"],
"labels": labels,
- "status": (commit["status"] or {}).get("context"),
- "base_name": node["baseRefName"],
"base_head": base.get("oid"),
})
@@ -220,7 +197,6 @@ def list_pulls():
base_tree = trees.get(pull["base_head"])
pull["tree"] = trees[pull["head"]]
pull["base_tree"] = base_tree
- pull["base"] = f"{pull['base_name']}@{(base_tree or 'none')[:12]}"
# Same tree as base means pull request neither modifies .github
# nor is out of date, merge base is not needed
@@ -299,69 +275,10 @@ def update_outdated_label(pulls):
lambda p: p["base_tree"] != p["merge_base_tree"])
-def workflows_status(pull):
- """Returns status to report, or None if reported one is up to date"""
- status = pull["status"]
-
- if WORKFLOWS_LABEL in pull["labels"]:
- new = {"state": "success", "description": SKIPPED}
- elif pull["tree"] == pull["base_tree"]:
- new = {"state": "success",
- "description": f".github matches {pull['base']}"}
- else:
- new = {"state": "failure",
- "description": f".github differs from {pull['base']}, "
- f"rebase or apply '{WORKFLOWS_LABEL}' label"}
-
- if status and (status["state"], status["description"]) == \
- (new["state"], new["description"]):
- return None
-
- return {"sha": pull["head"], **new}
-
-
-def check():
- """
- Updates CI approval and outdated labels, returns number of errors
- and step outputs:
- pr-numbers - newline separated pull requests for path based labeling
- statuses - JSON list of workflows commit statuses to report
- """
- pulls, errors = list_pulls()
- print(f"{len(pulls)} open pull requests")
- errors += update_ci_label(pulls)
- # Pull requests without merge base tree failed and were reported
- pulls_ok = [p for p in pulls if "merge_base_tree" in p]
- errors += update_outdated_label(pulls_ok)
- statuses = [s for s in map(workflows_status, pulls_ok) if s]
- statuses = statuses[:MAX_PULLS_PER_RUN]
-
- for status in statuses:
- print(f"{status['sha']}: {status['description']}")
-
- return errors, {
- "pr-numbers": "\n".join(str(p["number"]) for p in pulls),
- "statuses": json.dumps(statuses),
- }
-
-
-def report(statuses, target_url):
- """Reports statuses computed by check(), returns number of errors"""
- errors = 0
-
- for status in statuses:
- try:
- gh("api", "-X", "POST", f"repos/{REPO}/statuses/{status['sha']}",
- "-f", f"state={status['state']}",
- "-f", f"description={status['description']}",
- "-f", f"context={STATUS_CONTEXT}",
- "-f", f"target_url={target_url}")
- print(f"{status['sha']}: {status['state']}")
- except subprocess.CalledProcessError as e:
- error(f"{status['sha']}: {e.stderr.strip()}")
- errors += 1
-
- return errors
+def update_update_label(pulls):
+ """Returns number of errors"""
+ return sync_label(pulls, UPDATE_LABEL, "FBCA04",
+ lambda p: p["tree"] != p["merge_base_tree"])
def write_outputs(outputs):
@@ -374,20 +291,24 @@ def write_outputs(outputs):
def main():
- if sys.argv[1:] == ["check"]:
- try:
- errors, outputs = check()
- except subprocess.CalledProcessError as e:
- sys.exit(f"Failed to list pull requests: {e.stderr.strip()}")
- # Outputs are written even on errors so following steps can
- # handle pull requests that were processed successfully
- write_outputs(outputs)
- elif sys.argv[1:] == ["report"]:
- url = f"{os.environ['GITHUB_SERVER_URL']}/{REPO}/actions/runs/" \
- f"{os.environ['GITHUB_RUN_ID']}"
- errors = report(json.loads(os.environ["STATUSES"]), url)
- else:
- sys.exit(__doc__)
+ """
+ Updates labels and writes step outputs:
+ pr-numbers - newline separated pull requests for path based labeling
+ """
+ try:
+ pulls, errors = list_pulls()
+ except subprocess.CalledProcessError as e:
+ sys.exit(f"Failed to list pull requests: {e.stderr.strip()}")
+
+ print(f"{len(pulls)} open pull requests")
+ # Written first so following step can handle pull requests even if
+ # updating labels fails
+ write_outputs({"pr-numbers": "\n".join(str(p["number"]) for p in pulls)})
+ errors += update_ci_label(pulls)
+ # Pull requests without merge base tree failed and were reported
+ pulls = [p for p in pulls if "merge_base_tree" in p]
+ errors += update_outdated_label(pulls)
+ errors += update_update_label(pulls)
if errors:
sys.exit(f"{errors} error(s) occurred")
diff --git a/.github/workflows/pr_maintenance.yml b/.github/workflows/pr_maintenance.yml
index 0d4cd123a..c65e240bd 100644
--- a/.github/workflows/pr_maintenance.yml
+++ b/.github/workflows/pr_maintenance.yml
@@ -18,7 +18,7 @@
#
-# Maintenance of open pull requests (labels and workflows status check).
+# Maintenance of open pull requests labels.
# This runs periodically from master instead of pull_request_target, so
# no pull request code or configuration is used with elevated privileges.
@@ -34,7 +34,6 @@ permissions:
contents: read
issues: write
pull-requests: write
- statuses: write
concurrency:
group: ${{ github.workflow }}
@@ -52,13 +51,13 @@ jobs:
sparse-checkout: .github
persist-credentials: false
- - name: Check pull requests
+ - name: Update pull requests labels
id: check
env:
GH_TOKEN: ${{ github.token }}
- run: python3 .github/scripts/pr_maintenance.py check
+ run: python3 .github/scripts/pr_maintenance.py
- # Following steps run also if previous step failed, so that error in
+ # Following step runs also if previous step failed, so that error in
# one pull request doesn't block processing of others
- name: Assign labels based on paths
@@ -68,12 +67,3 @@ jobs:
pr-number: ${{ steps.check.outputs.pr-numbers }}
sync-labels: true
configuration-path: .github/labeler_cfg.yml
-
- - name: Report workflows status
- if: >-
- ${{ !cancelled() && steps.check.outputs.statuses != '' &&
- steps.check.outputs.statuses != '[]' }}
- env:
- GH_TOKEN: ${{ github.token }}
- STATUSES: ${{ steps.check.outputs.statuses }}
- run: python3 .github/scripts/pr_maintenance.py report