diff --git a/release-automation/src/stackbrew_generator/git_operations.py b/release-automation/src/stackbrew_generator/git_operations.py index 14dab035c..493c7e4a4 100644 --- a/release-automation/src/stackbrew_generator/git_operations.py +++ b/release-automation/src/stackbrew_generator/git_operations.py @@ -67,7 +67,7 @@ def list_remote_tags(self, major_version: int) -> List[Tuple[str, str]]: console.print(f"[dim]Listing remote tags for v{major_version}.*[/dim]") cmd = [ - "git", "ls-remote", "--refs", "--tags", + "git", "ls-remote", "--tags", self.remote, f"refs/tags/v{major_version}.*" ] @@ -76,11 +76,28 @@ def list_remote_tags(self, major_version: int) -> List[Tuple[str, str]]: if not result.stdout.strip(): raise GitOperationError(f"No tags found for major version {major_version}") - tags = [] + tag_commits = {} + + # always use peeled commits for annotated tags + # for annotated tags git ls-remote prints tag object hash, then actual commit hash with ^{} suffix + # https://stackoverflow.com/a/25996877 for line in result.stdout.strip().split('\n'): if line: commit, ref = line.split('\t', 1) - tags.append((commit, ref)) + if ref.endswith('^{}'): + # This is a peeled ref - extract the tag name + # always rewrite tag commits with peeled ref commits + tag_name = ref[:-3] # Remove '^{}' + tag_commits[tag_name] = commit + else: + # This is a regular tag + # rewrite only if not yet exists + if ref not in tag_commits: + tag_commits[ref] = commit + + tags = [] + for tag_ref, commit in tag_commits.items(): + tags.append((commit, tag_ref)) console.print(f"[dim]Found {len(tags)} tags[/dim]") return tags diff --git a/release-automation/tests/test_git_operations.py b/release-automation/tests/test_git_operations.py new file mode 100644 index 000000000..fddf2f4cb --- /dev/null +++ b/release-automation/tests/test_git_operations.py @@ -0,0 +1,41 @@ +"""Tests for git operations.""" + +from unittest.mock import Mock, patch + +from stackbrew_generator.git_operations import GitClient + + +class TestGitClient: + """Tests for GitClient class.""" + + @patch('stackbrew_generator.git_operations.console') + @patch.object(GitClient, '_run_command') + def test_list_remote_tags(self, mock_run_command, mock_console): + """ + Test that list_remote_tags returns the expected format for peeled refs. + + For tags with ^{} suffix, use the commit hash from that line. + For tags without ^{} suffix, use the commit hash from the tag line. + """ + mock_result = Mock() + mock_result.stdout = """101262a8cf05b98137d88bc17e77db90c24cc783\trefs/tags/v8.0.3 +2277e5ead99f0caacbd90e0d04693adb27ebfaa6\trefs/tags/v8.0.4^{} +c0125f5be8786d556a9d3edd70f51974fe045c1a\trefs/tags/v8.0.4 +f76d8a1cc979be6202aed93efddd2a0ebbfa0209\trefs/tags/v8.2.2 +c5846c13383c8b284897ff171e0c946868ed4c8c\trefs/tags/v8.2.2^{}""" + mock_run_command.return_value = mock_result + + client = GitClient() + result = client.list_remote_tags(8) + + # Expected behavior: + # - v8.0.3 has no peeled ref, so use the tag commit + # - v8.0.4 has a peeled ref, so use the peeled commit (2277e5ead99f0caacbd90e0d04693adb27ebfaa6) + # - v8.2.2 has a peeled ref, so use the peeled commit (c5846c13383c8b284897ff171e0c946868ed4c8c) + expected_result = [ + ("101262a8cf05b98137d88bc17e77db90c24cc783", "refs/tags/v8.0.3"), # No peeled ref + ("2277e5ead99f0caacbd90e0d04693adb27ebfaa6", "refs/tags/v8.0.4"), # Use peeled ref + ("c5846c13383c8b284897ff171e0c946868ed4c8c", "refs/tags/v8.2.2") # Use peeled ref + ] + + assert result == expected_result