[SCM] PostGIS branch master updated. 3.7.0alpha1-663-ge38d9450b
git at osgeo.org
git at osgeo.org
Mon Jul 20 05:54:42 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 e38d9450b77ac106efd404465cf1e62749837401 (commit)
via 65c8c977a7829b53ab7f7652ea9726cf74a3521f (commit)
from b52a08bfb7af19711e74bf987306af94cc4ad707 (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 e38d9450b77ac106efd404465cf1e62749837401
Merge: b52a08bfb 65c8c977a
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date: Mon Jul 20 05:54:41 2026 -0700
Merge pull request 'ci: distinguish stale passed and failed checks' (!472) from Komzpa/postgis:fix/ci-status-stale-labels-20260720 into master
commit 65c8c977a7829b53ab7f7652ea9726cf74a3521f
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Mon Jul 20 14:20:14 2026 +0400
ci: distinguish stale passed and failed checks
diff --git a/utils/ci-status.json b/utils/ci-status.json
index 56a3004d5..9f22524b2 100644
--- a/utils/ci-status.json
+++ b/utils/ci-status.json
@@ -113,7 +113,12 @@
"api_url": "https://woodie.osgeo.org/api/repos/30/pipelines",
"web_url": "https://woodie.osgeo.org/repos/30",
"branches": [
- "master"
+ "master",
+ "stable-3.6",
+ "stable-3.5",
+ "stable-3.4",
+ "stable-3.3",
+ "stable-3.2"
]
},
{
diff --git a/utils/ci-status.py b/utils/ci-status.py
index d6659a47c..477762ff4 100755
--- a/utils/ci-status.py
+++ b/utils/ci-status.py
@@ -26,6 +26,8 @@ FAILURE = "failure"
IN_PROGRESS = "in_progress"
UNKNOWN = "unknown"
STALE = "stale"
+STALE_PASSED = "stale-passed"
+STALE_FAILED = "stale-fail"
DISABLED = "disabled"
NOT_APPLICABLE = "not_applicable"
@@ -35,6 +37,8 @@ SYMBOLS = {
IN_PROGRESS: ("🔄", "RUN"),
UNKNOWN: ("⚠️", "UNKNOWN"),
STALE: ("⚠️", "STALE"),
+ STALE_PASSED: ("⚠️", "STALE-OK"),
+ STALE_FAILED: ("⚠️", "STALE-FAIL"),
DISABLED: ("➖", "DISABLED"),
NOT_APPLICABLE: ("➖", "N/A"),
}
@@ -45,6 +49,8 @@ COLORS = {
IN_PROGRESS: "\033[36m",
UNKNOWN: "\033[33m",
STALE: "\033[33m",
+ STALE_PASSED: "\033[33m",
+ STALE_FAILED: "\033[33m",
DISABLED: "\033[2m",
NOT_APPLICABLE: "\033[2m",
}
@@ -892,7 +898,7 @@ def apply_staleness(result, config, check):
stale["revision_compare_ref"] = distance_ref
stale["revision_distance"] = revision_distance_text(distance_count, distance_ref)
stale["stale_base_status"] = result["status"]
- stale["status"] = STALE
+ stale["status"] = stale_status(result["status"])
stale["status_label"] = stale_status_label(result["status"])
stale["message"] = f"{result.get('message', 'CI run')} ({stale['revision_distance']})"
return stale
@@ -913,7 +919,7 @@ def apply_staleness(result, config, check):
stale["revision_compare_ref"] = distance_ref
stale["revision_distance"] = distance_text
stale["stale_base_status"] = result["status"]
- stale["status"] = STALE
+ stale["status"] = stale_status(result["status"])
stale["status_label"] = stale_status_label(result["status"])
stale["message"] = f"{result.get('message', 'successful run')} (older than {threshold:g}h)"
return stale
@@ -980,7 +986,7 @@ def aggregate(config, results):
required = [check for check in checks if check.get("required") and check["status"] not in (DISABLED, NOT_APPLICABLE)]
if any(check["status"] == FAILURE for check in required):
status = FAILURE
- elif any(check["status"] in (UNKNOWN, STALE) for check in required):
+ elif any(check["status"] == UNKNOWN or is_stale_status(check["status"]) for check in required):
status = UNKNOWN
elif any(check["status"] == IN_PROGRESS for check in required):
status = IN_PROGRESS
@@ -1018,6 +1024,8 @@ def check_counts(branch):
IN_PROGRESS: 0,
UNKNOWN: 0,
STALE: 0,
+ STALE_PASSED: 0,
+ STALE_FAILED: 0,
}
for check in required:
counts[check["status"]] = counts.get(check["status"], 0) + 1
@@ -1029,15 +1037,30 @@ def check_counts(branch):
def stale_status_label(base_status):
return {
SUCCESS: "Stale passed",
- FAILURE: "Stale failed",
+ FAILURE: "Stale fail",
UNKNOWN: "Stale unknown",
}.get(base_status, "Stale")
+def stale_status(base_status):
+ return {
+ SUCCESS: STALE_PASSED,
+ FAILURE: STALE_FAILED,
+ }.get(base_status, STALE)
+
+
+def is_stale_status(status):
+ return status in (STALE, STALE_PASSED, STALE_FAILED)
+
+
def stale_count_bucket(check):
- if check["status"] != STALE:
+ if not is_stale_status(check["status"]):
return None
base_status = check.get("stale_base_status")
+ if check["status"] == STALE_PASSED:
+ return SUCCESS
+ if check["status"] == STALE_FAILED:
+ return FAILURE
if base_status in (SUCCESS, FAILURE):
return base_status
return UNKNOWN
@@ -1062,7 +1085,7 @@ def stale_summary_parts(branch):
counts = stale_check_counts(branch)
return nonzero_parts(
(counts[SUCCESS], f"{counts[SUCCESS]} stale-passed"),
- (counts[FAILURE], f"{counts[FAILURE]} stale-failed"),
+ (counts[FAILURE], f"{counts[FAILURE]} stale-fail"),
(counts[UNKNOWN], f"{counts[UNKNOWN]} stale-unknown"),
)
@@ -1130,13 +1153,15 @@ def interesting_checks(branch, verbose=False):
return branch["checks"]
has_visible_jenkins_problem = any(
check.get("provider") == "jenkins"
- and check["status"] in (FAILURE, UNKNOWN, STALE, IN_PROGRESS, NOT_APPLICABLE)
+ and (check["status"] in (FAILURE, UNKNOWN, IN_PROGRESS, NOT_APPLICABLE) or is_stale_status(check["status"]))
for check in branch["checks"]
)
status_order = {
FAILURE: 0,
UNKNOWN: 1,
STALE: 1,
+ STALE_FAILED: 1,
+ STALE_PASSED: 1,
IN_PROGRESS: 2,
SUCCESS: 3,
NOT_APPLICABLE: 4,
@@ -1144,7 +1169,7 @@ def interesting_checks(branch, verbose=False):
visible = []
for index, check in enumerate(branch["checks"]):
if (
- check["status"] in (FAILURE, UNKNOWN, STALE, IN_PROGRESS)
+ (check["status"] in (FAILURE, UNKNOWN, IN_PROGRESS) or is_stale_status(check["status"]))
or (check["status"] == NOT_APPLICABLE and check.get("provider") == "jenkins")
or (
has_visible_jenkins_problem
@@ -1261,7 +1286,7 @@ def exit_code_for_terminal(data):
statuses = [branch["status"] for branch in data["branches"]]
if any(status == FAILURE for status in statuses):
return 1
- if any(status in (IN_PROGRESS, UNKNOWN, STALE) for status in statuses):
+ if any(status == IN_PROGRESS or status == UNKNOWN or is_stale_status(status) for status in statuses):
return 2
return 0
@@ -1287,7 +1312,7 @@ def overall_status(branches):
statuses = [branch["status"] for branch in branches]
if any(status == FAILURE for status in statuses):
return FAILURE
- if any(status in (UNKNOWN, STALE) for status in statuses):
+ if any(status == UNKNOWN or is_stale_status(status) for status in statuses):
return UNKNOWN
if any(status == IN_PROGRESS for status in statuses):
return IN_PROGRESS
@@ -1315,6 +1340,8 @@ def html_status_label(status):
IN_PROGRESS: "Running",
UNKNOWN: "Unknown",
STALE: "Stale",
+ STALE_PASSED: "Stale passed",
+ STALE_FAILED: "Stale fail",
DISABLED: "Disabled",
NOT_APPLICABLE: "Not applicable",
}.get(status, "Unknown")
@@ -1327,6 +1354,8 @@ def status_mark(status):
IN_PROGRESS: "↻",
UNKNOWN: "?",
STALE: "!",
+ STALE_PASSED: "!",
+ STALE_FAILED: "!",
DISABLED: "–",
NOT_APPLICABLE: "–",
}.get(status, "?")
@@ -1350,7 +1379,7 @@ def html_branch_progress(branch):
(IN_PROGRESS, counts[IN_PROGRESS], "running"),
(UNKNOWN, counts[UNKNOWN], "unknown"),
("stale-passed", stale_counts[SUCCESS], "stale-passed"),
- ("stale-failed", stale_counts[FAILURE], "stale-failed"),
+ (STALE_FAILED, stale_counts[FAILURE], "stale-fail"),
("stale-unknown", stale_counts[UNKNOWN], "stale-unknown"),
]
labels = [f"{count} {label}" for status, count, label in segments if count]
@@ -1382,7 +1411,7 @@ def html_branch_summary(branch):
if stale_counts[SUCCESS]:
parts.append(("stale-passed", f"{stale_counts[SUCCESS]} stale-passed"))
if stale_counts[FAILURE]:
- parts.append(("stale-failed", f"{stale_counts[FAILURE]} stale-failed"))
+ parts.append((STALE_FAILED, f"{stale_counts[FAILURE]} stale-fail"))
if stale_counts[UNKNOWN]:
parts.append(("stale-unknown", f"{stale_counts[UNKNOWN]} stale-unknown"))
if not parts:
@@ -1447,7 +1476,7 @@ def html_check_rows(checks):
status = f"{status} <span class='previous-note'>previous: {html.escape(previous)}</span>"
message = " ".join(str(check.get("message") or "").split())
check_classes = [f"status-{check['status']}"]
- if check["status"] == STALE:
+ if is_stale_status(check["status"]):
check_classes.append(f"stale-{stale_count_bucket(check)}")
check_status = html.escape(" ".join(check_classes))
revision = html_revision(check)
@@ -1707,7 +1736,7 @@ h1 {{
color: #5b6519;
background: #f5f8e8;
}}
-.chip-stale-failed {{
+.chip-stale-fail {{
color: #9a4f00;
background: #fff3e0;
}}
@@ -1743,7 +1772,7 @@ h1 {{
.segment-stale-passed {{
background: #a4b85d;
}}
-.segment-stale-failed {{
+.segment-stale-fail {{
background: #d48a3a;
}}
.status-dot {{
@@ -1756,7 +1785,7 @@ h1 {{
.status-success .status-dot {{ background: var(--success); }}
.status-failure .status-dot {{ background: var(--failure); }}
.status-in_progress .status-dot {{ background: var(--running); }}
-.status-unknown .status-dot, .status-stale .status-dot {{ background: var(--unknown); }}
+.status-unknown .status-dot, .status-stale .status-dot, .status-stale-passed .status-dot, .status-stale-fail .status-dot {{ background: var(--unknown); }}
.status-disabled .status-dot, .status-not_applicable .status-dot {{ background: var(--disabled); }}
.status-pill {{
display: inline-flex;
@@ -1776,12 +1805,12 @@ h1 {{
.status-failure .status-pill {{ color: var(--failure); border-color: #ffc9cf; background: #fff5f6; }}
.status-in_progress .status-pill {{ color: var(--running); border-color: #bfdbfe; background: #f0f7ff; }}
.status-unknown .status-pill, .status-stale .status-pill {{ color: var(--unknown); border-color: #f1d08a; background: #fff8e5; }}
-.status-stale.stale-success .status-pill {{ color: #5b6519; border-color: #d8df9f; background: #f5f8e8; }}
-.status-stale.stale-failure .status-pill {{ color: #9a4f00; border-color: #efc07a; background: #fff3e0; }}
+.status-stale-passed .status-pill, .status-stale.stale-success .status-pill {{ color: #5b6519; border-color: #d8df9f; background: #f5f8e8; }}
+.status-stale-fail .status-pill, .status-stale.stale-failure .status-pill {{ color: #9a4f00; border-color: #efc07a; background: #fff3e0; }}
tr.status-success .status-pill {{ color: var(--success); border-color: #b7dfc1; background: #f0fff4; }}
tr.status-failure .status-pill {{ color: var(--failure); border-color: #ffc9cf; background: #fff5f6; }}
tr.status-in_progress .status-pill {{ color: var(--running); border-color: #bfdbfe; background: #f0f7ff; }}
-tr.status-unknown .status-pill, tr.status-stale .status-pill {{ color: var(--unknown); border-color: #f1d08a; background: #fff8e5; }}
+tr.status-unknown .status-pill, tr.status-stale .status-pill, tr.status-stale-passed .status-pill, tr.status-stale-fail .status-pill {{ color: var(--unknown); border-color: #f1d08a; background: #fff8e5; }}
.detail-section {{
background: var(--panel);
border: 1px solid var(--line);
diff --git a/utils/test_ci_status.py b/utils/test_ci_status.py
index 5e9891758..667b55b4d 100644
--- a/utils/test_ci_status.py
+++ b/utils/test_ci_status.py
@@ -1,4 +1,5 @@
import importlib.util
+import json
import pathlib
import unittest
from unittest import mock
@@ -25,6 +26,22 @@ def check(name, status, *, required=True, url=None):
class RequiredFailureHtmlTest(unittest.TestCase):
+ def test_woodpecker_covers_supported_release_branches(self):
+ config = json.loads(MODULE_PATH.with_suffix(".json").read_text(encoding="utf-8"))
+ woodpecker = next(check for check in config["checks"] if check["name"] == "Woodpecker")
+
+ self.assertEqual(
+ [
+ "master",
+ "stable-3.6",
+ "stable-3.5",
+ "stable-3.4",
+ "stable-3.3",
+ "stable-3.2",
+ ],
+ woodpecker["branches"],
+ )
+
def test_apply_staleness_labels_passed_and_failed_results(self):
config = {
"stale_after_hours": 168,
@@ -53,12 +70,12 @@ class RequiredFailureHtmlTest(unittest.TestCase):
"message": "build 2",
}, config, stale_check)
- self.assertEqual(CI_STATUS.STALE, failed["status"])
+ self.assertEqual(CI_STATUS.STALE_FAILED, failed["status"])
self.assertEqual(CI_STATUS.FAILURE, failed["stale_base_status"])
- self.assertEqual("Stale failed", failed["status_label"])
+ self.assertEqual("Stale fail", failed["status_label"])
self.assertIn("3 commits behind stable-synthetic", failed["message"])
- self.assertEqual(CI_STATUS.STALE, passed["status"])
+ self.assertEqual(CI_STATUS.STALE_PASSED, passed["status"])
self.assertEqual(CI_STATUS.SUCCESS, passed["stale_base_status"])
self.assertEqual("Stale passed", passed["status_label"])
@@ -69,18 +86,18 @@ class RequiredFailureHtmlTest(unittest.TestCase):
"status": CI_STATUS.UNKNOWN,
"failures": 0,
"checks": [
- check("Required / Passed", CI_STATUS.STALE),
- check("Required / Failed", CI_STATUS.STALE),
+ check("Required / Passed", CI_STATUS.STALE_PASSED),
+ check("Required / Failed", CI_STATUS.STALE_FAILED),
check("Required / Unknown", CI_STATUS.UNKNOWN),
],
}
branch["checks"][0]["stale_base_status"] = CI_STATUS.SUCCESS
branch["checks"][0]["status_label"] = "Stale passed"
branch["checks"][1]["stale_base_status"] = CI_STATUS.FAILURE
- branch["checks"][1]["status_label"] = "Stale failed"
+ branch["checks"][1]["status_label"] = "Stale fail"
self.assertEqual(
- "no known failures; 1 unknown, 1 stale-passed, 1 stale-failed",
+ "no known failures; 1 unknown, 1 stale-passed, 1 stale-fail",
CI_STATUS.summary_text(branch),
)
@@ -90,11 +107,11 @@ class RequiredFailureHtmlTest(unittest.TestCase):
})
self.assertIn("1 stale-passed", rendered)
- self.assertIn("1 stale-failed", rendered)
+ self.assertIn("1 stale-fail", rendered)
self.assertIn("Stale passed", rendered)
- self.assertIn("Stale failed", rendered)
- self.assertIn("status-stale stale-success", rendered)
- self.assertIn("status-stale stale-failure", rendered)
+ self.assertIn("Stale fail", rendered)
+ self.assertIn("status-stale-passed stale-success", rendered)
+ self.assertIn("status-stale-fail stale-failure", rendered)
def test_failure_summary_names_only_required_failed_checks(self):
branches = [
-----------------------------------------------------------------------
Summary of changes:
utils/ci-status.json | 7 +++++-
utils/ci-status.py | 67 +++++++++++++++++++++++++++++++++++--------------
utils/test_ci_status.py | 39 ++++++++++++++++++++--------
3 files changed, 82 insertions(+), 31 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list