[SCM] PostGIS branch master updated. 3.7.0beta2-13-g3e477a4cb

git at osgeo.org git at osgeo.org
Mon Aug 10 12:52:39 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  3e477a4cbce5e2823eebe4308ad31ceb02a17e5e (commit)
       via  568fc4b7c1dd99b542d6cd59310610a401caf71e (commit)
       via  dc9e4fd2bc217c4fb58eaee14d4c605646b462c5 (commit)
      from  2b8bb15d7a3a872ce9f0f6f4e680f9edd8b7415a (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 3e477a4cbce5e2823eebe4308ad31ceb02a17e5e
Merge: 2b8bb15d7 568fc4b7c
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date:   Mon Aug 10 12:52:37 2026 -0700

    Merge pull request 'ci: harden developer tooling edge cases' (!719) from Komzpa/postgis:fix/security-findings-tooling-20260810 into master
    
    This hardens several developer and CI surfaces that can otherwise produce misleading validation or unsafe local troubleshooting behavior.
    
    Changes included:
    
    - Key GitHub Actions concurrency by pull request number for pull_request events so same-named branches in different forks cannot cancel each other's runs.
    - Replay only checked-in fuzzer seed corpora during `make -C fuzzers check-corpus` instead of trusting arbitrary `*_seed_corpus.zip` files from `FUZZER_OUT`.
    - Treat root `spatial_ref_sys.sql` as upgrade-visible input for CI path gating and upgrade-surface checks.
    - Reject XML `DOCTYPE` declarations in the stdlib DocBook parser wrapper before SAX can expand internal entities.
    - Validate `cache_head_remote` as HTTP(S) and pass `--` to `git ls-remote`; count all stale terminal statuses in the terminal branch table's unknown column.
    - Keep explicit-precision Hausdorff tolerance for documented 2D WKT only, so Z/M examples require rounded textual equality.
    - Make local developer docs safer for Woodie token setup, documentation review servers, Docker PostgreSQL passwords, and `geometry_columns` wording.
    
    Validation:
    
    - `python3 -m unittest utils.docs.tests.test_ci_status utils.docs.tests.test_docbook_qa utils.docs.tests.test_postgis_exampletest`
    - `git diff --check upstream/master...HEAD`
    - scope gate: `git diff --name-only upstream/master...HEAD` contains only workflow, CI/tooling, fuzzer Makefile, and documentation/test files; generated/build artifact scan passed.
    - direct path-filter negative control: `spatial_ref_sys.sql` now matches both `ci_need_suite.py` and `check_upgrade_surface.py` upgrade-visible patterns.
    
    Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/719


commit 568fc4b7c1dd99b542d6cd59310610a401caf71e
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Aug 10 23:21:33 2026 +0400

    regress: fix geometry_columns count message

diff --git a/regress/core/tickets.sql b/regress/core/tickets.sql
index 3f14eaac4..deb4c3a95 100644
--- a/regress/core/tickets.sql
+++ b/regress/core/tickets.sql
@@ -1852,7 +1852,7 @@ BEGIN
     SELECT count(*) INTO n FROM geometry_columns
       WHERE f_table_schema = 'test6110';
     IF n <> 2002 THEN
-        RETURN 'FAIL: count=' || n || ' (expected 1000)';
+        RETURN 'FAIL: count=' || n || ' (expected 2002)';
     END IF;
 
     t0 := clock_timestamp();

commit dc9e4fd2bc217c4fb58eaee14d4c605646b462c5
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Aug 10 23:15:34 2026 +0400

    ci: harden developer tooling edge cases

diff --git a/.github/workflows/ci-freebsd.yml b/.github/workflows/ci-freebsd.yml
index 6906e059c..162412ac5 100644
--- a/.github/workflows/ci-freebsd.yml
+++ b/.github/workflows/ci-freebsd.yml
@@ -8,7 +8,7 @@ on:
   pull_request: ~
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml
index 954468fe1..dbaa9ee8e 100644
--- a/.github/workflows/ci-macos.yml
+++ b/.github/workflows/ci-macos.yml
@@ -8,7 +8,7 @@ on:
   pull_request: ~
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index dd2686ead..707beab9a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,7 +10,7 @@ on:
   pull_request: ~
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index d87640e06..d6792c403 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -5,7 +5,7 @@ on:
   pull_request: ~
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
 permissions:
diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
index 164cc8ae1..d75ca2f01 100644
--- a/.github/workflows/codespell.yml
+++ b/.github/workflows/codespell.yml
@@ -7,7 +7,7 @@ on:
   pull_request: ~
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/contributor-credits.yml b/.github/workflows/contributor-credits.yml
index c5237153f..3fd68fa8f 100644
--- a/.github/workflows/contributor-credits.yml
+++ b/.github/workflows/contributor-credits.yml
@@ -10,7 +10,7 @@ permissions:
   contents: read
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
 jobs:
diff --git a/.github/workflows/msys.yml b/.github/workflows/msys.yml
index 8e9e204df..1a06602b4 100644
--- a/.github/workflows/msys.yml
+++ b/.github/workflows/msys.yml
@@ -4,7 +4,7 @@ on:
   pull_request: ~
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
 jobs:
diff --git a/ci/ci_need_suite.py b/ci/ci_need_suite.py
index 75547bee8..9f0a122b5 100755
--- a/ci/ci_need_suite.py
+++ b/ci/ci_need_suite.py
@@ -64,6 +64,7 @@ LOADER_GLOBS = (
 
 UPGRADE_GLOBS = (
     "Version.config",
+    "spatial_ref_sys.sql",
     "extensions/**",
     "postgis/*.sql.in",
     "raster/rt_pg/*.sql.in",
diff --git a/doc/development/environment/docker.md b/doc/development/environment/docker.md
index 6ee062e52..c453df502 100644
--- a/doc/development/environment/docker.md
+++ b/doc/development/environment/docker.md
@@ -64,13 +64,16 @@ the cluster:
 ```sh
 export PGVER=15
 export PGPORT="$(grep ^port /etc/postgresql/${PGVER}/main/postgresql.conf | awk '{print $3}')"
+read -rs POSTGIS_DEV_PASSWORD
+printf '\n'
 psql -d postgres -c "ALTER SYSTEM SET listen_addresses='*';"
 printf '%s\n' "host all all 0.0.0.0/0 scram-sha-256" >> "/etc/postgresql/${PGVER}/main/pg_hba.conf"
-psql -d postgres -c "ALTER ROLE postgres PASSWORD 'change-me';"
+psql -d postgres -v password="$POSTGIS_DEV_PASSWORD" -c "ALTER ROLE postgres PASSWORD :'password';"
+unset POSTGIS_DEV_PASSWORD
 service postgresql restart "${PGVER}"
 ```
 
-Use a stronger password and narrower `pg_hba.conf` rule on shared hosts. The
+Use a strong password and narrower `pg_hba.conf` rule on shared hosts. The
 example is intended for a short-lived local development container.
 
 From the host, connect to the PostgreSQL version whose container port you
diff --git a/doc/development/manual.md b/doc/development/manual.md
index 3249af9f4..5937159d4 100644
--- a/doc/development/manual.md
+++ b/doc/development/manual.md
@@ -35,7 +35,7 @@ the old HTML directory:
 ```sh
 make -C /path/to/old-postgis/doc html
 make -C doc review-html REVIEW_HTML_BASE=/path/to/old-postgis/doc/html
-python3 -m http.server --directory . 8000
+python3 -m http.server --bind 127.0.0.1 --directory . 8000
 ```
 
 Open `http://127.0.0.1:8000/doc/html/review/`. The page keeps the old manual on
diff --git a/doc/development/testing/ci.md b/doc/development/testing/ci.md
index 319302543..95e6ac98b 100644
--- a/doc/development/testing/ci.md
+++ b/doc/development/testing/ci.md
@@ -194,9 +194,8 @@ before changing a pipeline:
 read -rs WOODPECKER_TOKEN
 printf '\n'
 umask 077
-woodpecker-cli setup --context osgeo \
-  --server https://woodie.osgeo.org \
-  --token "$WOODPECKER_TOKEN"
+WOODPECKER_TOKEN="$WOODPECKER_TOKEN" woodpecker-cli setup --context osgeo \
+  --server https://woodie.osgeo.org
 unset WOODPECKER_TOKEN
 woodpecker-cli context use osgeo
 woodpecker-cli info
diff --git a/doc/reference_management.xml b/doc/reference_management.xml
index 9cbe37ec2..739a81efd 100644
--- a/doc/reference_management.xml
+++ b/doc/reference_management.xml
@@ -231,8 +231,8 @@ WHERE f_table_name = 'my_spatial_table' AND f_table_schema = 'my_schema';</progr
 		<title>Description</title>
 
 		<para>Removes a geometry column from a spatial table. Note that
-		schema_name will need to match the f_table_schema field of the table's
-		row in the geometry_columns table.</para>
+		schema_name will need to match the f_table_schema field shown by the
+		<varname>geometry_columns</varname> view.</para>
 
 		<warning>
 		  <para><function>DropGeometryColumn</function> is retained for backward
diff --git a/fuzzers/Makefile b/fuzzers/Makefile
index 491d3de23..348cbaeac 100644
--- a/fuzzers/Makefile
+++ b/fuzzers/Makefile
@@ -78,7 +78,7 @@ check-corpus: dummyfuzzers
 	rm -rf "$(FUZZER_WORK)"; \
 	mkdir -p "$(FUZZER_WORK)"; \
 	trap 'rm -rf "$(FUZZER_WORK)"' EXIT HUP INT TERM; \
-	for corpus in $(SEED_CORPORA) "$(FUZZER_OUT)"/*_seed_corpus.zip __none__; do \
+	for corpus in $(SEED_CORPORA) __none__; do \
 		if [ "$${corpus}" = "__none__" ] || [ ! -e "$${corpus}" ]; then \
 			continue; \
 		fi; \
diff --git a/utils/check_upgrade_surface.py b/utils/check_upgrade_surface.py
index a6d88074f..199f42853 100755
--- a/utils/check_upgrade_surface.py
+++ b/utils/check_upgrade_surface.py
@@ -18,6 +18,7 @@ from pathlib import Path
 
 SQL_VISIBLE_GLOBS = (
     "Version.config",
+    "spatial_ref_sys.sql",
     "extensions/**",
     "postgis/*.sql.in",
     "raster/rt_pg/*.sql.in",
diff --git a/utils/docs/ci_status/report.py b/utils/docs/ci_status/report.py
index b0644ed7a..c2eaab039 100644
--- a/utils/docs/ci_status/report.py
+++ b/utils/docs/ci_status/report.py
@@ -1331,6 +1331,9 @@ def resolve_cache_heads(config, work, cache, timeout):
     remote = config.get("cache_head_remote")
     if not remote:
         return {}
+    parsed = urllib.parse.urlparse(remote)
+    if parsed.scheme not in ("https", "http") or not parsed.netloc or remote.startswith("-"):
+        raise ConfigError("cache_head_remote must be an HTTP(S) URL")
     branch_names = sorted({
         branch["name"]
         for branch, _check in work
@@ -1345,7 +1348,7 @@ def resolve_cache_heads(config, work, cache, timeout):
     refs = [f"refs/heads/{name}" for name in branch_names]
     try:
         completed = subprocess.run(
-            ["git", "ls-remote", "--exit-code", "--heads", remote, *refs],
+            ["git", "ls-remote", "--exit-code", "--heads", "--", remote, *refs],
             check=True,
             capture_output=True,
             text=True,
@@ -1738,7 +1741,7 @@ def print_branch_table(branches, use_color):
     print(terminal_style("-" * len(header), dim=True, enabled=use_color))
     for branch in branches:
         counts = check_counts(branch)
-        unknown = counts[UNKNOWN] + counts[STALE]
+        unknown = counts[UNKNOWN] + counts[STALE] + counts[STALE_PASSED] + counts[STALE_FAILED]
         label = f"{branch['label']:<10}"
         print(
             f"{label} "
diff --git a/utils/docs/postgis_exampletest.py b/utils/docs/postgis_exampletest.py
index 301c4f17e..af3611c47 100755
--- a/utils/docs/postgis_exampletest.py
+++ b/utils/docs/postgis_exampletest.py
@@ -461,7 +461,7 @@ class ExampleTester:
 
     def documented_wkt_uses_explicit_z_token(self, expected_wkt):
         return bool(re.search(
-            rf"\b(?:{WKT_TYPES})\s+Z(?:M)?\s*(?:\(|EMPTY\b)",
+            rf"\b(?:{WKT_TYPES})\s+(?:Z|M|ZM)\s*(?:\(|EMPTY\b)",
             expected_wkt or "",
             re.I,
         ))
@@ -575,7 +575,7 @@ class ExampleTester:
                 f"ST_AsText(actual, {documented_digits}) = "
                 f"ST_AsText(expected, {documented_digits})"
             )
-            if expected_wkt_digits is not None:
+            if expected_wkt_digits is not None and self.documented_wkt_is_2d(expected_wkt):
                 shape_match = (
                     f"({shape_match} OR "
                     f"ST_HausdorffDistance(actual, expected) <= {10 ** -expected_wkt_digits:.17g})"
diff --git a/utils/docs/tests/test_ci_status.py b/utils/docs/tests/test_ci_status.py
index d29a5b51a..2e7127a92 100644
--- a/utils/docs/tests/test_ci_status.py
+++ b/utils/docs/tests/test_ci_status.py
@@ -1,4 +1,5 @@
 import json
+import io
 import pathlib
 import subprocess
 import sys
@@ -241,6 +242,7 @@ class CIStatusTest(unittest.TestCase):
                 "ls-remote",
                 "--exit-code",
                 "--heads",
+                "--",
                 "https://example.test/postgis.git",
                 "refs/heads/master",
                 "refs/heads/stable-3.6",
@@ -251,6 +253,40 @@ class CIStatusTest(unittest.TestCase):
             timeout=7,
         )
 
+    def test_cache_head_lookup_rejects_option_like_remote(self):
+        config = {"cache_head_remote": "--upload-pack=sh"}
+        work = [({"name": "master", "label": "master"}, {"name": "Synthetic CI"})]
+        cache = CI_STATUS.index_status_cache({
+            "branches": [{
+                "name": "master",
+                "checks": [{
+                    "check": "Synthetic CI",
+                    "status": CI_STATUS.SUCCESS,
+                }],
+            }],
+        })
+
+        with self.assertRaises(CI_STATUS.ConfigError):
+            CI_STATUS.resolve_cache_heads(config, work, cache, timeout=7)
+
+    def test_branch_table_counts_all_stale_statuses_as_unknown(self):
+        branch = {
+            "name": "stable-synthetic",
+            "label": "Synthetic",
+            "status": CI_STATUS.UNKNOWN,
+            "checks": [
+                check("Required / Unknown", CI_STATUS.UNKNOWN),
+                check("Required / Stale", CI_STATUS.STALE),
+                check("Required / Passed", CI_STATUS.STALE_PASSED),
+                check("Required / Failed", CI_STATUS.STALE_FAILED),
+            ],
+        }
+
+        with mock.patch("sys.stdout", new_callable=io.StringIO) as stdout:
+            CI_STATUS.print_branch_table([branch], use_color=False)
+
+        self.assertIn("  4  ", stdout.getvalue())
+
     def test_missing_optional_status_cache_starts_empty(self):
         with tempfile.TemporaryDirectory() as tmpdir:
             missing = pathlib.Path(tmpdir) / "status.json"
diff --git a/utils/docs/tests/test_docbook_qa.py b/utils/docs/tests/test_docbook_qa.py
index e47181021..aa550da77 100644
--- a/utils/docs/tests/test_docbook_qa.py
+++ b/utils/docs/tests/test_docbook_qa.py
@@ -25,6 +25,7 @@ from docbook_qa import (
     load_xml,
     main,
 )
+from xml_tree import parse as parse_xml
 
 DOCBOOK_OPEN = '<book xmlns="http://docbook.org/ns/docbook" xmlns:xml="http://www.w3.org/XML/1998/namespace">'
 DOCBOOK_CLOSE = '</book>'
@@ -55,6 +56,18 @@ class DocBookSourceLintTest(unittest.TestCase):
         )
         self.assertCategories('<refentry xml:id="f"><programlisting>SELECT 1;</programlisting></refentry>', set())
 
+    def test_xml_tree_rejects_doctype(self):
+        path = write_tmp(
+            ".xml",
+            '<!DOCTYPE book [<!ENTITY expand "expanded">]>'
+            + DOCBOOK_OPEN
+            + '<para>&expand;</para>'
+            + DOCBOOK_CLOSE,
+        )
+
+        with self.assertRaises(Exception):
+            parse_xml(path)
+
     def test_mixed_programlisting_dash_run_markers_and_sql_comments(self):
         for output in ("left | right\n----|----\n1 | 2", "----RESULT output ---\n1"):
             with self.subTest(output=output):
diff --git a/utils/docs/tests/test_postgis_exampletest.py b/utils/docs/tests/test_postgis_exampletest.py
index d323bf46d..3d120d150 100644
--- a/utils/docs/tests/test_postgis_exampletest.py
+++ b/utils/docs/tests/test_postgis_exampletest.py
@@ -520,6 +520,18 @@ SELECT 'POINT(1 2)', $$LINESTRING(0 0,1 1)$$,
         )
         self.assertIn("ST_HausdorffDistance(actual, expected) <= 9.9999999999999995e-07", query)
 
+    def test_explicit_geometry_output_precision_keeps_z_textual(self):
+        tester = ExampleTester.__new__(ExampleTester)
+        query = tester.geometry_comparison_query(
+            "0101000080000000000000F03F00000000000000400000000000000840",
+            expected_wkt="POINT Z (1.000000 2.000000 3.000000)",
+            actual_type="POINT",
+            expected_wkt_digits=6,
+        )
+
+        self.assertIn("ST_AsText(actual, 6) = ST_AsText(expected, 6)", query)
+        self.assertNotIn("ST_HausdorffDistance", query)
+
     def test_surface_output_precision_preserves_canonical_face_comparison(self):
         tester = ExampleTester.__new__(ExampleTester)
         query = tester.geometry_comparison_query(
diff --git a/utils/docs/xml_tree.py b/utils/docs/xml_tree.py
index 203dc9a49..042a71be6 100644
--- a/utils/docs/xml_tree.py
+++ b/utils/docs/xml_tree.py
@@ -75,7 +75,21 @@ class _TreeBuilder(ContentHandler):
             current.text = (current.text or "") + content
 
 
+def _reject_doctype(path):
+    with Path(path).open("rb") as handle:
+        previous = b""
+        while True:
+            chunk = handle.read(8192)
+            if not chunk:
+                return
+            haystack = previous + chunk.upper()
+            if b"<!DOCTYPE" in haystack:
+                raise xml.sax.SAXException("DOCTYPE declarations are not supported")
+            previous = haystack[-8:]
+
+
 def parse(path):
+    _reject_doctype(path)
     parser = xml.sax.make_parser()
     parser.setFeature(feature_namespaces, True)
     for feature in (feature_external_ges, feature_external_pes):

-----------------------------------------------------------------------

Summary of changes:
 .github/workflows/ci-freebsd.yml             |  2 +-
 .github/workflows/ci-macos.yml               |  2 +-
 .github/workflows/ci.yml                     |  2 +-
 .github/workflows/codeql.yml                 |  2 +-
 .github/workflows/codespell.yml              |  2 +-
 .github/workflows/contributor-credits.yml    |  2 +-
 .github/workflows/msys.yml                   |  2 +-
 ci/ci_need_suite.py                          |  1 +
 doc/development/environment/docker.md        |  7 ++++--
 doc/development/manual.md                    |  2 +-
 doc/development/testing/ci.md                |  5 ++--
 doc/reference_management.xml                 |  4 ++--
 fuzzers/Makefile                             |  2 +-
 regress/core/tickets.sql                     |  2 +-
 utils/check_upgrade_surface.py               |  1 +
 utils/docs/ci_status/report.py               |  7 ++++--
 utils/docs/postgis_exampletest.py            |  4 ++--
 utils/docs/tests/test_ci_status.py           | 36 ++++++++++++++++++++++++++++
 utils/docs/tests/test_docbook_qa.py          | 13 ++++++++++
 utils/docs/tests/test_postgis_exampletest.py | 12 ++++++++++
 utils/docs/xml_tree.py                       | 14 +++++++++++
 21 files changed, 103 insertions(+), 21 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list