Daniel Fütterer
27.04.21 b07d2e00d40692f9076e65cfe41140d4515b7b32
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
170     commitsByType = {"Other":[], "Features":[], "Fixes":[]}
171     nonconformCommits = []
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()
186         if len(commits) == 0:
187             continue
188
189         for commit in commits:
190             if commit.body.scope == None:
191                 if "no scope" not in commitsByScope:
192                     commitsByScope["no scope"] = [commit]
193                 else:
194                     commitsByScope["no scope"].append(commit)
195             elif commit.body.scope in commitsByScope:
196                 commitsByScope[commit.body.scope].append(commit)
197             else:
198                 commitsByScope[commit.body.scope] = [commit]
199         
200         fileTemplate.append("\n### " + str(commitType))
201         while len(commitsByScope) > 0:
202             scope, commits = commitsByScope.popitem()
203             fileTemplate.append("- *" + str(scope) + "*")
204             for commit in commits:
205                 if commitType == "Other":
206                     fileTemplate.append("    - (" + commit.body.commitType + ") " + commit.body.subject + commit.appendShortHash())
207                 else:
208                     fileTemplate.append("    - " + commit.body.subject + commit.appendShortHash())
209     
210     # nonconform commits
211     if len(nonconformCommits) > 0:
212         fileTemplate.append("\n### Non-conform commits")
213         for commit in nonconformCommits:
214             fileTemplate.append("- " + commit.body.subject + commit.appendShortHash())
215
216
217 # write into changelog
218 with open(pathToRepo + "/changelog.md", "w") as file:
219     for line in fileTemplate:
220         file.write(line + "\n")