Skip to content

Instantly share code, notes, and snippets.

@MartinNowak
Last active July 14, 2017 13:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MartinNowak/fd343f909792f2e03f2355a068d54e9f to your computer and use it in GitHub Desktop.
Save MartinNowak/fd343f909792f2e03f2355a068d54e9f to your computer and use it in GitHub Desktop.
Get top contributors for a source file of a git repo.
// ==== phobos
// git log --grep='convert docstring examples to unittests' --pretty=%H
ba596549cc76547e1cb78177ed3d3d99668f9212
04654c071d1b7e59b53aa6f269dfe209966263cf
afc40287e02042df43bc763595869bedee15e8de
66e0dc39c5212821208b496800080210f7146ff9
ce795612149097cc6f2bcaa32da4a1023dedaa28
167b28679bd3b9f2639341e78b102616e2223efb
6c386d6f3165f062d0722155dc64a87de4c3e1b2
d81030ab8a15de5353735c646807308ec3bbf3b0
// ==== dmd
// cdmd => ddmd
aeb7e88f9fafd6be7e451d6f42788a744bac979b
// first tarball drops `git log --reverse --format=%H | head -n 160`
2c286cf3ecf209e4a9acd323e791809c45758f70
bde09435d149c699490bb888b832228e0d31c221
2ec66c2196e5945f97dd2650a4dcc3c5dee6e11f
7a3c6789b00e04d9b1b13ef69eb43ce0e56cdda3
2e3cc50a723c00e6560cb97d527c6f2768ad1e9d
d7541442bb5ddbe1d2b1d8403387ad9ffce25449
2efc3a9287879f5e1391c8b9873be0d484409d84
42b868c31085211cbb8838325b72d7522f9164d2
035399b626641bfe3b8bc487a806847045903c5e
a44859e6f45d3833d28cc1e8f0766d63787a9cc6
1175c257dd075e4f6c2672dc728a38c982956eaa
0ad54c6d37b62631d48d452dfb06bb9a932ceb96
89b6e47fa531ae0033692f6cbd36c7c0beb1d2e8
5303bbf3046d10b4b2eeb7d52739fb341375d832
abcb9164e5d229cfcaf70676e827798a25ac28e4
f02208c08d8f7ad6789f470cfccc7f9416c4977a
18c3ee0baf852d52d136a01e3acef87b4856d5dd
915df2445895765f282c5cfbbace31aaa3d80e5b
cf7c5f06a309b88eb6a3bbf26a58585ff3096833
4de91747b97b7d1bb84e7ed541fd20dc6f453069
037348ba5a875790f6952a5f4c97e7b5c0aed9d3
9840a79adfcdb99a67d7ef60f67a31ec6b140168
36e97f0b1fd283f6cd79c6420c13b1558b47528e
0ce2f8a10e97d79e87e5ceabe3a78377b2e16a60
0779f5ff5c03832ab3bd5e262098bf8bd4c8bdc6
071025511114cf7fe08eac6d8367454c48a44a91
b2d80468a8c7e4e9aaf6e17407e383b62ce13b2c
cd225fd4c10b5eba77caa654e4f7340d6f67d7d4
58cd5d6c1b071e8d1e9310317421a242389cee5a
76cdb4a8d4345bc83593a28fb580c5465345968b
2097b6a47d216ee39b5e3c8a6f4eedae6f382d88
0a07a84a2dacefaa9a55f361c39ccdb5d7ff9042
73cc237c2f4293aabd9c801707e50fe4c5d7adcb
15d4546d07d5dedb7424677ae4dc0b18e57ef6da
a70194afb19a6489b50468903ead48c399edc046
6346f36e7ba4935f8c7781e1be038190fce15230
8b888497ce3b221615881e0c214a62ad2c734488
32376ab9bf0f3dea3d22c2dc3b95a9d22644a1bd
5e7a301cd45b25ad64ad19ce1d995fd549efba3f
d8fd0c8ba1eb1d16861e828998d75e0d41f5f957
aeb5e2417202bcd4deee224dd3490a0390cee2e3
0a673183772de2b1b8195d4c8dd8241f7ef2334e
5537cfeeeb852eadc0ac5c3fdc4a3ce4bd506641
7bef60362f9a4ea5f8bca549cf503dcef814e37a
7b0d785eb9da573a06dbf1ed2c2ac494fbb4bb71
fc643c1bab90ddc9565af5adff027f7fd51d5db7
ac28d809642852577612090274822b6988995753
61a2da6a70cf8d85b6ed84234ad6f70bd2e2f3a3
f72f6287e036b244ec11750fd4e808051d002e0b
46e49b52ff2c85ca00eea38b7610fbc11e85c27a
be47f8497e987c9cd4d9e7363ac13842774f2da5
d6ae620a9158f37cb32b3f75e114a431732f382c
3d222031e288f52b71f4cf4ee16408a6078306bd
f6bbbffe960a9814706e324d6c980baf93be7f3d
3ea3da40358561df32b9cc01f367363f6b3a0c3b
974fbedb41b4e1d5ea9d86fb9fb24e28eed294db
2d73ec0f367654da6798755e1442a1696e8d4dcf
32e4ad780fe4e69227a764f8c171b720d649ff6b
0e8c723dfabb489731abf71b5e39ce5dddb7f3be
8b000ef8fcb763990f07f89e95b1ee1850aa44a8
6648adaa832ad6ae6b17e9766244faa74ecb9635
b8b78a1d959a9bf9da5bb559e91d71dc941f6c10
8f07a9758cbd480ab74f8b251565e82eb1544092
527cd0ef986d719441a26ae2d3c727f1c6d5f6ff
0f1cdf542f97023b1edc1ade1ad481df464cc396
ca2a4aca0514dea05a778f8277f03569c653ef04
3410d9d5687b6e4549fd73cfd59b426bd1005c96
753dd2b68e592da5829b9c62920f8d82c62243b6
24a2a25b8284076f16e9ad318970c801d69e39a0
e32483c7240870c37dd0aab6254fea6356cbecb9
4c84ba1cbbcee8f9eea80c762d5913637d6b69f8
b7aec61b1d0dabfd8a52e1d940287e6b6bfb2394
5fe83223d3318907ce95837797e41e408797ec87
afbef9e7a37dcb2a39678f34a600e8db9bd75184
8c4dd918fa45c627257e6bf1052186ae31d6099b
0ecd702db3d797df0e48a543ba7b0f4607013358
8262db899e7ba11ae449e64258be7a8ecc8b7e4b
656053b23329e38a29e6954ab154ef83a389ac9d
d9dab4a3b753aa3072ad624823ed6228cd8cf428
1e8aa7a50a570a21dbda09b85d2533b7baceb2b1
dad5a36a265a147a422f96301ebe1e7050899cc5
d6244dae42808f6c69b509372a0f565c637fb62b
d93a346410537edd55c245f26fe5dfb421fd576d
1f67b21858abdf6a8d99db0194087d9abc0e9cf6
319ac62274e99ed33a3cab821d4f4f4802747305
5eb6556ce2741ffbb8028238ac7e83dfb25af75d
8cfcbf6777c2ac9b41d6c9f07bf8d6053ee75ca6
6bebcb557e6926c199557a01e5c76995e2396a9f
29fc3d9ef5f3e874dc051bd5ede2e26772df3ab6
fdff4534349c1e41894879cb81232277d7f3e04c
83e53aed7b8745e93b839194f3448032f24e7821
e668eb0b599100ce891f381a47a105103ec55545
1b0bfb683cce1e1eae63150d0f3a0313e8884a29
05d595b7c361ecdd28a1fdd2b02af194daec5210
e614a784068fa7ca8bff0801f4e99807e6c591b2
fd61ac13e42efb9c974675fc15e59864096e590b
37f2ff76fee87baa57b4320b6f1c2f09311e56e3
a2b1fa4978948ca4d293c38666bd607c03ba791a
189fa015f7d9e8b4987517cf899a96fc6d7bb8bb
43a336d81c38817ae545becf02b7459836025c60
123aa2e76b04458e25438ec937e7bf93c6610728
90322043bf4cbbc19734070948265e918d8af981
0b7300972e3715648d6c3974e286548fb65a0ddf
9ce66e0e385ca4a24e06054b26fd756f5198ec93
91390c4cc779b96575da68239ea10a79bde713ac
b76b07b25ffe6939f0cf8f5e387a8db58be8722c
adf46573a6726306302d48a227949d15455359d5
8f53e2d1c947516d98190d79ec81eb1063f8d221
6a7866b38f07bb2f821b2b0356530baa2fb1c907
8feb3a57bd8f3aaee13e7dc671b6b46cb793e2a7
93341a47391053927ba8ce6730c0a453e96260e5
a2d41e2a63c4c187bd8d36653a0734afa1978d5d
8594c660ebe16329daadbc2083ed6667a4fcc9ae
6f8eca420397f3da642f70e95b3ac7f2d50d6598
88df46c179ed08521a9e5361af8feeeebf38f1b5
509a54c423bad5fdb733e9bdc42c5dfe18925d7f
a2dc056f7a724a50d408533d21e1d089752dc2ae
92dbe9eac67de7c7e42671b951f87a3cfc39dbe1
fd2d32d2eac0e14cb5a5e8321f9b260df91cb2df
3fc712b509ebd9cbbb95d251799183268912bc18
18e447e743924b85207896ecffecac5fb7a75784
fcf188d0e4533fd3fb88dab58199848dc4da0b2d
f8001872c45e2112e3bb557b69b84c99d90008f5
86eeed0da4fa494d9fc69fc8ca124be0b75b9164
78568244ba6f320ec435586748f5ac2c52c2b20b
56a5e7a7564897d3c3fa84d874bb5701d76c0c13
669f3179d2ca3c6d8fcc79d6b4cce548758d3e08
d5d9fb7323abc1e96dd3428bb8281ab4bf98aee9
ff328c91063b70b8e8eb9b8734564e7c2672e8b3
e56081448fff1af90b8eda765d649a131bb19c3c
ddead63bc3502113f683dc855afac5bc03777a71
ccc70dca956c21b05d3f408965d31d3b492797a9
edc963e4a5d9ba79706b2b74ca815812605eb2e3
cd9ae56c0ddbfbb4213786603bc9d9be6b4b4c3c
2cc1bac591c52d015bd1e86d2145666aff5b7e21
4e732b411f661eb90e30d8b0bec1c549bb9799da
2f8da4f6f166d191b154d446093807d6c563a9e5
3b5f969d412277b35a88d6aa3d76a47e0196be2b
2633f3565457ba01120bc6e9df2156c5a65af7f6
d550a1aec80976453590cece8c9849837b2079a8
8dcd67aa8dcbe5953d251cac557fd998ca29598c
939fedf67d8a4b4d9f3f3982912fe1463e2484b8
b35589bfa8d7b1102920c908bac42770d846a044
8326f0536f8b82e434c104deb819afc9fcd96c28
c0ff81d3cbfc6c6176ca5593734e1180291df2a4
894019b96c98efb493f6c8c4948d71e9e975c5fb
f87c229d74e02f7f6d9522068ce80b1bbcd1c3db
2d92a591607daa358decf30e02fb60f82992174f
54f235566ff39778caff12bc4c8618723a9961df
788b8a6285a46f1a1ce2952d42e9bac3c9dbe7f7
60d5d2168949702bf3106f515b9c5d0cd225c91e
00337ef8d8c4c1c08da68f95963e2fe1658a49ec
f77efd17ecdfb65a2d6e7ea66b0be30d8e063199
4c0b53e057aae6cc4f2031122b91db3cab4c679a
5cc6c750084c41217d5ab52bdc9544e171206826
a10665aa73658ba605426a708c2c5c0366f59db5
2b851936a669b194776703496be18035fc6423f0
c45a5e0a0e3a7763dd250ee72692ab7d519f6c7c
3f31b6e6f1c97f413e7df0142c85b91530e93a39
49eae2491631d9fe2bba9dbf070caa2d5ce61784
import std.algorithm, std.conv, std.datetime, std.file, std.path, std.stdio, std.string,
std.process;
// exclude reformatting commits that aren't catched by --ignore-all-space
alias SetVal = void[0];
immutable SetVal[string] excludedCommits;
shared static this()
{
SetVal[string] aa;
foreach (line; File(thisExePath.dirName.buildPath("excludedCommits"), "r").byLine)
{
if (line.startsWith("//"))
continue;
aa[line.idup] = SetVal.init;
}
excludedCommits = cast(immutable) aa;
}
float[string] getContributions(string path)
{
immutable now = Clock.currTime;
float[string] contributions;
auto pipe = pipeProcess(["git", "blame", "--line-porcelain", "-w", "--", path]);
string author;
size_t i;
bool excluded;
foreach (line; pipe.stdout.byLine)
{
//writeln(line);
switch (i)
{
case 0: // hash LO LF N
excluded = !!(line.splitter().front in excludedCommits);
++i;
break;
case 1:
auto parts = line.findSplit(" ");
switch (parts[0])
{
case "author":
author = parts[2].dup;
break;
case "author-mail":
if (author.empty)
writeln(line);
author ~= " " ~ parts[2];
break;
case "author-time":
immutable authoredAt = parts[2].to!uint.unixTimeToStdTime.SysTime;
if (!excluded)
contributions[author] = contributions.get(author, 0.0f) + score(now - authoredAt);
break;
default:
break;
case "filename":
++i;
break;
}
break;
case 2: // source line
i = 0;
break;
default:
assert(0);
}
}
pipe.pid.wait;
return contributions;
}
enum gravity = 2.0; // how fast the weight of contributions age
/// (additions + deletions) / (age / years + 1) ^^ G
float score(Duration age)
{
import std.math;
return pow(age.total!"days" / 365.0 + 1.0f, -gravity);
}
void main(string[] args)
{
import std.array : array;
import std.typecons : tuple, Tuple;
import std.math : isNaN;
immutable now = Clock.currTime;
writeln("module,author,score");
foreach (arg; args[1 .. $])
{
Tuple!(string, float)[5] tops;
auto contribs = getContributions(arg).
byKeyValue.
map!(p => tuple(p.key, p.value)).
topNCopy!((a, b) => a[1] > b[1])(tops[], SortOutput.yes);
foreach (t; tops[].filter!(t => !t[1].isNaN))
writeln(arg, ",", t[0], ",", t[1]);
}
}
@MartinNowak
Copy link
Author

Usage

cd dmd
find ./src/ddmd -name '*.[chdimak]' | xargs ../module_contributors/module_contributors | tee ~/dmd_contributors.txt
cd ../druntime
find ./src -name '*.[chdimak]' | xargs ../module_contributors/module_contributors | tee ~/druntime_contributors.txt
cd ../phobos
find . -name '*.[chdimak]' | xargs ../module_contributors/module_contributors | tee ~/phobos_contributors.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment