auto-semver/semver/semver.py

219 lines
7.7 KiB
Python

from typing import List, Union
from pathlib import Path
import re
import toml
from semver.logger import logger
from semver.scm import SCM
from semver.version_type import VersionType
from semver.exceptions import (
NoMergeFoundException,
NotMainBranchException,
NoGitFlowException,
)
class SemVer:
"""Primary class for handling auto-semver processing"""
def __init__(
self,
scm: SCM,
main_branches: Union[List[str], None] = None,
major_branches: Union[List[str], None] = None,
minor_branches: Union[List[str], None] = None,
patch_branches: Union[List[str], None] = None,
):
"""
Initialize the SemVer object
:param global_user: Toggles git user at a global level, useful for build servers
:param scm: The source control manager to use
:param main_branches: Branches to run versioning on
:param major_branches: List of prefixes for major branches
:param minor_branches: List of prefixes for minor branches
:param patch_branches: List of prefixes for patch branches
"""
self._merged_branch: Union[str, None] = None
self._branch: Union[str, None] = None
self._version_type: Union[VersionType, None] = None
self._main_branches: List[str] = main_branches if main_branches else []
self._major_branches: List[str] = major_branches if major_branches else []
self._minor_branches: List[str] = minor_branches if minor_branches else []
self._patch_branches: List[str] = patch_branches if patch_branches else []
self._scm: SCM = scm
def _version_repo(self) -> str:
"""
Use bump_version to update the repo version
:return: The new version
"""
version = self._scm.get_tag_version()
if not self._version_type:
raise NoMergeFoundException()
logger.debug(f"Running bumpversion of type: {self._version_type.name}")
return self._bump_version(version, self._version_type)
def _process_config_string(self, cfg_string, new_version, version):
return cfg_string.replace("{new_version}", new_version).replace(
"{current_version}", version
)
def _bump_version(
self,
version: str,
index: VersionType = VersionType.MINOR,
tag_repo: bool = True,
update_files: bool = True,
) -> str:
"""
Bump the version of the repo
:param version: The current version
:param index: The index of the version to bump
:param tag_repo: Whether or not to tag the repo
:param update_files: Whether or not to update the files
:return: The new version
"""
v: List[str] = version.split(".")
# Bump version
v[index] = str(int(v[index]) + 1)
# Reset subversions
i = len(v) - 1
while i > index:
v[i] = "0"
i = i - 1
# Get new version
new_version = ".".join(v)
# Tag new version
if tag_repo and version != new_version:
self._scm.tag_version(new_version)
# Update local files
if update_files:
self._update_file_version(new_version, version)
return new_version
def _update_file_version(self, new_version: str, version: str = "0.0.0"):
"""
Update the version in the config file
:param new_version: The new version
:param version: The current version
"""
# Open up config file
config = toml.load("./.bumpversion.cfg")
bump_version_file_prefix = "bumpversion:file:"
bump_version_file_prefix_len = len(bump_version_file_prefix)
for section in config:
if section.startswith(bump_version_file_prefix):
file_path = Path(section[bump_version_file_prefix_len:])
section_data = config[section]
if file_path.is_file():
# Get search val from config
search_val = section_data["search"]
search_val = self._process_config_string(
search_val, new_version, version
)
# Get replace val from config
replace_val = section_data["replace"]
replace_val = self._process_config_string(
replace_val, new_version, version
)
# Update replace values in file
with open(file_path, "r") as file:
filedata = file.read()
filedata = filedata.replace(search_val, replace_val)
with open(file_path, "w") as file:
file.write(filedata)
else:
logger.warning(
f"Tried to version file: '{file_path}' but it doesn't exist!"
)
def get_version(
self, build: int = 0, version_format: Union[str, None] = None, dot: bool = False
):
"""
Get the version of the repo
:param build: The build number
:param version_format: The format of the version
:param dot: Whether or not to replace / with .
:return: The version
"""
version = self._scm.get_tag_version()
# Get the commit hash of the version
v_hash = self._scm.get_version_hash(version)
# Get the current commit hash
c_hash = self._scm.get_hash()
# If the version commit hash and current commit hash
# do not match return the branch name else return the version
if v_hash != c_hash:
logger.debug("v_hash and c_hash do not match!")
branch = self._scm.get_branch()
logger.debug("merged branch is: {}".format(branch))
version_type = self._scm.get_version_type(
branch, self._major_branches, self._minor_branches, self._patch_branches
)
logger.debug("version type is: {}".format(version_type))
if version_type:
next_version = self._bump_version(
self._scm.get_tag_version(), version_type, False, False
)
if version_format in ("npm", "docker"):
return "{}-{}.{}".format(
next_version, re.sub(r"[/_]", "-", branch), build
)
if version_format == "maven":
qualifier = "SNAPSHOT" if build == 0 else build
return "{}-{}-{}".format(
next_version, re.sub(r"[/_]", "-", branch), qualifier
)
if dot:
branch = branch.replace("/", ".")
return branch
return version
def run(self, push=True):
"""
Run the versioning process
1) get branches from last commit message
2) see if we're merging into a main branch
3) see what type of versioning we should do
4) version the repo
:param push: Whether or not to push the changes
"""
self._branch = self._scm.get_branch()
self._merged_branch = self._scm.get_merge_branch()
if not self._merged_branch:
raise NoMergeFoundException("No merge found")
if self._branch not in self._main_branches:
raise NotMainBranchException("Not a main branch")
self._version_type = self._scm.get_version_type(
self._branch,
self._major_branches,
self._minor_branches,
self._patch_branches,
)
if not self._version_type:
raise NoGitFlowException("Could not determine version type")
self._version_repo()
if push:
self._scm.commit_and_push(self._branch)