· 8 years ago · May 26, 2018, 03:54 AM
1require 'rubygems'
2require 'grit'
3
4$KCODE = 'u'
5RAILS_DIR = '/Users/fxn/prj/rails'
6
7$changelogs = Marshal.load(File.open('changelogs').read) rescue {}
8END { File.open('changelogs', 'wb') {|f| Marshal.dump($changelogs, f)} }
9
10# Simple trick to be able to post this script with readable addresses.
11def email(user, domain)
12 user + '@' + domain
13end
14
15# Some people appear in Rails logs under different names, there are nicks,
16# typos, email addresses, shortenings, etc. This is a hand-made list to map
17# them in order to be able to aggregate commits from the same real author.
18SEEN_IN_LOG_ALSO_AS = {
19 'Aliaksey Kandratsenka' => 'Aleksey Kondratenko',
20 'Andrew Kaspick' => [email('akaspick', 'gmail.com'), 'akaspick'],
21 'Anthony Eden' => 'aeden',
22 'Austin Ziegler' => 'Thanks to Austin Ziegler for Transaction::Simple',
23 'Blaine' => ['blaine', email('blaine', 'odeo.com')],
24 'Bob Silva' => 'BobSilva',
25 'Brad Greenlee' => 'bgreenlee',
26 'Cheah Chu Yeow' => ['Chu Yeow', 'chuyeow'],
27 'court3nay' => ['courtenay', email('court3nay', 'gmail.com')],
28 'Chris McGrath' => [email('c.r.mcgrath', 'gmail.com'), 'c.r.mcgrath', email('chris', 'octopod.info'), 'octopod'],
29 'Chris Roos' => 'chrisroos',
30 'Damian Janowski' => 'djanowski',
31 'Dan Manges' => 'dcmanges',
32 'Daniel Morrison' => 'danielmorrison',
33 'Daniel Von Fange' => ['Suggested by Daniel Von Fange', 'Spotted by Daniel Von Fange'],
34 'Dave Thomas' => [email('dave', 'pragprog.com'), 'After much pestering from Dave Thomas'],
35 'David Heinemeier Hansson' => 'DHH',
36 'Don Park' => email('don.park', 'gmail.com'),
37 'Dreamer3' => email('dreamer3', 'gmail.com'),
38 'Duncan Beevers' => 'duncanbeevers',
39 'Eloy Duran' => 'alloy',
40 'Emilio Tagua' => 'miloops',
41 'Ernesto Jimenez' => 'ernesto.jimenez',
42 'Frederick Cheung' => ['fcheung', 'Fred Cheung', 'frederick.cheung', email('frederick.cheung', 'gmail.com')],
43 'Geoff Buesing' => ['gbuesing', 'Geoffrey Buesing'],
44 'Geoff Garside' => 'ggarside',
45 'Grant Hollingworth' => email('grant', 'antiflux.org'),
46 'Hampton Catlin' => email('hcatlin', 'gmail.com'),
47 'Hongli Lai (Phusion)' => ['Hongli Lai (Phusion', 'FooBarWidget'],
48 'Ian White' => email('ian.w.white', 'gmail.com'),
49 'Isaac Feliu' => 'isaacfeliu',
50 'Jack Danger Canty' => %w(danger Danger),
51 'Jarkko Laine' => [email('jarkko', 'jlaine.net'), 'Jarkko', 'jarkko'],
52 'James Adam' => 'lazyatom',
53 'James Mead' => 'floehopper',
54 'Jeremy Evans' => [email('jeremyevans0', 'gmail.com'), 'jeremyevans'],
55 'Jeremy Kemper' => 'bitsweat',
56 'Jeremy McAnally' => ['jeremymcnally', 'jeremymcanally'],
57 'John Barnette' => 'jbarnette',
58 'Jonathan del Strother' => ['Catfish', 'catfish'],
59 'Jonathan Weiss' => 'jweiss',
60 'Jordi Bunster' => 'jordi',
61 'José Valim' => 'josevalim',
62 'Josh Knowles' => email('joshknowles', 'gmail.com'),
63 'Josh Peek' => ['josh', 'Josh', 'Joshua Peek', 'joshpeek', email('josh', 'joshpeek.com')],
64 'Josh Starcher' => email('josh.starcher', 'gmail.com'),
65 'Josh Susser' => ['hasmanyjosh', email('josh', 'hasmanythrough.com')],
66 'Juanjo Bazan' => 'juanjo.bazan',
67 'Justin French' => 'justinfrench',
68 'Kamal Fariz Mahyuddin' => 'kamal',
69 'Ken Barker' => email('ken.barker', 'gmail.com'),
70 'Ken Miller' => email('kenneth.miller', 'bitfield.net'),
71 'Kevin Clark' => "Kevin Clark #{email('kevin.clark', 'gmail.com')}",
72 'Luke Redpath' => email('contact', 'lukeredpath.co.uk'),
73 'Manfred Stienstra' => [email('m.stienstra', 'fngtps.com'), 'manfred'],
74 'Marcel Molina' => ['Marcel Molina Jr.', 'Marcel'],
75 'Mark Somerville' => 'Spakman',
76 'Michael Klishin' => 'Michael S. Klishin',
77 'Michael Koziarski' => %w(Koz nzkoz),
78 'Michael Schoen' => 'Michael A. Schoen',
79 'Michael Schubert' => [email('michael', 'schubert'), email('michael', 'schubert.cx')],
80 'Michael Schuerig' => [email('michael', 'schuerig.de'), 'Michael Shuerig'],
81 'Mike Naberezny' => 'mnaberez',
82 'Mikel Lindsaar' => ['mikel', 'raasdnil'],
83 'Mislav Marohnić' => ['mislav', 'mislaw', email('mislav', 'nippur.irb.hr')],
84 'Nathan Weizenbaum' => 'Nex3',
85 'Nick Sieger' => ['nicksieger', email('nicksieger', 'gmail.com')],
86 'Nik Wakelin' => 'nik.wakelin',
87 'Obie Fernandez' => 'ObieFernandez',
88 'Philip Hallstrom' => 'phallstrom',
89 'Pratik Naik' => %w(Pratik lifofifo lifo),
90 'Rick Olson' => ['rick', 'Rick', 'Rick Olsen', email('technoweenie', 'gmail.com')],
91 'Rob Biedenharn' => ['rabiedenharn', email('Rob', 'AgileConsultingLLC.com')],
92 'Robby Russell' => 'robbyrussell',
93 'Ryan Bates' => 'ryanb',
94 'Ryan Davis' => 'zenspider',
95 'Ryan Tomayko' => email('rtomayko', 'gmail.com'),
96 'Sam Granieri' => 'sjgman9',
97 'Sebastian Kanthak' => [email('sebastian.kanthak', 'muehlheim.de'), 'sebastian.kanthak', 'skanthak'],
98 'Seth Rasmussen' => 'loincloth',
99 'Stefan Kaes' => [email('skaes', 'web.de'), 'skaes', 'Stephan Kaes', 'Skaes', 'skaes.web.de'],
100 'Steve Purcell' => email('stephen_purcell', 'yahoo.com'),
101 'Tarmo Tänav' => ['tarmo', 'tarmo_t', 'Tarmo Täna'],
102 'Thijs van der Vossen' => ['thijsv', email('thijs', 'vandervossen.net'), email('thijs', 'fngtps.com')],
103 'Tim Pope' => 'tpope',
104 'Tobias Lütke' => ['Tobias Luetke', 'TobiasLuetke'],
105 'Tom Ward' => ['Tom ward', 'tomafro'],
106 'Will Bryant' => 'will.bryant',
107 'Xavier Noria' => 'fxn',
108 'Zach Dennis' => 'zdennis',
109}
110
111# Reverse SEEN_IN_LOG_ALSO_AS as NAME_NORMALIZER.
112NAME_NORMALIZER = {}
113SEEN_IN_LOG_ALSO_AS.each do |name, also_as|
114 [*also_as].each { |alt| NAME_NORMALIZER[alt] = name }
115end
116
117def normalize_name(name)
118 name = name.sub(/\s*<[^>]+>/, '') # remove any email address in angles
119 name.strip!
120 NAME_NORMALIZER[name] || name
121end
122
123def commit_from_svn?(commit)
124 commit.message.include?('git-svn-id:')
125end
126
127# When Rails had a svn repo there was a convention for authors: the committer
128# put their name between brackets at the end of the commit or changelog message.
129# For example:
130#
131# Fix case-sensitive validates_uniqueness_of. Closes #11366 [miloops]
132#
133# Of course this is not robust, but it is the best we can get.
134def extract_svn_authors_from_message(message)
135 svn_authors = []
136 if message =~ /\[([^\]]+)\]\s*$/ # end-of-line anchor on purpose
137 # [Adam Milligan, Pratik]
138 # [Rick Olson/Nicholas Seckar]
139 # [Kevin Clark & Jeremy Hopple]
140 $1.split(%r{\s*[,/&]\s*}).each do |c|
141 if looks_like_an_author_name(c)
142 svn_authors << c
143 end
144 end
145 end
146 svn_authors
147end
148
149# Returns a list of authors extracted from the corresponding svn commit.
150def extract_svn_authors(commit)
151 svn_authors = extract_svn_authors_from_message(commit.message)
152 if svn_authors.empty?
153 svn_authors = extract_svn_authors_diffing(commit)
154 end
155 svn_authors = [commit.author.name] if svn_authors.empty?
156 svn_authors.map {|a| normalize_name(a)}.uniq
157end
158
159# Searches for author names in CHANGELOGs.
160def extract_svn_authors_diffing(commit)
161 unless $changelogs.has_key?(commit.id)
162 $changelogs[commit.id] = []
163 Dir.chdir(RAILS_DIR) do
164 in_changelog = false
165 git_show(commit).each do |line|
166 if line =~ /^diff --git/
167 in_changelog = false
168 next
169 end
170 if line =~ /^\+\+\+.*changelog$/i
171 in_changelog = true
172 next
173 end
174 if in_changelog && line =~ /^\+\s*\*/
175 $changelogs[commit.id] << line
176 end
177 end
178 end
179 end
180 $changelogs[commit.id].map {|line| extract_svn_authors_from_message(line)}.flatten
181end
182
183def git_show(commit)
184 `git show #{commit.id}`
185end
186
187# Author name extraction in svn commits returns a few strings we just ignore.
188def looks_like_an_author_name(str)
189 str !~ /\A\d+\z/ && # Remove side effects of [5684]
190 str !~ /\A\s*\z/ &&
191 str != 'See rails ML' &&
192 str != 'subject "Text::Format Licence Exception" on Oct 15' &&
193 str !~ /RubyConf/ && # example: RubyConf '05
194 str !~ /^Includes duplicates of changes/ # example: Includes duplicates of changes from 1.1.4 - 1.2.3
195end
196
197def with_munged_email_addresses(name)
198 name.gsub(/([\w.]+)@([\w.]+)/, "\\1 ~ at ~ \\2")
199end
200
201def print_authors(authors)
202 authors.sort {|a,b| b[1] <=> a[1]}.each do |name, count|
203 puts "%4d %s" % [count, with_munged_email_addresses(name)]
204 end
205end
206
207def process_commits!
208 authors = Hash.new(0)
209 repo = Grit::Repo.new(RAILS_DIR)
210 i = 0
211 page_size = 100
212
213 loop do
214 commits = repo.commits('master', page_size, i)
215 break if commits.empty?
216 commits.each do |commit|
217 if commit_from_svn?(commit)
218 extract_svn_authors(commit).each {|a| authors[normalize_name(a)] += 1}
219 else
220 author_name = commit.author.name
221 if looks_like_an_author_name(author_name)
222 authors[normalize_name(author_name)] += 1
223 end
224 end
225 i += 1
226 end
227 end
228
229 print_authors(authors)
230end
231
232process_commits!