Major semver update and refactor
This commit is contained in:
		
							
								
								
									
										76
									
								
								semver/scm/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								semver/scm/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
			
		||||
from abc import ABC, abstractmethod
 | 
			
		||||
from typing import Union, List
 | 
			
		||||
 | 
			
		||||
from semver.version_type import VersionType
 | 
			
		||||
from semver.logger import logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SCM(ABC):
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def get_tag_version(self) -> str:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def get_branch(self) -> str:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def get_merge_branch(self) -> Union[str, None]:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def commit_and_push(self, branch: str) -> None:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def tag_version(self, version: str) -> None:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def get_file_version(self, config: dict) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        :param config: The bumpversion config as a dict
 | 
			
		||||
        :return: The current version from the config file
 | 
			
		||||
        """
 | 
			
		||||
        bumpversion: Union[str, None] = config.get("bumpversion", None)
 | 
			
		||||
        version: Union[str, None] = (
 | 
			
		||||
            bumpversion.get("current_version", None) if bumpversion else None
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if not bumpversion:
 | 
			
		||||
            config["bumpversion"] = {}
 | 
			
		||||
            version = "0.0.0"
 | 
			
		||||
 | 
			
		||||
        if not version:
 | 
			
		||||
            config["bumpversion"]["current_version"] = "0.0.0"
 | 
			
		||||
            version = "0.0.0"
 | 
			
		||||
 | 
			
		||||
        return version
 | 
			
		||||
 | 
			
		||||
    def get_version_type(
 | 
			
		||||
        self,
 | 
			
		||||
        merged_branch: str,
 | 
			
		||||
        major_branches: List[str],
 | 
			
		||||
        minor_branches: List[str],
 | 
			
		||||
        patch_branches: List[str],
 | 
			
		||||
    ) -> Union[VersionType, None]:
 | 
			
		||||
        """
 | 
			
		||||
        Get the version type based on the branches involved in the merge
 | 
			
		||||
        :param merged_branch: The branch that was merged
 | 
			
		||||
        :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
 | 
			
		||||
        :return: The version type
 | 
			
		||||
        """
 | 
			
		||||
        logger.info(f"Merged branch is {merged_branch}")
 | 
			
		||||
 | 
			
		||||
        merged_prefix = merged_branch.split("/")[-1].rstrip("/")
 | 
			
		||||
 | 
			
		||||
        version_type: Union[VersionType, None] = None
 | 
			
		||||
        if merged_prefix:
 | 
			
		||||
            if merged_prefix in major_branches:
 | 
			
		||||
                version_type = VersionType.MAJOR
 | 
			
		||||
            if merged_prefix in minor_branches:
 | 
			
		||||
                version_type = VersionType.MINOR
 | 
			
		||||
            if merged_prefix in patch_branches:
 | 
			
		||||
                version_type = VersionType.PATCH
 | 
			
		||||
        return version_type
 | 
			
		||||
							
								
								
									
										137
									
								
								semver/scm/git.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								semver/scm/git.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,137 @@
 | 
			
		||||
import re
 | 
			
		||||
import subprocess
 | 
			
		||||
from typing import Union, List
 | 
			
		||||
from functools import cache
 | 
			
		||||
 | 
			
		||||
import toml
 | 
			
		||||
 | 
			
		||||
from semver.scm import SCM
 | 
			
		||||
from semver.logger import logger
 | 
			
		||||
from semver.version_type import VersionType
 | 
			
		||||
from semver.exceptions import SemverException
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Git(SCM):
 | 
			
		||||
    def __init__(self, global_user: bool = False) -> None:
 | 
			
		||||
        self.git_commit_pattern = re.compile(
 | 
			
		||||
            r"Merge (branch|pull request) '?([^']+)'? (into|from) (?:'(.+)'|[^\/]+\/([^\n\\]+))"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.git_bin = "git"
 | 
			
		||||
 | 
			
		||||
        self.global_user: bool = global_user
 | 
			
		||||
        self.git_email: str = "versioner@semver.com"
 | 
			
		||||
        self.git_user: str = "Semantic Versioner"
 | 
			
		||||
        self._setup_git_user()
 | 
			
		||||
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
    def _run_command(self, *args: str) -> subprocess.CompletedProcess[str]:
 | 
			
		||||
        return subprocess.run(
 | 
			
		||||
            args,
 | 
			
		||||
            capture_output=True,
 | 
			
		||||
            text=True,
 | 
			
		||||
            check=True,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _setup_git_user(self) -> None:
 | 
			
		||||
        self._run_command(
 | 
			
		||||
            self.git_bin,
 | 
			
		||||
            "config",
 | 
			
		||||
            "--global" if self.global_user else "--local",
 | 
			
		||||
            "user.email",
 | 
			
		||||
            f'"{self.git_email}"',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self._run_command(
 | 
			
		||||
            self.git_bin,
 | 
			
		||||
            "config",
 | 
			
		||||
            "--global" if self.global_user else "--local",
 | 
			
		||||
            "user.name",
 | 
			
		||||
            f'"{self.git_user}"',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_tag_version(self) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        Get the latest tagged version from git tags
 | 
			
		||||
        :return: The latest tagged version
 | 
			
		||||
        """
 | 
			
		||||
        config: dict = toml.load("./.bumpversion.cfg")
 | 
			
		||||
 | 
			
		||||
        tag_expression: str = config["bumpversion"]["tag_name"].replace(
 | 
			
		||||
            "{new_version}", "[0-9]*.[0-9]*.[0-9]*"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        logger.debug(f"Tag expression: {tag_expression}")
 | 
			
		||||
 | 
			
		||||
        # Default version is `0.0.0` or what is found in
 | 
			
		||||
        version = self.get_file_version(config)
 | 
			
		||||
 | 
			
		||||
        # If a version is found in git tags, use that the latest tagged version
 | 
			
		||||
        tagged_versions: Union[List[str], None] = None
 | 
			
		||||
        try:
 | 
			
		||||
            proc = self._run_command(
 | 
			
		||||
                self.git_bin, "tag", "--sort=v:refname", "-l", tag_expression
 | 
			
		||||
            )
 | 
			
		||||
            tagged_versions = proc.stdout.rstrip().split("\n")
 | 
			
		||||
        except subprocess.CalledProcessError as e:
 | 
			
		||||
            raise RuntimeError(
 | 
			
		||||
                f"Error getting latest tagged git version: {str(e.stderr).rstrip()}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if len(tagged_versions) > 0 and tagged_versions[-1] != "":
 | 
			
		||||
            version = tagged_versions[-1]
 | 
			
		||||
 | 
			
		||||
        logger.debug(f"Tag Version: {version}")
 | 
			
		||||
        return version
 | 
			
		||||
 | 
			
		||||
    @cache
 | 
			
		||||
    def get_branch(self) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        Get the main branch
 | 
			
		||||
        """
 | 
			
		||||
        proc = self._run_command(self.git_bin, "rev-parse", "--abbrev-ref", "HEAD")
 | 
			
		||||
        return proc.stdout.rstrip()
 | 
			
		||||
 | 
			
		||||
    @cache
 | 
			
		||||
    def get_merge_branch(self) -> Union[str, None]:
 | 
			
		||||
        """
 | 
			
		||||
        Get the branches involved in the merge
 | 
			
		||||
        :return: The branch involved in the merge
 | 
			
		||||
        """
 | 
			
		||||
        proc = self._run_command(self.git_bin, "log", "-1")
 | 
			
		||||
        message: str = proc.stdout
 | 
			
		||||
 | 
			
		||||
        branch: str = self.get_branch()
 | 
			
		||||
 | 
			
		||||
        logger.info(f"Main branch is {branch}")
 | 
			
		||||
 | 
			
		||||
        matches = self.git_commit_pattern.search(
 | 
			
		||||
            message.replace("\\n", "\n").replace("\\", "")
 | 
			
		||||
        )
 | 
			
		||||
        merged_branch: Union[str, None] = None
 | 
			
		||||
        if matches:
 | 
			
		||||
            merged_branch = str(
 | 
			
		||||
                matches.group(2) if matches.group(4) == branch else matches.group(5)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        return merged_branch
 | 
			
		||||
 | 
			
		||||
    def commit_and_push(self, branch: str) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Commit and push the versioning changes
 | 
			
		||||
        :param branch: The branch to push
 | 
			
		||||
        """
 | 
			
		||||
        proc = self._run_command(self.git_bin, "push", "origin", branch)
 | 
			
		||||
        if proc.returncode != 0:
 | 
			
		||||
            raise SemverException(
 | 
			
		||||
                f"Error pushing versioning changes to {branch}: {proc.stderr}"
 | 
			
		||||
            )
 | 
			
		||||
        proc = self._run_command(self.git_bin, "push", "origin", "--tags")
 | 
			
		||||
        if proc.returncode != 0:
 | 
			
		||||
            raise SemverException(
 | 
			
		||||
                f"Error pushing versioning changes to {branch}: {proc.stderr}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    def tag_version(self, version: str) -> None:
 | 
			
		||||
        self._run_command(self.git_bin, "tag", version)
 | 
			
		||||
							
								
								
									
										49
									
								
								semver/scm/perforce.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								semver/scm/perforce.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
from typing import Union, List
 | 
			
		||||
 | 
			
		||||
import toml
 | 
			
		||||
 | 
			
		||||
from semver.scm import SCM
 | 
			
		||||
from semver.logger import logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Perforce(SCM):
 | 
			
		||||
    def __init__(self) -> None:
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
    def get_tag_version(self) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        Get the latest tagged version from Perforce labels
 | 
			
		||||
        :return: The latest tagged version
 | 
			
		||||
        """
 | 
			
		||||
        config: dict = toml.load("./.bumpversion.cfg")
 | 
			
		||||
 | 
			
		||||
        tag_expression: str = config["bumpversion"]["tag_name"].replace(
 | 
			
		||||
            "{new_version}", "[0-9]*.[0-9]*.[0-9]*"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        logger.debug("Tag expression: " + str(tag_expression))
 | 
			
		||||
 | 
			
		||||
        # Default version is `0.0.0` or what is found in
 | 
			
		||||
        version = self.get_file_version(config)
 | 
			
		||||
 | 
			
		||||
        # If a version is found in Perforce labels, use that the latest labeled version
 | 
			
		||||
        labeled_versions: Union[List[str], None] = None
 | 
			
		||||
        try:
 | 
			
		||||
            proc = subprocess.run(
 | 
			
		||||
                ["p4", "labels", "-e", tag_expression, "-m1"],
 | 
			
		||||
                capture_output=True,
 | 
			
		||||
                text=True,
 | 
			
		||||
                check=True,
 | 
			
		||||
            )
 | 
			
		||||
            labeled_versions = proc.stdout.rstrip().split("\n")
 | 
			
		||||
        except subprocess.CalledProcessError as e:
 | 
			
		||||
            raise RuntimeError(
 | 
			
		||||
                f"Error getting latest labeled Perforce version: {str(e.stderr).rstrip()}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if len(labeled_versions) > 0 and labeled_versions[-1] != "":
 | 
			
		||||
            version = labeled_versions[-1]
 | 
			
		||||
 | 
			
		||||
        logger.debug("Label Version: " + str(version))
 | 
			
		||||
        return version
 | 
			
		||||
		Reference in New Issue
	
	Block a user