aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBence Ferdinandy <bence@ferdinandy.com>2023-10-14 23:26:37 +0200
committerRobin Jarry <robin@jarry.cc>2023-10-22 15:12:12 +0200
commit53b455b084d3330384e63574876a70224c77923b (patch)
treede948619fadca3a1a5bd778ffd3cfcc10b0abca6
parent14fc59f189775dbe95daf582c08c9c1b3cd949b2 (diff)
downloadaerc-53b455b084d3330384e63574876a70224c77923b.tar.gz
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 <bence@ferdinandy.com> Acked-by: Robin Jarry <robin@jarry.cc>
-rwxr-xr-xcontrib/git-stats-graph.py120
1 files changed, 120 insertions, 0 deletions
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 <bence@ferdinandy.com>
+
+"""
+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)