Compare commits

...

47 Commits

Author SHA1 Message Date
cclecle
48114149fb fix: typo in jenkins pipeline file 2023-03-19 19:54:46 +00:00
cclecle
5eb2f1c5cf fix: pipeline 2023-03-19 19:50:23 +00:00
cclecle
65927077aa fix: git credentials for pushing 2023-03-19 19:49:57 +00:00
cclecle
0c02512814 fix: remove branch on git push (useless) 2023-03-19 19:35:37 +00:00
cclecle
9f900ba597 fix git tag cmd 2023-03-19 19:31:37 +00:00
cclecle
97612703b7 fix git username/address in jenkinsfile 2023-03-19 19:26:04 +00:00
cclecle
5bb8b35c73 update jenkinsfile 2023-03-19 19:21:48 +00:00
cclecle
a6c2513d9f fix fstrings 2023-03-19 18:55:58 +00:00
cclecle
70ef90a529 fix jenkinsfile python 2023-03-19 18:53:52 +00:00
cclecle
f728842a5a random fixes 2023-03-19 18:51:35 +00:00
cclecle
4837a99ac6 fix deps 2023-03-19 18:46:52 +00:00
cclecle
3e130e6bdf revert to full setuptools-git-versioning in toml file 2023-03-19 18:42:24 +00:00
cclecle
d7fbf52647 test 2023-03-19 18:23:42 +00:00
cclecle
f5c97757b6 make setuptools-git-versioning use pygitversionhelper
+ add getCurrentFormatedVersion()
2023-03-19 18:22:26 +00:00
cclecle
3c0e5bebc2 update doc 2023-03-19 18:02:02 +00:00
cclecle
c5962d533b fix tag -> version api 2023-03-19 17:59:27 +00:00
cclecle
e9e25793d8 fix doc bullets 2023-03-19 17:57:28 +00:00
cclecle
17f74d6675 test gitversionhelper calls 2023-03-19 17:57:20 +00:00
cclecle
264c51de2e update CI/CD 2023-03-19 17:51:11 +00:00
cclecle
ac942480cb feature: add log-line in pipeline 2023-03-19 10:34:34 +00:00
cclecle
be9ef684e4 update jenkinsFile => no error when master build are skipped because of
no-tag
2023-03-19 10:26:23 +00:00
cclecle
792666c6ec fix: rename hash -> commit_hash to avoid using builtin symbol 2023-03-19 10:17:58 +00:00
cclecle
2951e70c47 fix: escapes git strings options 2023-03-19 10:15:23 +00:00
cclecle
63b3f25b33 fix quality warning + commit messages unittest 2023-03-19 10:03:50 +00:00
cclecle
cefa964a3a add commit class 2023-03-19 09:52:42 +00:00
cclecle
38cde9d700 reword 2023-03-18 21:13:11 +00:00
cclecle
fff52596b1 update doc 2023-03-18 21:06:39 +00:00
cclecle
3ce09a0194 fix typo 2023-03-18 21:04:03 +00:00
cclecle
961f92d9c6 update doc and docstring 2023-03-18 21:02:49 +00:00
cclecle
a1cf4983ff improve code quality 2023-03-18 20:21:59 +00:00
cclecle
c9e6f4f436 improve detection of wrong case: both pre and post index + test 2023-03-18 20:20:11 +00:00
cclecle
7d9f2a7792 improve coverage and unittest and quality 2023-03-18 20:11:14 +00:00
cclecle
a223500509 cleanup code 2023-03-18 19:38:29 +00:00
cclecle
04578b3066 fix typing + add formated_output option to bump
add more unittest
2023-03-18 19:34:26 +00:00
cclecle
2598a41227 fix kwargs 2023-03-18 09:25:43 +00:00
cclecle
4e43629dfd test docstrings 2023-03-18 02:26:12 +00:00
cclecle
05093566d6 fix doc + try **kwargs 2023-03-18 01:59:21 +00:00
cclecle
f3f3459ccf improve quality 2023-03-18 01:42:53 +00:00
cclecle
df74016945 improve quality 2023-03-18 01:39:25 +00:00
cclecle
bce35f0a60 fix quality 2023-03-18 01:32:44 +00:00
cclecle
39e7d8236c update: update: quality column length check 120 -> 140 2023-03-18 01:23:33 +00:00
cclecle
6a1331d1bf improve quality 2023-03-18 01:22:32 +00:00
cclecle
96250682ec fix missing user/mail for git 2023-03-18 00:59:35 +00:00
cclecle
014e8ac0d2 fix + more unittests 2023-03-18 00:49:33 +00:00
cclecle
b74269b39b more tests and features 2023-03-18 00:10:00 +00:00
cclecle
46ee6dec57 fix: quality score 2023-03-17 18:54:36 +00:00
cclecle
c0d5e4480c fix: code quality 2023-03-17 18:48:18 +00:00
8 changed files with 2062 additions and 260 deletions

80
Jenkinsfile vendored
View File

