[SCM] PostGIS branch master updated. 3.7.0beta1-12-g435127bbb
git at osgeo.org
git at osgeo.org
Thu Jul 23 08:56:38 PDT 2026
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".
The branch, master has been updated
via 435127bbbbb1196881abeb3a4a74fb704a636144 (commit)
from 7538038dc5e255a033f3664dfad038311e771e17 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 435127bbbbb1196881abeb3a4a74fb704a636144
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date: Thu Jul 23 08:56:36 2026 -0700
ci: show Woodpecker child workflow failures (!495)
The CI dashboard already names non-success Jenkins matrix children. Woodpecker was still reported as one opaque row, which made the current master push failure look like a whole Woodpecker failure even though pipeline 5430 has exactly one killed child workflow: regress/18 on the quarantined runner.
This patch fetches the selected Woodpecker pipeline detail, summarizes non-success child workflows in the row message, and links directly to the child workflow when there is exactly one. Duplicate workflow names are disambiguated with the Woodpecker pid, e.g. regress/18.
---------
Co-authored-by: Darafei Praliaskouski <me at komzpa.net>
Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/495
diff --git a/utils/ci-status.py b/utils/ci-status.py
index 6410cafc5..8ba4cf87c 100755
--- a/utils/ci-status.py
+++ b/utils/ci-status.py
@@ -382,6 +382,72 @@ def woodpecker_matches_branch(build, check, branch):
return build.get("ref") in (None, expected_ref)
+def woodpecker_workflow_label(workflow, duplicate_names):
+ name = workflow.get("name") or f"workflow {workflow.get('pid') or workflow.get('id')}"
+ pid = workflow.get("pid")
+ if pid is not None and name in duplicate_names:
+ return f"{name}/{pid}"
+ return name
+
+
+def woodpecker_workflow_url(web_url, pipeline, workflow):
+ if not web_url or not pipeline.get("number") or workflow.get("pid") is None:
+ return None
+ return f"{web_url}/pipeline/{pipeline['number']}/{workflow['pid']}"
+
+
+def woodpecker_pipeline_detail_url(api_url, pipeline):
+ if not pipeline.get("number"):
+ return None
+ return f"{api_url.rstrip('/')}/{pipeline['number']}"
+
+
+def woodpecker_workflow_details(pipeline, web_url):
+ workflows = pipeline.get("workflows") or []
+ if not workflows:
+ return None
+
+ name_counts = {}
+ for workflow in workflows:
+ name = workflow.get("name") or f"workflow {workflow.get('pid') or workflow.get('id')}"
+ name_counts[name] = name_counts.get(name, 0) + 1
+ duplicate_names = {name for name, count in name_counts.items() if count > 1}
+
+ buckets = [
+ ("failed", []),
+ ("running", []),
+ ("unknown", []),
+ ]
+ by_bucket = dict(buckets)
+ for workflow in sorted(workflows, key=lambda item: item.get("pid") or item.get("id") or 0):
+ status = normalize_woodpecker_status(workflow.get("state") or workflow.get("status"))
+ if status == SUCCESS:
+ continue
+ label = woodpecker_workflow_label(workflow, duplicate_names)
+ if status == FAILURE:
+ by_bucket["failed"].append((label, workflow))
+ elif status == IN_PROGRESS:
+ by_bucket["running"].append((label, workflow))
+ else:
+ by_bucket["unknown"].append((label, workflow))
+
+ parts = []
+ non_success = []
+ for heading, items in buckets:
+ if not items:
+ continue
+ labels = [label for label, workflow in items]
+ parts.append(f"{heading}: {', '.join(labels)}")
+ non_success.extend(workflow for label, workflow in items)
+ if not parts:
+ return None
+
+ details = {"message": "; ".join(parts)}
+ if len(non_success) == 1:
+ details["url"] = woodpecker_workflow_url(web_url, pipeline, non_success[0])
+ return details
+
+
def woodpecker_check(check, branch, timeout):
query = urllib.parse.urlencode({
"branch": branch["name"],
@@ -404,6 +470,18 @@ def woodpecker_check(check, branch, timeout):
run_url = current.get("link") or current.get("url")
if not run_url and web_url and current.get("number"):
run_url = f"{web_url}/pipeline/{current['number']}"
+ detail_url = woodpecker_pipeline_detail_url(api_url, current)
+ if detail_url and "workflows" not in current:
+ try:
+ current = {**current, **http_json(detail_url, timeout=timeout)}
+ except RECOVERABLE_PROVIDER_ERRORS:
+ pass
+ message = current.get("message")
+ if normalize_woodpecker_status(current.get("status")) != SUCCESS:
+ details = woodpecker_workflow_details(current, web_url)
+ if details:
+ message = details["message"]
+ run_url = details.get("url") or run_url
result = make_result(
check,
branch,
@@ -412,7 +490,7 @@ def woodpecker_check(check, branch, timeout):
debug_url=url,
revision=current.get("commit"),
completed_at=current.get("finished") or current.get("updated") or current.get("created"),
- message=current.get("message"),
+ message=message,
)
if previous:
result.update(previous_fields(normalize_woodpecker_status(previous.get("status")), previous))
diff --git a/utils/test_ci_status.py b/utils/test_ci_status.py
index 5401aaeab..efdac188c 100644
--- a/utils/test_ci_status.py
+++ b/utils/test_ci_status.py
@@ -172,6 +172,79 @@ class RequiredFailureHtmlTest(unittest.TestCase):
self.assertEqual("build 7808", result["message"])
self.assertEqual("https://ci.example.test/job/PostGIS_Make_Dist/7808/", result["url"])
+ def test_woodpecker_failure_names_single_failed_workflow(self):
+ check_config = {
+ "name": "Woodpecker",
+ "provider": "woodpecker",
+ "required": True,
+ "api_url": "https://woodie.example.test/api/repos/30/pipelines",
+ "web_url": "https://woodie.example.test/repos/30",
+ }
+ branch = {"name": "master", "label": "master"}
+ pipeline = {
+ "number": 5430,
+ "event": "push",
+ "branch": "master",
+ "ref": "refs/heads/master",
+ "status": "killed",
+ "commit": "a" * 40,
+ "message": "opaque commit message",
+ }
+ pipeline_detail = {
+ **pipeline,
+ "workflows": [
+ {"pid": 1, "id": 24450, "name": "regress", "state": "success"},
+ {"pid": 18, "id": 24467, "name": "regress", "state": "killed"},
+ {"pid": 27, "id": 24476, "name": "tools", "state": "success"},
+ ],
+ }
+
+ with mock.patch.object(CI_STATUS, "http_json", side_effect=([pipeline], pipeline_detail)) as http_json:
+ result = CI_STATUS.woodpecker_check(check_config, branch, timeout=5)
+
+ self.assertEqual(CI_STATUS.FAILURE, result["status"])
+ self.assertEqual("failed: regress/18", result["message"])
+ self.assertEqual("https://woodie.example.test/repos/30/pipeline/5430/18", result["url"])
+ self.assertEqual("a" * 40, result["revision"])
+ self.assertEqual(
+ "https://woodie.example.test/api/repos/30/pipelines/5430",
+ http_json.call_args_list[1].args[0],
+ )
+
+ def test_woodpecker_running_workflows_are_summarized(self):
+ check_config = {
+ "name": "Woodpecker",
+ "provider": "woodpecker",
+ "required": True,
+ "api_url": "https://woodie.example.test/api/repos/30/pipelines",
+ "web_url": "https://woodie.example.test/repos/30",
+ }
+ branch = {"name": "stable-3.6", "label": "3.6"}
+ pipeline = {
+ "number": 5434,
+ "event": "pull_request",
+ "branch": "stable-3.6",
+ "ref": "refs/heads/stable-3.6",
+ "status": "running",
+ "commit": "b" * 40,
+ "workflows": [
+ {"pid": 1, "id": 24484, "name": "docs", "state": "success"},
+ {"pid": 2, "id": 24485, "name": "regress", "state": "running"},
+ {"pid": 3, "id": 24486, "name": "tools", "state": "success"},
+ ],
+ }
+
+ with mock.patch.object(CI_STATUS, "http_json", return_value=[pipeline]):
+ result = CI_STATUS.woodpecker_check(
+ {**check_config, "event": "pull_request"},
+ branch,
+ timeout=5,
+ )
+
+ self.assertEqual(CI_STATUS.IN_PROGRESS, result["status"])
+ self.assertEqual("running: regress", result["message"])
+ self.assertEqual("https://woodie.example.test/repos/30/pipeline/5434/2", result["url"])
+
def test_stale_summary_distinguishes_passed_and_failed(self):
branch = {
"name": "stable-synthetic",
-----------------------------------------------------------------------
Summary of changes:
utils/ci-status.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++-
utils/test_ci_status.py | 73 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 152 insertions(+), 1 deletion(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list