Skip to content

Commit 77c05df

Browse files
authored
Merge pull request #210 from DimitriPapadopoulos/authors.py
Update authors.py
2 parents 7654b4c + 469c04f commit 77c05df

File tree

1 file changed

+78
-32
lines changed

1 file changed

+78
-32
lines changed

util/authors.py

Lines changed: 78 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
#!/usr/bin/env python
2+
# -*- encoding:utf-8 -*-
23
"""
3-
git-authors [OPTIONS] REV1..REV2
4+
List the authors who contributed within a given revision interval::
45
5-
List the authors who contributed within a given revision interval.
6+
python tools/authors.py REV1..REV2
7+
8+
`REVx` being a commit hash.
9+
10+
To change the name mapping, edit .mailmap on the top-level of the
11+
repository.
612
713
"""
814
# Author: Pauli Virtanen <[email protected]>. This script is in the public domain.
@@ -11,25 +17,20 @@
1117
import re
1218
import sys
1319
import os
20+
import io
1421
import subprocess
22+
import collections
1523

16-
17-
from scipy._lib.six import u, PY3
18-
if PY3:
19-
stdout_b = sys.stdout.buffer
20-
else:
21-
stdout_b = sys.stdout
22-
23-
24-
NAME_MAP = {
25-
u('Helder'): u('Helder Oliveira'),
26-
}
24+
stdout_b = sys.stdout.buffer
25+
MAILMAP_FILE = os.path.join(os.path.dirname(__file__), "..", ".mailmap")
2726

2827

2928
def main():
3029
p = optparse.OptionParser(__doc__.strip())
3130
p.add_option("-d", "--debug", action="store_true",
3231
help="print debug output")
32+
p.add_option("-n", "--new", action="store_true",
33+
help="print debug output")
3334
options, args = p.parse_args()
3435

3536
if len(args) != 1:
@@ -40,36 +41,38 @@ def main():
4041
except ValueError:
4142
p.error("argument is not a revision range")
4243

44+
NAME_MAP = load_name_map(MAILMAP_FILE)
45+
4346
# Analyze log data
4447
all_authors = set()
45-
authors = set()
48+
authors = collections.Counter()
4649

4750
def analyze_line(line, names, disp=False):
4851
line = line.strip().decode('utf-8')
4952

5053
# Check the commit author name
51-
m = re.match(u('^@@@([^@]*)@@@'), line)
54+
m = re.match(u'^@@@([^@]*)@@@', line)
5255
if m:
5356
name = m.group(1)
5457
line = line[m.end():]
5558
name = NAME_MAP.get(name, name)
5659
if disp:
5760
if name not in names:
5861
stdout_b.write((" - Author: %s\n" % name).encode('utf-8'))
59-
names.add(name)
62+
names.update((name,))
6063

6164
# Look for "thanks to" messages in the commit log
62-
m = re.search(u(r'([Tt]hanks to|[Cc]ourtesy of) ([A-Z][A-Za-z]*? [A-Z][A-Za-z]*? [A-Z][A-Za-z]*|[A-Z][A-Za-z]*? [A-Z]\. [A-Z][A-Za-z]*|[A-Z][A-Za-z ]*? [A-Z][A-Za-z]*|[a-z0-9]+)($|\.| )'), line)
65+
m = re.search(r'([Tt]hanks to|[Cc]ourtesy of|Co-authored-by:) ([A-Z][A-Za-z]*? [A-Z][A-Za-z]*? [A-Z][A-Za-z]*|[A-Z][A-Za-z]*? [A-Z]\. [A-Z][A-Za-z]*|[A-Z][A-Za-z ]*? [A-Z][A-Za-z]*|[a-z0-9]+)($|\.| )', line)
6366
if m:
6467
name = m.group(2)
65-
if name not in (u('this'),):
68+
if name not in (u'this',):
6669
if disp:
6770
stdout_b.write(" - Log : %s\n" % line.strip().encode('utf-8'))
6871
name = NAME_MAP.get(name, name)
69-
names.add(name)
72+
names.update((name,))
7073

7174
line = line[m.end():].strip()
72-
line = re.sub(u(r'^(and|, and|, ) '), u('Thanks to '), line)
75+
line = re.sub(r'^(and|, and|, ) ', u'Thanks to ', line)
7376
analyze_line(line.encode('utf-8'), names)
7477

