Skip to content

Commit

Permalink
fixup! feat: Support PEP440 versioning scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
dummy committed Mar 25, 2024
1 parent c2a53ee commit 16ba83e
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 83 deletions.
1 change: 1 addition & 0 deletions duties.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def clean(ctx: Context) -> None:
Parameters:
ctx: The context instance (passed automatically).
"""

def _rm(*targets: str) -> None:
for target in targets:
ctx.run(f"rm -rf {target}")
Expand Down
101 changes: 26 additions & 75 deletions src/git_changelog/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
import sys
import warnings
from subprocess import CalledProcessError, check_output
from typing import TYPE_CHECKING, ClassVar, Literal, Type, Union
from typing import TYPE_CHECKING, Callable, ClassVar, Literal, Type, Union
from urllib.parse import urlsplit, urlunsplit

from packaging.version import Version as PEP440Version
from semver import Version as SemverVersion

from git_changelog.commit import (
AngularConvention,
BasicConvention,
Expand All @@ -21,74 +18,14 @@
ConventionalCommitConvention,
)
from git_changelog.providers import Bitbucket, GitHub, GitLab, ProviderRefParser
from git_changelog.versioning import ParsedVersion, bump_pep440, bump_semver, parse_pep440, parse_semver

if TYPE_CHECKING:
from pathlib import Path

ConventionType = Union[str, CommitConvention, Type[CommitConvention]]


def version_prefix(version: str) -> tuple[str, str]:
"""Return a version and its optional `v` prefix.
Arguments:
version: The full version.
Returns:
version: The version without its prefix.
prefix: The version prefix.
"""
prefix = ""
if version[0] == "v":
prefix = "v"
version = version[1:]
return version, prefix


def bump_semver(version: str, part: Literal["major", "minor", "patch"] = "patch", *, zerover: bool = True) -> str:
"""Bump a SemVer version.
Arguments:
version: The version to bump.
part: The part of the version to bump (major, minor, or patch).
zerover: Keep major version at zero, even for breaking changes.
Returns:
The bumped version.
"""
version, prefix = version_prefix(version)
semver_version = SemverVersion.parse(version)
if part == "major" and (semver_version.major != 0 or not zerover):
semver_version = semver_version.bump_major()
elif part == "minor" or (part == "major" and semver_version.major == 0):
semver_version = semver_version.bump_minor()
elif part == "patch" and not semver_version.prerelease:
semver_version = semver_version.bump_patch()
return prefix + str(semver_version)


def bump_pep440(version: str, part: Literal["major", "minor", "patch"] = "patch", *, zerover: bool = True) -> str:
"""Bump a PEP440 version.
Arguments:
version: The version to bump.
part: The part of the version to bump (major, minor, or patch).
zerover: Keep major version at zero, even for breaking changes.
Returns:
The bumped version.
"""
version, prefix = version_prefix(version)
pep440_version = PEP440Version(version)
if part == "major" and (pep440_version.major != 0 or not zerover):
semver_version = pep440_version.bump_major()
elif part == "minor" or (part == "major" and pep440_version.major == 0):
semver_version = semver_version.bump_minor()
elif part == "patch" and not pep440_version.prerelease:
semver_version = semver_version.bump_patch()
return prefix + str(semver_version)


class Section:
"""A list of commits grouped by section_type."""

Expand Down Expand Up @@ -225,6 +162,7 @@ def __init__(
bump: str | None = None,
zerover: bool = True,
filter_commits: str | None = None,
versioning: Literal["semver", "pep440"] = "semver",
):
"""Initialization method.
Expand Down Expand Up @@ -293,8 +231,14 @@ def __init__(
self.tag_commits: list[Commit] = [commit for commit in self.commits[1:] if commit.tag]
self.tag_commits.insert(0, self.commits[0])

# get version parser based on selected versioning scheme
version_parser, version_bumper = {
"semver": (parse_semver, bump_semver),
"pep440": (parse_pep440, bump_pep440),
}[versioning]

# apply dates to commits and group them by version
v_list, v_dict = self._group_commits_by_version()
v_list, v_dict = self._group_commits_by_version(version_parser=version_parser)
self.versions_list = v_list
self.versions_dict = v_dict

Expand All @@ -308,7 +252,7 @@ def __init__(
if bump is None:
bump = "auto"
if bump:
self._bump(bump)
self._bump(bump, version_bumper=version_bumper)

# fix a single, initial version to the user specified version or 0.1.0 if none is specified
self._fix_single_version(bump)
Expand Down Expand Up @@ -421,12 +365,19 @@ def parse_commits(self) -> list[Commit]:

return list(commits_map.values())

def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]:
def _group_commits_by_version(
self,
version_parser: Callable[[str], tuple[ParsedVersion, str]],
) -> tuple[list[Version], dict[str, Version]]:
"""Group commits into versions.
Commits are assigned to the version they were first released with.
A commit is assigned to exactly one version.
Parameters:
version_parser: Version parser to use when grouping commits by versions.
Versions that cannot be parsed by the given parser will be ignored.
Returns:
versions_list: The list of versions order descending by timestamp.
versions_dict: A dictionary of versions with the tag name as keys.
Expand All @@ -444,14 +395,14 @@ def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]:

# Find all commits for this version by following the commit graph.
version.add_commit(tag_commit)
previous_semver: SemverVersion | None = None
previous_parsed_version: ParsedVersion | None = None
next_commits = tag_commit.parent_commits # Always new: we can mutate it.
while next_commits:
next_commit = next_commits.pop(0)
if next_commit.tag:
semver, _ = parse_version(next_commit.tag)
if not previous_semver or semver.compare(previous_semver) > 0:
previous_semver = semver
parsed_version, _ = version_parser(next_commit.tag)
if not previous_parsed_version or parsed_version > previous_parsed_version:
previous_parsed_version = parsed_version
previous_versions[version.tag] = next_commit.tag
elif not next_commit.version:
version.add_commit(next_commit)
Expand Down Expand Up @@ -492,7 +443,7 @@ def _assign_previous_versions(self, versions_dict: dict[str, Version], previous_
target=version.tag or "HEAD",
)

def _bump(self, version: str) -> None:
def _bump(self, version: str, version_bumper: Callable[..., str]) -> None:
last_version = self.versions_list[0]
if not last_version.tag and last_version.previous_version:
last_tag = last_version.previous_version.tag
Expand All @@ -506,9 +457,9 @@ def _bump(self, version: str) -> None:
if commit.convention["is_minor"]:
version = "minor"
if version in {"major", "minor", "patch"}:
# bump version (don't fail on non-semver versions)
# bump version
try:
last_version.planned_tag = bump(last_tag, version, zerover=self.zerover) # type: ignore[arg-type]
last_version.planned_tag = version_bumper(last_tag, version, zerover=self.zerover)
except ValueError:
return
else:
Expand Down
Loading

0 comments on commit 16ba83e

Please sign in to comment.