You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2026/06/26 17:02:58 UTC

(tooling-trusted-releases) branch arm updated (570d12f8 -> ccdb6a33)

This is an automated email from the ASF dual-hosted git repository.

alitheg pushed a change to branch arm
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


 discard 570d12f8 #478 - Add is_archived for releases to allow null dates where we don't have one
     new ccdb6a33 #478 - Add is_archived for releases to allow null dates where we don't have one

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (570d12f8)
            \
             N -- N -- N   refs/heads/arm (ccdb6a33)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 tests/unit/test_catalog.py | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org


(tooling-trusted-releases) 01/01: #478 - Add is_archived for releases to allow null dates where we don't have one

Posted by ar...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alitheg pushed a commit to branch arm
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit ccdb6a334efe29afcbe9b885e4b58eae22edc9c2
Author: Alastair McFarlane <ar...@apache.org>
AuthorDate: Fri Jun 26 17:52:44 2026 +0100

    #478 - Add is_archived for releases to allow null dates where we don't have one
---
 atr/db/interaction.py                           |  2 +-
 atr/models/sql.py                               | 10 ++++++---
 atr/shared/catalog.py                           |  2 +-
 atr/storage/writers/announce.py                 |  4 ++--
 atr/storage/writers/release.py                  |  4 ++--
 atr/svn/catalog.py                              |  2 +-
 atr/svn/dist.py                                 | 18 ++++++----------
 atr/templates/releases-finished.html            |  8 +++----
 migrations/versions/0098_2026.06.26_9a948e08.py | 28 +++++++++++++++++++++++++
 tests/unit/test_catalog.py                      | 19 +++++++++++++++--
 tests/unit/test_decompose.py                    | 15 +++++++++++++
 11 files changed, 84 insertions(+), 28 deletions(-)

diff --git a/atr/db/interaction.py b/atr/db/interaction.py
index bd5241ba..8ae27272 100644
--- a/atr/db/interaction.py
+++ b/atr/db/interaction.py
@@ -540,7 +540,7 @@ async def prior_release_for_archive(
         .where(
             sql.Release.project_key == project.key,
             sql.Release.phase == sql.ReleasePhase.RELEASE,
-            via(sql.Release.archived).is_(None),
+            via(sql.Release.is_archived).is_(False),
         )
         .where(via(sql.Release.version) != version)
     )
diff --git a/atr/models/sql.py b/atr/models/sql.py
index e41b5bd4..b950abae 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -1291,14 +1291,18 @@ class Release(sqlmodel.SQLModel, table=True):
         sa_column=sqlalchemy.Column(UTCDateTime),
         **example(datetime.datetime(2025, 6, 1, 1, 2, 3, tzinfo=datetime.UTC)),
     )
-    # Mirrors the latest archive LifecycleEvent for this release. Null for
-    # releases that haven't been archived. Sourced by whatever archival flow
-    # #507 eventually settles on.
+    # Mirrors the latest archive LifecycleEvent for this release - the date it was
+    # archived. Null when there's no such event, which includes a release that's
+    # archived but undated (a catalogued historical release whose date we don't have);
+    # is_archived carries the status. Sourced by whatever archival flow #507 settles on.
     archived: datetime.datetime | None = sqlmodel.Field(
         default=None,
         sa_column=sqlalchemy.Column(UTCDateTime),
         **example(datetime.datetime(2026, 1, 15, 1, 2, 3, tzinfo=datetime.UTC)),
     )
