Daniel Fütterer
18.02.21 56795e887def81307eb5710ce2ac82cc269c307b
commit | author | age
61574e 1 import os.path
DF 2
56795e 3 ## To Do
DF 4 # - Breaking Changes
5 # - Merge Commits
6 # - Error Handling
7
61574e 8 def getSeparatedGitLog(repo):
DF 9     try:
10         stream = os.popen("git -C {} log --format=%B--SEP--%H--SEP--%d--END--".format(repo))
11     except:
12         raise ValueError("Not a valid git-repository!")
13     else:
14         gitLog = stream.read()
15         commitList = gitLog.split("--END--")
16         del commitList[-1]
17         return commitList
18      
19
20 class RawCommit:
21     def __init__(self, completeCommit):
22         self.raw = completeCommit.split("--SEP--")
23         self.body = self.raw[0].strip()
24         self.hash = self.raw[1].strip()
25         self.tag = self.raw[2].strip()
26
27
28 class CommitBody:
29     commitTypes = ("build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "style", "test")
30     def __init__(self, completeMessage):
31         self.completeMessage = completeMessage
32         #self.subject = None
33         #self.body = None
34         #self.scope = None
35         #self.commitType = None
36
37         self.setSubjectAndBody()
38         self.setScope()
39         self.setCommitType()
40     
41     def setSubjectAndBody(self):
56795e 42         try:
DF 43             subStart = self.completeMessage.index(": ")+2
44         except:
45             try:
46                 subStart = self.completeMessage.index("\n")
47             except:
48                 subStart = 0
61574e 49         try:
DF 50             subEnd = self.completeMessage.index("\n")
51         except:
52             self.subject = self.completeMessage[subStart:]
53             self.body = None
54         else:
55             self.subject = self.completeMessage[subStart:subEnd]
56             self.body = self.completeMessage[subEnd:].strip()
57     
58     def setScope(self):
59         try:
60             start = self.completeMessage.index("(")+1
61             end = self.completeMessage.index(")")
62         except:
63             self.scope = None
64         else:
65             self.scope = self.completeMessage[start:end]
66     
67     def setCommitType(self):
68         for commitType in self.commitTypes:
69             if (self.completeMessage.startswith((commitType + ": "))) or (self.completeMessage.startswith((commitType + "("))):
70                 self.commitType = commitType
71                 break
56795e 72             else:
DF 73                 self.commitType = "nonconform"
74     
75     def getCommitMessageWithType(self):
76         return self.commitType + ": " + self.subject
61574e 77         
DF 78
79 class CommitTag:
80     def __init__(self, completeTag):
81         if not ("tag: " in completeTag):
82             self.completeTag = None
83             self.tagAsString = None
84             self.major = None
85             self.minor = None
86             self.bugfix = None
87         else:
88             self.completeTag = completeTag
89             self.setTagAsString()
90             self.setMajorMinorBugfix()
91
92     def setTagAsString(self):
93         try: ## this one is temporary
94             self.tagAsString = self.completeTag[(self.completeTag.rindex(": v.")+4):-1]
95         except:
96             try:
97                 self.tagAsString = self.completeTag[(self.completeTag.rindex(": v")+3):-1]
98             except:
99                 self.tagAsString = self.completeTag[(self.completeTag.rindex(": ")+2):-1]
100
101     def setMajorMinorBugfix(self):
102         versionList = self.tagAsString.split(".")
103         self.major = versionList[0]
104         self.minor = versionList[1]
105         self.bugfix = versionList[2]
106     
107     @staticmethod
108     def getUpdateType(newTag, previousTag):
109         if newTag.major > previousTag.major:
110             return "major"
111         elif newTag.minor > previousTag.minor:
112             return "minor"
113         elif newTag.bugfix > previousTag.bugfix:
114             return "bugfix"
115
116
117 class Commit:
118     def __init__(self, rawCommit):
119         self.body = CommitBody(rawCommit.body)
120         self.tag = CommitTag(rawCommit.tag)
121         self.hash = rawCommit.hash
56795e 122     
DF 123     def appendShortHash(self):
124         return " (" + self.hash[:6] + ")"
61574e 125
DF 126
127
128 #### Main ####
129
130 commitList = getSeparatedGitLog("/Users/daniel/Desktop/testrepo")
131
132 # Create a list of commits
133 commitHistory = []
134 for commit in commitList:
135     commitHistory.append(Commit(RawCommit(commit)))
136
137 # Create a two-dimensional list by tags: [[tag, [commits]],[tag, [commits]],...]
138 taggedHistory = []
139 for commit in commitHistory:
140     if commit.tag.tagAsString:
141         taggedHistory.append([commit.tag, commit])
142     else:
143         if len(taggedHistory) == 0:
144             taggedHistory.append([None, commit])
145         else:
146             taggedHistory[-1].append(commit)
147
148
149 # Construction of the changelog-file
150 fileTemplate = ["# Changelog"]
151 for tag in taggedHistory:
152     # A Dictionairy to store grouped commits
56795e 153     commitsByType = {"Fixes":[], "Features":[], "Other":[], "Nonconform":[]}
61574e 154     commitsByScope = {}
DF 155
156     # If latest commit has no tag:
157     if not tag[0]:
158         fileTemplate.append("\n## No version number yet ")
159     else:
160         fileTemplate.append("\n## Version " + tag[0].tagAsString)
161
162     # Grouping by CommitTypes
163     for commit in tag[1:]:
164         # Dealing with scopes
165         if commit.body.scope:
166             if commit.body.scope not in commitsByScope:
167                 commitsByScope[commit.body.scope] = [commit]
168             else:
169                 commitsByScope[commit.body.scope].append(commit)
170             for scope in commitsByScope:
56795e 171                 fileTemplate.append("### Scope: " + scope)
61574e 172                 for commit in commitsByScope[scope]:
56795e 173                     fileTemplate.append("- " + commit.body.getCommitMessageWithType() + commit.appendShortHash())
61574e 174         else:
DF 175             # Sorting in predifend groups
176             if commit.body.commitType == CommitBody.commitTypes[5]: # fix
177                 commitsByType["Fixes"].append(commit)
178             elif commit.body.commitType == CommitBody.commitTypes[4]: # feat
179                 commitsByType["Features"].append(commit)
56795e 180             elif commit.body.commitType == "nonconform":
DF 181                 commitsByType["Nonconform"].append(commit)
61574e 182             else:
DF 183                 commitsByType["Other"].append(commit)
184     
185     if len(commitsByType["Features"]) != 0:
186         fileTemplate.append("### Features")
187         for feature in commitsByType["Features"]:
56795e 188             fileTemplate.append("- " + feature.body.subject + feature.appendShortHash())
61574e 189     if len(commitsByType["Fixes"]) != 0:
DF 190         fileTemplate.append("### Fixes")
191         for fix in commitsByType["Fixes"]:
56795e 192             fileTemplate.append("- " + fix.body.subject + fix.appendShortHash())
61574e 193     if len(commitsByType["Other"]) != 0:
DF 194         fileTemplate.append("### Other")
195         for other in commitsByType["Other"]:
56795e 196             fileTemplate.append("- " + other.body.getCommitMessageWithType() + other.appendShortHash())
DF 197     if len(commitsByType["Nonconform"]) != 0:
198         fileTemplate.append("### Non-conventional")
199         for nonconform in commitsByType["Nonconform"]:
200             fileTemplate.append("- " + nonconform.body.subject + nonconform.appendShortHash())
61574e 201         
DF 202
203 # write into changelog
204 with open("changelog.md", "w") as file:
205     for line in fileTemplate:
206         file.write(line + "\n")