Daniel Fütterer
28.05.21 b2e7285431fd668e5c6ea529b6ac941241672b97
commit | author | age
b07d2e 1 import os.path
DF 2
3 ## To Do
4 # - Breaking Changes
5 # - Merge Commits
6 # - Error Handling
7
8 def getSeparatedGitLog(repo):
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):
42         try:
43             subStart = self.completeMessage.index(": ")+2
44         except:
45             try:
46                 subStart = self.completeMessage.index("\n")
47             except:
48                 subStart = 0
49         try:
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].strip().lower()
66     
67     def setCommitType(self):
68         for commitType in self.commitTypes:
69             if (self.completeMessage.startswith((commitType + ": "))) or (self.completeMessage.startswith((commitType + "("))) or (self.completeMessage.startswith((commitType + " ("))):
70                 self.commitType = commitType
71                 break
72             else:
73                 self.commitType = "nonconform"
74     
75     def getCommitMessageWithType(self):
76         return self.commitType + ": " + self.subject
77         
78 class Scope:
79     def __init__(self, name):
80         self.name = name.strip().lower()
81     
82     @staticmethod
83     def createScope(name):
84         return Scope(name)
85
86 class CommitTag:
87     def __init__(self, completeTag):
88         if not ("tag: " in completeTag):
89             self.completeTag = None
90             self.tagAsString = None
91             self.major = None
92             self.minor = None
93             self.bugfix = None
94         else:
95             self.completeTag = completeTag
96             self.setTagAsString()
97             self.setMajorMinorBugfix()
98
99     def setTagAsString(self):
100         try: ## this one is temporary
101             self.tagAsString = self.completeTag[(self.completeTag.rindex(": v.")+4):-1]
102         except:
103             try:
104                 self.tagAsString = self.completeTag[(self.completeTag.rindex(": v")+3):-1]
105             except:
106                 self.tagAsString = self.completeTag[(self.completeTag.rindex(": ")+2):-1]
107
108     def setMajorMinorBugfix(self):
109         versionList = self.tagAsString.split(".")
110         self.major = versionList[0]
111         self.minor = versionList[1]
112         self.bugfix = versionList[2]
113     
114     @staticmethod
115     def getUpdateType(newTag, previousTag):
116         if newTag.major > previousTag.major:
117             return "major"
118         elif newTag.minor > previousTag.minor:
119             return "minor"
120         elif newTag.bugfix > previousTag.bugfix:
121             return "bugfix"
122
123
124 class Commit:
125     def __init__(self, rawCommit):
126         self.body = CommitBody(rawCommit.body)
127         self.tag = CommitTag(rawCommit.tag)
128         self.hash = rawCommit.hash
129     
130     def appendShortHash(self):
131         return " (" + self.hash[:6] + ")"
132
133
134
135 #### Main ####
136
137 pathToRepo = "/Users/daniel/Developer/Repos/HfM/schumacher/Prisma-Binauralize"
138 #pathToRepo = "/Users/daniel/Desktop/testrepo"
139
140 commitList = getSeparatedGitLog(pathToRepo)
141
142 # Create a list of commits
143 commitHistory = []
144 for commit in commitList:
145     commitHistory.append(Commit(RawCommit(commit)))
146
147 # Create a two-dimensional list by tags: [[tag, [commits]],[tag, [commits]],...]
148 taggedHistory = []
149 for commit in commitHistory:
150     if commit.tag.tagAsString:
151         taggedHistory.append([commit.tag, commit])
152     else:
153         if len(taggedHistory) == 0:
154             taggedHistory.append([None, commit])
155         else:
156             taggedHistory[-1].append(commit)
157
158
159 # Construction of the changelog-file
160 fileTemplate = ["# Changelog"]
161 for tag in taggedHistory:
162     # If latest commit has no tag:
163     if not tag[0]:
164         fileTemplate.append("\n## Without version number")
165     else:
166         fileTemplate.append("\n## Version " + tag[0].tagAsString)
167
168
169     # Grouping by Type
b2e728 170     commitsByType = {"Other":[], "Fixes":[], "Features":[]}
b07d2e 171     nonconformCommits = []
DF 172     for commit in tag[1:]:
173         if commit.body.commitType == CommitBody.commitTypes[5]: # fix
174             commitsByType["Fixes"].append(commit)
175         elif commit.body.commitType == CommitBody.commitTypes[4]: # feat
176             commitsByType["Features"].append(commit)
177         elif commit.body.commitType == "nonconform":
178             nonconformCommits.append(commit)
179         else:
180             commitsByType["Other"].append(commit)
181
182     # Sub-Grouping by Scopes within Types    
183     while len(commitsByType) > 0:
184         commitsByScope = {}
185         commitType, commits = commitsByType.popitem()
b2e728 186         noScope = []
b07d2e 187         if len(commits) == 0:
DF 188             continue
189
190         for commit in commits:
191             if commit.body.scope == None:
b2e728 192                 noScope.append(commit)
b07d2e 193             elif commit.body.scope in commitsByScope:
DF 194                 commitsByScope[commit.body.scope].append(commit)
195             else:
196                 commitsByScope[commit.body.scope] = [commit]
197         
198         fileTemplate.append("\n### " + str(commitType))
199         while len(commitsByScope) > 0:
200             scope, commits = commitsByScope.popitem()
201             fileTemplate.append("- *" + str(scope) + "*")
202             for commit in commits:
203                 if commitType == "Other":
204                     fileTemplate.append("    - (" + commit.body.commitType + ") " + commit.body.subject + commit.appendShortHash())
205                 else:
206                     fileTemplate.append("    - " + commit.body.subject + commit.appendShortHash())
b2e728 207         if len(noScope) > 0:
DF 208             fileTemplate.append("- *no scope*")
209         for commit in noScope:
210             fileTemplate.append("    - " + commit.body.subject + commit.appendShortHash())
b07d2e 211     
DF 212     # nonconform commits
213     if len(nonconformCommits) > 0:
214         fileTemplate.append("\n### Non-conform commits")
215         for commit in nonconformCommits:
216             fileTemplate.append("- " + commit.body.subject + commit.appendShortHash())
217
218
219 # write into changelog
220 with open(pathToRepo + "/changelog.md", "w") as file:
221     for line in fileTemplate:
222         file.write(line + "\n")