From cefa964a3a8fcf5debe0c578964eeaa16408fd7d Mon Sep 17 00:00:00 2001 From: cclecle Date: Sun, 19 Mar 2023 09:52:42 +0000 Subject: [PATCH] add commit class --- Jenkinsfile | 1 - src/pygitversionhelper/gitversionhelper.py | 115 +++++++++++- test/test_gitversionhelper.py | 204 ++++++++++++++++++++- 3 files changed, 309 insertions(+), 11 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ceb6d3a..2b80254 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -181,7 +181,6 @@ pipeline { 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__' diff --git a/src/pygitversionhelper/gitversionhelper.py b/src/pygitversionhelper/gitversionhelper.py index 30d8fd5..9f70d58 100644 --- a/src/pygitversionhelper/gitversionhelper.py +++ b/src/pygitversionhelper/gitversionhelper.py @@ -5,7 +5,6 @@ # # You should have received a copy of the license along with this # work. If not, see . - """ This project try to help doing handy operations with git when dealing with project versioning and tags on python project - @@ -13,6 +12,7 @@ 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. @@ -23,8 +23,7 @@ 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 +Note: _Other Parameters_ are **kwargs """ from __future__ import annotations @@ -37,7 +36,7 @@ import logging from packaging.version import VERSION_PATTERN as packaging_VERSION_PATTERN -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 Args: @@ -47,13 +46,16 @@ def _exec(cmd: str, root: str | os.PathLike | None = None) -> list[str]: a list of command's return lines """ - p = subprocess.run(cmd.split(), text=True, cwd=root, capture_output=True, check=False, timeout=2) + 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()] @@ -108,6 +110,103 @@ class gitversionhelper: # pylint: disable=too-few-public-methods True if it is dirty """ 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,**kwargs) + + 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,**kwargs)) + + 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, id:str, **kwargs) -> str: + """ + retrieve a commit message from repository + Args: + id: id of the commit + Returns: + the commit message + """ + try: + res=_exec(f"git log -z --pretty=tformat:%B%-C() -n 1 {id}",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,**kwargs) -> 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: """ @@ -132,7 +231,7 @@ class gitversionhelper: # pylint: disable=too-few-public-methods """ @classmethod - def getTags(cls,sort:str = "taggerdate",**kwargs) -> list[str]: + def getTags(cls,sort:str = "taggerdate",**kwargs) -> list[str|None]: """ retrieve all tags from a repository Args: @@ -457,8 +556,8 @@ class gitversionhelper: # pylint: disable=too-few-public-methods VersionStd = "PEP440" if not bFound : - raise gitversionhelper.version.noValidVersion("no valid version found in tags") - + 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.") diff --git a/test/test_gitversionhelper.py b/test/test_gitversionhelper.py index 869b00a..0d7db2b 100644 --- a/test/test_gitversionhelper.py +++ b/test/test_gitversionhelper.py @@ -13,6 +13,8 @@ import os import pathlib import re import copy +import time +import subprocess print(__name__) print(__package__) @@ -507,8 +509,7 @@ class Test_gitversionhelper(unittest.TestCase): self.assertEqual(_v.major,0) self.assertEqual(_v.minor,1) self.assertEqual(_v.patch,0) - - + def test_nominal__version___AUTO_bump_commits(self): with open("demofile.txt", "w+t") as tmpFile: tmpFile.write("testvalue") @@ -1114,6 +1115,205 @@ class Test_gitversionhelper(unittest.TestCase): self.assertEqual(_v.minor,1) self.assertEqual(_v.patch,3) + def test_nominal__commit_getLast(self): + # sleeps are needed because commit date have a 1-sec accuracy :-/ + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue") + os.system("git add .") + os.system("git commit -m \"commit\"") + time.sleep(1) + + initial_commit = pygitversionhelper.gitversionhelper.commit.getLast() + + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue2") + os.system("git add .") + os.system("git commit -m \"commit\"") + time.sleep(1) + + second_commit = pygitversionhelper.gitversionhelper.commit.getLast() + + self.assertNotEqual(initial_commit,second_commit) + + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue3") + os.system("git add .") + os.system("git commit -m \"commit\"") + time.sleep(1) + + third_commit = pygitversionhelper.gitversionhelper.commit.getLast() + + self.assertNotEqual(initial_commit,third_commit) + self.assertNotEqual(second_commit,third_commit) + + + os.system("git checkout -b dev") + + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue4") + os.system("git add .") + os.system("git commit -m \"commit\"") + time.sleep(1) + + fourth_commit = pygitversionhelper.gitversionhelper.commit.getLast() + + self.assertNotEqual(initial_commit,fourth_commit) + self.assertNotEqual(second_commit,fourth_commit) + self.assertNotEqual(third_commit,fourth_commit) + + os.system("git switch master") + + fourth2_commit = pygitversionhelper.gitversionhelper.commit.getLast() + self.assertEqual(fourth2_commit,fourth_commit) + + fourth3_commit = pygitversionhelper.gitversionhelper.commit.getLast(same_branch=True) + + self.assertNotEqual(fourth3_commit,fourth_commit) + self.assertEqual(fourth3_commit,third_commit) + + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue5") + os.system("git add .") + os.system("git commit -m \"commit\"") + time.sleep(1) + + fifth_commit = pygitversionhelper.gitversionhelper.commit.getLast() + self.assertNotEqual(fifth_commit,fourth3_commit) + + os.system("git switch dev") + + fifth2_commit = pygitversionhelper.gitversionhelper.commit.getLast() + self.assertEqual(fifth2_commit,fifth_commit) + + fifth2_commit = pygitversionhelper.gitversionhelper.commit.getLast(same_branch=True) + self.assertEqual(fifth2_commit,fourth_commit) + + def test_defect__commit_notfound(self): + + with self.assertRaises(pygitversionhelper.gitversionhelper.commit.commitNotFound) : + pygitversionhelper.gitversionhelper.commit.getLast() + + with self.assertRaises(pygitversionhelper.gitversionhelper.commit.commitNotFound) : + pygitversionhelper.gitversionhelper.commit.getLast(same_branch=True) + + def test_nominal__commit_getFromTag(self): + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue") + os.system("git add .") + os.system("git commit -m \"commit\"") + + initial_commit = pygitversionhelper.gitversionhelper.commit.getLast() + + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue2") + os.system("git add .") + os.system("git commit -m \"commit2\"") + os.system(f"git tag TAG") + + second_commit = pygitversionhelper.gitversionhelper.commit.getLast() + self.assertNotEqual(second_commit,initial_commit) + second_commit2 = pygitversionhelper.gitversionhelper.commit.getFromTag("TAG") + self.assertEqual(second_commit,second_commit2) + self.assertNotEqual(second_commit2,initial_commit) + + def test_defect__commit_notfound2(self): + + with self.assertRaises(pygitversionhelper.gitversionhelper.commit.commitNotFound) : + pygitversionhelper.gitversionhelper.commit.getFromTag("TAG") + + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue") + os.system("git add .") + os.system("git commit -m \"commit\"") + + with self.assertRaises(pygitversionhelper.gitversionhelper.commit.commitNotFound) : + pygitversionhelper.gitversionhelper.commit.getFromTag("TAG") + + os.system(f"git tag OTHER_TAG") + + with self.assertRaises(pygitversionhelper.gitversionhelper.commit.commitNotFound) : + pygitversionhelper.gitversionhelper.commit.getFromTag("TAG") + + def test_nominal__commit_getMessage(self): + commit_message="AAAABBB CCCCDDDD" + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue") + os.system("git add .") + os.system(f"git commit -m \"{commit_message}\"") + commit = pygitversionhelper.gitversionhelper.commit.getLast() + message = pygitversionhelper.gitversionhelper.commit.getMessage(commit) + self.assertEqual(message,commit_message) + + def test_nominal__commit_getMessage2(self): + commit_message="""AAAABBB + CCCCDDDD + -f dfsds dfsdfs $""" + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue") + os.system("git add .") + + cmd = "git commit -m".split() + cmd.append(commit_message) + subprocess.run(cmd,text=True,check=True) + + commit = pygitversionhelper.gitversionhelper.commit.getLast() + message = pygitversionhelper.gitversionhelper.commit.getMessage(commit) + print(message) + self.assertEqual(message,commit_message) + + def test_nominal__commit_getMessagesSinceLastTag(self): + commit_message1="1.1 update this"+ os.linesep+\ + "1.1 fix that" + os.linesep+\ + "1.1 test" + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue") + os.system("git add .") + + cmd = "git commit -m".split() + cmd.append(commit_message1) + subprocess.run(cmd,text=True,check=True) + os.system(f"git tag 0.1.1") + + commit_message2="2.1 update this"+ os.linesep+\ + "2.1 fix that" + os.linesep+\ + "2.1 test" + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue2") + os.system("git add .") + + cmd = "git commit -m".split() + cmd.append(commit_message2) + subprocess.run(cmd,text=True,check=True) + + commit_message3="3.1 update this"+ os.linesep+\ + "3.1 fix that" + os.linesep+\ + "3.1 test" + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue3") + os.system("git add .") + + cmd = "git commit -m".split() + cmd.append(commit_message3) + subprocess.run(cmd,text=True,check=True) + + commit_message4="4.1 update this" + os.linesep+\ + "4.1 fix that" + os.linesep+\ + "4.1 test" + with open("demofile.txt", "w+t") as tmpFile: + tmpFile.write("testvalue4") + os.system("git add .") + + cmd = "git commit -m".split() + cmd.append(commit_message4) + subprocess.run(cmd,text=True,check=True) + + res = pygitversionhelper.gitversionhelper.commit.getMessagesSinceTag("0.1.1") + self.assertEqual( [commit_message4,commit_message3,commit_message2],res) + + res = pygitversionhelper.gitversionhelper.commit.getMessagesSinceTag("0.1.1",merged_output=True) + self.assertEqual( os.linesep.join([commit_message4,commit_message3,commit_message2]),res) + + def tearDown(self): os.chdir("/")