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

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

commit 790ca41aa2f07ff8dada3da289e59ea9094acd2b
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Jan 30 18:36:39 2026 +0000

    Add a release policy setting for the upstream branch in compose
---
 atr/api/__init__.py                             | 45 +++++++++++++------------
 atr/get/projects.py                             |  1 +
 atr/models/api.py                               |  1 +
 atr/models/sql.py                               |  8 +++++
 atr/shared/projects.py                          |  8 +++++
 atr/storage/writers/policy.py                   |  1 +
 migrations/versions/0046_2026.01.30_72330898.py | 27 +++++++++++++++
 tests/e2e/policy/conftest.py                    |  2 ++
 tests/e2e/policy/helpers.py                     |  8 +++++
 tests/e2e/policy/test_post.py                   | 32 ++++++++++++++++++
 10 files changed, 111 insertions(+), 22 deletions(-)

diff --git a/atr/api/__init__.py b/atr/api/__init__.py
index b5b28a0..b1041e6 100644
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -283,28 +283,6 @@ async def distribute_ssh_register(data: 
models.api.DistributeSshRegisterArgs) ->
     ).model_dump(), 200
 
 
[email protected]("/distribute/task/status", methods=["POST"])
-@quart_schema.validate_request(models.api.DistributeStatusUpdateArgs)
-async def update_distribution_task_status(data: 
models.api.DistributeStatusUpdateArgs) -> DictResponse:
-    """
-    Update the status of a distribution task
-    """
-    _payload, _asf_uid = await 
interaction.validate_trusted_jwt(data.publisher, data.jwt)
-    async with db.session() as db_data:
-        status = await db_data.workflow_status(
-            workflow_id=data.workflow,
-            project_name=data.project_name,
-            run_id=int(data.run_id),
-        ).demand(exceptions.NotFound(f"Workflow {data.workflow} not found"))
-        status.status = data.status
-        status.message = data.message
-        await db_data.commit()
-    return models.api.DistributeStatusUpdateResults(
-        endpoint="/distribute/task/status",
-        success=True,
-    ).model_dump(), 200
-
-
 @api.route("/distribution/record", methods=["POST"])
 @jwtoken.require
 @quart_schema.security_scheme([{"BearerAuth": []}])
@@ -673,6 +651,7 @@ async def project_policy(name: str) -> DictResponse:
         policy_binary_artifact_paths=project.policy_binary_artifact_paths,
         
policy_github_compose_workflow_path=project.policy_github_compose_workflow_path,
         
policy_github_finish_workflow_path=project.policy_github_finish_workflow_path,
+        
policy_github_repository_branch=project.policy_github_repository_branch,
         policy_github_repository_name=project.policy_github_repository_name,
         
policy_github_vote_workflow_path=project.policy_github_vote_workflow_path,
         policy_license_check_mode=project.policy_license_check_mode,
@@ -1237,6 +1216,28 @@ async def tasks_list(query_args: 
models.api.TasksListQuery) -> DictResponse:
     ).model_dump(), 200
 
 
[email protected]("/distribute/task/status", methods=["POST"])
+@quart_schema.validate_request(models.api.DistributeStatusUpdateArgs)
+async def update_distribution_task_status(data: 
models.api.DistributeStatusUpdateArgs) -> DictResponse:
+    """
+    Update the status of a distribution task
+    """
+    _payload, _asf_uid = await 
interaction.validate_trusted_jwt(data.publisher, data.jwt)
+    async with db.session() as db_data:
+        status = await db_data.workflow_status(
+            workflow_id=data.workflow,
+            project_name=data.project_name,
+            run_id=int(data.run_id),
+        ).demand(exceptions.NotFound(f"Workflow {data.workflow} not found"))
+        status.status = data.status
+        status.message = data.message
+        await db_data.commit()
+    return models.api.DistributeStatusUpdateResults(
+        endpoint="/distribute/task/status",
+        success=True,
+    ).model_dump(), 200
+
+
 @api.route("/user/info")
 @rate_limiter.rate_limit(10, datetime.timedelta(hours=1))
 @jwtoken.require
diff --git a/atr/get/projects.py b/atr/get/projects.py
index 8703c57..3075375 100644
--- a/atr/get/projects.py
+++ b/atr/get/projects.py
@@ -340,6 +340,7 @@ def _render_compose_form(project: sql.Project) -> 
htm.Element:
                 "source_excludes_rat": 
"\n".join(project.policy_source_excludes_rat),
                 "binary_artifact_paths": 
"\n".join(project.policy_binary_artifact_paths),
                 "github_repository_name": 
