"""Module for Git tag-related constants and functions."""
from __future__ import annotations
import subprocess
import typing
from dataclasses import dataclass
from devops import __GLOBAL_CONFIG__
if typing.TYPE_CHECKING:
from devops.config import GitConfig
# TODO(97gamjak): centralize exception handling
# https://github.com/97gamjak/devops/issues/24
[docs]
class GitTagError(Exception):
"""Exception raised for Git tag-related errors in devops checks."""
def __init__(self, message: str) -> None:
"""Initialize the exception with a message."""
super().__init__(f"GitTagError: {message}")
self.message = message
[docs]
@dataclass(frozen=True, order=True)
class GitTag:
"""Class representing a Git tag."""
major: int
minor: int
patch: int
prefix: str
def __str__(self) -> str:
"""Return the string representation of the Git tag.
Returns
-------
str
The string representation of the Git tag
in the format 'v<major>.<minor>.<patch>'.
"""
return f"{self.prefix}{self.major}.{self.minor}.{self.patch}"
[docs]
def increase_major(self) -> GitTag:
"""Increase the major version by 1 and reset minor and patch to 0.
Returns
-------
GitTag
A new GitTag instance with the increased major version.
"""
return GitTag(self.major + 1, 0, 0, self.prefix)
[docs]
def increase_minor(self) -> GitTag:
"""Increase the minor version by 1 and reset patch to 0.
Returns
-------
GitTag
A new GitTag instance with the increased minor version.
"""
return GitTag(self.major, self.minor + 1, 0, self.prefix)
[docs]
def increase_patch(self) -> GitTag:
"""Increase the patch version by 1.
Returns
-------
GitTag
A new GitTag instance with the increased patch version.
"""
return GitTag(self.major, self.minor, self.patch + 1, self.prefix)
[docs]
@staticmethod
def from_string(tag: str, config: GitConfig = __GLOBAL_CONFIG__.git) -> GitTag:
"""Create a GitTag instance from a string.
Parameters
----------
tag: str
The Git tag string in the format '<prefix><major>.<minor>.<patch>'.
config: GitConfig
The Git configuration containing the expected prefix.
Returns
-------
GitTag
The GitTag instance created from the string.
Raises
------
GitTagError
If the tag string does not start with the expected prefix.
If the tag string is not in the correct format.
"""
original_tag = tag
prefix = config.tag_prefix
if not tag.startswith(prefix):
msg = (
f"Tag '{original_tag}' does not start "
f"with the expected prefix '{prefix}'"
)
raise GitTagError(msg)
tag = tag.removeprefix(prefix)
parts = tag.split(".")
# TODO(97gamjak): implement support for different version schemes
# https://97gamjak.atlassian.net/browse/DEV-49
if len(parts) != 3: # noqa: PLR2004 this will be removed and cleaned up with further naming schemes
msg = f"Invalid tag format: {original_tag}"
raise GitTagError(msg)
try:
major, minor, patch = map(int, parts)
except ValueError as exc:
msg = f"Invalid numeric components in tag: {original_tag}"
raise GitTagError(msg) from exc
return GitTag(major, minor, patch, prefix)
[docs]
def get_latest_tag(
config: GitConfig = __GLOBAL_CONFIG__.git,
) -> GitTag:
"""Get the latest Git tag in the repository.
Parameters
----------
config: GitConfig
The Git configuration containing the expected prefix
and empty tag list allowance.
Returns
-------
GitTag
The latest Git tag. If no tags exist, returns GitTag(0, 0, 0, prefix).
"""
tags = get_all_tags(config=config)
if not tags:
return GitTag(0, 0, 0, config.tag_prefix)
return max(tags)