diff options
author | Robert Adam <krzmbrzl@gmail.com> | 2021-02-26 18:17:13 +0300 |
---|---|---|
committer | Robert Adam <krzmbrzl@gmail.com> | 2021-02-26 18:18:45 +0300 |
commit | 35b138b1557f9c29488a09322ddf7cdea7bf3d23 (patch) | |
tree | 49c1f696013a97a86a8286b0cf03c1374ca815f4 /scripts | |
parent | 384196a99f3446cff9984c873882a3eb37af0013 (diff) |
MAINT: Added script to automatically generate Changelog
The script automatically parses the new merge commits between two given
tags and tries its best to generate a Changelog out of it.
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/commitMessage/CommitMessage.py | 56 | ||||
-rwxr-xr-x | scripts/generateChangelog.py | 141 |
2 files changed, 197 insertions, 0 deletions
diff --git a/scripts/commitMessage/CommitMessage.py b/scripts/commitMessage/CommitMessage.py new file mode 100644 index 000000000..a9a778ed9 --- /dev/null +++ b/scripts/commitMessage/CommitMessage.py @@ -0,0 +1,56 @@ +import re + +class CommitFormatError(Exception): + + def __init__(self, msg): + Exception(msg) + +class CommitMessage: + knownTypes = ["BREAK", "FEAT", "FIX", "FORMAT", "DOCS", "TEST", "MAINT", "CI", "REFAC", "BUILD", "TRANSLATION"] + + def __init__(self, commitString): + lines = commitString.strip().split("\n") + + if len(lines) < 1 or lines[0].strip() == "": + raise CommitFormatError("Invalid commit content") + + subjectPattern = re.compile("^([A-Z\-_/]+)(?:\(([0-9a-zA-Z\-_, ]+)\))?:\s*(.+)$") + + match = re.match(subjectPattern, lines[0]) + + if not match: + raise CommitFormatError("The provided commit's subject line does not follow the required pattern") + + types = match.group(1).split("/") if not match.group(1) is None else [] + scopes = match.group(2).split(",") if not match.group(2) is None else [] + summary = match.group(3).strip() if not match.group(3) is None else "" + + if len(types) == 0: + raise CommitFormatError("Missing type") + + if len(summary) == 0: + raise CommitFormatError("Missing summary") + + self.m_summary = summary + + types = [x.strip() for x in types] + scopes = [x.strip().lower() for x in scopes] + + for currentType in types: + if currentType == "": + raise CommitFormatError("Subsequent \"/\" not allowed for separating types") + + if not currentType in self.knownTypes: + raise CommitFormatError("Unknown type \"%s\" (or incorrect spelling)" % currentType) + + self.m_types = types + + for currentScope in scopes: + if currentScope == "": + raise CommitFormatError("Empty scope not allowed") + + self.m_scopes = scopes + + self.m_body = "\n".join(lines[1 : ]) if len(lines) > 1 else "" + + diff --git a/scripts/generateChangelog.py b/scripts/generateChangelog.py new file mode 100755 index 000000000..f6cf1c03c --- /dev/null +++ b/scripts/generateChangelog.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# +# Copyright 2021 The Mumble Developers. All rights reserved. +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file at the root of the +# Mumble source tree or at <https://www.mumble.info/LICENSE>. + +import argparse +import platform +import subprocess + +from commitMessage.CommitMessage import CommitMessage, CommitFormatError + +def cmd(args): + shell = platform.system() == 'Windows' + p = subprocess.Popen(args, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if p.returncode != 0: + raise Exception('cmd(): {0} failed with status {1}: {2}'.format(args, p.returncode, stderr)) + return stdout.decode('utf-8') + +def isRelevantCommit(commitLine): + if commitLine.strip() == "": + return False + + components = commitLine.split() + + if len(components) < 2: + return False + + # First component is commit hash + # We only consider merge commits to be relevant + return components[1].lower() == "merge" + +def formatChangeLine(line, prNumber, target): + if target == "github": + return "- {} (#{})".format(line, prNumber) + elif target == "website": + return "- {} ({{{{< issue {} >}}}})".format(line, prNumber) + else: + return "- {} (https://github.com/mumble-voip/mumble/pull/{})".format(line, prNumber) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("FROM_TAG", help="Tag or commit to start the diff from") + parser.add_argument("TO_TAG", help="Tag or commit to end the diff at") + parser.add_argument("--target", choices=["github", "website", "other"], help="Generate the Changelog in a format suitable for this target", + default="other") + args = parser.parse_args() + + commits = cmd(["git", "log" ,"--format=oneline", "--date=short", "{}..{}".format(args.FROM_TAG, args.TO_TAG)]).split("\n") + commits = list(filter(isRelevantCommit, commits)) + + serverChanges = [] + clientChanges = [] + sharedChanges = [] + miscChanges = [] + + skipTypes = set(["FORMAT", "DOCS", "TEST", "MAINT", "CI", "REFAC", "BUILD", "TRANSLATION"]) + + for commitLine in commits: + parts = commitLine.split(maxsplit=1) + commitHash = parts[0] + commitTitle = parts[1] + + assert ":" in commitTitle + assert "#" in commitTitle + + prTagStart = commitTitle.find("#") + prTagEnd = commitTitle.find(":") + assert prTagStart + 1 < prTagEnd + + # Extract PR number + prNumber = commitTitle[prTagStart + 1 : prTagEnd] + # Cut out PR information from commit title + commitTitle = commitTitle[prTagEnd + 1 : ].strip() + + try: + commit = CommitMessage(commitTitle) + + if len(set(commit.m_types) & skipTypes) > 0: + # We don't list these changes in the changelog + continue + + targetGroups = [] + if "client" in commit.m_scopes: + targetGroups.append(clientChanges) + if "server" in commit.m_scopes: + targetGroups.append(serverChanges) + if "shared" in commit.m_scopes: + targetGroups.append(sharedChanges) + if len(targetGroups) == 0: + targetGroups.append(miscChanges) + + prefix = "" + if "FEAT" in commit.m_types: + prefix = "Added: " + elif "FIX" in commit.m_types: + prefix = "Fixed: " + else: + prefix = "Unknown: " + + for currentTarget in targetGroups: + currentTarget.append(formatChangeLine(prefix + commit.m_summary, prNumber, args.target)) + except CommitFormatError: + # We can't classify the change -> assume misc + miscChanges.append(formatChangeLine("Unknown: " + commitTitle, prNumber, args.target)) + + + if len(clientChanges) > 0: + print("### Client") + print() + print("\n".join(sorted(clientChanges))) + print() + print() + + if len(serverChanges) > 0: + print("### Server") + print() + print("\n".join(sorted(serverChanges))) + print() + print() + + if len(sharedChanges) > 0: + print("### Both") + print() + print("\n".join(sorted(sharedChanges))) + print() + print() + + if len(miscChanges): + print("### Miscellaneous") + print() + print("\n".join(sorted(miscChanges))) + print() + + + +if __name__ == "__main__": + main() |