##// END OF EJS Templates
scripts: prepare for giving credit for contributions that have been integrated into other changesets
Mads Kiilerich -
r7661:a44228cd default
parent child Browse files
Show More
@@ -1,90 +1,96 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Some committers are so wrong that it doesn't point at any contributor:
3 # Some committers are so wrong that it doesn't point at any contributor:
4 total_ignore = set()
4 total_ignore = set()
5 total_ignore.add('*** failed to import extension hggit: No module named hggit')
5 total_ignore.add('*** failed to import extension hggit: No module named hggit')
6 total_ignore.add('<>')
6 total_ignore.add('<>')
7
7
8 # Normalize some committer names where people have contributed under different
8 # Normalize some committer names where people have contributed under different
9 # names or email addresses:
9 # names or email addresses:
10 name_fixes = {}
10 name_fixes = {}
11 name_fixes['Andrew Shadura'] = "Andrej Shadura <andrew@shadura.me>"
11 name_fixes['Andrew Shadura'] = "Andrej Shadura <andrew@shadura.me>"
12 name_fixes['aparkar'] = "Aparkar <aparkar@icloud.com>"
12 name_fixes['aparkar'] = "Aparkar <aparkar@icloud.com>"
13 name_fixes['Aras Pranckevicius'] = "Aras Pranckevičius <aras@unity3d.com>"
13 name_fixes['Aras Pranckevicius'] = "Aras Pranckevičius <aras@unity3d.com>"
14 name_fixes['Augosto Hermann'] = "Augusto Herrmann <augusto.herrmann@planejamento.gov.br>"
14 name_fixes['Augosto Hermann'] = "Augusto Herrmann <augusto.herrmann@planejamento.gov.br>"
15 name_fixes['"Bradley M. Kuhn" <bkuhn@ebb.org>'] = "Bradley M. Kuhn <bkuhn@sfconservancy.org>"
15 name_fixes['"Bradley M. Kuhn" <bkuhn@ebb.org>'] = "Bradley M. Kuhn <bkuhn@sfconservancy.org>"
16 name_fixes['dmitri.kuznetsov'] = "Dmitri Kuznetsov"
16 name_fixes['dmitri.kuznetsov'] = "Dmitri Kuznetsov"
17 name_fixes['Dmitri Kuznetsov'] = "Dmitri Kuznetsov"
17 name_fixes['Dmitri Kuznetsov'] = "Dmitri Kuznetsov"
18 name_fixes['domruf'] = "Dominik Ruf <dominikruf@gmail.com>"
18 name_fixes['domruf'] = "Dominik Ruf <dominikruf@gmail.com>"
19 name_fixes['Ingo von borstel'] = "Ingo von Borstel <kallithea@planetmaker.de>"
19 name_fixes['Ingo von borstel'] = "Ingo von Borstel <kallithea@planetmaker.de>"
20 name_fixes['Jan Heylen'] = "Jan Heylen <heyleke@gmail.com>"
20 name_fixes['Jan Heylen'] = "Jan Heylen <heyleke@gmail.com>"
21 name_fixes['Jason F. Harris'] = "Jason Harris <jason@jasonfharris.com>"
21 name_fixes['Jason F. Harris'] = "Jason Harris <jason@jasonfharris.com>"
22 name_fixes['Jelmer Vernooij'] = "Jelmer VernooΔ³ <jelmer@samba.org>"
22 name_fixes['Jelmer Vernooij'] = "Jelmer VernooΔ³ <jelmer@samba.org>"
23 name_fixes['jfh <jason@jasonfharris.com>'] = "Jason Harris <jason@jasonfharris.com>"
23 name_fixes['jfh <jason@jasonfharris.com>'] = "Jason Harris <jason@jasonfharris.com>"
24 name_fixes['Leonardo Carneiro<leonardo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
24 name_fixes['Leonardo Carneiro<leonardo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
25 name_fixes['leonardo'] = "Leonardo Carneiro <leonardo@unity3d.com>"
25 name_fixes['leonardo'] = "Leonardo Carneiro <leonardo@unity3d.com>"
26 name_fixes['Leonardo <leo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
26 name_fixes['Leonardo <leo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
27 name_fixes['Les Peabody'] = "Les Peabody <lpeabody@gmail.com>"
27 name_fixes['Les Peabody'] = "Les Peabody <lpeabody@gmail.com>"
28 name_fixes['"Lorenzo M. Catucci" <lorenzo@sancho.ccd.uniroma2.it>'] = "Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>"
28 name_fixes['"Lorenzo M. Catucci" <lorenzo@sancho.ccd.uniroma2.it>'] = "Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>"
29 name_fixes['Lukasz Balcerzak'] = "Łukasz Balcerzak <lukaszbalcerzak@gmail.com>"
29 name_fixes['Lukasz Balcerzak'] = "Łukasz Balcerzak <lukaszbalcerzak@gmail.com>"
30 name_fixes['mao <mao@lins.fju.edu.tw>'] = "Ching-Chen Mao <mao@lins.fju.edu.tw>"
30 name_fixes['mao <mao@lins.fju.edu.tw>'] = "Ching-Chen Mao <mao@lins.fju.edu.tw>"
31 name_fixes['marcink'] = "Marcin KuΕΊmiΕ„ski <marcin@python-works.com>"
31 name_fixes['marcink'] = "Marcin KuΕΊmiΕ„ski <marcin@python-works.com>"
32 name_fixes['Marcin Kuzminski'] = "Marcin KuΕΊmiΕ„ski <marcin@python-works.com>"
32 name_fixes['Marcin Kuzminski'] = "Marcin KuΕΊmiΕ„ski <marcin@python-works.com>"
33 name_fixes['nansenat16@null.tw'] = "nansenat16 <nansenat16@null.tw>"
33 name_fixes['nansenat16@null.tw'] = "nansenat16 <nansenat16@null.tw>"
34 name_fixes['Peter Vitt'] = "Peter Vitt <petervitt@web.de>"
34 name_fixes['Peter Vitt'] = "Peter Vitt <petervitt@web.de>"
35 name_fixes['philip.j@hostdime.com'] = "Philip Jameson <philip.j@hostdime.com>"
35 name_fixes['philip.j@hostdime.com'] = "Philip Jameson <philip.j@hostdime.com>"
36 name_fixes['SΓΈren LΓΈvborg'] = "SΓΈren LΓΈvborg <sorenl@unity3d.com>"
36 name_fixes['SΓΈren LΓΈvborg'] = "SΓΈren LΓΈvborg <sorenl@unity3d.com>"
37 name_fixes['Thomas De Schampheleire'] = "Thomas De Schampheleire <thomas.de_schampheleire@nokia.com>"
37 name_fixes['Thomas De Schampheleire'] = "Thomas De Schampheleire <thomas.de_schampheleire@nokia.com>"
38 name_fixes['Hosted Weblate'] = "<>"
38 name_fixes['Hosted Weblate'] = "<>"
39 name_fixes['Weblate'] = "<>"
39 name_fixes['Weblate'] = "<>"
40 name_fixes['xpol'] = "xpol <xpolife@gmail.com>"
40 name_fixes['xpol'] = "xpol <xpolife@gmail.com>"
41 name_fixes['Lars <devel@sumpfralle.de>'] = "Lars Kruse <devel@sumpfralle.de>"
41 name_fixes['Lars <devel@sumpfralle.de>'] = "Lars Kruse <devel@sumpfralle.de>"
42
42
43 # Some committer email address domains that indicate that another entity might
43 # Some committer email address domains that indicate that another entity might
44 # hold some copyright too:
44 # hold some copyright too:
45 domain_extra = {}
45 domain_extra = {}
46 domain_extra['unity3d.com'] = "Unity Technologies"
46 domain_extra['unity3d.com'] = "Unity Technologies"
47 domain_extra['rhodecode.com'] = "RhodeCode GmbH"
47 domain_extra['rhodecode.com'] = "RhodeCode GmbH"
48
48
49 # Repository history show some old contributions that traditionally hasn't been
49 # Repository history show some old contributions that traditionally hasn't been
50 # listed in about.html - preserve that:
50 # listed in about.html - preserve that:
51 no_about = set(total_ignore)
51 no_about = set(total_ignore)
52 # The following contributors were traditionally not listed in about.html and it
52 # The following contributors were traditionally not listed in about.html and it
53 # seems unclear if the copyright is personal or belongs to a company.
53 # seems unclear if the copyright is personal or belongs to a company.
54 no_about.add(('Thayne Harbaugh <thayne@fusionio.com>', '2011'))
54 no_about.add(('Thayne Harbaugh <thayne@fusionio.com>', '2011'))
55 no_about.add(('Dies Koper <diesk@fast.au.fujitsu.com>', '2012'))
55 no_about.add(('Dies Koper <diesk@fast.au.fujitsu.com>', '2012'))
56 no_about.add(('Erwin Kroon <e.kroon@smartmetersolutions.nl>', '2012'))
56 no_about.add(('Erwin Kroon <e.kroon@smartmetersolutions.nl>', '2012'))
57 no_about.add(('Vincent Caron <vcaron@bearstech.com>', '2012'))
57 no_about.add(('Vincent Caron <vcaron@bearstech.com>', '2012'))
58 # These contributors' contributions might be too small to be copyrightable:
58 # These contributors' contributions might be too small to be copyrightable:
59 no_about.add(('philip.j@hostdime.com', '2012'))
59 no_about.add(('philip.j@hostdime.com', '2012'))
60 no_about.add(('Stefan Engel <mail@engel-stefan.de>', '2012'))
60 no_about.add(('Stefan Engel <mail@engel-stefan.de>', '2012'))
61 no_about.add(('Ton Plomp <tcplomp@gmail.com>', '2013'))
61 no_about.add(('Ton Plomp <tcplomp@gmail.com>', '2013'))
62 # Was reworked and contributed later and shadowed by other contributions:
62 # Was reworked and contributed later and shadowed by other contributions:
63 no_about.add(('Sean Farley <sean.michael.farley@gmail.com>', '2013'))
63 no_about.add(('Sean Farley <sean.michael.farley@gmail.com>', '2013'))
64
64
65 # Contributors in about.html and CONTRIBUTORS not appearing in repository
66 # history:
67 other = [
68 # Work folded into commits attributed to others:
69 ]
70
65 # Preserve contributors listed in about.html but not appearing in repository
71 # Preserve contributors listed in about.html but not appearing in repository
66 # history:
72 # history:
67 other_about = [
73 other_about = [
68 ("2011", "Aparkar <aparkar@icloud.com>"),
74 ("2011", "Aparkar <aparkar@icloud.com>"),
69 ("2010", "RhodeCode GmbH"),
75 ("2010", "RhodeCode GmbH"),
70 ("2011", "RhodeCode GmbH"),
76 ("2011", "RhodeCode GmbH"),
71 ("2012", "RhodeCode GmbH"),
77 ("2012", "RhodeCode GmbH"),
72 ("2013", "RhodeCode GmbH"),
78 ("2013", "RhodeCode GmbH"),
73 ]
79 ]
74
80
75 # Preserve contributors listed in CONTRIBUTORS but not appearing in repository
81 # Preserve contributors listed in CONTRIBUTORS but not appearing in repository
76 # history:
82 # history:
77 other_contributors = [
83 other_contributors = [
78 ("", "Andrew Kesterson <andrew@aklabs.net>"),
84 ("", "Andrew Kesterson <andrew@aklabs.net>"),
79 ("", "cejones"),
85 ("", "cejones"),
80 ("", "David A. SjΓΈen <david.sjoen@westcon.no>"),
86 ("", "David A. SjΓΈen <david.sjoen@westcon.no>"),
81 ("", "James Rhodes <jrhodes@redpointsoftware.com.au>"),
87 ("", "James Rhodes <jrhodes@redpointsoftware.com.au>"),
82 ("", "Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>"),
88 ("", "Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>"),
83 ("", "larikale"),
89 ("", "larikale"),
84 ("", "RhodeCode GmbH"),
90 ("", "RhodeCode GmbH"),
85 ("", "Sebastian Kreutzberger <sebastian@rhodecode.com>"),
91 ("", "Sebastian Kreutzberger <sebastian@rhodecode.com>"),
86 ("", "Steve Romanow <slestak989@gmail.com>"),
92 ("", "Steve Romanow <slestak989@gmail.com>"),
87 ("", "SteveCohen"),
93 ("", "SteveCohen"),
88 ("", "Thomas <thomas@rhodecode.com>"),
94 ("", "Thomas <thomas@rhodecode.com>"),
89 ("", "Thomas Waldmann <tw-public@gmx.de>"),
95 ("", "Thomas Waldmann <tw-public@gmx.de>"),
90 ]
96 ]
@@ -1,164 +1,164 b''
1 #!/usr/bin/env python2
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3
3
4 """
4 """
5 Kallithea script for maintaining contributor lists from version control
5 Kallithea script for maintaining contributor lists from version control
6 history.
6 history.
7
7
8 This script and the data in it is a best effort attempt at reverse engineering
8 This script and the data in it is a best effort attempt at reverse engineering
9 previous attributions and correlate that with version control history while
9 previous attributions and correlate that with version control history while
10 preserving all existing copyright statements and attribution. This script is
10 preserving all existing copyright statements and attribution. This script is
11 processing and summarizing information found elsewhere - it is not by itself
11 processing and summarizing information found elsewhere - it is not by itself
12 making any claims. Comments in the script are an attempt at reverse engineering
12 making any claims. Comments in the script are an attempt at reverse engineering
13 possible explanations - they are not showing any intent or confirming it is
13 possible explanations - they are not showing any intent or confirming it is
14 correct.
14 correct.
15
15
16 Three files are generated / modified by this script:
16 Three files are generated / modified by this script:
17
17
18 kallithea/templates/about.html claims to show copyright holders, and the GPL
18 kallithea/templates/about.html claims to show copyright holders, and the GPL
19 license requires such existing "legal notices" to be preserved. We also try to
19 license requires such existing "legal notices" to be preserved. We also try to
20 keep it updated with copyright holders, but do not claim it is a correct list.
20 keep it updated with copyright holders, but do not claim it is a correct list.
21
21
22 CONTRIBUTORS has the purpose of giving credit where credit is due and list all
22 CONTRIBUTORS has the purpose of giving credit where credit is due and list all
23 the contributor names in the source.
23 the contributor names in the source.
24
24
25 kallithea/templates/base/base.html contains the copyright years in the page
25 kallithea/templates/base/base.html contains the copyright years in the page
26 footer.
26 footer.
27
27
28 Both make a best effort of listing all copyright holders, but revision control
28 Both make a best effort of listing all copyright holders, but revision control
29 history might be a better and more definitive source.
29 history might be a better and more definitive source.
30
30
31 Contributors are sorted "fairly" by copyright year and amount of
31 Contributors are sorted "fairly" by copyright year and amount of
32 contribution.
32 contribution.
33
33
34 New contributors are listed, without considering if the contribution contains
34 New contributors are listed, without considering if the contribution contains
35 copyrightable work.
35 copyrightable work.
36
36
37 When the copyright might belong to a different legal entity than the
37 When the copyright might belong to a different legal entity than the
38 contributor, the legal entity is given credit too.
38 contributor, the legal entity is given credit too.
39 """
39 """
40
40
41 import os
41 import os
42 import re
42 import re
43 from collections import defaultdict
43 from collections import defaultdict
44 import contributor_data
44 import contributor_data
45
45
46
46
47 def sortkey(x):
47 def sortkey(x):
48 """Return key for sorting contributors "fairly":
48 """Return key for sorting contributors "fairly":
49 * latest contribution
49 * latest contribution
50 * first contribution
50 * first contribution
51 * number of contribution years
51 * number of contribution years
52 * name (with some unicode normalization)
52 * name (with some unicode normalization)
53 The entries must be 2-tuples of a list of string years and the unicode name"""
53 The entries must be 2-tuples of a list of string years and the unicode name"""
54 return (x[0] and -int(x[0][-1]),
54 return (x[0] and -int(x[0][-1]),
55 x[0] and int(x[0][0]),
55 x[0] and int(x[0][0]),
56 -len(x[0]),
56 -len(x[0]),
57 x[1].decode('utf-8').lower().replace(u'\xe9', u'e').replace(u'\u0142', u'l')
57 x[1].decode('utf-8').lower().replace(u'\xe9', u'e').replace(u'\u0142', u'l')
58 )
58 )
59
59
60
60
61 def nice_years(l, dash='-', join=' '):
61 def nice_years(l, dash='-', join=' '):
62 """Convert a list of years into brief range like '1900-1901, 1921'."""
62 """Convert a list of years into brief range like '1900-1901, 1921'."""
63 if not l:
63 if not l:
64 return ''
64 return ''
65 start = end = int(l[0])
65 start = end = int(l[0])
66 ranges = []
66 ranges = []
67 for year in l[1:] + [0]:
67 for year in l[1:] + [0]:
68 year = int(year)
68 year = int(year)
69 if year == end + 1:
69 if year == end + 1:
70 end = year
70 end = year
71 continue
71 continue
72 if start == end:
72 if start == end:
73 ranges.append('%s' % start)
73 ranges.append('%s' % start)
74 else:
74 else:
75 ranges.append('%s%s%s' % (start, dash, end))
75 ranges.append('%s%s%s' % (start, dash, end))
76 start = end = year
76 start = end = year
77 assert start == 0 and end == 0, (start, end)
77 assert start == 0 and end == 0, (start, end)
78 return join.join(ranges)
78 return join.join(ranges)
79
79
80
80
81 def insert_entries(
81 def insert_entries(
82 filename,
82 filename,
83 all_entries,
83 all_entries,
84 no_entries,
84 no_entries,
85 domain_extra,
85 domain_extra,
86 split_re,
86 split_re,
87 normalize_name,
87 normalize_name,
88 format_f):
88 format_f):
89 """Update file with contributor information.
89 """Update file with contributor information.
90 all_entries: list of tuples with year and name
90 all_entries: list of tuples with year and name
91 no_entries: set of names or name and year tuples to ignore
91 no_entries: set of names or name and year tuples to ignore
92 domain_extra: map domain name to extra credit name
92 domain_extra: map domain name to extra credit name
93 split_re: regexp matching the part of file to rewrite
93 split_re: regexp matching the part of file to rewrite
94 normalize_name: function to normalize names for grouping and display
94 normalize_name: function to normalize names for grouping and display
95 format_f: function formatting year list and name to a string
95 format_f: function formatting year list and name to a string
96 """
96 """
97 name_years = defaultdict(set)
97 name_years = defaultdict(set)
98
98
99 for year, name in all_entries:
99 for year, name in all_entries:
100 if name in no_entries or (name, year) in no_entries:
100 if name in no_entries or (name, year) in no_entries:
101 continue
101 continue
102 domain = name.split('@', 1)[-1].rstrip('>')
102 domain = name.split('@', 1)[-1].rstrip('>')
103 if domain in domain_extra:
103 if domain in domain_extra:
104 name_years[domain_extra[domain]].add(year)
104 name_years[domain_extra[domain]].add(year)
105 name_years[normalize_name(name)].add(year)
105 name_years[normalize_name(name)].add(year)
106
106
107 l = [(list(sorted(year for year in years if year)), name)
107 l = [(list(sorted(year for year in years if year)), name)
108 for name, years in name_years.items()]
108 for name, years in name_years.items()]
109 l.sort(key=sortkey)
109 l.sort(key=sortkey)
110
110
111 with open(filename) as f:
111 with open(filename) as f:
112 pre, post = re.split(split_re, f.read())
112 pre, post = re.split(split_re, f.read())
113
113
114 with open(filename, 'w') as f:
114 with open(filename, 'w') as f:
115 f.write(pre +
115 f.write(pre +
116 ''.join(format_f(years, name) for years, name in l) +
116 ''.join(format_f(years, name) for years, name in l) +
117 post)
117 post)
118
118
119
119
120 def main():
120 def main():
121 repo_entries = [
121 repo_entries = [
122 (year, contributor_data.name_fixes.get(name) or contributor_data.name_fixes.get(name.rsplit('<', 1)[0].strip()) or name)
122 (year, contributor_data.name_fixes.get(name) or contributor_data.name_fixes.get(name.rsplit('<', 1)[0].strip()) or name)
123 for year, name in
123 for year, name in
124 (line.strip().split(' ', 1)
124 (line.strip().split(' ', 1)
125 for line in os.popen("""hg log -r '::.' -T '{date(date,"%Y")} {author}\n'""").readlines())
125 for line in os.popen("""hg log -r '::.' -T '{date(date,"%Y")} {author}\n'""").readlines())
126 ]
126 ]
127
127
128 insert_entries(
128 insert_entries(
129 filename='kallithea/templates/about.html',
129 filename='kallithea/templates/about.html',
130 all_entries=repo_entries + contributor_data.other_about,
130 all_entries=repo_entries + contributor_data.other_about + contributor_data.other,
131 no_entries=contributor_data.no_about,
131 no_entries=contributor_data.no_about,
132 domain_extra=contributor_data.domain_extra,
132 domain_extra=contributor_data.domain_extra,
133 split_re=r'(?: <li>Copyright &copy; [^\n]*</li>\n)*',
133 split_re=r'(?: <li>Copyright &copy; [^\n]*</li>\n)*',
134 normalize_name=lambda name: name.split('<', 1)[0].strip(),
134 normalize_name=lambda name: name.split('<', 1)[0].strip(),
135 format_f=lambda years, name: ' <li>Copyright &copy; %s, %s</li>\n' % (nice_years(years, '&ndash;', ', '), name),
135 format_f=lambda years, name: ' <li>Copyright &copy; %s, %s</li>\n' % (nice_years(years, '&ndash;', ', '), name),
136 )
136 )
137
137
138 insert_entries(
138 insert_entries(
139 filename='CONTRIBUTORS',
139 filename='CONTRIBUTORS',
140 all_entries=repo_entries + contributor_data.other_contributors,
140 all_entries=repo_entries + contributor_data.other_contributors + contributor_data.other,
141 no_entries=contributor_data.total_ignore,
141 no_entries=contributor_data.total_ignore,
142 domain_extra=contributor_data.domain_extra,
142 domain_extra=contributor_data.domain_extra,
143 split_re=r'(?: [^\n]*\n)*',
143 split_re=r'(?: [^\n]*\n)*',
144 normalize_name=lambda name: name,
144 normalize_name=lambda name: name,
145 format_f=lambda years, name: (' %s%s%s\n' % (name, ' ' if years else '', nice_years(years))),
145 format_f=lambda years, name: (' %s%s%s\n' % (name, ' ' if years else '', nice_years(years))),
146 )
146 )
147
147
148 insert_entries(
148 insert_entries(
149 filename='kallithea/templates/base/base.html',
149 filename='kallithea/templates/base/base.html',
150 all_entries=repo_entries,
150 all_entries=repo_entries,
151 no_entries=contributor_data.total_ignore,
151 no_entries=contributor_data.total_ignore,
152 domain_extra={},
152 domain_extra={},
153 split_re=r'(?<=&copy;) .* (?=by various authors)',
153 split_re=r'(?<=&copy;) .* (?=by various authors)',
154 normalize_name=lambda name: '',
154 normalize_name=lambda name: '',
155 format_f=lambda years, name: ' ' + nice_years(years, '&ndash;', ', ') + ' ',
155 format_f=lambda years, name: ' ' + nice_years(years, '&ndash;', ', ') + ' ',
156 )
156 )
157
157
158
158
159 if __name__ == '__main__':
159 if __name__ == '__main__':
160 main()
160 main()
161
161
162
162
163 # To list new contributors since last tagging:
163 # To list new contributors since last tagging:
164 # { hg log -r '::tagged()' -T ' {author}\n {author}\n'; hg log -r '::.' -T ' {author}\n' | sort | uniq; } | sort | uniq -u
164 # { hg log -r '::tagged()' -T ' {author}\n {author}\n'; hg log -r '::.' -T ' {author}\n' | sort | uniq; } | sort | uniq -u
General Comments 0
You need to be logged in to leave comments. Login now