7578
# Find all authors before the named range
@@ -84,24 +87,39 @@ def analyze_line(line, names, disp=False):
8487

8588
# Sort
8689
def name_key(fullname):
87-
m = re.search(u(' [a-z ]*[A-Za-z-\']+$'), fullname)
90+
m = re.search(u' [a-z ]*[A-Za-z-]+$', fullname)
8891
if m:
8992
forename = fullname[:m.start()].strip()
9093
surname = fullname[m.start():].strip()
9194
else:
9295
forename = ""
9396
surname = fullname.strip()
94-
surname = surname.replace('\'', '')
95-
if surname.startswith(u('van der ')):
97+
if surname.startswith(u'van der '):
9698
surname = surname[8:]
97-
if surname.startswith(u('de ')):
99+
if surname.startswith(u'de '):
98100
surname = surname[3:]
99-
if surname.startswith(u('von ')):
101+
if surname.startswith(u'von '):
100102
surname = surname[4:]
101103
return (surname.lower(), forename.lower())
102104

103-
authors = list(authors)
104-
authors.sort(key=name_key)
105+
# generate set of all new authors
106+
if vars(options)['new']:
107+
new_authors = set(authors.keys()).difference(all_authors)
108+
n_authors = list(new_authors)
109+
n_authors.sort(key=name_key)
110+
# Print some empty lines to separate
111+
stdout_b.write(("\n\n").encode('utf-8'))
112+
for author in n_authors:
113+
stdout_b.write(("- %s\n" % author).encode('utf-8'))
114+
# return for early exit so we only print new authors
115+
return
116+
117+
try:
118+
authors.pop('GitHub')
119+
except KeyError:
120+
pass
121+
# Order by name. Could order by count with authors.most_common()
122+
authors = sorted(authors.items(), key=lambda i: name_key(i[0]))
105123

106124
# Print
107125
stdout_b.write(b"""
@@ -110,11 +128,14 @@ def name_key(fullname):
110128
111129
""")
112130

113-
for author in authors:
131+
for author, count in authors:
132+
# remove @ if only GH handle is available
133+
author_clean = author.strip('@')
134+
114135
if author in all_authors:
115-
stdout_b.write(("* %s\n" % author).encode('utf-8'))
136+
stdout_b.write((f"* {author_clean} ({count})\n").encode('utf-8'))
116137
else:
117-
stdout_b.write(("* %s +\n" % author).encode('utf-8'))
138+
stdout_b.write((f"* {author_clean} ({count}) +\n").encode('utf-8'))
118139

119140
stdout_b.write(("""
120141
A total of %(count)d people contributed to this release.
@@ -123,8 +144,32 @@ def name_key(fullname):
123144
124145
""" % dict(count=len(authors))).encode('utf-8'))
125146

126-
stdout_b.write("\nNOTE: Check this list manually! It is automatically generated "
127-
"and some names\n may be missing.\n")
147+
stdout_b.write(("\nNOTE: Check this list manually! It is automatically generated "
148+
"and some names\n may be missing.\n").encode('utf-8'))
149+
150+
151+
def load_name_map(filename):
152+
name_map = {}
153+
154+
with io.open(filename, 'r', encoding='utf-8') as f:
155+
for line in f:
156+
line = line.strip()
157+
if line.startswith(u"#") or not line:
158+
continue
159+
160+
m = re.match(r'^(.*?)\s*<(.*?)>(.*?)\s*<(.*?)>\s*$', line)
161+
if not m:
162+
print("Invalid line in .mailmap: '{!r}'".format(line), file=sys.stderr)
163+
sys.exit(1)
164+
165+
new_name = m.group(1).strip()
166+
old_name = m.group(3).strip()
167+
168+
if old_name and new_name:
169+
name_map[old_name] = new_name
170+
171+
return name_map
172+
128173

129174
#------------------------------------------------------------------------------
130175
# Communicating with Git
@@ -182,6 +227,7 @@ def test(self, command, *a, **kw):
182227
call=True, **kw)
183228
return (ret == 0)
184229

230+
185231
git = Cmd("git")
186232

187233
#------------------------------------------------------------------------------

0 commit comments

Comments
 (0)