Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions tagbot/action/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,23 @@ 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
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
return self.__release_branch or self._repo.default_branch

def _only(self, val: Union[T, List[T]]) -> T:
Expand Down Expand Up @@ -498,6 +512,25 @@ 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
if not pr.body:
return None
# Look for "- Branch: <branch_name>" or "Branch: <branch_name>" 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)
Expand Down Expand Up @@ -669,9 +702,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]:
Expand Down Expand Up @@ -1429,10 +1462,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 <branch> as the target, GitHub will show
# "<n> commits to <branch> 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
Expand Down
58 changes: 55 additions & 3 deletions test/action/test_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -477,6 +493,39 @@ 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 is None
r._registry_pr.return_value.body = None
assert r._branch_from_registry_pr("v1.0.0") is None

# PR body with "- Branch: <branch_name>" 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: <branch_name>" 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()
Expand Down Expand Up @@ -673,8 +722,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")


Expand Down Expand Up @@ -1020,6 +1070,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"
Expand Down Expand Up @@ -1118,6 +1169,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._registry_pr = Mock(return_value=None)
r._repo.get_contents = Mock(
return_value=Mock(decoded_content=b"""name = "Foo"\nuuid="abc-def"\n""")
)
Expand Down