From 53b455b084d3330384e63574876a70224c77923b Mon Sep 17 00:00:00 2001 From: Bence Ferdinandy Date: Sat, 14 Oct 2023 23:26:37 +0200 Subject: contrib: add a script to generate release statistics Add a new python script, that will generate two types of statistics for each minor release (and HEAD) from the local repository and create a graph. First statistics is number of commits since the previous release, the second statistics is number of lines inserted and deleted since the previous release. The placement of the text annotations (tag names) is mildly smart, so the distribution changes significantly the hard coded numbers might have to be tweaked. Requires matplotlib to be installed (developed with 3.7.3). It generates a png by default, but this can be changed by passing the output file option with a different file extension as long as matplotlib knows what to do with it. Signed-off-by: Bence Ferdinandy Acked-by: Robin Jarry --- contrib/git-stats-graph.py | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100755 contrib/git-stats-graph.py (limited to 'contrib/git-stats-graph.py') diff --git a/contrib/git-stats-graph.py b/contrib/git-stats-graph.py new file mode 100755 index 00000000..7632d566 --- /dev/null +++ b/contrib/git-stats-graph.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +# Copyright (c) 2023 Bence Ferdinandy + +""" +Create graphs about development statistics of releases. +""" + +from datetime import date +from subprocess import check_output + +from matplotlib import pyplot as plt + + +def git(*args): + return check_output(["git"] + list(args)).decode("utf-8").strip() + + +def stats(): + """ + Returns statistics from the git repo: + + tags: sorted list of minor version tags (assumes semver) + The first element is the hash of the first commit, last element is HEAD. All + the other return values are one shorter as, there's no statistics returned for + the first commit. + counts: number of commits (between this and the previous release) + dates: dates of the releases + files: number of files changed (between this and the previous release) + inserts: number of lines inserted (between this and the previous release) + deletions: number of lines deleted (between this and the previous release) + """ + + tags = git("tag").split("\n") + tags = [t for t in tags if t.split(".")[-1] == "0"] # drop patch versions + tags = sorted(tags, key=lambda x: [int(t) for t in x.split(".")]) + first_commit = git("rev-list", "--max-parents=0", "HEAD") + tags = [first_commit] + tags + ["HEAD"] + counts = [] + dates = [] + files = [] + inserts = [] + deletions = [] + for i, t in enumerate(tags[:-1]): + counts.append(int(git("rev-list", f"{t}..{tags[i+1]}", "--count"))) + dates.append( + date.fromisoformat(git("show", "-s", "--format=%cs", t).split("\n")[-1]) + ) + statline = git("diff", "--stat", t, tags[i + 1]).split("\n")[-1] + fnum, _, _, ins, _, dels, _ = statline.split() + files.append(int(fnum)) + inserts.append(int(ins)) + deletions.append(int(dels)) + return tags, counts, dates, files, inserts, deletions + + +def main(output): + tags, counts, dates, files, inserts, deletions = stats() + + fig, (ax1, ax2) = plt.subplots(2, figsize=(8, 11)) + fig.suptitle("aerc release statistics", fontweight="bold") + # commit counts subplot + ax1.plot(dates, counts, "o-") + + # alternate placement of text above and below for readability + text_y = [] + for i, t in enumerate(tags[1:]): + downpad = 25 if len(t) == 5 else 30 + p = counts[i] + (-1) ** (i + 1) * 10 - (i + 1) % 2 * downpad + if p < 5: + p = counts[i] + 10 + text_y.append(p) + for i, t in enumerate(tags[1:]): + ax1.text( + dates[i], + text_y[i], + t, + horizontalalignment="center", + rotation="vertical", + ) + ax1.set_ylabel("# of commits") + ax1.set_ylim(bottom=0) + ax1.set_title("commits per release") + + # lines added/deleted subplot + # + ax2.plot(dates, inserts, "o-", label="insertions(+)", color="green") + ax2.plot(dates, deletions, "o-.", label="deletions(-)", color="red") + ax2.set_ylabel("# of lines") + ax2.legend(loc="upper left") + ax2.set_ylim(top=max(max(inserts), max(deletions)) + 2000) + for i, t in enumerate(tags[1:]): + ax2.text( + dates[i], + max(inserts[i], deletions[i]) + 500, + t, + horizontalalignment="center", + rotation="vertical", + ) + ax2.set_xlabel("date") + ax2.set_title("insertion/deletions per release") + plt.tight_layout() + plt.savefig(output, dpi=300) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "-o", + "--output", + default="aerc-release-stats.png", + help=""" + Path to output image (defaults to 'aerc-release-stats.png', + respects file extensions via matplotlib) + """, + ) + args = parser.parse_args() + main(args.output) -- cgit