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
5 changes: 5 additions & 0 deletions DEVGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ make pytest black flake8 mypy

**Test locations**: `test/action/`, `test/web/`, `test/test_tagbot.py`

> **Warning**: `poetry run pytest` can silently run against a stale copy of
> tagbot installed in the venv's site-packages instead of the local sources,
> making test results not reflect your edits. Use `poetry run python -m pytest`
> (or `make pytest`), which puts the repo root first on `sys.path`.

### What NOT to Do

- ❌ Print statements (use logger)
Expand Down
19 changes: 13 additions & 6 deletions tagbot/action/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1749,21 +1749,28 @@ def commit_sha_of_version(self, version: str) -> Optional[str]:
return self._commit_sha_of_tree(tree)

def branches_of_commit(self, sha: str) -> List[str]:
"""Return short names of non-default remote branches that contain sha."""
"""Return short names of non-default remote branches that contain sha.

Returns an empty list if the default branch contains the commit: a
commit reachable from the default branch is a mainline release, even
if other branches (e.g. feature branches based on it) also contain it
(see issue #574).
"""
try:
output = self._git.command("branch", "-r", "--contains", sha)
default = f"origin/{self._repo.default_branch}"
prefix = "origin/"
prefix_len = len(prefix)
return [
b.strip()[prefix_len:]
for b in output.splitlines()
if b.strip() and " -> " not in b and b.strip() != default
branches = [
b.strip() for b in output.splitlines() if b.strip() and " -> " not in b
]
if default in branches:
return []
return [b[prefix_len:] for b in branches]
except Abort:
logger.debug("Failed to get branches for commit", exc_info=True)
return []

def is_backport_commit(self, sha: str) -> bool:
"""Check if the commit is on a non-default branch."""
"""Check if the commit is only reachable from non-default branches."""
return bool(self.branches_of_commit(sha))
11 changes: 8 additions & 3 deletions test/action/test_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1517,9 +1517,10 @@ def test_is_backport_commit():
r._git.command.return_value = " origin/main\n"
assert not r.is_backport_commit("abc123")

# Commit on default branch and another branch
# Commit on default branch and another branch → mainline, not a backport
# (e.g. a feature branch based on the release commit; issue #574)
r._git.command.return_value = " origin/main\n origin/release-1.0\n"
assert r.is_backport_commit("abc123")
assert not r.is_backport_commit("abc123")

# Commit on non-default branch only
r._git.command.return_value = " origin/release-1.0\n"
Expand Down Expand Up @@ -1557,8 +1558,12 @@ def test_branches_of_commit():
r._git.command.return_value = " origin/main\n"
assert r.branches_of_commit("abc123") == []

# One non-default branch
# Default branch plus other branches → mainline, so empty (issue #574)
r._git.command.return_value = " origin/main\n origin/release-1.0\n"
assert r.branches_of_commit("abc123") == []

# One non-default branch
r._git.command.return_value = " origin/release-1.0\n"
assert r.branches_of_commit("abc123") == ["release-1.0"]

# Multiple non-default branches, no default
Expand Down