From 0c7184f726267630e2589a731780cecada665e79 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 10 Jan 2026 03:50:42 +0530 Subject: [PATCH 1/4] Honor Registrator branch when creating releases --- tagbot/action/repo.py | 43 ++++++++++++++++++++++++++------- test/action/test_repo.py | 52 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/tagbot/action/repo.py b/tagbot/action/repo.py index 63449ac..c640665 100644 --- a/tagbot/action/repo.py +++ b/tagbot/action/repo.py @@ -329,9 +329,19 @@ def _registry_url(self) -> Optional[str]: raise InvalidProject("Package.toml is missing the 'repo' key") return self.__registry_url - @property - def _release_branch(self) -> str: - """Get the name of the release branch.""" + def _release_branch(self, version: str) -> str: + """Get the name of the release branch for a specific version. + + Priority: + 1. Branch specified by Registrator invocation (from PR body) + 2. Release branch specified in TagBot config + 3. Default branch + """ + # First check if Registrator specified a branch for this version + pr_branch = self._branch_from_registry_pr(version) + if pr_branch: + return pr_branch + # Fall back to config branch or default return self.__release_branch or self._repo.default_branch def _only(self, val: Union[T, List[T]]) -> T: @@ -498,6 +508,23 @@ def _registry_pr(self, version: str) -> Optional[PullRequest]: logger.debug(f"Did not find registry PR for branch {head}") return None + def _branch_from_registry_pr(self, version: str) -> Optional[str]: + """Extract release branch name from registry PR body. + + Registrator includes branch info in PR body like: + - Branch: my-branch + """ + pr = self._registry_pr(version) + if not pr: + return None + # Look for "- Branch: " or "Branch: " in PR body + m = re.search(r"^-?\s*Branch:\s*(.+)$", pr.body, re.MULTILINE) + if m: + branch = m[1].strip() + logger.debug(f"Found branch '{branch}' in registry PR for {version}") + return branch + return None + def _commit_sha_from_registry_pr(self, version: str, tree: str) -> Optional[str]: """Look up the commit SHA of version from its registry PR.""" pr = self._registry_pr(version) @@ -669,9 +696,9 @@ def _commit_sha_of_tag(self, version_tag: str) -> Optional[str]: return resolved_sha return sha - def _commit_sha_of_release_branch(self) -> str: - """Get the latest commit SHA of the release branch.""" - branch = self._repo.get_branch(self._release_branch) + def _commit_sha_of_release_branch(self, version: str) -> str: + """Get the latest commit SHA of the release branch for a specific version.""" + branch = self._repo.get_branch(self._release_branch(version)) return cast(str, branch.commit.sha) def _highest_existing_version(self) -> Optional[VersionInfo]: @@ -1429,10 +1456,10 @@ def create_release(self, version: str, sha: str, is_latest: bool = True) -> None them as latest. """ target = sha - if self._commit_sha_of_release_branch() == sha: + if self._commit_sha_of_release_branch(version) == sha: # If we use as the target, GitHub will show # " commits to since this release" on the release page. - target = self._release_branch + target = self._release_branch(version) version_tag = self._get_version_tag(version) logger.debug(f"Release {version_tag} target: {target}") # Check if a release for this tag already exists before doing work diff --git a/test/action/test_repo.py b/test/action/test_repo.py index 91b4b03..a1859b2 100644 --- a/test/action/test_repo.py +++ b/test/action/test_repo.py @@ -281,9 +281,25 @@ def test_registry_url_missing_repo_key(): def test_release_branch(): r = _repo() r._repo = Mock(default_branch="a") - assert r._release_branch == "a" + r._registry_pr = Mock(return_value=None) + assert r._release_branch("v1.0.0") == "a" + r = _repo(branch="b") - assert r._release_branch == "b" + r._registry_pr = Mock(return_value=None) + assert r._release_branch("v1.0.0") == "b" + + # Test PR branch has highest priority + r = _repo(branch="config-branch") + r._repo = Mock(default_branch="default-branch") + pr_body = "foo\n- Branch: pr-branch\nbar" + r._registry_pr = Mock(return_value=Mock(body=pr_body)) + assert r._release_branch("v1.0.0") == "pr-branch" + + # Test that missing branch in PR falls back to config + r = _repo(branch="config-branch") + r._repo = Mock(default_branch="default-branch") + r._registry_pr = Mock(return_value=Mock(body="no branch here")) + assert r._release_branch("v1.0.0") == "config-branch" def test_only(): @@ -477,6 +493,33 @@ def test_commit_sha_from_registry_pr(logger): assert r._commit_sha_from_registry_pr("v4.5.6", "def") == "sha" +@patch("tagbot.action.repo.logger") +def test_branch_from_registry_pr(logger): + """Test extracting branch from registry PR body.""" + r = _repo() + + # No PR found + r._registry_pr = Mock(return_value=None) + assert r._branch_from_registry_pr("v1.0.0") is None + + # PR body without branch info + r._registry_pr.return_value = Mock(body="foo\nbar\nbaz") + assert r._branch_from_registry_pr("v1.0.0") is None + + # PR body with "- Branch: " format + r._registry_pr.return_value.body = "foo\n- Branch: my-release-branch\nbar" + assert r._branch_from_registry_pr("v1.0.0") == "my-release-branch" + logger.debug.assert_called_with("Found branch 'my-release-branch' in registry PR for v1.0.0") + + # PR body with "Branch: " format (without dash) + r._registry_pr.return_value.body = "foo\nBranch: another-branch\nbar" + assert r._branch_from_registry_pr("v2.0.0") == "another-branch" + + # PR body with extra whitespace + r._registry_pr.return_value.body = "foo\n- Branch: spaced-branch \nbar" + assert r._branch_from_registry_pr("v3.0.0") == "spaced-branch" + + def test_commit_sha_of_tree(): """Test tree→commit lookup using git log cache.""" r = _repo() @@ -673,8 +716,9 @@ def test_version_with_latest_commit_marks_latest_when_newer(logger): def test_commit_sha_of_release_branch(): r = _repo() r._repo = Mock(default_branch="a") + r._registry_pr = Mock(return_value=None) r._repo.get_branch.return_value.commit.sha = "sha" - assert r._commit_sha_of_release_branch() == "sha" + assert r._commit_sha_of_release_branch("v1.0.0") == "sha" r._repo.get_branch.assert_called_with("a") @@ -1020,6 +1064,7 @@ def test_handle_release_branch_subdir(): def test_create_release(): r = _repo(user="user", email="email") r._commit_sha_of_release_branch = Mock(return_value="a") + r._registry_pr = Mock(return_value=None) r._git.create_tag = Mock() r._repo = Mock(default_branch="default") r._repo.create_git_tag.return_value.sha = "t" @@ -1118,6 +1163,7 @@ def test_create_release_handles_existing_release_error(): def test_create_release_subdir(): r = _repo(user="user", email="email", subdir="path/to/Foo.jl") r._commit_sha_of_release_branch = Mock(return_value="a") + r._branch_from_registry_pr = Mock(return_value=None) r._repo.get_contents = Mock( return_value=Mock(decoded_content=b"""name = "Foo"\nuuid="abc-def"\n""") ) From a03521cf363e129b0272abbe4f6f8a721d0dba1f Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 10 Jan 2026 03:55:38 +0530 Subject: [PATCH 2/4] Handle missing registry PR branch lookup safely --- tagbot/action/repo.py | 10 +++++++--- test/action/test_repo.py | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/tagbot/action/repo.py b/tagbot/action/repo.py index c640665..0f59820 100644 --- a/tagbot/action/repo.py +++ b/tagbot/action/repo.py @@ -331,14 +331,18 @@ def _registry_url(self) -> Optional[str]: def _release_branch(self, version: str) -> str: """Get the name of the release branch for a specific version. - + Priority: 1. Branch specified by Registrator invocation (from PR body) 2. Release branch specified in TagBot config 3. Default branch """ # First check if Registrator specified a branch for this version - pr_branch = self._branch_from_registry_pr(version) + try: + pr_branch = self._branch_from_registry_pr(version) + except Exception as e: + logger.debug(f"Skipping registry PR branch lookup: {e}") + pr_branch = None if pr_branch: return pr_branch # Fall back to config branch or default @@ -510,7 +514,7 @@ def _registry_pr(self, version: str) -> Optional[PullRequest]: def _branch_from_registry_pr(self, version: str) -> Optional[str]: """Extract release branch name from registry PR body. - + Registrator includes branch info in PR body like: - Branch: my-branch """ diff --git a/test/action/test_repo.py b/test/action/test_repo.py index a1859b2..57b5268 100644 --- a/test/action/test_repo.py +++ b/test/action/test_repo.py @@ -287,14 +287,14 @@ def test_release_branch(): r = _repo(branch="b") r._registry_pr = Mock(return_value=None) assert r._release_branch("v1.0.0") == "b" - + # Test PR branch has highest priority r = _repo(branch="config-branch") r._repo = Mock(default_branch="default-branch") pr_body = "foo\n- Branch: pr-branch\nbar" r._registry_pr = Mock(return_value=Mock(body=pr_body)) assert r._release_branch("v1.0.0") == "pr-branch" - + # Test that missing branch in PR falls back to config r = _repo(branch="config-branch") r._repo = Mock(default_branch="default-branch") @@ -497,24 +497,26 @@ def test_commit_sha_from_registry_pr(logger): def test_branch_from_registry_pr(logger): """Test extracting branch from registry PR body.""" r = _repo() - + # No PR found r._registry_pr = Mock(return_value=None) assert r._branch_from_registry_pr("v1.0.0") is None - + # PR body without branch info r._registry_pr.return_value = Mock(body="foo\nbar\nbaz") assert r._branch_from_registry_pr("v1.0.0") is None - + # PR body with "- Branch: " format r._registry_pr.return_value.body = "foo\n- Branch: my-release-branch\nbar" assert r._branch_from_registry_pr("v1.0.0") == "my-release-branch" - logger.debug.assert_called_with("Found branch 'my-release-branch' in registry PR for v1.0.0") - + logger.debug.assert_called_with( + "Found branch 'my-release-branch' in registry PR for v1.0.0" + ) + # PR body with "Branch: " format (without dash) r._registry_pr.return_value.body = "foo\nBranch: another-branch\nbar" assert r._branch_from_registry_pr("v2.0.0") == "another-branch" - + # PR body with extra whitespace r._registry_pr.return_value.body = "foo\n- Branch: spaced-branch \nbar" assert r._branch_from_registry_pr("v3.0.0") == "spaced-branch" From 687586ef0ac417828e7260053b963afb3648d7c6 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 10 Jan 2026 04:08:36 +0530 Subject: [PATCH 3/4] Update test/action/test_repo.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/action/test_repo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/action/test_repo.py b/test/action/test_repo.py index 57b5268..654a4f2 100644 --- a/test/action/test_repo.py +++ b/test/action/test_repo.py @@ -1165,7 +1165,7 @@ def test_create_release_handles_existing_release_error(): def test_create_release_subdir(): r = _repo(user="user", email="email", subdir="path/to/Foo.jl") r._commit_sha_of_release_branch = Mock(return_value="a") - r._branch_from_registry_pr = Mock(return_value=None) + r._registry_pr = Mock(return_value=None) r._repo.get_contents = Mock( return_value=Mock(decoded_content=b"""name = "Foo"\nuuid="abc-def"\n""") ) From 1342bcd4436c997e9bd9b0e4a8e26c2cff8a8b07 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 10 Jan 2026 04:09:58 +0530 Subject: [PATCH 4/4] copilot: python --- tagbot/action/repo.py | 2 ++ test/action/test_repo.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/tagbot/action/repo.py b/tagbot/action/repo.py index 0f59820..30fe80e 100644 --- a/tagbot/action/repo.py +++ b/tagbot/action/repo.py @@ -521,6 +521,8 @@ def _branch_from_registry_pr(self, version: str) -> Optional[str]: pr = self._registry_pr(version) if not pr: return None + if not pr.body: + return None # Look for "- Branch: " or "Branch: " in PR body m = re.search(r"^-?\s*Branch:\s*(.+)$", pr.body, re.MULTILINE) if m: diff --git a/test/action/test_repo.py b/test/action/test_repo.py index 654a4f2..8ff1da4 100644 --- a/test/action/test_repo.py +++ b/test/action/test_repo.py @@ -506,6 +506,10 @@ def test_branch_from_registry_pr(logger): r._registry_pr.return_value = Mock(body="foo\nbar\nbaz") assert r._branch_from_registry_pr("v1.0.0") is None + # PR body is None + r._registry_pr.return_value.body = None + assert r._branch_from_registry_pr("v1.0.0") is None + # PR body with "- Branch: " format r._registry_pr.return_value.body = "foo\n- Branch: my-release-branch\nbar" assert r._branch_from_registry_pr("v1.0.0") == "my-release-branch"