@@ -120,17 +120,21 @@ pipeline {
echo("_GITEA_BASE_URL: . . . . . . . . $_GITEA_BASE_URL")
echo("GIT_COMMIT:. . . . . . . . . . . $GIT_COMMIT ")
echo("_PROJECT_USER_NAME:. . . . . . . $_PROJECT_USER_NAME")
echo("_GITEA_PROJECT_NAME: . . . . . . $_PROJECT_NAME")
echo("_PROJECT_NAME: . . . . . . . . . $_PROJECT_NAME")
echo("_MaintainerEmail:. . . . . . . . $_MaintainerEmail")
echo("_MaintainerName:. . . . . . . . $_MaintainerName")
sh("virtualenv --pip=embed --setuptools=embed --wheel=embed --no-periodic-update --activators bash,python BUILD_ENV")
sh("virtualenv --pip=embed --setuptools=embed --wheel=embed --no-periodic-update --activators bash,python TEST_ENV")
sh("virtualenv --pip=embed --setuptools=embed --wheel=embed --no-periodic-update --activators bash,python TOOLS_ENV")
sh(". ~/BUILD_ENV/bin/activate && pip install --upgrade setuptools build pip copier jinja2-slug toml \"setuptools-git-versioning<2\"")
sh(". ~/TOOLS_ENV/bin/activate && pip install simple_rest_client requests")
sh(". ~/TOOLS_ENV/bin/activate && pip install git+https://chacha.ddns.net/gitea/chacha/pygitversionhelper.git@master")
sh("git config --global user.email $_MaintainerEmail")
sh("git config --global user.name $_MaintainerName")
}
}
@@ -148,23 +152,67 @@ pipeline {
{
if(sh(returnStdout: true, script: "git tag --points-at HEAD").trim().isEmpty())
{
error("master push/merge must have an explicit tag release number")
BUMPED_VERSION = sh(script: """#!/bin/sh -
|. ~/TOOLS_ENV/bin/activate
|exec python - << '__EOWRAPPER__'
|
|from pygitversionhelper import gitversionhelper
|import re
|
|lastcommit=gitversionhelper.commit.getLast(same_branch=True)
|msg=gitversionhelper.commit.getMessage(lastcommit)
|
|_match=re.search(r"\\s*(?:#)?\\s*(?<=new-tag:)(?:\\s*)(?P<TAG>\\S*)",msg)
|print(_match.group("TAG"),end="")
|
|__EOWRAPPER__
""".stripMargin(),
returnStdout: true).trim()
if(BUMPED_VERSION.isEmpty())
{
echo "master push/merge must have an explicit tag release number, stopping pipeline"
currentBuild.getRawBuild().getExecutor().doStop()
}
else
{
echo "new-tag requested in commit message: $BUMPED_VERSION"
withCredentials([usernamePassword(credentialsId: _SCMCredentials, passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
sh("git remote set-url origin https://${GIT_USERNAME}:${GIT_PASSWORD}@chacha.ddns.net/gitea/${GIT_USERNAME}/${_PROJECT_NAME}.git")
}
sh("git tag $BUMPED_VERSION")
sh("git push origin --tags")
}
}
}
// using git to get the latest tag on this branch
def latestTag = sh(returnStdout: true, script: "git tag --sort=-creatordate | head -n 1").trim()
echo("latestTag:. . . . . . . . . . . . $latestTag ")
// using setuptools_git_versioning to get the generated version number based on Git tags and logs
// TODO: read dev_template from toml
sh(script: """#!/bin/sh -
|. ~/TOOLS_ENV/bin/activate
|exec python - << '__EOWRAPPER__'
|
|from pygitversionhelper import gitversionhelper
|
|print(f"most recent repository tag: {gitversionhelper.tag.getLastTag()}")
|print(f"most recent repository tag: {gitversionhelper.tag.getLastTag(same_branch=True)}")
|print(f"number of commit since last tag: {gitversionhelper.tag.getDistanceFromTag()}")
|print(f"number of commit since last tag: {gitversionhelper.tag.getDistanceFromTag(same_branch=True)}")
|print(f"most recent repository version: {gitversionhelper.version.getLastVersion(formated_output=True)}")
|print(f'current repository version: {gitversionhelper.version.getCurrentVersion(formated_output=True,version_std="PEP440",bump_type="dev",bump_dev_strategy="post")}')
|
|__EOWRAPPER__
""".stripMargin())
// get current (or bumped) version number from git history
PY_PROJECT_VERSION = sh(script: """#!/bin/sh -
|. ~/BUILD_ENV/bin/activate
|. ~/TOOLS_ENV/bin/activate
|exec python - << '__EOWRAPPER__'
|
|from setuptools_git_versioning import version_from_git
|from pygitversionhelper import gitversionhelper
|
|print(str(version_from_git(tag_filter="^\\d+\\.\\d+\\.\\d+\$",dev_template = "{tag}.post{ccount}")),end ="")
|print(gitversionhelper.version.getCurrentVersion(formated_output=True,version_std="PEP440",bump_type="dev",bump_dev_strategy="post"),end ="")
|
|__EOWRAPPER__
""".stripMargin(),
@@ -176,12 +224,9 @@ pipeline {
// => little hacky... creating a new git repo with a commit/tag corresponding to HEAD of the official one
if(_PROJECT_NAME=="pyChaChaDummyProject") //specific case to test the template itself
{
sh("git config --global user.email $_MaintainerEmail")
sh("git config --global user.name $_MaintainerName")
sh("rm -Rf ~/_gitrepo || true")
//sh(". ~/BUILD_ENV/bin/activate && python -m copier --vcs-ref HEAD --prereleases --defaults ./ ~/_gitrepo")
sh(script: """#!/bin/sh -
|. ~/BUILD_ENV/bin/activate
|exec python - << '__EOWRAPPER__'
@@ -388,13 +433,15 @@ pipeline {
def GITEA_LOGIN_TOKEN=credentials("GiteaCHACHAPush")
}
steps {
sh("python3 -m pip install simple_rest_client requests")
dir("gitrepo") {
script {
def CurrentDateTime=java.time.LocalDateTime.now()
withCredentials([string( credentialsId: _MkDocsWebCredentials,variable: 'MKDOCSTOKEN' )])
{
sh(script: """#!/usr/bin/env python3
sh(script: """#!/bin/sh -
|. ~/TOOLS_ENV/bin/activate
|exec python - << '__EOWRAPPER__'
|
|from simple_rest_client.api import API
|from simple_rest_client.resource import Resource
|import json
@@ -478,8 +525,9 @@ pipeline {
|response=requests.post("http://${_MkDocsWebURL}/API.php?REQ=pushDoc",data=reqData,files=files)
|if response.status_code != 200:
| raise RuntimeError(f"Wrong server response: {response.status_code}")
|
""".stripMargin())
|
|__EOWRAPPER__
""".stripMargin())
}
}
}

View File

@@ -3,46 +3,33 @@
![](docs-static/Library.jpg)
# Python project template
# pyGitVersionHelper
A nice template to start a blank python projet.
This template automate a lot of handy things and allow CI/CD automatic releases generation.
_A tiny library to help versioning management of git python projects_
It is also collectings data to feed Jenkins build.
Because a good developer is a lazy developer and version management in CI/CD can be very time consuming.
Checkout [Latest Documentation](https://chacha.ddns.net/mkdocs-web/chacha/pychachadummyproject/{{branch}}/latest/).
Checkout [Latest Documentation](https://chacha.ddns.net/mkdocs-web/chacha/pygitversionhelper/{{branch}}/latest/).
## Features
### Generic pipeline skeleton:
- Prepare
- GetCode
- BuildPackage
- Install
- CheckCode
- PlotMetrics
- RunUnitTests
- GenDOC
- PostRelease
- list tags
- get last tag
- get last version
- get current version (bumped)
- convert / switch from SemVer to PEP440 (both ways)
- automatic version format detection (SemVer by default)
### CI/CD Environment
- Jenkins
- Gitea (with patch for dynamic Readme variables: https://chacha.ddns.net/gitea/chacha/GiteaMarkupVariable)
- Docker
- MkDocsWeb
### CI/CD Helper libs
- VirtualEnv
- Changelog generation based on commits
- copier
- pylint + pylint_json2html
- mypy
- unittest + xmlrunner + junitparser + junit2htmlreport
- mkdocs
## Options
- restrict to same branch
- both SemVer and PEP440 support
- custom output format
- configurable default bump type major, minor, patch or dev
- configurable default bump type: post, pre-patch, pre-minor, pre-major
- ignore non-version tag
- force version format
### Python project
- Full .toml implementation
- .whl automatic generation
- dynamic versionning using git repository
- embedded unit-test
## Process
- full CI/CD developpment: Gitea / Jenkins + few python libs (pytlint, coverage, unittest, mkdocs)
- documentation generated mkdocs and self-hosted
- CI/CD on Linux, manually tested in Windows environnement

View File

@@ -1,16 +1,128 @@
# Usage
## Pulvinar dolor
Donec dapibus est fermentum justo volutpat condimentum. Integer quis nunc neque. Donec dictum vehicula justo, in facilisis ex tincidunt in.
Vivamus sollicitudin sem dui, id mollis orci facilisis ut. Proin sed pulvinar dolor. Donec volutpat commodo urna imperdiet pulvinar. Fusce eget aliquam risus.
Vivamus viverra luctus ex, in finibus mi. Nullam elementum dapibus mollis. Ut suscipit volutpat ex, quis feugiat lacus consectetur eu.
## Installation
## Condimentum faucibus
Quisque auctor egestas sem, luctus suscipit ex maximus vitae. Duis facilisis augue et condimentum faucibus.
Donec cursus, enim a sagittis egestas, lectus lorem eleifend libero, at tincidunt leo magna at libero.
Nunc eros velit, suscipit luctus tempor vel, finibus et est. Curabitur efficitur pretium pulvinar.
Donec urna lectus, vulputate quis turpis sed, placerat congue urna. Phasellus aliquet fermentum quam, non auctor elit porta nec. Morbi eu ligula at nisl ultricies condimentum vitae id ante.
From master repository:
## Aliquam lacinia
In volutpat lorem ex, et fringilla nibh faucibus quis. Mauris et arcu elementum, auctor dui vitae, egestas arcu. Duis sit amet aliquam quam.
Phasellus a odio turpis. Etiam tristique mi eu enim varius, eget facilisis est vestibulum. Aliquam lacinia nec purus sed luctus. Cras at laoreet erat.
python -m pip install git+https://chacha.ddns.net/gitea/chacha/pygitversionhelper.git@master
From local .whl file:
python -m pip install pygitversionhelper-<VERSION>-py3-none-any.whl
From public repository:
TBD
## Import in your project
Add this line on the top of your python script:
#from pygitversionhelper import gitversionhelper
[optionnal] If you need to catch exception from this module:
#from pygitversionhelper import gitversionhelperException
## Basic API
All the API commands are static so it is not needed to create instantiate any object.
They are all executed in the current active directory.
One easy way to change directory:
import os
os.chdir("<YOUR DIRECTORY>")
### sublib: repository
To check if a repository is dirty:
if gitversionhelper.repository.isDirty():
print("repository is dirty")
### sublib: tag
List all tags [default to taggerdate order]:
for tag in gitversionhelper.tag.getTags():
print(f"found tag: {tag}")
List all tags [using git refname order]:
for tag in gitversionhelper.tag.getTags("v:refname"):
print(f"found tag: {tag}")
Get the last tag:
print(f"most recent repository tag: {gitversionhelper.tag.getLastTag()}")
Get the last tag [only on same branch]:
print(f"most recent repository tag: {gitversionhelper.tag.getLastTag(same_branch=True)}")
Get the distance from HEAD to last tag:
print(f"number of commit since last tag: {gitversionhelper.tag.getDistanceFromTag()}")
Get the distance from HEAD to last tag [only on same branch]:
print(f"number of commit since last tag: {gitversionhelper.tag.getDistanceFromTag(same_branch=True)}")
### sublib: version
Get the last found version in the repository [return MetaVersion object]:
print(f"most recent repository version: {gitversionhelper.version.getLastVersion()}")
Get the last found version in the repository [return formated string]:
print(f"most recent repository version: {gitversionhelper.version.getLastVersion(formated_output=True)}")
Others kwargs available to this function:
* version_std: string to force a version standard for rendering ["PEP440" or "SemVer"]
* same_branch: boolean to force searching on same branch
* ignore\_unknown\_tags: boolean to allow unknown tag to be ignored
Get the current version of the repository, automatically bump it if the last one is not tagged [returns MetaVersion object]:
print(f"current repository version: {gitversionhelper.version.getCurrentVersion()}")
Or with formated output:
print(f"current repository version: {gitversionhelper.version.getCurrentVersion(formated_output=True)}")
Typical usage in CI/CD env:
bumped_version = gitversionhelper.version.getCurrentVersion( formated_output=True, \
version_std="PEP440", \
bump_type="dev", \
bump_dev_strategy="post")
print(f"current repository version: {bumped_version}")
kwargs available to this function:
* All same args as getLastVersion()
* bump_type: if version need to be pump, allow to configure next release update type: __major, minor, patch, dev__
* bump\_dev\_strategy: if bump\_type is dev, allow to choose dev update strategy: __post, pre-patch, pre-minor, pre-major__
A version object can also be manually formated:
_version = gitversionhelper.tag.getCurrentVersion()
_version.doFormatVersion()
#or
gitversionhelper.version.doFormatVersion(_version)
kwargs available to those function:
- output_format: string to choose a rendering format ["Auto","PEP440" or "SemVer"]
## Limitations
There is unfortunately some technical limitation :
* MultiThreading and async behavior is not tested.
* Multiple tag on the same commit is not supported.
* Branch filter when searching for a version is only tested with -no-ff strategy

View File

@@ -61,7 +61,7 @@ class quality_check(helper_withresults_base):
'--ignore=_version.py',
'--reports=y',
'--score=yes',
'--max-line-length=120',
'--max-line-length=140',
'src.' + cls.pyproject['project']['name']], exit=False)
with open(cls.get_result_dir()/"report.json","w+", encoding='utf-8') as Outfile:

View File

@@ -36,6 +36,7 @@ classifiers = [
]
dependencies = [
'importlib-metadata; python_version<"3.9"',
'packaging'
]
dynamic = ["version"]
@@ -49,10 +50,6 @@ where = ["src"]
[tool.setuptools.package-data]
"pygitversionhelper.data" = ["*.*"]
#[tool.setuptools_scm]
#write_to = "src/pygitversionhelper/_version.py"
#version_scheme = "python-simplified-semver"
[project.urls]
Homepage = "https://chacha.ddns.net/gitea/chacha/pygitversionhelper"
Documentation = "https://chacha.ddns.net/gitea/chacha/pygitversionhelper/wiki"

View File

@@ -19,4 +19,4 @@ except PackageNotFoundError: # pragma: no cover
warnings.warn("can not read __version__, assuming local test context, setting it to ?.?.?")
__version__ = "?.?.?"
from .gitversionhelper import gitversionhelper
from .gitversionhelper import gitversionhelper, gitversionhelperException

View File

@@ -1,317 +1,663 @@
# pygitversionhelper (c) by chacha
#
# pygitversionhelper is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
"""
This project try to help doing handy operations with git when
dealing with project versioning and tags on python project -
at leat for project using PEP440 or SemVer standards.
One requirement is to keep it compact and to not cover too much fancy features.
This is the reason why it is one single file with nested classes.
=> Design is on-purpose poorly expandable to keep the scope light.
This library is maid for repository using tag as version.
Support for non-version tags is optional and not well tested.
This module is the main project file, containing all the code.
Read the read me for more information.
Check the unittest s for usage samples.
Note: _Other Parameters_ are **kwargs
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import os
import subprocess
import re
from copy import copy
import logging
from packaging.version import VERSION_PATTERN as packaging_VERSION_PATTERN
if TYPE_CHECKING: # Only imports the below statements during type checking
from typing import Union
def _exec(cmd: str, root: str | os.PathLike | None = None) -> list[str]:
def _exec(cmd: str, root: str | os.PathLike | None = None, raw:bool = False) -> list[str]:
"""
Helper function to handle system cmd execution
helper function to handle system cmd execution
Args:
cmd: command line to be executed
root: root directory where the command need to be executed
Return:
Returns:
a list of command's return lines
"""
try:
stdout = subprocess.check_output(cmd, shell=True, text=True, cwd=root)
except subprocess.CalledProcessError as e:
stdout = e.output
lines = stdout.splitlines()
p = subprocess.run(cmd, text=True, cwd=root, capture_output=True, check=False, timeout=2,shell=True)
if re.search("not a git repository",p.stderr):
raise gitversionhelper.repository.notAGitRepository()
if re.search("fatal:",p.stderr): #pragma: nocover
raise gitversionhelper.unknownGITFatalError(p.stderr)
if int(p.returncode) < 0: #pragma: nocover
raise gitversionhelper.unknownGITError(p.stderr)
if raw:
return p.stdout
lines = p.stdout.splitlines()
return [line.rstrip() for line in lines if line.rstrip()]
class gitversionhelper: # pylint: disable=too-few-public-methods
class gitversionhelperException(Exception):
"""
Main gitversionhelper class
general Module Exception
"""
class gitversionhelper: # pylint: disable=too-few-public-methods
"""
main gitversionhelper class
"""
class wrongArguments(gitversionhelperException):
"""
wrong argument generic exception
"""
class unknownGITError(gitversionhelperException):
"""
unknown git error generic exception
"""
class unknownGITFatalError(unknownGITError):
"""
unknown fatal git error generic exception
"""
class repository:
"""
class containing methods focusing on repository
"""
class repositoryException(gitversionhelperException):
"""
generic repository exeption
"""
class notAGitRepository(repositoryException):
"""
not a git repository exception
"""
class repositoryDirty(repositoryException):
"""
dirty repository exception
"""
@classmethod
def isDirty(cls) -> bool:
"""
Check if the repository is in dirty state
Return:
check if the repository is in dirty state
Returns:
True if it is dirty
"""
return True if _exec(f"git status --short") else False
return bool(_exec("git status --short"))
class commit:
"""
class containing methods focusing on commits
"""
__OptDict = {"same_branch": "same_branch",
"merged_output":"merged_output"}
class commitException(gitversionhelperException):
"""
generic commit exception
"""
class commitNotFound(commitException):
"""
tag not found exception
"""
@classmethod
def getMessagesSinceTag(cls,tag:str,**kwargs) -> str:
"""
retrieve a commits message history from repository
from LastCommit to the given tag
Keyword Arguments:
merged_output: output one single merged string
same_branch(bool): force searching only in the same branch
Returns:
the commit message
"""
current_commit_id=cls.getLast(**kwargs)
tag_commit_id=cls.getFromTag(tag)
if ((cls.__OptDict["same_branch"] in kwargs) and (kwargs[cls.__OptDict["same_branch"]] is True)):
commits = _exec(f"git rev-list --first-parent --ancestry-path {tag_commit_id}..{current_commit_id}")
else:
commits = _exec(f"git rev-list --ancestry-path {tag_commit_id}..{current_commit_id}")
result=[]
for commit in commits:
result.append(cls.getMessage(commit))
if ((cls.__OptDict["merged_output"] in kwargs) and (kwargs[cls.__OptDict["merged_output"]] is True)):
print("JOIN")
return os.linesep.join(result)
return result
@classmethod
def getMessage(cls, commit_hash:str) -> str:
"""
retrieve a commit message from repository
Args:
commit_hash: id of the commit
Returns:
the commit message
"""
try:
res=_exec(f"git log -z --pretty=\"tformat:%B%-C()\" -n 1 {commit_hash}",None,True).rstrip('\x00')
except gitversionhelper.unknownGITFatalError as _e:
raise cls.commitNotFound("no commit found in commit history") from _e
return res.replace('\r\n','\n').replace('\n','\r\n')
@classmethod
def getFromTag(cls,tag:str) -> str:
"""
retrieve a commit from repository associated to a tag
Args:
tag: tag of the commit
Returns:
the commit Id
"""
try:
res=_exec(f"git rev-list -n 1 {tag}")
except gitversionhelper.unknownGITFatalError as _e:
raise cls.commitNotFound("no commit found in commit history") from _e
if len(res)==0:
raise cls.commitNotFound("no commit found in commit history")
return res[0]
@classmethod
def getLast(cls,**kwargs) -> str:
"""
retrieve last commit from repository
Keyword Arguments:
same_branch(bool): force searching only in the same branch
Returns:
the commit Id
"""
if ((cls.__OptDict["same_branch"] in kwargs) and (kwargs[cls.__OptDict["same_branch"]] is True)):
try:
res = _exec("git rev-parse HEAD")
except gitversionhelper.unknownGITFatalError as _e:
raise cls.commitNotFound("no commit found in commit history") from _e
else:
res = _exec("git for-each-ref --sort=-committerdate refs/heads/ --count 1 --format=\"%(objectname)\"")
if len(res)==0:
raise cls.commitNotFound("no commit found in commit history")
return res[0]
class tag:
"""
class containing methods focusing on tags
"""
__OptDict = {"same_branch": "same_branch"}
__validGitTagSort=["","v:refname","-v:refname","taggerdate","committerdate","-taggerdate","-committerdate"]
class tagException(gitversionhelperException):
"""
generic tag exception
"""
class tagNotFound(tagException):
"""
tag not found exception
"""
class moreThanOneTag(tagException):
"""
more than one tag exception
"""
@classmethod
def getTags(cls,sort:str = "version:refname") -> list[str]:
def getTags(cls,sort:str = "taggerdate",**kwargs) -> list[str|None]:
"""
retrieve all tags from a repository
Args:
sort: sorting constraints (git format)
Return:
Returns:
the tags list
"""
if sort not in cls.__validGitTagSort:
raise RuntimeError("sort option not in allowed list")
return _exec(f"git tag -l --sort={sort}")
raise gitversionhelper.wrongArguments("sort option not in allowed list")
if ((cls.__OptDict["same_branch"] in kwargs) and (kwargs[cls.__OptDict["same_branch"]] is True)):
currentBranch = _exec("git rev-parse --abbrev-ref HEAD")
return list(reversed(_exec(f"git tag --merged {currentBranch[0]} --sort={sort}")))
return list(reversed(_exec(f"git tag -l --sort={sort}")))
@classmethod
def getLastTag(cls,**kwargs) -> Union[str,None]:
f"""
def getLastTag(cls,**kwargs) -> str | None:
"""
retrieve the last tag from a repository
Arguments:
sort: sorting constraints (git format)
Kwargs:
{cls.__OptDict["same_branch"]}: force searching only in the same branch
Return:
Keyword Arguments:
same_branch(bool): force searching only in the same branch
Returns:
the tag
"""
if ((cls.__OptDict["same_branch"] in kwargs) and (kwargs[cls.__OptDict["same_branch"]] is True)):
return _exec("git describe --tags --abbrev=0")[0]
tag = _exec("git rev-list --tags --max-count=1")
return _exec(f"git describe --tags {tag[0]}")[0]
res = _exec("git describe --tags --first-parent --abbrev=0")
else:
res = _exec("git rev-list --tags --date-order --max-count=1")
if len(res)==1:
res = _exec(f"git describe --tags {res[0]}")
if len(res)==0:
raise cls.tagNotFound("no tag found in commit history")
if len(res)!=1: #pragma: nocover
raise cls.moreThanOneTag("multiple tags on same commit is unsupported")
return res[0]
@classmethod
def getDistanceFromTag(cls,tag=None,**kwargs) -> int:
f"""
retrieve the distance from tag in the repository
def getDistanceFromTag(cls,tag:str=None,**kwargs) -> int:
"""
retrieve the distance between HEAD and tag in the repository
Arguments:
tag: reference tag, if None the most recent one will be used
Kwargs:
{cls.__OptDict["same_branch"]}: force searching only in the same branch
Return:
Keyword Arguments:
same_branch(bool): force searching only in the same branch
Returns:
the tag
"""
if (tag is None):
if tag is None:
tag = cls.getLastTag(**kwargs)
return int(_exec(f"git rev-list {tag}..HEAD --count")[0])
class version:
"""
class containing methods focusing on versions
"""
__OptDict = { "version_std": "version_std",
"formated_output": "formated_output",
"output_format": "output_format"}
DefaultInputFormat = "PEP440"
VersionStds = { "SemVer" : { "regex" : r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"},
"PEP440" : { "regex" : packaging_VERSION_PATTERN }
"output_format": "output_format",
"ignore_unknown_tags": "ignore_unknown_tags"}
DefaultInputFormat = "Auto"
VersionStds = { "SemVer" : { "regex" : r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)"\
r"(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"\
r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"\
r"(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$",
"regex_preversion_num": r"(?:\.)(?P<num>(?:\d+(?!\w))+)",
"regex_build_num" : r"(?:\.)(?P<num>(?:\d+(?!\w))+)"
},
"PEP440" : { "regex" : packaging_VERSION_PATTERN,
"Auto" : None
}
}
__versionReseted = False
class versionException(gitversionhelperException):
"""
generic version exception
"""
class noValidVersion(versionException):
"""
no valid version found exception
"""
class PreAndPostVersionUnsupported(versionException):
"""
pre and post release can not be present at the same time
"""
class MetaVersion:
"""
generic version object
"""
__OptDict = { "bump_type": "bump_type",
"bump_dev_strategy": "bump_dev_strategy"}
__OptDict = { "bump_type": "bump_type",
"bump_dev_strategy": "bump_dev_strategy",
"formated_output": "formated_output"}
DefaultBumpType = "patch"
BumpTypes = ["major","minor","patch","dev"]
DefaultBumpDevStrategy = "post"
BumpDevStrategys = ["post","pre","force_post","force_pre"]
major: int = 0
minor: int = 1
patch: int = 0
pre_count:int = 0
post_count:int = 0
raw:str = "0.1.0"
def __init__(self,major=0,minor=1,patch=0,pre_count=0,post_count=0,raw="0.1.0"):
self.major,self.minor,self.patch,self.pre_count,self.post_count,self.raw = major,minor,patch,pre_count,post_count,raw
BumpDevStrategys = ["post","pre-patch","pre-minor","pre-major"]
version_std: str = "None"
major: int = 0
minor: int = 1
patch: int = 0
pre_count:int = 0
post_count:int = 0
raw:str = "0.1.0"
def __init__(self,version_std,major=0,minor=1,patch=0,pre_count=0,post_count=0,raw="0.1.0"): #pylint: disable=R0913
self.version_std = version_std
self.major = major
self.minor = minor
self.patch = patch
self.pre_count = pre_count
self.post_count = post_count
self.raw = raw
@classmethod
def _getBumpDevStrategy(cls,**kwargs) -> str:
f"""
"""
get selected bump_dev_strategy
Kwargs:
{cls.__OptDict["bump_dev_strategy"]}: the given bump_dev_strategy (can be None)
Return:
Kwargs given {cls.__OptDict["bump_dev_strategy"]} or the default one.
Keyword Arguments:
bump_dev_strategy(str): the given bump_dev_strategy (can be None)
Returns:
Kwargs given bump_dev_strategy or the default one.
"""
BumpDevStrategy = cls.DefaultBumpDevStrategy
if (cls.__OptDict["bump_dev_strategy"] in kwargs):
if cls.__OptDict["bump_dev_strategy"] in kwargs:
if kwargs[cls.__OptDict["bump_dev_strategy"]] in cls.BumpDevStrategys:
BumpDevStrategy = kwargs[cls.__OptDict["bump_dev_strategy"]]
else:
raise RuntimeError(f"invalid {cls.__OptDict['bump_type']} requested")
raise gitversionhelper.wrongArguments(f"invalid {cls.__OptDict['bump_type']} requested")
return BumpDevStrategy
@classmethod
def _getBumpType(cls,**kwargs) -> str:
f"""
"""
get selected bump_type
Kwargs:
{cls.__OptDict["bump_type"]}: the given bump_type (can be None)
Return:
Kwargs given {cls.__OptDict["bump_type"]} or the default one.
Keyword Arguments:
bump_type(str): the given bump_type (can be None)
Returns:
Kwargs given bump_type or the default one.
"""
BumpType = cls.DefaultBumpType
if (cls.__OptDict["bump_type"] in kwargs):
if cls.__OptDict["bump_type"] in kwargs:
if kwargs[cls.__OptDict["bump_type"]] in cls.BumpTypes:
BumpType = kwargs[cls.__OptDict["bump_type"]]
else:
raise RuntimeError(f"invalid {cls.__OptDict['bump_type']} requested")
raise gitversionhelper.wrongArguments(f"invalid {cls.__OptDict['bump_type']} requested")
return BumpType
def bump(self,**kwargs):
f"""
def bump(self,amount:int=1,**kwargs) -> gitversionhelper.version.MetaVersion | str : # pylint: disable=R0912
"""
bump the version to the next one
Kwargs:
{self.__OptDict["bump_type"]}: the given bump_type (can be None)
{self.__OptDict["bump_dev_strategy"]}: the given bump_dev_strategy (can be None)
Return:
Keyword Arguments:
bump_type(str): the given bump_type (can be None)
bump_dev_strategy(str): the given bump_dev_strategy (can be None)
Returns:
the bumped version
"""
BumpType = self._getBumpType(**kwargs)
BumpDevStrategy=self._getBumpDevStrategy(**kwargs)
_v=copy(self)
if BumpType is "dev":
if BumpDevStrategy is "post":
if BumpType == "dev":
if BumpDevStrategy == "post":
if _v.pre_count > 0:
_v.pre_count = _v.pre_count + 1
_v.pre_count = _v.pre_count + amount
else:
_v.post_count = _v.post_count + 1
elif BumpDevStrategy is "pre":
_v.post_count = _v.post_count + amount
#elif BumpDevStrategy in ["pre-patch","pre-minor","pre-major"]:
else:
if _v.post_count > 0:
_v.post_count = _v.post_count + 1
_v.post_count = _v.post_count + amount
else:
_v.pre_count = _v.pre_count + 1
elif BumpDevStrategy is "force_post":
pass
elif BumpDevStrategy is "force_pre":
pass
if _v.pre_count == 0:
if BumpDevStrategy == "pre-patch":
_v.patch = _v.patch + 1
elif BumpDevStrategy == "pre-minor":
_v.minor = _v.minor + 1
_v.patch = 0
#elif BumpDevStrategy == "pre-major":
else:
_v.major = _v.major + 1
_v.minor = 0
_v.patch = 0
_v.pre_count = _v.pre_count + amount
else:
if BumpType is "major":
_v.major = _v.major + 1
elif BumpType is "minor":
_v.minor = _v.minor + 1
elif BumpType is "patch":
_v.patch = _v.patch + 1
if BumpType == "major":
_v.major = _v.major + amount
elif BumpType == "minor":
_v.minor = _v.minor + amount
#elif BumpType == "patch":
else:
_v.patch = _v.patch + amount
_v.pre_count=0
_v.post_count=0
_v.raw="{major}.{minor}.{patch}{revpattern}{revcount}".format(major=_v.major,minor=_v.minor,patch=_v.patch,revpattern="",revcount="")
_v.raw=_v.doFormatVersion(**kwargs)
if ((self.__OptDict["formated_output"] in kwargs) and (kwargs[self.__OptDict["formated_output"]] is True)):
return _v.doFormatVersion(**kwargs)
return _v
def doFormatVersion(self,**kwargs):
def doFormatVersion(self,**kwargs) -> str:
"""
output a formated version string
Keyword Arguments:
output_format: output format to render ("Auto" or "PEP440" or "SemVer")
Returns:
formated version string
"""
return gitversionhelper.version.doFormatVersion(self,**kwargs)
@classmethod
def _getVersionStd(cls,**kwargs):
def _getVersionStd(cls,**kwargs) -> str:
"""
get selected version_std
Keyword Arguments:
version_std(str): the given version_std (can be None)
Returns:
Kwargs given version_std or the default one.
"""
VersionStd = cls.DefaultInputFormat
if (cls.__OptDict["version_std"] in kwargs):
if cls.__OptDict["version_std"] in kwargs:
if kwargs[cls.__OptDict["version_std"]] in cls.VersionStds:
VersionStd = kwargs[cls.__OptDict["version_std"]]
else:
raise RuntimeError(f"invalid {cls.__OptDict['version_std']} requested")
raise gitversionhelper.wrongArguments(f"invalid {cls.__OptDict['version_std']} requested")
return VersionStd
@classmethod
def getLastVersion(cls,**kwargs):
def getCurrentVersion(cls,**kwargs) -> MetaVersion | str :
"""
get last version from git tags
get the current version or bump depending of repository state
Keyword Arguments:
version_std(str): the given version_std (can be None)
same_branch(bool): force searching only in the same branch
formated_output(bool) : output a formated version string
bump_type(str): the given bump_type (can be None)
bump_dev_strategy(str): the given bump_dev_strategy (can be None)
Returns:
the last version
"""
VersionStd = cls._getVersionStd(**kwargs)
_r=re.compile(r"^\s*" + cls.VersionStds[VersionStd]["regex"] + r"\s*$", re.VERBOSE | re.IGNORECASE)
lastTag = gitversionhelper.tag.getLastTag(**kwargs)
_m = re.match(_r,lastTag)
if not _m:
raise RuntimeError("no valid version found in tags")
if VersionStd is "PEP440":
ver=_m.group("release").split(".")
ver += ["0"] * (3 - len(ver))
ver[0]=int(ver[0])
ver[1]=int(ver[1])
ver[2]=int(ver[2])
major, minor, patch = tuple(ver)
pre_count = int(_m.group("pre_n")) if _m.group("pre_n") else 0
post_count = int(_m.group("post_n2")) if _m.group("post_n2") else 0
elif VersionStd is "SemVer":
major, minor, patch = int(_m.group("major")),int(_m.group("minor")),int(_m.group("patch")),(_m.group("prerelease") if _m.group("prerelease") else ""), ""
pre_count = 0
post_count = 0
_v = cls.MetaVersion(major, minor, patch, pre_count, post_count, lastTag)
if gitversionhelper.repository.isDirty() is not False:
raise gitversionhelper.repository.repositoryDirty( "The repository is dirty and a current version" \
" can not be generated.")
saved_kwargs = copy(kwargs)
if "formated_output" in kwargs:
del saved_kwargs["formated_output"]
_v = cls.getLastVersion(**saved_kwargs)
if not cls.__versionReseted:
amount = gitversionhelper.tag.getDistanceFromTag(_v.raw,**kwargs)
_v = _v.bump(amount,**saved_kwargs)
if ((cls.__OptDict["formated_output"] in kwargs) and (kwargs[cls.__OptDict["formated_output"]] is True)):
return cls.doFormatVersion(_v,**kwargs)
else:
return _v
return _v.doFormatVersion(**kwargs)
return _v
@classmethod
def doFormatVersion(cls,inputversion:MetaVersion,**kwargs):
def getCurrentFormatedVersion(cls,**kwargs) -> str :
"""
output a formated version
Same as getCurrentVersion() with formated_output kwarg activated
"""
kwargs["formated_output"]=True
return cls.getCurrentVersion(kwargs)
@classmethod
def _parseTag(cls,tag,**kwargs): # pylint: disable=R0914, R0912, R0915
"""get the last version from tags
Arguments:
tag: the tag to be parsed
Keyword Arguments:
version_std(str): the given version_std (can be None)
ignore_unknown_tags(bool): skip tags with not decoded versions (default to False)
Returns:
the last version
"""
VersionStd = cls._getVersionStd(**kwargs)
bAutoVersionStd = False
if VersionStd == "Auto":
bAutoVersionStd = True
bFound = False
if VersionStd == "SemVer" or (bAutoVersionStd is True) :
_r=re.compile(r"^\s*" + cls.VersionStds["SemVer"]["regex"] + r"\s*$", re.VERBOSE | \
re.IGNORECASE)
_m = re.match(_r,tag)
if not _m:
pass
else:
major, minor, patch = int(_m.group("major")),\
int(_m.group("minor")),\
int(_m.group("patch"))
pre_count = 0
if _pre := _m.group("prerelease"):
if (_match := re.search (cls.VersionStds["SemVer"]["regex_preversion_num"],_pre)) is not None:
pre_count = int(_match.group("num"))
else:
pre_count = 1
post_count = 0
if _post := _m.group("buildmetadata"):
if (_match := re.search (cls.VersionStds["SemVer"]["regex_build_num"],_post)) is not None:
post_count = int(_match.group("num"))
else:
post_count = 1
bFound = True
VersionStd = "SemVer"
if VersionStd == "PEP440" or ( (bAutoVersionStd is True) and (bFound is not True)):
_r=re.compile(r"^\s*" + cls.VersionStds["PEP440"]["regex"] + r"\s*$", re.VERBOSE | \
re.IGNORECASE)
_m = re.match(_r,tag)
if not _m:
pass
else:
ver=_m.group("release").split(".")
ver += ["0"] * (3 - len(ver))
ver[0]=int(ver[0])
ver[1]=int(ver[1])
ver[2]=int(ver[2])
major, minor, patch = tuple(ver)
pre_count = int(_m.group("pre_n")) if _m.group("pre_n") else 0
post_count = int(_m.group("post_n2")) if _m.group("post_n2") else 0
bFound = True
VersionStd = "PEP440"
if not bFound :
raise gitversionhelper.version.noValidVersion("no valid version found in tags")
if pre_count > 0 and post_count > 0:
raise cls.PreAndPostVersionUnsupported("can not parse a version with both pre" \
" and post release number.")
return cls.MetaVersion(VersionStd, major, minor, patch, pre_count, post_count, tag)
@classmethod
def getLastVersion(cls,**kwargs) -> MetaVersion | str : # pylint: disable=R0914, R0912, R0915
"""get the last version from tags
Keyword Arguments:
version_std(str): the given version_std (can be None)
same_branch(bool): force searching only in the same branch
formated_output(bool) : output a formated version string
ignore_unknown_tags(bool): skip tags with not decoded versions (default to False)
Returns:
the last version
"""
lastTag=cls.MetaVersion.raw
cls.__versionReseted = False
try:
lastTag = gitversionhelper.tag.getLastTag(**kwargs)
except gitversionhelper.tag.tagNotFound:
logging.warning('tag not found, reseting versionning')
cls.__versionReseted = True
_v=None
try:
_v=cls._parseTag(lastTag,**kwargs)
except gitversionhelper.version.noValidVersion as _ex:
if ((cls.__OptDict["ignore_unknown_tags"] in kwargs) and (kwargs[cls.__OptDict["ignore_unknown_tags"]] is True)):
tags = gitversionhelper.tag.getTags(sort= "taggerdate",**kwargs)
_v = None
for _tag in tags:
try:
_v=cls._parseTag(_tag,**kwargs)
break
except gitversionhelper.version.noValidVersion:
continue
if _v is None:
raise gitversionhelper.version.noValidVersion() from _ex
if ((cls.__OptDict["formated_output"] in kwargs) and (kwargs[cls.__OptDict["formated_output"]] is True)):
return _v.doFormatVersion(**kwargs)
return _v
@classmethod
def doFormatVersion(cls,inputversion:MetaVersion,**kwargs) -> str:
"""
output a formated version string
Keyword Arguments:
output_format: output format to render ("Auto" or "PEP440" or "SemVer")
Args:
inputversion: version to be rendered
Returns:
formated version string
"""
VersionStd = cls._getVersionStd(**kwargs)
if VersionStd=="Auto" :
VersionStd = inputversion.version_std
OutputFormat = None
revpattern=""
revcount=""
post_count = inputversion.post_count
pre_count = inputversion.pre_count
patch = inputversion.patch
if (cls.__OptDict["output_format"] in kwargs):
if cls.__OptDict["output_format"] in kwargs:
OutputFormat=kwargs[cls.__OptDict["output_format"]]
if OutputFormat is None:
if VersionStd is "PEP440":
OutputFormat = "{major}.{minor}.{patch}{revpattern}{revcount}"
OutputFormat = "{major}.{minor}.{patch}{revpattern}{revcount}"
if post_count > 0 and pre_count > 0:
raise gitversionhelper.version.PreAndPostVersionUnsupported("cannot output a version with both pre " \
"and post release number.")
if VersionStd == "PEP440":
if post_count > 0:
revpattern=".post"
revcount=f"{post_count}"
elif VersionStd is "SemVer":
OutputFormat = "{major}.{minor}.{patch}{revpattern}{revcount}"
if post_count > 0:
pre_count = pre_count + post_count
post_count = 0
patch = patch + 1
if pre_count > 0:
revpattern="-pre"
elif pre_count > 0:
revpattern=".pre"
revcount=f"{pre_count}"
return OutputFormat.format(major=inputversion.major,minor=inputversion.minor,patch=patch,revpattern=revpattern,revcount=revcount)
#print(gitversionhelper.head.isDirty())
#print(gitversionhelper.tag.getTags())
#print(gitversionhelper.tag.getLastTag())
#print(gitversionhelper.tag.getLastTag(same_branch=True))
#print(gitversionhelper.tag.getDistanceFromLastTag())
#print(gitversionhelper.tag.getDistanceFromLastTag(same_branch=True))
#print(gitversionhelper.version.getLastVersion())
#print(gitversionhelper.version.getLastVersion(same_branch=True,version_std="PEP440"))
#print(gitversionhelper.version.getLastVersion(same_branch=True,version_std="PEP440"))
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440"))
#print(gitversionhelper.version.getLastVersion(same_branch=True,version_std="PEP440",formated_output=True))
#print(gitversionhelper.version.getLastVersion(same_branch=True,version_std="PEP440",formated_output=True,output_format="v{major}.{minor}.{patch}"))
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump())
#print("")
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump(bump_type="dev").doFormatVersion(version_std="SemVer"))
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump(bump_type="patch").doFormatVersion(version_std="SemVer"))
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump(bump_type="minor").doFormatVersion(version_std="SemVer"))
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump(bump_type="major").doFormatVersion(version_std="SemVer"))
#print("")
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump(bump_type="dev").doFormatVersion(version_std="PEP440"))
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump(bump_type="patch").doFormatVersion(version_std="PEP440"))
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump(bump_type="minor").doFormatVersion(version_std="PEP440"))
#print(gitversionhelper.version.getLastVersion(same_branch=False,version_std="PEP440").bump(bump_type="major").doFormatVersion(version_std="PEP440"))
#from setuptools_git_versioning import version_from_git
#print(str(version_from_git(tag_filter="^\d+\.\d+\.\d+$",dev_template = "{tag}.post{ccount}",sort_by="v:refname")),end ="")
#elif VersionStd == "SemVer":
else:
if post_count > 0:
revpattern="+post"
revcount=f".{post_count}"
elif pre_count > 0:
revpattern="-pre"
revcount=f".{pre_count}"
return OutputFormat.format( major=inputversion.major, \
minor=inputversion.minor, \
patch=patch, \
revpattern=revpattern, \
revcount=revcount)

File diff suppressed because it is too large Load Diff