project.policy_github_repository_name or "",
+                "github_repository_branch": 
project.policy_github_repository_branch or "",
                 "github_compose_workflow_path": 
"\n".join(project.policy_github_compose_workflow_path),
                 "file_tag_mappings": atr_tag_yaml,
                 "strict_checking": project.policy_strict_checking,
diff --git a/atr/models/api.py b/atr/models/api.py
index 8a242f6..9464a63 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -295,6 +295,7 @@ class ProjectPolicyResults(schema.Strict):
     policy_binary_artifact_paths: list[str]
     policy_github_compose_workflow_path: list[str]
     policy_github_finish_workflow_path: list[str]
+    policy_github_repository_branch: str
     policy_github_repository_name: str
     policy_github_vote_workflow_path: list[str]
     policy_license_check_mode: sql.LicenseCheckMode
diff --git a/atr/models/sql.py b/atr/models/sql.py
index cdbc51f..0dc7780 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -738,6 +738,12 @@ Thanks,
             return ""
         return policy.github_repository_name
 
+    @property
+    def policy_github_repository_branch(self) -> str:
+        if (policy := self.release_policy) is None:
+            return ""
+        return policy.github_repository_branch
+
     @property
     def policy_github_compose_workflow_path(self) -> list[str]:
         if (policy := self.release_policy) is None:
@@ -1090,6 +1096,7 @@ class ReleasePolicy(sqlmodel.SQLModel, table=True):
     )
     strict_checking: bool = sqlmodel.Field(default=False)
     github_repository_name: str = sqlmodel.Field(default="")
+    github_repository_branch: str = sqlmodel.Field(default="")
     github_compose_workflow_path: list[str] = sqlmodel.Field(
         default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, 
nullable=False)
     )
@@ -1128,6 +1135,7 @@ class ReleasePolicy(sqlmodel.SQLModel, table=True):
             source_excludes_rat=list(self.source_excludes_rat),
             strict_checking=self.strict_checking,
             github_repository_name=self.github_repository_name,
+            github_repository_branch=self.github_repository_branch,
             
github_compose_workflow_path=list(self.github_compose_workflow_path),
             github_vote_workflow_path=list(self.github_vote_workflow_path),
             github_finish_workflow_path=list(self.github_finish_workflow_path),
diff --git a/atr/shared/projects.py b/atr/shared/projects.py
index 23360ca..88162e6 100644
--- a/atr/shared/projects.py
+++ b/atr/shared/projects.py
@@ -136,6 +136,10 @@ class ComposePolicyForm(form.Form):
         "GitHub repository name",
         "The name of the GitHub repository to use for the release, excluding 
the apache/ prefix.",
     )
