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

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


The following commit(s) were added to refs/heads/sbp by this push:
     new 83eeaf2a Record 3-way merge metadata on revisions
83eeaf2a is described below

commit 83eeaf2ab5846db68691a0ea2da6801990bb4a0b
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Mar 6 15:30:37 2026 +0000

    Record 3-way merge metadata on revisions
---
 atr/models/sql.py                               |  1 +
 atr/storage/writers/revision.py                 | 19 +++++++++++++----
 migrations/versions/0055_2026.03.06_522f2417.py | 27 +++++++++++++++++++++++++
 tests/unit/test_create_revision.py              |  2 ++
 tests/unit/test_create_revision_quarantine.py   |  4 +++-
 5 files changed, 48 insertions(+), 5 deletions(-)

diff --git a/atr/models/sql.py b/atr/models/sql.py
index cbb875b8..07625b6e 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -1287,6 +1287,7 @@ class Revision(sqlmodel.SQLModel, table=True):
     child: Optional["Revision"] = 
sqlmodel.Relationship(back_populates="parent")
 
     description: str | None = sqlmodel.Field(default=None, **example("This is 
a description"))
+    merge_base_revision_name: str | None = sqlmodel.Field(default=None, 
**example("example-0.0.1 00001"))
     tag: str | None = sqlmodel.Field(default=None, **example("rc1"))
     was_quarantined: bool = sqlmodel.Field(default=False, **example(False))
 
diff --git a/atr/storage/writers/revision.py b/atr/storage/writers/revision.py
index e3bcde32..05d2a4cf 100644
--- a/atr/storage/writers/revision.py
+++ b/atr/storage/writers/revision.py
@@ -91,7 +91,7 @@ async def finalise_revision(
     was_quarantined: bool = False,
 ) -> sql.Revision:
     try:
-        previous_attestable, _, merged_release = await _lock_and_merge(
+        previous_attestable, merge_base_revision_name, _, merged_release = 
await _lock_and_merge(
             data,
             base_hashes=base_hashes,
             base_inodes=base_inodes,
@@ -115,6 +115,7 @@ async def finalise_revision(
         data,
         asf_uid=asf_uid,
         description=description,
+        merge_base_revision_name=merge_base_revision_name,
         path_to_hash=path_to_hash,
         path_to_size=path_to_size,
         previous_attestable=previous_attestable,
@@ -132,6 +133,7 @@ async def _commit_new_revision(
     *,
     asf_uid: str,
     description: str | None,
+    merge_base_revision_name: str | None,
     path_to_hash: dict[str, str],
     path_to_size: dict[str, int],
     previous_attestable: atr.models.attestable.AttestableV1 | None,
@@ -154,6 +156,7 @@ async def _commit_new_revision(
             created=datetime.datetime.now(datetime.UTC),
             phase=release.phase,
             description=description,
+            merge_base_revision_name=merge_base_revision_name,
             was_quarantined=was_quarantined,
         )
         data.add(new_revision)
@@ -243,7 +246,7 @@ async def _lock_and_merge(
     _release_name: str,
     temp_dir_path: pathlib.Path,
     version_name: str,
-) -> tuple[atr.models.attestable.AttestableV1 | None, str | None, sql.Release]:
+) -> tuple[atr.models.attestable.AttestableV1 | None, str | None, str | None, 
sql.Release]:
     # Acquire the write lock
     # We need this write lock for moving the directory afterwards atomically
     # But it also helps to make models.populate_revision_sequence_and_name 
safe against races
@@ -255,12 +258,14 @@ async def _lock_and_merge(
     prior_revision_name = latest.name if latest else None
 
     # Merge with the prior revision if there was an intervening change
+    merge_base_revision_name: str | None = None
     if (
         merge_enabled
         and (old_revision is not None)
         and (prior_revision_name is not None)
         and (prior_revision_name != old_revision.name)
     ):
+        merge_base_revision_name = prior_revision_name
         prior_number = prior_revision_name.split()[-1]
         prior_dir = paths.release_directory_base(merged_release) / prior_number
         await merge.merge(
@@ -277,7 +282,7 @@ async def _lock_and_merge(
         )
         previous_attestable = await attestable.load(project_name, 
version_name, prior_number)
 
-    return previous_attestable, prior_revision_name, merged_release
+    return previous_attestable, merge_base_revision_name, prior_revision_name, 
merged_release
 
 
 class GeneralPublic:
@@ -417,7 +422,12 @@ class CommitteeParticipant(FoundationCommitter):
 
         async with SafeSession(temp_dir) as data:
             try:
-                previous_attestable, prior_revision_name, merged_release = 
await _lock_and_merge(
+                (
+                    previous_attestable,
+                    merge_base_revision_name,
+                    prior_revision_name,
+                    merged_release,
+                ) = await _lock_and_merge(
                     data,
                     base_hashes=base_hashes,
                     base_inodes=base_inodes,
@@ -458,6 +468,7 @@ class CommitteeParticipant(FoundationCommitter):
                 data,
                 asf_uid=asf_uid,
                 description=description,
+                merge_base_revision_name=merge_base_revision_name,
                 path_to_hash=path_to_hash,
                 path_to_size=path_to_size,
                 previous_attestable=previous_attestable,
diff --git a/migrations/versions/0055_2026.03.06_522f2417.py 
b/migrations/versions/0055_2026.03.06_522f2417.py
new file mode 100644
index 00000000..6eff6918
--- /dev/null
+++ b/migrations/versions/0055_2026.03.06_522f2417.py
@@ -0,0 +1,27 @@
+"""Add 3-way merge metadata to revisions
+
+Revision ID: 0055_2026.03.06_522f2417
+Revises: 0054_2026.03.02_3799e8e6
+Create Date: 2026-03-06 15:22:14.422556+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0055_2026.03.06_522f2417"
+down_revision: str | None = "0054_2026.03.02_3799e8e6"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    with op.batch_alter_table("revision", schema=None) as batch_op:
+        batch_op.add_column(sa.Column("merge_base_revision_name", 
sa.VARCHAR(), nullable=True))
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("revision", schema=None) as batch_op:
+        batch_op.drop_column("merge_base_revision_name")
diff --git a/tests/unit/test_create_revision.py 
b/tests/unit/test_create_revision.py
index 9b1ec01f..8a94458d 100644
--- a/tests/unit/test_create_revision.py
+++ b/tests/unit/test_create_revision.py
@@ -43,11 +43,13 @@ class FakeRevision:
         created: object,
         phase: sql.ReleasePhase,
         description: str | None,
+        merge_base_revision_name: str | None = None,
         was_quarantined: bool = False,
     ):
         self.asfuid = asfuid
         self.created = created
         self.description = description
+        self.merge_base_revision_name = merge_base_revision_name
         self.name = ""
         self.number = ""
         self.parent_name: str | None = None
diff --git a/tests/unit/test_create_revision_quarantine.py 
b/tests/unit/test_create_revision_quarantine.py
index 33342922..3ece74b8 100644
--- a/tests/unit/test_create_revision_quarantine.py
+++ b/tests/unit/test_create_revision_quarantine.py
@@ -137,7 +137,9 @@ async def 
test_no_quarantine_returns_revision_when_no_archives(tmp_path: pathlib
         mock.patch.object(revision.detection, 
"detect_archives_requiring_quarantine", return_value=[]),
         mock.patch.object(revision.interaction, "latest_revision", 
new_callable=mock.AsyncMock, return_value=None),
         mock.patch.object(revision, "_commit_new_revision", 
new_callable=mock.AsyncMock, return_value=fake_revision),
-        mock.patch.object(revision, "_lock_and_merge", 
new_callable=mock.AsyncMock, return_value=(None, None, release)),
+        mock.patch.object(
+            revision, "_lock_and_merge", new_callable=mock.AsyncMock, 
return_value=(None, None, None, release)
+        ),
         mock.patch.object(revision, "SafeSession", 
return_value=MockQuarantineSession(MockQuarantineData(None))),
         mock.patch.object(revision.paths, "get_tmp_dir", 
return_value=tmp_path),
         mock.patch.object(revision.util, "chmod_directories"),


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to