Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: python-semantic-release/python-semantic-release
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v9.0.2
Choose a base ref
...
head repository: python-semantic-release/python-semantic-release
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v9.0.3
Choose a head ref
  • 6 commits
  • 5 files changed
  • 4 contributors

Commits on Feb 8, 2024

  1. chore: modernize ruff configuration to work with ruff >= 0.2

    afuetterer authored and relekang committed Feb 8, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    613d240 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    2c8a36e View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    02df305 View commit details
  4. perf(algorithm): refactor bfs search to use queue rather than recursion

    codejedi365 authored and relekang committed Feb 8, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    8b742d3 View commit details
  5. style: beautify 8b742d3

    actions-user committed Feb 8, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    f95be0c View commit details
  6. 9.0.3

    Automatically generated by python-semantic-release
    semantic-release committed Feb 8, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c90b2f5 View commit details
Showing with 155 additions and 46 deletions.
  1. +23 −0 CHANGELOG.md
  2. +18 −17 pyproject.toml
  3. +1 −1 semantic_release/__init__.py
  4. +45 −26 semantic_release/version/algorithm.py
  5. +68 −2 tests/unit/semantic_release/version/test_algorithm.py
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,29 @@



## v9.0.3 (2024-02-08)

### Chore

* chore: modernize ruff configuration to work with ruff >= 0.2 ([`613d240`](https://github.com/python-semantic-release/python-semantic-release/commit/613d240499c081b185c5774d1d8a4665d3f5cc28))

### Fix

* fix(algorithm): correct bfs to not abort on previously visited node ([`02df305`](https://github.com/python-semantic-release/python-semantic-release/commit/02df305db43abfc3a1f160a4a52cc2afae5d854f))

### Performance

* perf(algorithm): refactor bfs search to use queue rather than recursion ([`8b742d3`](https://github.com/python-semantic-release/python-semantic-release/commit/8b742d3db6652981a7b5f773a74b0534edc1fc15))

### Style

* style: beautify 8b742d3db6652981a7b5f773a74b0534edc1fc15 ([`f95be0c`](https://github.com/python-semantic-release/python-semantic-release/commit/f95be0c207e499e214b12c578825bfcee0f2bbf8))

### Test

* test(algorithm): add bfs unit test on fake git history ([`2c8a36e`](https://github.com/python-semantic-release/python-semantic-release/commit/2c8a36ea5b1d1fb19cfe90a3b8a1bce5077c717c))


## v9.0.2 (2024-02-08)

### Chore
35 changes: 18 additions & 17 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "python-semantic-release"
version = "9.0.2"
version = "9.0.3"
description = "Automatic Semantic Versioning for Python projects"
requires-python = ">=3.8"
license = { text = "MIT" }
@@ -163,9 +163,17 @@ allow_incomplete_defs = true
allow_untyped_calls = true

[tool.ruff]
line-length = 88
target-version = "py38"
force-exclude = true
output-format = "grouped"
show-fixes = true
src = ["semantic_release", "tests"]

[tool.ruff.lint]
select = ["ALL"]

# See https://beta.ruff.rs/docs/rules
# See https://docs.astral.sh/ruff/rules/
# for any of these codes you can also run `ruff rule [CODE]`
# which explains it in the terminal
ignore = [
@@ -257,22 +265,15 @@ ignore = [
]

external = ["V"]
target-version = "py38"
force-exclude = true
line-length = 88
output-format = "grouped"
ignore-init-module-imports = true
show-source = true
show-fixes = true
src = ["semantic_release", "tests"]
task-tags = ["NOTE", "TODO", "FIXME", "XXX"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"

[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
# Imported but unused
"__init__.py" = ["F401"]
# pydantic 1 can't handle __future__ annotations-enabled syntax on < 3.10
@@ -306,28 +307,28 @@ line-ending = "lf"
"ANN",
]

[tool.ruff.mccabe]
[tool.ruff.lint.mccabe]
max-complexity = 10

[tool.ruff.flake8-implicit-str-concat]
[tool.ruff.lint.flake8-implicit-str-concat]
allow-multiline = true

[tool.ruff.flake8-quotes]
[tool.ruff.lint.flake8-quotes]
inline-quotes = "double"
multiline-quotes = "double"

[tool.ruff.flake8-tidy-imports]
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.ruff.flake8-type-checking]
[tool.ruff.lint.flake8-type-checking]
strict = true

[tool.ruff.flake8-pytest-style]
[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false
parametrize-names-type = "csv"

[tool.ruff.isort]
[tool.ruff.lint.isort]
# required-imports = ["from __future__ import annotations"]
combine-as-imports = true
known-first-party = ["semantic_release"]
2 changes: 1 addition & 1 deletion semantic_release/__init__.py
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@
tags_and_versions,
)

__version__ = "9.0.2"
__version__ = "9.0.3"


def setup_hook(argv: list[str]) -> None:
71 changes: 45 additions & 26 deletions semantic_release/version/algorithm.py
Original file line number Diff line number Diff line change
@@ -71,48 +71,67 @@ def _bfs_for_latest_version_in_history(
`merge_base`'s parents' history. If no commits in the history correspond
to a released version, return None
"""
tag_sha_2_version_lookup = {
tag.commit.hexsha: version for tag, version in full_release_tags_and_versions
}

# Step 3. Latest full release version within the history of the current branch
# Breadth-first search the merge-base and its parent commits for one which matches
# the tag of the latest full release tag in history
def bfs(visited: set[Commit], q: Queue[Commit]) -> Version | None:
if q.empty():
log.debug("queue is empty, returning none")
return None
def bfs(start_commit: Commit | TagObject | Blob | Tree) -> Version | None:
# Derived from Geeks for Geeks
# https://www.geeksforgeeks.org/python-program-for-breadth-first-search-or-bfs-for-a-graph/?ref=lbp

node = q.get()
if node in visited:
log.debug("commit %s already visited, returning none", node.hexsha)
return None
# Create a queue for BFS
q: Queue[Commit | TagObject | Blob | Tree] = Queue()

for tag, version in full_release_tags_and_versions:
log.debug(
"checking if tag %r (%s) matches commit %s",
tag.name,
tag.commit.hexsha,
node.hexsha,
)
if tag.commit == node:
# Create a set to store visited graph nodes (commit objects in this case)
visited: set[Commit | TagObject | Blob | Tree] = set()

# Add the source node in the queue & mark as visited to start the search
q.put(start_commit)
visited.add(start_commit)

# Initialize the result to None
result = None

# Traverse the git history until it finds a version tag if one exists
while not q.empty():
node = q.get()
visited.add(node)

log.debug("checking if commit %s matches any tags", node.hexsha)
version = tag_sha_2_version_lookup.get(node.hexsha, None)

if version is not None:
log.info(
"found latest version in branch history: %r (%s)",
str(version),
node.hexsha[:7],
)
return version
result = version
break

log.debug("commit %s doesn't match any tags", node.hexsha)

visited.add(node)
for parent in node.parents:
log.debug("queuing parent commit %s", parent.hexsha)
q.put(parent)
# Add all parent commits to the queue if they haven't been visited
for parent in node.parents:
if parent in visited:
log.debug("parent commit %s already visited", node.hexsha)
continue

log.debug("queuing parent commit %s", parent.hexsha)
q.put(parent)

return bfs(visited, q)
return result

q: Queue[Commit] = Queue()
q.put(merge_base)
latest_version = bfs(set(), q)
# Run a Breadth First Search to find the latest version in the history
latest_version = bfs(merge_base)
if latest_version is not None:
log.info("the latest version in this branch's history is %s", latest_version)
else:
log.info("no version tags found in this branch's history")

log.info("the latest version in this branch's history is %s", latest_version)
return latest_version


70 changes: 68 additions & 2 deletions tests/unit/semantic_release/version/test_algorithm.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,78 @@
import pytest
from git import Repo
from git import Commit, Repo, TagReference

from semantic_release.enums import LevelBump
from semantic_release.version.algorithm import _increment_version, tags_and_versions
from semantic_release.version.algorithm import (
_bfs_for_latest_version_in_history,
_increment_version,
tags_and_versions,
)
from semantic_release.version.translator import VersionTranslator
from semantic_release.version.version import Version


def test_bfs_for_latest_version_in_history():
# Setup fake git graph
"""
* merge commit 6 (start)
|\
| * commit 5
| * commit 4
|/
* commit 3
* commit 2
* commit 1
* v1.0.0
"""
repo = Repo()
expected_version = Version.parse("1.0.0")
v1_commit = Commit(repo, binsha=b"0" * 20)

class TagReferenceOverride(TagReference):
commit = v1_commit # type: ignore - mocking the commit property

v1_tag = TagReferenceOverride(repo, "refs/tags/v1.0.0", check_path=False)

trunk = Commit(
repo,
binsha=b"3" * 20,
parents=[
Commit(
repo,
binsha=b"2" * 20,
parents=[
Commit(repo, binsha=b"1" * 20, parents=[v1_commit]),
],
),
],
)
start_commit = Commit(
repo,
binsha=b"6" * 20,
parents=[
trunk,
Commit(
repo,
binsha=b"5" * 20,
parents=[
Commit(repo, binsha=b"4" * 20, parents=[trunk]),
],
),
],
)

# Execute
actual = _bfs_for_latest_version_in_history(
start_commit,
[
(v1_tag, expected_version),
],
)

# Verify
assert expected_version == actual


@pytest.mark.parametrize(
"tags, sorted_tags",
[