import os.path
|
import sys
|
|
## To Do
|
# - Breaking Changes
|
# - Merge Commits
|
# - Deal with wrong user input (spelling, no repo)
|
|
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 ####
|
|
inputPath = input("Please enter the base path of the repository: ")
|
userDecision = input("Should the generated changelog be stored in another location (y/n)? ").lower()
|
if userDecision == "y":
|
outputPath = (input("Please enter the output path: "))
|
elif userDecision == "n":
|
print("The changelog will be stored in the same location as the repository.")
|
outputPath = inputPath
|
else:
|
print("invalid input")
|
sys.exit(1)
|
|
commitList = getSeparatedGitLog(inputPath)
|
|
# 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
|
featType = ["Features"]
|
fixType = ["Fixes"]
|
otherType = ["Other"]
|
nonconformCommits = []
|
|
for commit in tag[1:]:
|
if commit.body.commitType == CommitBody.commitTypes[5]: # fix
|
fixType.append(commit)
|
elif commit.body.commitType == CommitBody.commitTypes[4]: # feat
|
featType.append(commit)
|
elif commit.body.commitType == "nonconform":
|
nonconformCommits.append(commit)
|
else:
|
otherType.append(commit)
|
|
# Sub-Grouping by Scopes within Types
|
commitlistByType = [featType, fixType, otherType]
|
for commitsByType in commitlistByType:
|
if len(commitsByType) == 1:
|
continue
|
commitlistByScope = {}
|
noScope = []
|
for commit in commitsByType:
|
if type(commit) == str:
|
continue
|
if commit.body.scope == None:
|
noScope.append(commit)
|
elif commit.body.scope in commitlistByScope:
|
commitlistByScope[commit.body.scope].append(commit)
|
else:
|
commitlistByScope[commit.body.scope] = [commit]
|
|
fileTemplate.append("\n### " + commitsByType[0])
|
while len(commitlistByScope) > 0:
|
scope, commits = commitlistByScope.popitem()
|
fileTemplate.append("- *" + str(scope) + "*")
|
for commit in commits:
|
if commitsByType[0] == "Other":
|
fileTemplate.append(" - (" + commit.body.commitType + ") " + commit.body.subject + commit.appendShortHash())
|
else:
|
fileTemplate.append(" - " + commit.body.subject + commit.appendShortHash())
|
if len(noScope) > 0:
|
fileTemplate.append("- *no scope*")
|
for commit in noScope:
|
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(outputPath + "/changelog.md", "w") as file:
|
for line in fileTemplate:
|
file.write(line + "\n")
|