From e3dbef2b8a6578d13091802c7683f3271be920c3 Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Sat, 22 Jul 2017 04:44:28 +0200 Subject: [PATCH 1/3] Add script to query for contributors between two releases --- .mailmap | 55 +++++++++++++++++ contributors.d | 163 +++++++++++++++++++++++++++++++++++++++++++++++++ posix.mak | 1 + 3 files changed, 219 insertions(+) create mode 100644 .mailmap create mode 100755 contributors.d diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..951678f514 --- /dev/null +++ b/.mailmap @@ -0,0 +1,55 @@ +# See https://github.com/git/git/blob/master/Documentation/mailmap.txt +adil +adil +aG0aep6G +Alex Rønne Petersen +amaury +Anders Ronnbrant +Andrej Mitrovic +Andrey Zherikov +biotronic +Brian Schott +Christian Juelg +Clement Courbet +Daniel Murphy +Dave Gallets +David +Denis Shelomovskij +Ed McCardell +Hara Kenji +Iain Buclaw +Iain Buclaw ibuclaw +Ilya Yaroshenko +Jacob Carlborg +Jakob Øvrum +Jakob Øvrum +John Colvin +Jonathan M Davis +Jordi Sayol +Kai Nacke +kinke +Mark Barbone <30397247+mb64@users.noreply.github.com> +Mark Barbone <30397247+mb64@users.noreply.github.com> Mark Barbone <> +Martin Nowak +Mathias Lang +Mathias Lang +Mathias Lang +monarchdodra +Petar Kirov +ponce +Rainer Schuetze +Razvan Nitu +Robert burner Schadek +Robert burner Schadek +Safety0ff +Safety0ff +shoo +Stefan Koch +Stefan Rohe +Timothee Cour +Torarin +Ulrich Küttler +Walter Waldron +Yazan Dabain +Михаил Страшун +Yao Gómez diff --git a/contributors.d b/contributors.d new file mode 100755 index 0000000000..ff0c4ebcc8 --- /dev/null +++ b/contributors.d @@ -0,0 +1,163 @@ +#!/usr/bin/env rdmd + +/** +Query contributors between two D releases. + +Copyright: D Language Foundation 2017. + +License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Example usage: + +--- +./contributors.d "v2.074.0..v2.075.0" +--- + +Author: Sebastian Wilzbach +*/ + +import std.array; +import std.algorithm; +import std.conv; +import std.exception; +import std.file; +import std.format; +import std.process; +import std.path; +import std.range; +import std.stdio; +import std.string; +import std.typecons; + +/// Name +struct GitAuthor +{ + string name, email; + string toString() + { + return "%s <%s>".format(name, email); + } +} + +/// Reads a GitAuthor in the format of "Name " +GitAuthor readUser(string s) +{ + auto ps = s.splitter("<"); + GitAuthor author = { + name: ps.front.to!string.strip, + email: ps.dropOne.front.filter!(a => a != '"').until('>').to!string + }; + return author; +} + +/// Options for finding authors +struct FindConfig +{ + bool refreshTags; /// will query github.com for new tags + bool noMerges; // will ignore merge commits + bool showAllContributrs; // will ignore the revRange and show all contributors + string cwd; // working directory (should be tools) + string mailmapFile; // location to the .mailmap file +} + +/** +Search all git commit messages within revRange of all D repositories +Returns: Array that maps each git `Author: ...` line to a GitAuthor +*/ +auto findAuthors(string revRange, FindConfig config) +{ + Appender!(GitAuthor[]) authors; + int commits; + foreach (repo; ["dmd", "druntime", "phobos", "dlang.org", "tools", "installer"] + .map!(r => buildPath(config.cwd, "..", r))) + { + if (config.refreshTags) + { + auto cmd = ["git", "-C", repo, "fetch", "--tags", "https://github.com/dlang/" ~ repo.baseName, + "+refs/heads/*:refs/remotes/upstream/*"]; + auto p = pipeProcess(cmd, Redirect.stdout); + enforce(wait(p.pid) == 0, "Failed to execute '%(%s %)'.".format(cmd)); + } + + auto cmd = ["git", "-c", "mailmap.file=%s".format(config.mailmapFile), "-C", repo, "log", "--use-mailmap"]; + if (!config.showAllContributrs) + cmd ~= revRange; + if (config.noMerges) + cmd ~= "--no-merges"; + + auto p = pipeProcess(cmd, Redirect.stdout); + scope(exit) enforce(wait(p.pid) == 0, "Failed to execute '%(%s %)'.".format(cmd)); + + static immutable Author = "Author: "; + p.stdout + .byLineCopy + .filter!(line => line.startsWith(Author)) + .tee!(_ => commits++) + .map!((line){ + line.skipOver(Author); + return line.readUser; + }) + .filter!(a => a.name != "The Dlang Bot") + .each!(author => authors ~= author); + } + if (!config.showAllContributrs) + stderr.writefln("Looked at %d commits in %s", commits, revRange); + else + stderr.writefln("Looked at %d commits", commits); + return authors.data; +} + +/// Sorts the authors and filters for duplicates +auto reduceAuthors(GitAuthors)(GitAuthors authors) +{ + import std.uni : sicmp; + return authors + .sort!((a, b) => sicmp(a.name, b.name) < 0) + .release + .uniq!((a, b) => a.name == b.name); +} + +int main(string[] args) +{ + import std.getopt; + string revRange; + FindConfig config = { + cwd: __FILE_FULL_PATH__.dirName.asNormalizedPath.to!string, + }; + config.mailmapFile = config.cwd.buildPath(".mailmap"); + + enum PrintMode { name, markdown, ddoc, csv, git} + PrintMode printMode; + + auto helpInformation = getopt( + args, + std.getopt.config.passThrough, + "f|format", "Result format (name, markdown, ddoc, csv, git)", &printMode, + "a|all", "Show all contributors", &config.showAllContributrs, + "refresh-tags", "Refresh tags", &config.refreshTags, + "no-merges", "Ignore merge commits", &config.noMerges, + ); + + if (helpInformation.helpWanted || (args.length < 2 && !config.showAllContributrs)) + { +`D contributors extractor. +./contributors.d "v2.075.0..v2.076.0"`.defaultGetoptPrinter(helpInformation.options); + return 1; + } + + revRange = args.length > 1 ? args[1] : null; + revRange.findAuthors(config) + .reduceAuthors + .each!((a){ + with(PrintMode) + final switch (printMode) + { + case name: a.name.writeln; break; + case markdown: writefln("- %s", a.name); break; + case ddoc: writefln("$(D_CONTRIBUTOR %s)", a.name); break; + case csv: writefln("%s, %s", a.name, a.email); break; + case git: writefln("%s <%s>", a.name, a.email); break; + } + }); + return 0; +} diff --git a/posix.mak b/posix.mak index 906880819d..33836cb289 100644 --- a/posix.mak +++ b/posix.mak @@ -42,6 +42,7 @@ DUBFLAGS = --arch=$(subst 32,x86,$(subst 64,x86_64,$(MODEL))) TOOLS = \ $(ROOT)/catdoc \ $(ROOT)/checkwhitespace \ + $(ROOT)/contributors \ $(ROOT)/ddemangle \ $(ROOT)/detab \ $(ROOT)/rdmd \ From b6ecfa71773515eed8aad138dd2f0ad78f733e2c Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Tue, 5 Dec 2017 05:35:34 +0100 Subject: [PATCH 2/3] Address review --- contributors.d | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contributors.d b/contributors.d index ff0c4ebcc8..5196531631 100755 --- a/contributors.d +++ b/contributors.d @@ -89,7 +89,7 @@ auto findAuthors(string revRange, FindConfig config) scope(exit) enforce(wait(p.pid) == 0, "Failed to execute '%(%s %)'.".format(cmd)); static immutable Author = "Author: "; - p.stdout + authors ~= p.stdout .byLineCopy .filter!(line => line.startsWith(Author)) .tee!(_ => commits++) @@ -97,8 +97,7 @@ auto findAuthors(string revRange, FindConfig config) line.skipOver(Author); return line.readUser; }) - .filter!(a => a.name != "The Dlang Bot") - .each!(author => authors ~= author); + .filter!(a => a.name != "The Dlang Bot"); } if (!config.showAllContributrs) stderr.writefln("Looked at %d commits in %s", commits, revRange); @@ -113,7 +112,6 @@ auto reduceAuthors(GitAuthors)(GitAuthors authors) import std.uni : sicmp; return authors .sort!((a, b) => sicmp(a.name, b.name) < 0) - .release .uniq!((a, b) => a.name == b.name); } From 6bb4c248dcc9844d7032aabb4e50e6bdf0d69b5c Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Thu, 7 Dec 2017 06:16:53 +0100 Subject: [PATCH 3/3] Use git log --pretty=format --- contributors.d | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/contributors.d b/contributors.d index 5196531631..f99535adf6 100755 --- a/contributors.d +++ b/contributors.d @@ -39,17 +39,6 @@ struct GitAuthor } } -/// Reads a GitAuthor in the format of "Name " -GitAuthor readUser(string s) -{ - auto ps = s.splitter("<"); - GitAuthor author = { - name: ps.front.to!string.strip, - email: ps.dropOne.front.filter!(a => a != '"').until('>').to!string - }; - return author; -} - /// Options for finding authors struct FindConfig { @@ -79,7 +68,7 @@ auto findAuthors(string revRange, FindConfig config) enforce(wait(p.pid) == 0, "Failed to execute '%(%s %)'.".format(cmd)); } - auto cmd = ["git", "-c", "mailmap.file=%s".format(config.mailmapFile), "-C", repo, "log", "--use-mailmap"]; + auto cmd = ["git", "-c", "mailmap.file=%s".format(config.mailmapFile), "-C", repo, "log", "--use-mailmap", "--pretty=format:%aN|%aE"]; if (!config.showAllContributrs) cmd ~= revRange; if (config.noMerges) @@ -88,14 +77,12 @@ auto findAuthors(string revRange, FindConfig config) auto p = pipeProcess(cmd, Redirect.stdout); scope(exit) enforce(wait(p.pid) == 0, "Failed to execute '%(%s %)'.".format(cmd)); - static immutable Author = "Author: "; authors ~= p.stdout .byLineCopy - .filter!(line => line.startsWith(Author)) .tee!(_ => commits++) .map!((line){ - line.skipOver(Author); - return line.readUser; + auto ps = line.splitter("|"); + return GitAuthor(ps.front, ps.dropOne.front); }) .filter!(a => a.name != "The Dlang Bot"); }