Skip to content

Semver

semver

Semantic versioning — parse commits and compute next version.

VersionBump dataclass

Result of a semver computation.

Attributes:

Name Type Description
current str

Current tag (e.g. "v0.7.0").

next str

Next tag (e.g. "v0.8.0").

bump str

Bump type ("major", "minor", or "patch").

commits list[str]

One-line commit summaries since last tag.

breaking bool

Whether a breaking change was detected.

Source code in packages/axm-git/src/axm_git/core/semver.py
Python
@dataclass(frozen=True)
class VersionBump:
    """Result of a semver computation.

    Attributes:
        current: Current tag (e.g. ``"v0.7.0"``).
        next: Next tag (e.g. ``"v0.8.0"``).
        bump: Bump type (``"major"``, ``"minor"``, or ``"patch"``).
        commits: One-line commit summaries since last tag.
        breaking: Whether a breaking change was detected.
    """

    current: str
    next: str
    bump: str
    commits: list[str]
    breaking: bool

classify_commit(subject)

Classify a single conventional-commit subject.

Reuses the module regexes so per-commit labelling stays consistent with :func:compute_bump. Internal-public: importable by sibling tools (e.g. release_diff) but intentionally absent from __all__.

Parameters:

Name Type Description Default
subject str

A commit subject line, optionally prefixed by a short hash (<hash> <message>); the hash is stripped when present.

required

Returns:

Type Description
str

(type, breaking) where type is the real conventional type

bool

("feat", "fix", "docs", "refactor", "chore",

tuple[str, bool]

"build", "ci", "perf", "style", "revert", …)

tuple[str, bool]

when the subject carries a conventional prefix, falling back to

tuple[str, bool]

"other" otherwise. breaking is True for feat!:-style

tuple[str, bool]

commits or BREAKING CHANGE: bodies. Display-only: the bump

tuple[str, bool]

logic (:func:compute_bump) is unaffected by the extra types.

Source code in packages/axm-git/src/axm_git/core/semver.py
Python
def classify_commit(subject: str) -> tuple[str, bool]:
    """Classify a single conventional-commit *subject*.

    Reuses the module regexes so per-commit labelling stays consistent
    with :func:`compute_bump`. Internal-public: importable by sibling
    tools (e.g. ``release_diff``) but intentionally absent from ``__all__``.

    Args:
        subject: A commit subject line, optionally prefixed by a short
            hash (``<hash> <message>``); the hash is stripped when present.

    Returns:
        ``(type, breaking)`` where ``type`` is the real conventional type
        (``"feat"``, ``"fix"``, ``"docs"``, ``"refactor"``, ``"chore"``,
        ``"build"``, ``"ci"``, ``"perf"``, ``"style"``, ``"revert"``, …)
        when the subject carries a conventional prefix, falling back to
        ``"other"`` otherwise. ``breaking`` is True for ``feat!:``-style
        commits or ``BREAKING CHANGE:`` bodies. Display-only: the bump
        logic (:func:`compute_bump`) is unaffected by the extra types.
    """
    head, sep, rest = subject.partition(" ")
    msg = rest if sep and _SHORT_HASH_RE.match(head) else subject
    breaking = bool(_BREAKING_RE.match(msg)) or "BREAKING CHANGE:" in msg
    if _FEAT_RE.match(msg):
        return "feat", breaking
    if _FIX_RE.match(msg):
        return "fix", breaking
    match = _CONVENTIONAL_PREFIX_RE.match(msg)
    return (match.group(1) if match else "other"), breaking

compute_bump(commits, current_tag)

Compute the next semver version from commit messages.

Rules (pre-1.0, i.e. major == 0): - feat!: or BREAKING CHANGE:minor bump - feat:minor bump - everything else → patch bump

Rules (post-1.0): - feat!: or BREAKING CHANGE:major bump - feat:minor bump - everything else → patch bump

Accepts both git log --oneline lines (<short-hash> <message>) and raw conventional-commit messages (feat: x). The leading token is stripped only when it matches a short-hash shape (hex, 3-40 chars); otherwise the whole line is treated as the message.

Parameters:

Name Type Description Default
commits list[str]

Commit lines, either oneline (<hash> <msg>) or raw conventional-commit messages.

required
current_tag str

Current version tag (e.g. "v0.7.0").

required

Returns:

Type Description
VersionBump

VersionBump with computed next version.

Source code in packages/axm-git/src/axm_git/core/semver.py
Python
def compute_bump(commits: list[str], current_tag: str) -> VersionBump:
    """Compute the next semver version from commit messages.

    Rules (pre-1.0, i.e. major == 0):
        - ``feat!:`` or ``BREAKING CHANGE:`` → **minor** bump
        - ``feat:`` → **minor** bump
        - everything else → **patch** bump

    Rules (post-1.0):
        - ``feat!:`` or ``BREAKING CHANGE:`` → **major** bump
        - ``feat:`` → **minor** bump
        - everything else → **patch** bump

    Accepts both ``git log --oneline`` lines (``<short-hash> <message>``)
    and raw conventional-commit messages (``feat: x``). The leading token
    is stripped only when it matches a short-hash shape
    (hex, 3-40 chars); otherwise the whole line is treated as the message.

    Args:
        commits: Commit lines, either oneline (``<hash> <msg>``) or raw
            conventional-commit messages.
        current_tag: Current version tag (e.g. ``"v0.7.0"``).

    Returns:
        VersionBump with computed next version.
    """
    logger.info("Computing version bump from %s", current_tag)
    major, minor, patch = parse_tag(current_tag)
    has_breaking, has_feat = _classify_commits(commits)
    bump, next_version = _next_version(
        major, minor, patch, has_breaking=has_breaking, has_feat=has_feat
    )

    return VersionBump(
        current=current_tag,
        next=next_version,
        bump=bump,
        commits=commits,
        breaking=has_breaking,
    )

parse_tag(tag)

Parse a semver tag string into (major, minor, patch).

Parameters:

Name Type Description Default
tag str

Version string, with or without v prefix.

required

Returns:

Type Description
tuple[int, int, int]

Tuple of (major, minor, patch).

Raises:

Type Description
ValueError

If the tag doesn't match semver format.

Source code in packages/axm-git/src/axm_git/core/semver.py
Python
def parse_tag(tag: str) -> tuple[int, int, int]:
    """Parse a semver tag string into ``(major, minor, patch)``.

    Args:
        tag: Version string, with or without ``v`` prefix.

    Returns:
        Tuple of (major, minor, patch).

    Raises:
        ValueError: If the tag doesn't match semver format.
    """
    m = _TAG_RE.match(tag)
    if not m:
        msg = f"Invalid semver tag: {tag!r}"
        raise ValueError(msg)
    return int(m.group(1)), int(m.group(2)), int(m.group(3))