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] 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 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: # A Dictionairy to store grouped commits commitsByType = {"fix":[], "feat":[], "Other":[], "Nonconform":[]} # If latest commit has no tag: if not tag[0]: fileTemplate.append("\n## No version number yet") else: fileTemplate.append("\n## Version " + tag[0].tagAsString) # Grouping by CommitTypes for commit in tag[1:]: if commit.body.commitType == CommitBody.commitTypes[5]: # fix commitsByType["fix"].append(commit) elif commit.body.commitType == CommitBody.commitTypes[4]: # feat commitsByType["feat"].append(commit) elif commit.body.commitType == "nonconform": commitsByType["Nonconform"].append(commit) else: commitsByType["Other"].append(commit) if len(commitsByType["feat"]) != 0: fileTemplate.append("### Features") for feature in commitsByType["feat"]: if feature.body.scope: fileTemplate.append("- **" + feature.body.scope + "**: " + feature.body.subject + feature.appendShortHash()) else: fileTemplate.append("- " + feature.body.subject + feature.appendShortHash()) if len(commitsByType["fix"]) != 0: fileTemplate.append("### Fixes") for fix in commitsByType["fix"]: if fix.body.scope: fileTemplate.append("- **" + fix.body.scope + "**: " + fix.body.scope + fix.appendShortHash()) else: fileTemplate.append("- " + fix.body.subject + fix.appendShortHash()) if len(commitsByType["Other"]) != 0: fileTemplate.append("### Other") for other in commitsByType["Other"]: if other.body.scope: fileTemplate.append("- **" + other.body.scope + "**: " + other.body.getCommitMessageWithType() + other.appendShortHash()) else: fileTemplate.append("- " + other.body.getCommitMessageWithType() + other.appendShortHash()) if len(commitsByType["Nonconform"]) != 0: fileTemplate.append("### Non-conventional") for nonconform in commitsByType["Nonconform"]: fileTemplate.append("- " + nonconform.body.subject + nonconform.appendShortHash()) # write into changelog with open(pathToRepo + "/changelog.md", "w") as file: for line in fileTemplate: file.write(line + "\n")