Daniel Fütterer
27.04.21 b07d2e00d40692f9076e65cfe41140d4515b7b32
feat: Added version with scopes

additional script with sorts by scopes withon types
1 files added
220 ■■■■■ changed files
oop_changelog_scope.py 220 ●●●●● patch | view | raw | blame | history
oop_changelog_scope.py
New file
@@ -0,0 +1,220 @@
import os.path
## To Do
# - Breaking Changes
# - Merge Commits
# - Error Handling
def getSeparatedGitLog(repo):
    try:
        stream = os.popen("git -C {} log --format=%B--SEP--%H--SEP--%d--END--".format(repo))
    except:
        raise ValueError("Not a valid git-repository!")
    else:
        gitLog = stream.read()
        commitList = gitLog.split("--END--")
        del commitList[-1]
        return commitList
class RawCommit:
    def __init__(self, completeCommit):
        self.raw = completeCommit.split("--SEP--")
        self.body = self.raw[0].strip()
        self.hash = self.raw[1].strip()
        self.tag = self.raw[2].strip()
class CommitBody:
    commitTypes = ("build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "style", "test")
    def __init__(self, completeMessage):
        self.completeMessage = completeMessage
        #self.subject = None
        #self.body = None
        #self.scope = None
        #self.commitType = None
        self.setSubjectAndBody()
        self.setScope()
        self.setCommitType()
    def setSubjectAndBody(self):
        try:
            subStart = self.completeMessage.index(": ")+2
        except:
            try:
                subStart = self.completeMessage.index("\n")
            except:
                subStart = 0
        try:
            subEnd = self.completeMessage.index("\n")
        except:
            self.subject = self.completeMessage[subStart:]
            self.body = None
        else:
            self.subject = self.completeMessage[subStart:subEnd]
            self.body = self.completeMessage[subEnd:].strip()
    def setScope(self):
        try:
            start = self.completeMessage.index("(")+1
            end = self.completeMessage.index(")")
        except:
            self.scope = None
        else:
            self.scope = self.completeMessage[start:end].strip().lower()
    def setCommitType(self):
        for commitType in self.commitTypes:
            if (self.completeMessage.startswith((commitType + ": "))) or (self.completeMessage.startswith((commitType + "("))) or (self.completeMessage.startswith((commitType + " ("))):
                self.commitType = commitType
                break
            else:
                self.commitType = "nonconform"
    def getCommitMessageWithType(self):
        return self.commitType + ": " + self.subject
class Scope:
    def __init__(self, name):
        self.name = name.strip().lower()
    @staticmethod
    def createScope(name):
        return Scope(name)
class CommitTag:
    def __init__(self, completeTag):
        if not ("tag: " in completeTag):
            self.completeTag = None
            self.tagAsString = None
            self.major = None
            self.minor = None
            self.bugfix = None
        else:
            self.completeTag = completeTag
            self.setTagAsString()
            self.setMajorMinorBugfix()
    def setTagAsString(self):
        try: ## this one is temporary
            self.tagAsString = self.completeTag[(self.completeTag.rindex(": v.")+4):-1]
        except:
            try:
                self.tagAsString = self.completeTag[(self.completeTag.rindex(": v")+3):-1]
            except:
                self.tagAsString = self.completeTag[(self.completeTag.rindex(": ")+2):-1]
    def setMajorMinorBugfix(self):
        versionList = self.tagAsString.split(".")
        self.major = versionList[0]
        self.minor = versionList[1]
        self.bugfix = versionList[2]
    @staticmethod
    def getUpdateType(newTag, previousTag):
        if newTag.major > previousTag.major:
            return "major"
        elif newTag.minor > previousTag.minor:
            return "minor"
        elif newTag.bugfix > previousTag.bugfix:
            return "bugfix"
class Commit:
    def __init__(self, rawCommit):
        self.body = CommitBody(rawCommit.body)
        self.tag = CommitTag(rawCommit.tag)
        self.hash = rawCommit.hash
    def appendShortHash(self):
        return " (" + self.hash[:6] + ")"
#### Main ####
pathToRepo = "/Users/daniel/Developer/Repos/HfM/schumacher/Prisma-Binauralize"
#pathToRepo = "/Users/daniel/Desktop/testrepo"
commitList = getSeparatedGitLog(pathToRepo)
# Create a list of commits
commitHistory = []
for commit in commitList:
    commitHistory.append(Commit(RawCommit(commit)))
# Create a two-dimensional list by tags: [[tag, [commits]],[tag, [commits]],...]
taggedHistory = []
for commit in commitHistory:
    if commit.tag.tagAsString:
        taggedHistory.append([commit.tag, commit])
    else:
        if len(taggedHistory) == 0:
            taggedHistory.append([None, commit])
        else:
            taggedHistory[-1].append(commit)
# Construction of the changelog-file
fileTemplate = ["# Changelog"]
for tag in taggedHistory:
    # If latest commit has no tag:
    if not tag[0]:
        fileTemplate.append("\n## Without version number")
    else:
        fileTemplate.append("\n## Version " + tag[0].tagAsString)
    # Grouping by Type
    commitsByType = {"Other":[], "Features":[], "Fixes":[]}
    nonconformCommits = []
    for commit in tag[1:]:
        if commit.body.commitType == CommitBody.commitTypes[5]: # fix
            commitsByType["Fixes"].append(commit)
        elif commit.body.commitType == CommitBody.commitTypes[4]: # feat
            commitsByType["Features"].append(commit)
        elif commit.body.commitType == "nonconform":
            nonconformCommits.append(commit)
        else:
            commitsByType["Other"].append(commit)
    # Sub-Grouping by Scopes within Types
    while len(commitsByType) > 0:
        commitsByScope = {}
        commitType, commits = commitsByType.popitem()
        if len(commits) == 0:
            continue
        for commit in commits:
            if commit.body.scope == None:
                if "no scope" not in commitsByScope:
                    commitsByScope["no scope"] = [commit]
                else:
                    commitsByScope["no scope"].append(commit)
            elif commit.body.scope in commitsByScope:
                commitsByScope[commit.body.scope].append(commit)
            else:
                commitsByScope[commit.body.scope] = [commit]
        fileTemplate.append("\n### " + str(commitType))
        while len(commitsByScope) > 0:
            scope, commits = commitsByScope.popitem()
            fileTemplate.append("- *" + str(scope) + "*")
            for commit in commits:
                if commitType == "Other":
                    fileTemplate.append("    - (" + commit.body.commitType + ") " + commit.body.subject + commit.appendShortHash())
                else:
                    fileTemplate.append("    - " + commit.body.subject + commit.appendShortHash())
    # nonconform commits
    if len(nonconformCommits) > 0:
        fileTemplate.append("\n### Non-conform commits")
        for commit in nonconformCommits:
            fileTemplate.append("- " + commit.body.subject + commit.appendShortHash())
# write into changelog
with open(pathToRepo + "/changelog.md", "w") as file:
    for line in fileTemplate:
        file.write(line + "\n")