+    # The archived status. The archived date above can be null while this is true,
+    # for a catalogued historical release we know is archived but can't date
+    is_archived: bool = sqlmodel.Field(default=False)
     activity_at: datetime.datetime = sqlmodel.Field(
         default_factory=lambda: datetime.datetime.now(datetime.UTC),
         sa_column=sqlalchemy.Column(UTCDateTime, nullable=False),
diff --git a/atr/shared/catalog.py b/atr/shared/catalog.py
index aa0fccb1..203cda68 100644
--- a/atr/shared/catalog.py
+++ b/atr/shared/catalog.py
@@ -143,7 +143,7 @@ def _lifecycle_badge(cycle: sql.ProjectCycle, now: datetime.datetime) -> str:
 
 def _status(release: sql.Release | None) -> Literal["released", "archived"]:
     # Treat a missing release as archived, so it is never shown as downloadable.
-    if (release is not None) and (release.archived is None):
+    if (release is not None) and not release.is_archived:
         return "released"
     return "archived"
 
diff --git a/atr/storage/writers/announce.py b/atr/storage/writers/announce.py
index 80076f7d..5432414c 100644
--- a/atr/storage/writers/announce.py
+++ b/atr/storage/writers/announce.py
@@ -381,8 +381,8 @@ class ReleaseManager(CommitteeParticipant):
         update_stmt = (
             sqlmodel.update(sql.Release)
             .where(via(sql.Release.key) == release_row.key)
-            .where(via(sql.Release.archived).is_(None))
-            .values(archived=archive_date)
+            .where(via(sql.Release.is_archived).is_(False))
+            .values(archived=archive_date, is_archived=True)
         )
         update_result = await self.__data.execute_query(update_stmt)
         if getattr(update_result, "rowcount", 0) != 1:
diff --git a/atr/storage/writers/release.py b/atr/storage/writers/release.py
index 273134f9..5dba83e9 100644
--- a/atr/storage/writers/release.py
+++ b/atr/storage/writers/release.py
@@ -108,8 +108,8 @@ async def _archive_release(
     update_stmt = (
         sqlmodel.update(sql.Release)
         .where(via(sql.Release.key) == release.key)
-        .where(via(sql.Release.archived).is_(None))
-        .values(archived=archive_date)
+        .where(via(sql.Release.is_archived).is_(False))
+        .values(archived=archive_date, is_archived=True)
     )
     update_result = await data.execute_query(update_stmt)
     if getattr(update_result, "rowcount", 0) != 1:
diff --git a/atr/svn/catalog.py b/atr/svn/catalog.py
index 8078d337..211946aa 100644
--- a/atr/svn/catalog.py
+++ b/atr/svn/catalog.py
@@ -194,7 +194,7 @@ async def _resolve_archive(
     if project is None:
         return None
     release_record = await data.release(key=f"{project.key}-{version}").get()
-    if (release_record is None) or (release_record.archived is not None):
+    if (release_record is None) or release_record.is_archived:
         return None
     return _safe_keys(project.key, version)
 
diff --git a/atr/svn/dist.py b/atr/svn/dist.py
index 548aac08..73e6816a 100644
--- a/atr/svn/dist.py
+++ b/atr/svn/dist.py
@@ -116,17 +116,17 @@ def version_from_filename(filename: str) -> str | None:
 def _choose_version(
     dir_version: str | None, dir_source: Literal["dir", "combined", "unknown"], filename: str | None
 ) -> tuple[str | None, Literal["dir", "combined", "filename", "unknown"]]:
-    # The dir and the filename can each carry a version. The filename usually wins (a 4.5/ series
-    # dir vs the 4.5.13 file), but when its leading version is a distractor (kafka_2.13-4.3.1.tgz,
-    # the 2.13 Scala build) and the dir pins a version the filename confirms, the dir wins - unless
-    # the filename merely refines the dir version (a 1.0.0-incubating tail)
+    # The dir and the filename can each carry a version. An explicit version dir is the release
+    # boundary, so a file under it whose own version doesn't refine the dir is a sub-component -
+    # an opendal language binding at its own version, or the kafka_2.13-4.3.1 Scala build - and
+    # stays on the dir's release. The filename wins only when it refines the dir (a 4.5 series dir
+    # the 4.5.13 file pins, a 1.0.0-incubating tail) or there's no dir version to anchor to
     if dir_version is not None:
         dir_version = _normalise_version(dir_version)
     name_version = version_from_filename(filename) if filename else None
     if name_version is None:
         return dir_version, dir_source
-    dir_confirmed = (dir_version is not None) and (filename is not None) and _version_in_filename(dir_version, filename)
-    if dir_confirmed and (dir_version is not None) and not name_version.startswith(dir_version):
+    if (dir_version is not None) and not name_version.startswith(dir_version):
         return dir_version, dir_source
     return name_version, "filename"
 
@@ -232,9 +232,3 @@ def _subproject_and_dir_version(
         if deeper is not None:
             return first, deeper[1], "combined"
     return first, None, "unknown"
-
-
-def _version_in_filename(version: str, filename: str) -> bool:
-    # The version appears in the filename as its own token, not as the prefix of a longer
-    # version (4.5 inside 4.5.13) nor tacked onto another number
-    return re.search(rf"(?<![\d.]){re.escape(version)}(?!\.?\d)", filename) is not None
diff --git a/atr/templates/releases-finished.html b/atr/templates/releases-finished.html
index 8c3b0f4e..b1232f25 100644
--- a/atr/templates/releases-finished.html
+++ b/atr/templates/releases-finished.html
@@ -16,19 +16,19 @@
     <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
       {% for release in releases %}
         <div class="col">
-          <div class="card h-100 shadow-sm{% if release.archived %} border-secondary atr-archived-release{% endif %}">
+          <div class="card h-100 shadow-sm{% if release.is_archived %} border-secondary atr-archived-release{% endif %}">
             <div class="card-body d-flex flex-column">
               <div class="d-flex align-items-center gap-2 mb-1">
                 <strong class="card-title fs-5 mb-0">{{ release.version }}</strong>
-                {% if release.archived %}
+                {% if release.is_archived %}
                   <span class="badge bg-secondary" title="This release has been archived">
                     <i class="bi bi-archive me-1"></i>Archived
                   </span>
                 {% endif %}
               </div>
               <p class="card-text text-muted mb-1">Released on {{ format_datetime(release.released or release.created) }}</p>
-              <p class="card-text text-muted small mb-2{% if not release.archived %} invisible{% endif %}">
-                {% if release.archived %}Archived on {{ format_datetime(release.archived) }}{% else %}&nbsp;{% endif %}
+              <p class="card-text text-muted small mb-2{% if not release.is_archived %} invisible{% endif %}">
+                {% if release.archived %}Archived on {{ format_datetime(release.archived) }}{% elif release.is_archived %}Archived{% else %}&nbsp;{% endif %}
               </p>
               <div class="mt-auto">
                 <a href="{{ as_url(get.download.all_selected, project_key=release.project.key, version_key=release.version) }}"
diff --git a/migrations/versions/0098_2026.06.26_9a948e08.py b/migrations/versions/0098_2026.06.26_9a948e08.py
new file mode 100644
index 00000000..4066f80b
--- /dev/null
+++ b/migrations/versions/0098_2026.06.26_9a948e08.py
@@ -0,0 +1,28 @@
+"""Add is_archived to release
+
+Revision ID: 0098_2026.06.26_9a948e08
+Revises: 0097_2026.06.24_6741a40a
+Create Date: 2026-06-26 15:33:58.125528+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0098_2026.06.26_9a948e08"
+down_revision: str | None = "0097_2026.06.24_6741a40a"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    with op.batch_alter_table("release", schema=None) as batch_op:
+        batch_op.add_column(sa.Column("is_archived", sa.Boolean(), nullable=False, server_default=sa.false()))
+    op.execute(sa.text("UPDATE release SET is_archived = 1 WHERE archived IS NOT NULL"))
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("release", schema=None) as batch_op:
+        batch_op.drop_column("is_archived")
diff --git a/tests/unit/test_catalog.py b/tests/unit/test_catalog.py
index ee6d77df..24db804b 100644
--- a/tests/unit/test_catalog.py
+++ b/tests/unit/test_catalog.py
@@ -91,7 +91,7 @@ def test_lifecycle_badge_prefers_lts_then_eol_then_active() -> None:
 def test_only_released_versions_are_downloadable() -> None:
     project = _project()
     released = _release(project, "5.0.2", cycle_key="cassandra-default", released=_NOW)
-    archived = _release(project, "4.1.7", cycle_key="cassandra-default", released=_NOW, archived=_NOW)
+    archived = _release(project, "4.1.7", cycle_key="cassandra-default", released=_NOW, archived=_NOW, is_archived=True)
     artifacts = [
         _artifact(project, "5.0.2", "a-5.0.2.tar.gz", release=released),
         _artifact(project, "4.1.7", "a-4.1.7.tar.gz", release=archived, svn_revision=28114),
@@ -123,7 +123,7 @@ def test_simple_projects_use_the_flat_layout() -> None:
 def test_status_reflects_release_and_archive_state() -> None:
     project = _project()
     released = _release(project, "5.0.2", cycle_key="cassandra-default", released=_NOW)
-    archived = _release(project, "4.1.7", cycle_key="cassandra-default", released=_NOW, archived=_NOW)
+    archived = _release(project, "4.1.7", cycle_key="cassandra-default", released=_NOW, archived=_NOW, is_archived=True)
     artifacts = [
         _artifact(project, "5.0.2", "a-5.0.2.tar.gz", release=released),
         _artifact(project, "4.1.7", "a-4.1.7.tar.gz", release=archived),
@@ -135,6 +135,19 @@ def test_status_reflects_release_and_archive_state() -> None:
     assert versions["4.1.7"].status == "archived"
 
 
+def test_archived_status_holds_without_an_archive_date() -> None:
+    # A release known to be archived but with no date (a catalogued historical release) carries
+    # is_archived alone; it still reads as archived and isn't downloadable
+    project = _project()
+    undated = _release(project, "3.11.0", cycle_key="cassandra-default", released=_NOW, is_archived=True)
+    artifacts = [_artifact(project, "3.11.0", "a-3.11.0.tar.gz", release=undated)]
+
+    versions = {str(v.version): v for v in catalog._versions(artifacts, {}, project.committee_key)}
+
+    assert versions["3.11.0"].status == "archived"
+    assert versions["3.11.0"].artifacts[0].downloadable is False
+
+
 def test_svn_revision_alone_does_not_make_a_version_managed() -> None:
     project = _project()
     artifacts = [_artifact(project, "2.0.0", "a-2.0.0.tar.gz", svn_revision=999, managed=False)]
@@ -242,6 +255,7 @@ def _release(
     cycle_key: str,
     released: datetime.datetime | None = None,
     archived: datetime.datetime | None = None,
+    is_archived: bool = False,
 ) -> sql.Release:
     return sql.Release(
         phase=sql.ReleasePhase.RELEASE,
@@ -252,4 +266,5 @@ def _release(
         created=datetime.datetime(2025, 1, 1, tzinfo=datetime.UTC),
         released=released,
         archived=archived,
+        is_archived=is_archived,
     )
diff --git a/tests/unit/test_decompose.py b/tests/unit/test_decompose.py
index 9958dec1..ff14dfc8 100644
--- a/tests/unit/test_decompose.py
+++ b/tests/unit/test_decompose.py
@@ -116,6 +116,21 @@ _CASES: list[tuple[str, tuple[str, ...], str | None, str | None, str | None, cla
     # has no marker, so it falls to source, but both still attach to kafka 4.3.1
     ("kafka", ("4.3.1",), "kafka_2.13-4.3.1.tgz", None, "4.3.1", _SOURCE),
     ("kafka", ("4.3.1",), "kafka-4.3.1-src.tgz", None, "4.3.1", _SOURCE),
+    # A versioned sub-component inside a release's version dir is an artifact of that release, so
+    # the dir version holds and the component's own version is ignored - otherwise each binding
+    # forks a spurious release (opendal ships a dozen language bindings at independent versions)
+    ("opendal", ("0.45.1",), "apache-opendal-bindings-go-0.0.0-src.tar.gz", None, "0.45.1", _SOURCE),
+    # A component that happens to share the release version still lands on it
+    ("opendal", ("0.45.1",), "apache-opendal-core-0.45.1-src.tar.gz", None, "0.45.1", _SOURCE),
+    # A sub-component versioned higher than its release (an integration at 1.0.2 under 0.51.0) still
+    # collapses onto the release, so it can't mint a spurious higher release
+    ("opendal", ("0.51.0",), "apache-opendal-integrations-compat-1.0.2-src.tar.gz", None, "0.51.0", _SOURCE),
+    # A build variant whose version refines the dir (a hadoop1 build of the 0.98.13 release) keeps
+    # the filename version, since it's the same release built differently
+    ("hbase", ("0.98.13",), "hbase-0.98.13-hadoop1-bin.tar.gz", None, "0.98.13", _BINARY),
+    # A sub-component published in its own name-version dir, not inside a release dir, stays a
+    # distinct release
+    ("hbase", ("hbase-connectors-1.0.0",), "hbase-connectors-1.0.0-src.tar.gz", "hbase-connectors", "1.0.0", _SOURCE),
     # A series dir is still less specific than the filename, which pins the point release
     ("example", ("4.5",), "example-4.5.13.tar.gz", None, "4.5.13", _SOURCE),
     # A leading source/ dir is a build bucket, so a flat TLP release reads from the filename


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org