Skip to content
Merged
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
14 changes: 14 additions & 0 deletions DEVGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,20 @@ for commit in repo.get_commits(): # Paginated API calls!

### Testing Requirements

**Local development setup**:

```bash
# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install package with dependencies
pip install .

# Install dev tools
pip install pytest pytest-cov black flake8 mypy boto3
```

**Before submitting changes**:

```bash
Expand Down
29 changes: 21 additions & 8 deletions tagbot/action/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union

from github import UnknownObjectException
from github.GitRelease import GitRelease
from github.Issue import Issue
from github.NamedUser import NamedUser
Expand Down Expand Up @@ -49,12 +50,13 @@ def _previous_release(self, version_tag: str) -> Optional[GitRelease]:
cur_ver = VersionInfo.parse(version_tag[i_start:])
prev_ver = VersionInfo(0)
prev_rel = None
tag_prefix = self._repo._tag_prefix()
for r in self._repo._repo.get_releases():
if not r.tag_name.startswith(tag_prefix):
tags = self._repo.get_all_tags()

for tag_name in tags:
if not tag_name.startswith(tag_prefix):
continue
try:
ver = VersionInfo.parse(r.tag_name[i_start:])
ver = VersionInfo.parse(tag_name[i_start:])
except ValueError:
continue
if ver.prerelease or ver.build:
Expand All @@ -63,7 +65,17 @@ def _previous_release(self, version_tag: str) -> Optional[GitRelease]:
# That means if we're creating a backport v1.1, an already existing v2.0,
# despite being newer than v1.0, will not be selected.
if ver < cur_ver and ver > prev_ver:
prev_rel = r
# Get the GitHub release for this tag if it exists
try:
prev_rel = self._repo._repo.get_release(tag_name)
except UnknownObjectException:
# Release doesn't exist - get commit datetime from the tag
commit_time = self._repo._git.time_of_commit(tag_name)
prev_rel = type(
"obj",
(object,),
{"tag_name": tag_name, "created_at": commit_time},
)()
prev_ver = ver
return prev_rel

Expand All @@ -75,8 +87,8 @@ def _is_backport(self, version: str, tags: Optional[List[str]] = None) -> bool:
)

if tags is None:
# Populate the tags list with tag names from the releases
tags = [r.tag_name for r in self._repo._repo.get_releases()]
# Use Git tags instead of GitHub releases
tags = self._repo.get_all_tags()

# Extract any package name prefix and version number from the input
match = version_pattern.match(version)
Expand Down Expand Up @@ -266,7 +278,8 @@ def _collect_data(self, version_tag: str, sha: str) -> Dict[str, object]:
prev_tag = None
compare = None
if previous:
start = previous.created_at
if previous.created_at:
start = previous.created_at
prev_tag = previous.tag_name
compare = f"{self._repo._repo.html_url}/compare/{prev_tag}...{version_tag}"
# When the last commit is a PR merge, the commit happens a second or two before
Expand Down
9 changes: 9 additions & 0 deletions tagbot/action/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,15 @@ def _highest_existing_version(self) -> Optional[VersionInfo]:

return highest

def get_all_tags(self) -> List[str]:
"""Get all Git tag names in the repository.

Returns a list of tag names (without 'refs/tags/' prefix).
Uses the tags cache to avoid repeated API calls.
"""
tags_cache = self._build_tags_cache()
return list(tags_cache.keys())

def version_with_latest_commit(self, versions: Dict[str, str]) -> Optional[str]:
"""Find the version with the most recent commit datetime.

Expand Down
35 changes: 33 additions & 2 deletions test/action/test_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ def test_slug():
def test_previous_release():
c = _changelog()
tags = ["ignore", "v1.2.4-ignore", "v1.2.3", "v1.2.2", "v1.0.2", "v1.0.10"]
c._repo._repo.get_releases = Mock(return_value=[Mock(tag_name=t) for t in tags])
c._repo.get_all_tags = Mock(return_value=tags)
# Mock get_release to return a minimal release-like object
c._repo._repo.get_release = Mock(
side_effect=lambda tag: type("obj", (object,), {"tag_name": tag})()
)
assert c._previous_release("v1.0.0") is None
assert c._previous_release("v1.0.2") is None
rel = c._previous_release("v1.2.5")
Expand All @@ -51,6 +55,29 @@ def test_previous_release():
assert rel and rel.tag_name == "v1.0.2"


def test_previous_release_no_github_release():
"""Test that _previous_release falls back to commit time when no GitHub release."""
from datetime import datetime
from github import UnknownObjectException

c = _changelog()
tags = ["v1.0.0", "v1.1.0"]
c._repo.get_all_tags = Mock(return_value=tags)
# Simulate no GitHub release existing for the tag
c._repo._repo.get_release = Mock(
side_effect=UnknownObjectException(404, "Not Found", {})
)
# Mock time_of_commit to return a datetime
mock_time = datetime(2025, 1, 1, 12, 0, 0)
c._repo._git.time_of_commit = Mock(return_value=mock_time)

rel = c._previous_release("v1.1.1")
assert rel is not None
assert rel.tag_name == "v1.1.0"
assert rel.created_at == mock_time
c._repo._git.time_of_commit.assert_called_with("v1.1.0")


def test_previous_release_subdir():
True
c = _changelog(subdir="Foo")
Expand All @@ -67,7 +94,11 @@ def test_previous_release_subdir():
"v2.0.1",
"Foo-v2.0.0",
]
c._repo._repo.get_releases = Mock(return_value=[Mock(tag_name=t) for t in tags])
c._repo.get_all_tags = Mock(return_value=tags)
# Mock get_release to return a minimal release-like object
c._repo._repo.get_release = Mock(
side_effect=lambda tag: type("obj", (object,), {"tag_name": tag})()
)
assert c._previous_release("Foo-v1.0.0") is None
assert c._previous_release("Foo-v1.0.2") is None
rel = c._previous_release("Foo-v1.2.5")
Expand Down