+    github_repository_branch: str = form.label(
+        "GitHub repository branch",
+        "Branch used for release builds (for example, main or 2.5.x). 
Optional.",
+    )
     github_compose_workflow_path: str = form.label(
         "GitHub compose workflow paths",
         "The full paths to the GitHub workflows to use for the release, 
including the .github/workflows/ prefix.",
@@ -154,12 +158,16 @@ class ComposePolicyForm(form.Form):
     @pydantic.model_validator(mode="after")
     def validate_github_fields(self) -> ComposePolicyForm:
         github_repository_name = self.github_repository_name.strip()
+        github_repository_branch = self.github_repository_branch.strip()
         compose_raw = self.github_compose_workflow_path or ""
         compose = [p.strip() for p in compose_raw.split("\n") if p.strip()]
 
         if compose and (not github_repository_name):
             raise ValueError("GitHub repository name is required when any 
workflow path is set.")
 
+        if github_repository_branch and (not github_repository_name):
+            raise ValueError("GitHub repository name is required when a GitHub 
branch is set.")
+
         if github_repository_name and ("/" in github_repository_name):
             raise ValueError("GitHub repository name must not contain a 
slash.")
 
diff --git a/atr/storage/writers/policy.py b/atr/storage/writers/policy.py
index fc264b1..8c37638 100644
--- a/atr/storage/writers/policy.py
+++ b/atr/storage/writers/policy.py
@@ -124,6 +124,7 @@ class CommitteeMember(CommitteeParticipant):
         release_policy.source_excludes_rat = 
_split_lines_verbatim(form.source_excludes_rat)
         release_policy.binary_artifact_paths = 
_split_lines(form.binary_artifact_paths)
         release_policy.github_repository_name = 
form.github_repository_name.strip()
+        release_policy.github_repository_branch = 
form.github_repository_branch.strip()
         release_policy.github_compose_workflow_path = 
_split_lines(form.github_compose_workflow_path)
         release_policy.file_tag_mappings = atr_tags_dict
         release_policy.strict_checking = form.strict_checking
diff --git a/migrations/versions/0046_2026.01.30_72330898.py 
b/migrations/versions/0046_2026.01.30_72330898.py
new file mode 100644
index 0000000..43bacd2
--- /dev/null
+++ b/migrations/versions/0046_2026.01.30_72330898.py
@@ -0,0 +1,27 @@
+"""Add a GitHub repository branch property to release policies
+
+Revision ID: 0046_2026.01.30_72330898
+Revises: 0045_2026.01.30_9664bcb9
+Create Date: 2026-01-30 17:27:25.246498+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0046_2026.01.30_72330898"
+down_revision: str | None = "0045_2026.01.30_9664bcb9"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    with op.batch_alter_table("releasepolicy", schema=None) as batch_op:
+        batch_op.add_column(sa.Column("github_repository_branch", sa.String(), 
nullable=False, server_default=""))
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("releasepolicy", schema=None) as batch_op:
+        batch_op.drop_column("github_repository_branch")
diff --git a/tests/e2e/policy/conftest.py b/tests/e2e/policy/conftest.py
index 2918137..61affc8 100644
--- a/tests/e2e/policy/conftest.py
+++ b/tests/e2e/policy/conftest.py
@@ -42,5 +42,7 @@ def _clear_policy_excludes(page: Page) -> None:
     helpers.visit(page, policy_helpers.PROJECT_URL)
     policy_helpers.textarea_source_excludes_lightweight(page).fill("")
     policy_helpers.textarea_source_excludes_rat(page).fill("")
+    policy_helpers.input_github_repository_name(page).fill("")
+    policy_helpers.input_github_repository_branch(page).fill("")
     policy_helpers.compose_form_save_button(page).click()
     page.wait_for_load_state()
diff --git a/tests/e2e/policy/helpers.py b/tests/e2e/policy/helpers.py
index c9bcc09..f2e2474 100644
--- a/tests/e2e/policy/helpers.py
+++ b/tests/e2e/policy/helpers.py
@@ -27,6 +27,14 @@ def compose_form_save_button(page: Page) -> Locator:
     return page.locator('form.atr-canary button[type="submit"]').first
 
 
+def input_github_repository_branch(page: Page) -> Locator:
+    return page.locator('input[name="github_repository_branch"]')
+
+
+def input_github_repository_name(page: Page) -> Locator:
+    return page.locator('input[name="github_repository_name"]')
+
+
 def textarea_source_excludes_lightweight(page: Page) -> Locator:
     return page.locator('textarea[name="source_excludes_lightweight"]')
 
diff --git a/tests/e2e/policy/test_post.py b/tests/e2e/policy/test_post.py
index d3f6218..768a0c9 100644
--- a/tests/e2e/policy/test_post.py
+++ b/tests/e2e/policy/test_post.py
@@ -20,6 +20,38 @@ import e2e.policy.helpers as helpers
 from playwright.sync_api import Page, expect
 
 
+def test_github_repository_branch_can_be_cleared(page_project: Page) -> None:
+    repo_input = helpers.input_github_repository_name(page_project)
+    repo_input.fill("tooling-actions")
+    branch_input = helpers.input_github_repository_branch(page_project)
+    branch_input.fill("main")
+    helpers.compose_form_save_button(page_project).click()
+    page_project.wait_for_load_state()
+
+    root_helpers.visit(page_project, helpers.PROJECT_URL)
+    branch_input = helpers.input_github_repository_branch(page_project)
+    branch_input.fill("")
+    helpers.compose_form_save_button(page_project).click()
+    page_project.wait_for_load_state()
+
+    root_helpers.visit(page_project, helpers.PROJECT_URL)
+    branch_input = helpers.input_github_repository_branch(page_project)
+    expect(branch_input).to_have_value("")
+
+
+def test_github_repository_branch_value_persists(page_project: Page) -> None:
+    repo_input = helpers.input_github_repository_name(page_project)
+    repo_input.fill("tooling-actions")
+    branch_input = helpers.input_github_repository_branch(page_project)
+    branch_input.fill("2.5.x")
+    helpers.compose_form_save_button(page_project).click()
+    page_project.wait_for_load_state()
+
+    root_helpers.visit(page_project, helpers.PROJECT_URL)
+    branch_input = helpers.input_github_repository_branch(page_project)
+    expect(branch_input).to_have_value("2.5.x")
+
+
 def test_source_excludes_lightweight_can_be_cleared(page_project: Page) -> 
None:
     textarea = helpers.textarea_source_excludes_lightweight(page_project)
     textarea.fill("*.min.js")


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

Reply via email to