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
21 changes: 20 additions & 1 deletion commitizen/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def normalize_tag(
version = self.scheme(version) if isinstance(version, str) else version
tag_format = tag_format or self.tag_format

major, minor, patch = version.release
major, minor, patch = (list(version.release) + [0, 0, 0])[:3]
prerelease = version.prerelease or ""

t = Template(tag_format)
Expand All @@ -245,6 +245,25 @@ def find_tag_for(
) -> GitTag | None:
"""Find the first matching tag for a given version."""
version = self.scheme(version) if isinstance(version, str) else version
release = version.release

# If the requested version is incomplete (e.g., "1.2"), try to find the latest
# matching tag that shares the provided prefix.
if len(release) < 3:
matching_versions: list[tuple[Version, GitTag]] = []
for tag in tags:
try:
tag_version = self.extract_version(tag)
except InvalidVersion:
continue
if tag_version.release[: len(release)] != release:
continue
matching_versions.append((tag_version, tag))

if matching_versions:
_, latest_tag = max(matching_versions, key=lambda vt: vt[0])
return latest_tag

possible_tags = set(self.normalize_tag(version, f) for f in self.tag_formats)
candidates = [t for t in tags if t.name in possible_tags]
if len(candidates) > 1:
Expand Down
29 changes: 29 additions & 0 deletions tests/commands/test_bump_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,8 @@ def test_bump_invalid_manual_version_raises_exception(
"0.1.1",
"0.2.0",
"1.0.0",
"1.2",
"1",
],
)
def test_bump_manual_version(util: UtilFixture, manual_version):
Expand All @@ -842,6 +844,33 @@ def test_bump_manual_version_disallows_major_version_zero(util: UtilFixture):
assert expected_error_message in str(excinfo.value)


@pytest.mark.parametrize(
"initial_version, expected_version_after_bump",
[
("1", "1.1.0"),
("1.2", "1.3.0"),
],
)
def test_bump_version_with_less_components_in_config(
tmp_commitizen_project_initial,
initial_version,
expected_version_after_bump,
util: UtilFixture,
):
tmp_commitizen_project = tmp_commitizen_project_initial(version=initial_version)
util.run_cli("bump", "--yes")

tag_exists = git.tag_exist(expected_version_after_bump)
assert tag_exists is True

for version_file in [
tmp_commitizen_project.join("__version__.py"),
tmp_commitizen_project.join("pyproject.toml"),
]:
with open(version_file) as f:
assert expected_version_after_bump in f.read()


@pytest.mark.parametrize("commit_msg", ("feat: new file", "feat(user): new file"))
def test_bump_with_pre_bump_hooks(
commit_msg, mocker: MockFixture, tmp_commitizen_project, util: UtilFixture
Expand Down
101 changes: 101 additions & 0 deletions tests/test_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from commitizen.git import GitTag
from commitizen.tags import TagRules


def _git_tag(name: str) -> GitTag:
return GitTag(name, "rev", "2024-01-01")


def test_find_tag_for_partial_version_returns_latest_match():
tags = [
_git_tag("1.2.0"),
_git_tag("1.2.2"),
_git_tag("1.2.1"),
_git_tag("1.3.0"),
]

rules = TagRules()

found = rules.find_tag_for(tags, "1.2")

assert found is not None
assert found.name == "1.2.2"


def test_find_tag_for_full_version_remains_exact():
tags = [
_git_tag("1.2.0"),
_git_tag("1.2.2"),
_git_tag("1.2.1"),
]

rules = TagRules()

found = rules.find_tag_for(tags, "1.2.1")

assert found is not None
assert found.name == "1.2.1"


def test_find_tag_for_partial_version_with_prereleases_prefers_latest_version():
tags = [
_git_tag("1.2.0b1"),
_git_tag("1.2.0"),
_git_tag("1.2.1b1"),
]

rules = TagRules()

found = rules.find_tag_for(tags, "1.2")

assert found is not None
# 1.2.1b1 > 1.2.0 so it should be selected
assert found.name == "1.2.1b1"


def test_find_tag_for_partial_version_respects_tag_format():
tags = [
_git_tag("v1.2.0"),
_git_tag("v1.2.1"),
_git_tag("v1.3.0"),
]

rules = TagRules(tag_format="v$version")

found = rules.find_tag_for(tags, "1.2")

assert found is not None
assert found.name == "v1.2.1"

found = rules.find_tag_for(tags, "1")

assert found is not None
assert found.name == "v1.3.0"


def test_find_tag_for_partial_version_returns_none_when_no_match():
tags = [
_git_tag("2.0.0"),
_git_tag("2.1.0"),
]

rules = TagRules()

found = rules.find_tag_for(tags, "1.2")

assert found is None


def test_find_tag_for_partial_version_ignores_invalid_tags():
tags = [
_git_tag("not-a-version"),
_git_tag("1.2.0"),
_git_tag("1.2.1"),
]

rules = TagRules()

found = rules.find_tag_for(tags, "1.2")

assert found is not None
assert found.name == "1.2.1"