diff options
Diffstat (limited to 'scripts/get_maintainer.pl')
-rwxr-xr-x | scripts/get_maintainer.pl | 462 |
1 files changed, 380 insertions, 82 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl index 3e733146cd51..102b76608f35 100755 --- a/scripts/get_maintainer.pl +++ b/scripts/get_maintainer.pl | |||
@@ -13,7 +13,7 @@ | |||
13 | use strict; | 13 | use strict; |
14 | 14 | ||
15 | my $P = $0; | 15 | my $P = $0; |
16 | my $V = '0.16'; | 16 | my $V = '0.21'; |
17 | 17 | ||
18 | use Getopt::Long qw(:config no_auto_abbrev); | 18 | use Getopt::Long qw(:config no_auto_abbrev); |
19 | 19 | ||
@@ -27,14 +27,19 @@ my $email_git = 1; | |||
27 | my $email_git_penguin_chiefs = 0; | 27 | my $email_git_penguin_chiefs = 0; |
28 | my $email_git_min_signatures = 1; | 28 | my $email_git_min_signatures = 1; |
29 | my $email_git_max_maintainers = 5; | 29 | my $email_git_max_maintainers = 5; |
30 | my $email_git_min_percent = 5; | ||
30 | my $email_git_since = "1-year-ago"; | 31 | my $email_git_since = "1-year-ago"; |
32 | my $email_git_blame = 0; | ||
33 | my $email_remove_duplicates = 1; | ||
31 | my $output_multiline = 1; | 34 | my $output_multiline = 1; |
32 | my $output_separator = ", "; | 35 | my $output_separator = ", "; |
33 | my $scm = 0; | 36 | my $scm = 0; |
34 | my $web = 0; | 37 | my $web = 0; |
35 | my $subsystem = 0; | 38 | my $subsystem = 0; |
36 | my $status = 0; | 39 | my $status = 0; |
40 | my $keywords = 1; | ||
37 | my $from_filename = 0; | 41 | my $from_filename = 0; |
42 | my $pattern_depth = 0; | ||
38 | my $version = 0; | 43 | my $version = 0; |
39 | my $help = 0; | 44 | my $help = 0; |
40 | 45 | ||
@@ -65,7 +70,10 @@ if (!GetOptions( | |||
65 | 'git-chief-penguins!' => \$email_git_penguin_chiefs, | 70 | 'git-chief-penguins!' => \$email_git_penguin_chiefs, |
66 | 'git-min-signatures=i' => \$email_git_min_signatures, | 71 | 'git-min-signatures=i' => \$email_git_min_signatures, |
67 | 'git-max-maintainers=i' => \$email_git_max_maintainers, | 72 | 'git-max-maintainers=i' => \$email_git_max_maintainers, |
73 | 'git-min-percent=i' => \$email_git_min_percent, | ||
68 | 'git-since=s' => \$email_git_since, | 74 | 'git-since=s' => \$email_git_since, |
75 | 'git-blame!' => \$email_git_blame, | ||
76 | 'remove-duplicates!' => \$email_remove_duplicates, | ||
69 | 'm!' => \$email_maintainer, | 77 | 'm!' => \$email_maintainer, |
70 | 'n!' => \$email_usename, | 78 | 'n!' => \$email_usename, |
71 | 'l!' => \$email_list, | 79 | 'l!' => \$email_list, |
@@ -76,6 +84,8 @@ if (!GetOptions( | |||
76 | 'status!' => \$status, | 84 | 'status!' => \$status, |
77 | 'scm!' => \$scm, | 85 | 'scm!' => \$scm, |
78 | 'web!' => \$web, | 86 | 'web!' => \$web, |
87 | 'pattern-depth=i' => \$pattern_depth, | ||
88 | 'k|keywords!' => \$keywords, | ||
79 | 'f|file' => \$from_filename, | 89 | 'f|file' => \$from_filename, |
80 | 'v|version' => \$version, | 90 | 'v|version' => \$version, |
81 | 'h|help' => \$help, | 91 | 'h|help' => \$help, |
@@ -99,14 +109,19 @@ if ($#ARGV < 0) { | |||
99 | die "$P: argument missing: patchfile or -f file please\n"; | 109 | die "$P: argument missing: patchfile or -f file please\n"; |
100 | } | 110 | } |
101 | 111 | ||
112 | if ($output_separator ne ", ") { | ||
113 | $output_multiline = 0; | ||
114 | } | ||
115 | |||
102 | my $selections = $email + $scm + $status + $subsystem + $web; | 116 | my $selections = $email + $scm + $status + $subsystem + $web; |
103 | if ($selections == 0) { | 117 | if ($selections == 0) { |
104 | usage(); | 118 | usage(); |
105 | die "$P: Missing required option: email, scm, status, subsystem or web\n"; | 119 | die "$P: Missing required option: email, scm, status, subsystem or web\n"; |
106 | } | 120 | } |
107 | 121 | ||
108 | if ($email && ($email_maintainer + $email_list + $email_subscriber_list | 122 | if ($email && |
109 | + $email_git + $email_git_penguin_chiefs) == 0) { | 123 | ($email_maintainer + $email_list + $email_subscriber_list + |
124 | $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) { | ||
110 | usage(); | 125 | usage(); |
111 | die "$P: Please select at least 1 email option\n"; | 126 | die "$P: Please select at least 1 email option\n"; |
112 | } | 127 | } |
@@ -119,6 +134,8 @@ if (!top_of_kernel_tree($lk_path)) { | |||
119 | ## Read MAINTAINERS for type/value pairs | 134 | ## Read MAINTAINERS for type/value pairs |
120 | 135 | ||
121 | my @typevalue = (); | 136 | my @typevalue = (); |
137 | my %keyword_hash; | ||
138 | |||
122 | open(MAINT, "<${lk_path}MAINTAINERS") || die "$P: Can't open MAINTAINERS\n"; | 139 | open(MAINT, "<${lk_path}MAINTAINERS") || die "$P: Can't open MAINTAINERS\n"; |
123 | while (<MAINT>) { | 140 | while (<MAINT>) { |
124 | my $line = $_; | 141 | my $line = $_; |
@@ -132,6 +149,12 @@ while (<MAINT>) { | |||
132 | $value =~ s@\.@\\\.@g; ##Convert . to \. | 149 | $value =~ s@\.@\\\.@g; ##Convert . to \. |
133 | $value =~ s/\*/\.\*/g; ##Convert * to .* | 150 | $value =~ s/\*/\.\*/g; ##Convert * to .* |
134 | $value =~ s/\?/\./g; ##Convert ? to . | 151 | $value =~ s/\?/\./g; ##Convert ? to . |
152 | ##if pattern is a directory and it lacks a trailing slash, add one | ||
153 | if ((-d $value)) { | ||
154 | $value =~ s@([^/])$@$1/@; | ||
155 | } | ||
156 | } elsif ($type eq "K") { | ||
157 | $keyword_hash{@typevalue} = $value; | ||
135 | } | 158 | } |
136 | push(@typevalue, "$type:$value"); | 159 | push(@typevalue, "$type:$value"); |
137 | } elsif (!/^(\s)*$/) { | 160 | } elsif (!/^(\s)*$/) { |
@@ -141,26 +164,81 @@ while (<MAINT>) { | |||
141 | } | 164 | } |
142 | close(MAINT); | 165 | close(MAINT); |
143 | 166 | ||
167 | my %mailmap; | ||
168 | |||
169 | if ($email_remove_duplicates) { | ||
170 | open(MAILMAP, "<${lk_path}.mailmap") || warn "$P: Can't open .mailmap\n"; | ||
171 | while (<MAILMAP>) { | ||
172 | my $line = $_; | ||
173 | |||
174 | next if ($line =~ m/^\s*#/); | ||
175 | next if ($line =~ m/^\s*$/); | ||
176 | |||
177 | my ($name, $address) = parse_email($line); | ||
178 | $line = format_email($name, $address); | ||
179 | |||
180 | next if ($line =~ m/^\s*$/); | ||
181 | |||
182 | if (exists($mailmap{$name})) { | ||
183 | my $obj = $mailmap{$name}; | ||
184 | push(@$obj, $address); | ||
185 | } else { | ||
186 | my @arr = ($address); | ||
187 | $mailmap{$name} = \@arr; | ||
188 | } | ||
189 | } | ||
190 | close(MAILMAP); | ||
191 | } | ||
192 | |||
144 | ## use the filenames on the command line or find the filenames in the patchfiles | 193 | ## use the filenames on the command line or find the filenames in the patchfiles |
145 | 194 | ||
146 | my @files = (); | 195 | my @files = (); |
196 | my @range = (); | ||
197 | my @keyword_tvi = (); | ||
147 | 198 | ||
148 | foreach my $file (@ARGV) { | 199 | foreach my $file (@ARGV) { |
149 | next if ((-d $file)); | 200 | ##if $file is a directory and it lacks a trailing slash, add one |
150 | if (!(-f $file)) { | 201 | if ((-d $file)) { |
202 | $file =~ s@([^/])$@$1/@; | ||
203 | } elsif (!(-f $file)) { | ||
151 | die "$P: file '${file}' not found\n"; | 204 | die "$P: file '${file}' not found\n"; |
152 | } | 205 | } |
153 | if ($from_filename) { | 206 | if ($from_filename) { |
154 | push(@files, $file); | 207 | push(@files, $file); |
208 | if (-f $file && $keywords) { | ||
209 | open(FILE, "<$file") or die "$P: Can't open ${file}\n"; | ||
210 | while (<FILE>) { | ||
211 | my $patch_line = $_; | ||
212 | foreach my $line (keys %keyword_hash) { | ||
213 | if ($patch_line =~ m/^.*$keyword_hash{$line}/x) { | ||
214 | push(@keyword_tvi, $line); | ||
215 | } | ||
216 | } | ||
217 | } | ||
218 | close(FILE); | ||
219 | } | ||
155 | } else { | 220 | } else { |
156 | my $file_cnt = @files; | 221 | my $file_cnt = @files; |
222 | my $lastfile; | ||
157 | open(PATCH, "<$file") or die "$P: Can't open ${file}\n"; | 223 | open(PATCH, "<$file") or die "$P: Can't open ${file}\n"; |
158 | while (<PATCH>) { | 224 | while (<PATCH>) { |
225 | my $patch_line = $_; | ||
159 | if (m/^\+\+\+\s+(\S+)/) { | 226 | if (m/^\+\+\+\s+(\S+)/) { |
160 | my $filename = $1; | 227 | my $filename = $1; |
161 | $filename =~ s@^[^/]*/@@; | 228 | $filename =~ s@^[^/]*/@@; |
162 | $filename =~ s@\n@@; | 229 | $filename =~ s@\n@@; |
230 | $lastfile = $filename; | ||
163 | push(@files, $filename); | 231 | push(@files, $filename); |
232 | } elsif (m/^\@\@ -(\d+),(\d+)/) { | ||
233 | if ($email_git_blame) { | ||
234 | push(@range, "$lastfile:$1:$2"); | ||
235 | } | ||
236 | } elsif ($keywords) { | ||
237 | foreach my $line (keys %keyword_hash) { | ||
238 | if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) { | ||
239 | push(@keyword_tvi, $line); | ||
240 | } | ||
241 | } | ||
164 | } | 242 | } |
165 | } | 243 | } |
166 | close(PATCH); | 244 | close(PATCH); |
@@ -193,6 +271,7 @@ foreach my $file (@files) { | |||
193 | if ($type eq 'X') { | 271 | if ($type eq 'X') { |
194 | if (file_match_pattern($file, $value)) { | 272 | if (file_match_pattern($file, $value)) { |
195 | $exclude = 1; | 273 | $exclude = 1; |
274 | last; | ||
196 | } | 275 | } |
197 | } | 276 | } |
198 | } | 277 | } |
@@ -200,35 +279,52 @@ foreach my $file (@files) { | |||
200 | 279 | ||
201 | if (!$exclude) { | 280 | if (!$exclude) { |
202 | my $tvi = 0; | 281 | my $tvi = 0; |
282 | my %hash; | ||
203 | foreach my $line (@typevalue) { | 283 | foreach my $line (@typevalue) { |
204 | if ($line =~ m/^(\C):\s*(.*)/) { | 284 | if ($line =~ m/^(\C):\s*(.*)/) { |
205 | my $type = $1; | 285 | my $type = $1; |
206 | my $value = $2; | 286 | my $value = $2; |
207 | if ($type eq 'F') { | 287 | if ($type eq 'F') { |
208 | if (file_match_pattern($file, $value)) { | 288 | if (file_match_pattern($file, $value)) { |
209 | add_categories($tvi); | 289 | my $value_pd = ($value =~ tr@/@@); |
290 | my $file_pd = ($file =~ tr@/@@); | ||
291 | $value_pd++ if (substr($value,-1,1) ne "/"); | ||
292 | if ($pattern_depth == 0 || | ||
293 | (($file_pd - $value_pd) < $pattern_depth)) { | ||
294 | $hash{$tvi} = $value_pd; | ||
295 | } | ||
210 | } | 296 | } |
211 | } | 297 | } |
212 | } | 298 | } |
213 | $tvi++; | 299 | $tvi++; |
214 | } | 300 | } |
301 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { | ||
302 | add_categories($line); | ||
303 | } | ||
215 | } | 304 | } |
216 | 305 | ||
217 | if ($email && $email_git) { | 306 | if ($email && $email_git) { |
218 | recent_git_signoffs($file); | 307 | recent_git_signoffs($file); |
219 | } | 308 | } |
220 | 309 | ||
310 | if ($email && $email_git_blame) { | ||
311 | git_assign_blame($file); | ||
312 | } | ||
313 | } | ||
314 | |||
315 | if ($keywords) { | ||
316 | @keyword_tvi = sort_and_uniq(@keyword_tvi); | ||
317 | foreach my $line (@keyword_tvi) { | ||
318 | add_categories($line); | ||
319 | } | ||
221 | } | 320 | } |
222 | 321 | ||
223 | if ($email) { | 322 | if ($email) { |
224 | foreach my $chief (@penguin_chief) { | 323 | foreach my $chief (@penguin_chief) { |
225 | if ($chief =~ m/^(.*):(.*)/) { | 324 | if ($chief =~ m/^(.*):(.*)/) { |
226 | my $email_address; | 325 | my $email_address; |
227 | if ($email_usename) { | 326 | |
228 | $email_address = format_email($1, $2); | 327 | $email_address = format_email($1, $2); |
229 | } else { | ||
230 | $email_address = $2; | ||
231 | } | ||
232 | if ($email_git_penguin_chiefs) { | 328 | if ($email_git_penguin_chiefs) { |
233 | push(@email_to, $email_address); | 329 | push(@email_to, $email_address); |
234 | } else { | 330 | } else { |
@@ -250,22 +346,22 @@ if ($email || $email_list) { | |||
250 | } | 346 | } |
251 | 347 | ||
252 | if ($scm) { | 348 | if ($scm) { |
253 | @scm = sort_and_uniq(@scm); | 349 | @scm = uniq(@scm); |
254 | output(@scm); | 350 | output(@scm); |
255 | } | 351 | } |
256 | 352 | ||
257 | if ($status) { | 353 | if ($status) { |
258 | @status = sort_and_uniq(@status); | 354 | @status = uniq(@status); |
259 | output(@status); | 355 | output(@status); |
260 | } | 356 | } |
261 | 357 | ||
262 | if ($subsystem) { | 358 | if ($subsystem) { |
263 | @subsystem = sort_and_uniq(@subsystem); | 359 | @subsystem = uniq(@subsystem); |
264 | output(@subsystem); | 360 | output(@subsystem); |
265 | } | 361 | } |
266 | 362 | ||
267 | if ($web) { | 363 | if ($web) { |
268 | @web = sort_and_uniq(@web); | 364 | @web = uniq(@web); |
269 | output(@web); | 365 | output(@web); |
270 | } | 366 | } |
271 | 367 | ||
@@ -292,7 +388,7 @@ sub file_match_pattern { | |||
292 | sub usage { | 388 | sub usage { |
293 | print <<EOT; | 389 | print <<EOT; |
294 | usage: $P [options] patchfile | 390 | usage: $P [options] patchfile |
295 | $P [options] -f file | 391 | $P [options] -f file|directory |
296 | version: $V | 392 | version: $V |
297 | 393 | ||
298 | MAINTAINER field selection options: | 394 | MAINTAINER field selection options: |
@@ -301,11 +397,14 @@ MAINTAINER field selection options: | |||
301 | --git-chief-penguins => include ${penguin_chiefs} | 397 | --git-chief-penguins => include ${penguin_chiefs} |
302 | --git-min-signatures => number of signatures required (default: 1) | 398 | --git-min-signatures => number of signatures required (default: 1) |
303 | --git-max-maintainers => maximum maintainers to add (default: 5) | 399 | --git-max-maintainers => maximum maintainers to add (default: 5) |
400 | --git-min-percent => minimum percentage of commits required (default: 5) | ||
304 | --git-since => git history to use (default: 1-year-ago) | 401 | --git-since => git history to use (default: 1-year-ago) |
402 | --git-blame => use git blame to find modified commits for patch or file | ||
305 | --m => include maintainer(s) if any | 403 | --m => include maintainer(s) if any |
306 | --n => include name 'Full Name <addr\@domain.tld>' | 404 | --n => include name 'Full Name <addr\@domain.tld>' |
307 | --l => include list(s) if any | 405 | --l => include list(s) if any |
308 | --s => include subscriber only list(s) if any | 406 | --s => include subscriber only list(s) if any |
407 | --remove-duplicates => minimize duplicate email names/addresses | ||
309 | --scm => print SCM tree(s) if any | 408 | --scm => print SCM tree(s) if any |
310 | --status => print status if any | 409 | --status => print status if any |
311 | --subsystem => print subsystem name if any | 410 | --subsystem => print subsystem name if any |
@@ -313,15 +412,29 @@ MAINTAINER field selection options: | |||
313 | 412 | ||
314 | Output type options: | 413 | Output type options: |
315 | --separator [, ] => separator for multiple entries on 1 line | 414 | --separator [, ] => separator for multiple entries on 1 line |
415 | using --separator also sets --nomultiline if --separator is not [, ] | ||
316 | --multiline => print 1 entry per line | 416 | --multiline => print 1 entry per line |
317 | 417 | ||
318 | Default options: | ||
319 | [--email --git --m --n --l --multiline] | ||
320 | |||
321 | Other options: | 418 | Other options: |
419 | --pattern-depth => Number of pattern directory traversals (default: 0 (all)) | ||
420 | --keywords => scan patch for keywords (default: 1 (on)) | ||
322 | --version => show version | 421 | --version => show version |
323 | --help => show this help information | 422 | --help => show this help information |
324 | 423 | ||
424 | Default options: | ||
425 | [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates] | ||
426 | |||
427 | Notes: | ||
428 | Using "-f directory" may give unexpected results: | ||
429 | Used with "--git", git signators for _all_ files in and below | ||
430 | directory are examined as git recurses directories. | ||
431 | Any specified X: (exclude) pattern matches are _not_ ignored. | ||
432 | Used with "--nogit", directory is used as a pattern match, | ||
433 | no individual file within the directory or subdirectory | ||
434 | is matched. | ||
435 | Used with "--git-blame", does not iterate all files in directory | ||
436 | Using "--git-blame" is slow and may add old committers and authors | ||
437 | that are no longer active maintainers to the output. | ||
325 | EOT | 438 | EOT |
326 | } | 439 | } |
327 | 440 | ||
@@ -352,30 +465,99 @@ sub top_of_kernel_tree { | |||
352 | return 0; | 465 | return 0; |
353 | } | 466 | } |
354 | 467 | ||
355 | sub format_email { | 468 | sub parse_email { |
356 | my ($name, $email) = @_; | 469 | my ($formatted_email) = @_; |
470 | |||
471 | my $name = ""; | ||
472 | my $address = ""; | ||
473 | |||
474 | if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) { | ||
475 | $name = $1; | ||
476 | $address = $2; | ||
477 | } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) { | ||
478 | $address = $1; | ||
479 | } elsif ($formatted_email =~ /^(.+\@\S*).*$/) { | ||
480 | $address = $1; | ||
481 | } | ||
357 | 482 | ||
358 | $name =~ s/^\s+|\s+$//g; | 483 | $name =~ s/^\s+|\s+$//g; |
359 | $name =~ s/^\"|\"$//g; | 484 | $name =~ s/^\"|\"$//g; |
360 | $email =~ s/^\s+|\s+$//g; | 485 | $address =~ s/^\s+|\s+$//g; |
361 | 486 | ||
362 | my $formatted_email = ""; | 487 | if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars |
488 | $name =~ s/(?<!\\)"/\\"/g; ##escape quotes | ||
489 | $name = "\"$name\""; | ||
490 | } | ||
491 | |||
492 | return ($name, $address); | ||
493 | } | ||
494 | |||
495 | sub format_email { | ||
496 | my ($name, $address) = @_; | ||
497 | |||
498 | my $formatted_email; | ||
499 | |||
500 | $name =~ s/^\s+|\s+$//g; | ||
501 | $name =~ s/^\"|\"$//g; | ||
502 | $address =~ s/^\s+|\s+$//g; | ||
363 | 503 | ||
364 | if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars | 504 | if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars |
365 | $name =~ s/(?<!\\)"/\\"/g; ##escape quotes | 505 | $name =~ s/(?<!\\)"/\\"/g; ##escape quotes |
366 | $formatted_email = "\"${name}\"\ \<${email}\>"; | 506 | $name = "\"$name\""; |
507 | } | ||
508 | |||
509 | if ($email_usename) { | ||
510 | if ("$name" eq "") { | ||
511 | $formatted_email = "$address"; | ||
512 | } else { | ||
513 | $formatted_email = "$name <${address}>"; | ||
514 | } | ||
367 | } else { | 515 | } else { |
368 | $formatted_email = "${name} \<${email}\>"; | 516 | $formatted_email = $address; |
369 | } | 517 | } |
518 | |||
370 | return $formatted_email; | 519 | return $formatted_email; |
371 | } | 520 | } |
372 | 521 | ||
373 | sub add_categories { | 522 | sub find_starting_index { |
374 | my ($index) = @_; | 523 | my ($index) = @_; |
375 | 524 | ||
376 | $index = $index - 1; | 525 | while ($index > 0) { |
377 | while ($index >= 0) { | ||
378 | my $tv = $typevalue[$index]; | 526 | my $tv = $typevalue[$index]; |
527 | if (!($tv =~ m/^(\C):\s*(.*)/)) { | ||
528 | last; | ||
529 | } | ||
530 | $index--; | ||
531 | } | ||
532 | |||
533 | return $index; | ||
534 | } | ||
535 | |||
536 | sub find_ending_index { | ||
537 | my ($index) = @_; | ||
538 | |||
539 | while ($index < @typevalue) { | ||
540 | my $tv = $typevalue[$index]; | ||
541 | if (!($tv =~ m/^(\C):\s*(.*)/)) { | ||
542 | last; | ||
543 | } | ||
544 | $index++; | ||
545 | } | ||
546 | |||
547 | return $index; | ||
548 | } | ||
549 | |||
550 | sub add_categories { | ||
551 | my ($index) = @_; | ||
552 | |||
553 | my $i; | ||
554 | my $start = find_starting_index($index); | ||
555 | my $end = find_ending_index($index); | ||
556 | |||
557 | push(@subsystem, $typevalue[$start]); | ||
558 | |||
559 | for ($i = $start + 1; $i < $end; $i++) { | ||
560 | my $tv = $typevalue[$i]; | ||
379 | if ($tv =~ m/^(\C):\s*(.*)/) { | 561 | if ($tv =~ m/^(\C):\s*(.*)/) { |
380 | my $ptype = $1; | 562 | my $ptype = $1; |
381 | my $pvalue = $2; | 563 | my $pvalue = $2; |
@@ -396,19 +578,19 @@ sub add_categories { | |||
396 | } | 578 | } |
397 | } | 579 | } |
398 | } elsif ($ptype eq "M") { | 580 | } elsif ($ptype eq "M") { |
399 | my $p_used = 0; | 581 | my ($name, $address) = parse_email($pvalue); |
400 | if ($index >= 0) { | 582 | if ($name eq "") { |
401 | my $tv = $typevalue[$index - 1]; | 583 | if ($i > 0) { |
402 | if ($tv =~ m/^(\C):\s*(.*)/) { | 584 | my $tv = $typevalue[$i - 1]; |
403 | if ($1 eq "P") { | 585 | if ($tv =~ m/^(\C):\s*(.*)/) { |
404 | if ($email_usename) { | 586 | if ($1 eq "P") { |
405 | push_email_address(format_email($2, $pvalue)); | 587 | $name = $2; |
406 | $p_used = 1; | 588 | $pvalue = format_email($name, $address); |
407 | } | 589 | } |
408 | } | 590 | } |
409 | } | 591 | } |
410 | } | 592 | } |
411 | if (!$p_used) { | 593 | if ($email_maintainer) { |
412 | push_email_addresses($pvalue); | 594 | push_email_addresses($pvalue); |
413 | } | 595 | } |
414 | } elsif ($ptype eq "T") { | 596 | } elsif ($ptype eq "T") { |
@@ -418,31 +600,41 @@ sub add_categories { | |||
418 | } elsif ($ptype eq "S") { | 600 | } elsif ($ptype eq "S") { |
419 | push(@status, $pvalue); | 601 | push(@status, $pvalue); |
420 | } | 602 | } |
421 | |||
422 | $index--; | ||
423 | } else { | ||
424 | push(@subsystem,$tv); | ||
425 | $index = -1; | ||
426 | } | 603 | } |
427 | } | 604 | } |
428 | } | 605 | } |
429 | 606 | ||
607 | my %email_hash_name; | ||
608 | my %email_hash_address; | ||
609 | |||
610 | sub email_inuse { | ||
611 | my ($name, $address) = @_; | ||
612 | |||
613 | return 1 if (($name eq "") && ($address eq "")); | ||
614 | return 1 if (($name ne "") && exists($email_hash_name{$name})); | ||
615 | return 1 if (($address ne "") && exists($email_hash_address{$address})); | ||
616 | |||
617 | return 0; | ||
618 | } | ||
619 | |||
430 | sub push_email_address { | 620 | sub push_email_address { |
431 | my ($email_address) = @_; | 621 | my ($line) = @_; |
622 | |||
623 | my ($name, $address) = parse_email($line); | ||
432 | 624 | ||
433 | my $email_name = ""; | 625 | if ($address eq "") { |
434 | if ($email_address =~ m/([^<]+)<(.*\@.*)>$/) { | 626 | return 0; |
435 | $email_name = $1; | ||
436 | $email_address = $2; | ||
437 | } | 627 | } |
438 | 628 | ||
439 | if ($email_maintainer) { | 629 | if (!$email_remove_duplicates) { |
440 | if ($email_usename && $email_name) { | 630 | push(@email_to, format_email($name, $address)); |
441 | push(@email_to, format_email($email_name, $email_address)); | 631 | } elsif (!email_inuse($name, $address)) { |
442 | } else { | 632 | push(@email_to, format_email($name, $address)); |
443 | push(@email_to, $email_address); | 633 | $email_hash_name{$name}++; |
444 | } | 634 | $email_hash_address{$address}++; |
445 | } | 635 | } |
636 | |||
637 | return 1; | ||
446 | } | 638 | } |
447 | 639 | ||
448 | sub push_email_addresses { | 640 | sub push_email_addresses { |
@@ -458,7 +650,9 @@ sub push_email_addresses { | |||
458 | push_email_address($entry); | 650 | push_email_address($entry); |
459 | } | 651 | } |
460 | } else { | 652 | } else { |
461 | warn("Invalid MAINTAINERS address: '" . $address . "'\n"); | 653 | if (!push_email_address($address)) { |
654 | warn("Invalid MAINTAINERS address: '" . $address . "'\n"); | ||
655 | } | ||
462 | } | 656 | } |
463 | } | 657 | } |
464 | 658 | ||
@@ -474,6 +668,32 @@ sub which { | |||
474 | return ""; | 668 | return ""; |
475 | } | 669 | } |
476 | 670 | ||
671 | sub mailmap { | ||
672 | my @lines = @_; | ||
673 | my %hash; | ||
674 | |||
675 | foreach my $line (@lines) { | ||
676 | my ($name, $address) = parse_email($line); | ||
677 | if (!exists($hash{$name})) { | ||
678 | $hash{$name} = $address; | ||
679 | } elsif ($address ne $hash{$name}) { | ||
680 | $address = $hash{$name}; | ||
681 | $line = format_email($name, $address); | ||
682 | } | ||
683 | if (exists($mailmap{$name})) { | ||
684 | my $obj = $mailmap{$name}; | ||
685 | foreach my $map_address (@$obj) { | ||
686 | if (($map_address eq $address) && | ||
687 | ($map_address ne $hash{$name})) { | ||
688 | $line = format_email($name, $hash{$name}); | ||
689 | } | ||
690 | } | ||
691 | } | ||
692 | } | ||
693 | |||
694 | return @lines; | ||
695 | } | ||
696 | |||
477 | sub recent_git_signoffs { | 697 | sub recent_git_signoffs { |
478 | my ($file) = @_; | 698 | my ($file) = @_; |
479 | 699 | ||
@@ -482,6 +702,8 @@ sub recent_git_signoffs { | |||
482 | my $output = ""; | 702 | my $output = ""; |
483 | my $count = 0; | 703 | my $count = 0; |
484 | my @lines = (); | 704 | my @lines = (); |
705 | my %hash; | ||
706 | my $total_sign_offs; | ||
485 | 707 | ||
486 | if (which("git") eq "") { | 708 | if (which("git") eq "") { |
487 | warn("$P: git not found. Add --nogit to options?\n"); | 709 | warn("$P: git not found. Add --nogit to options?\n"); |
@@ -494,44 +716,120 @@ sub recent_git_signoffs { | |||
494 | } | 716 | } |
495 | 717 | ||
496 | $cmd = "git log --since=${email_git_since} -- ${file}"; | 718 | $cmd = "git log --since=${email_git_since} -- ${file}"; |
497 | $cmd .= " | grep -Ei \"^[-_ a-z]+by:.*\\\@.*\$\""; | 719 | |
720 | $output = `${cmd}`; | ||
721 | $output =~ s/^\s*//gm; | ||
722 | |||
723 | @lines = split("\n", $output); | ||
724 | |||
725 | @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); | ||
498 | if (!$email_git_penguin_chiefs) { | 726 | if (!$email_git_penguin_chiefs) { |
499 | $cmd .= " | grep -Ev \"${penguin_chiefs}\""; | 727 | @lines = grep(!/${penguin_chiefs}/i, @lines); |
500 | } | 728 | } |
501 | $cmd .= " | cut -f2- -d\":\""; | 729 | # cut -f2- -d":" |
502 | $cmd .= " | sort | uniq -c | sort -rn"; | 730 | s/.*:\s*(.+)\s*/$1/ for (@lines); |
731 | |||
732 | $total_sign_offs = @lines; | ||
733 | |||
734 | if ($email_remove_duplicates) { | ||
735 | @lines = mailmap(@lines); | ||
736 | } | ||
737 | |||
738 | @lines = sort(@lines); | ||
739 | |||
740 | # uniq -c | ||
741 | $hash{$_}++ for @lines; | ||
742 | |||
743 | # sort -rn | ||
744 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { | ||
745 | my $sign_offs = $hash{$line}; | ||
746 | $count++; | ||
747 | last if ($sign_offs < $email_git_min_signatures || | ||
748 | $count > $email_git_max_maintainers || | ||
749 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); | ||
750 | push_email_address($line); | ||
751 | } | ||
752 | } | ||
753 | |||
754 | sub save_commits { | ||
755 | my ($cmd, @commits) = @_; | ||
756 | my $output; | ||
757 | my @lines = (); | ||
503 | 758 | ||
504 | $output = `${cmd}`; | 759 | $output = `${cmd}`; |
505 | $output =~ s/^\s*//gm; | ||
506 | 760 | ||
507 | @lines = split("\n", $output); | 761 | @lines = split("\n", $output); |
508 | foreach my $line (@lines) { | 762 | foreach my $line (@lines) { |
509 | if ($line =~ m/([0-9]+)\s+(.*)/) { | 763 | if ($line =~ m/^(\w+) /) { |
510 | my $sign_offs = $1; | 764 | push (@commits, $1); |
511 | $line = $2; | ||
512 | $count++; | ||
513 | if ($sign_offs < $email_git_min_signatures || | ||
514 | $count > $email_git_max_maintainers) { | ||
515 | last; | ||
516 | } | ||
517 | } else { | ||
518 | die("$P: Unexpected git output: ${line}\n"); | ||
519 | } | 765 | } |
520 | if ($line =~ m/(.+)<(.+)>/) { | 766 | } |
521 | my $git_name = $1; | 767 | return @commits; |
522 | my $git_addr = $2; | 768 | } |
523 | if ($email_usename) { | 769 | |
524 | push(@email_to, format_email($git_name, $git_addr)); | 770 | sub git_assign_blame { |
525 | } else { | 771 | my ($file) = @_; |
526 | push(@email_to, $git_addr); | 772 | |
527 | } | 773 | my @lines = (); |
528 | } elsif ($line =~ m/<(.+)>/) { | 774 | my @commits = (); |
529 | my $git_addr = $1; | 775 | my $cmd; |
530 | push(@email_to, $git_addr); | 776 | my $output; |
531 | } else { | 777 | my %hash; |
532 | push(@email_to, $line); | 778 | my $total_sign_offs; |
779 | my $count; | ||
780 | |||
781 | if (@range) { | ||
782 | foreach my $file_range_diff (@range) { | ||
783 | next if (!($file_range_diff =~ m/(.+):(.+):(.+)/)); | ||
784 | my $diff_file = $1; | ||
785 | my $diff_start = $2; | ||
786 | my $diff_length = $3; | ||
787 | next if (!("$file" eq "$diff_file")); | ||
788 | $cmd = "git blame -l -L $diff_start,+$diff_length $file"; | ||
789 | @commits = save_commits($cmd, @commits); | ||
790 | } | ||
791 | } else { | ||
792 | if (-f $file) { | ||
793 | $cmd = "git blame -l $file"; | ||
794 | @commits = save_commits($cmd, @commits); | ||
533 | } | 795 | } |
534 | } | 796 | } |
797 | |||
798 | $total_sign_offs = 0; | ||
799 | @commits = uniq(@commits); | ||
800 | foreach my $commit (@commits) { | ||
801 | $cmd = "git log -1 ${commit}"; | ||
802 | |||
803 | $output = `${cmd}`; | ||
804 | $output =~ s/^\s*//gm; | ||
805 | @lines = split("\n", $output); | ||
806 | |||
807 | @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); | ||
808 | if (!$email_git_penguin_chiefs) { | ||
809 | @lines = grep(!/${penguin_chiefs}/i, @lines); | ||
810 | } | ||
811 | |||
812 | # cut -f2- -d":" | ||
813 | s/.*:\s*(.+)\s*/$1/ for (@lines); | ||
814 | |||
815 | $total_sign_offs += @lines; | ||
816 | |||
817 | if ($email_remove_duplicates) { | ||
818 | @lines = mailmap(@lines); | ||
819 | } | ||
820 | |||
821 | $hash{$_}++ for @lines; | ||
822 | } | ||
823 | |||
824 | $count = 0; | ||
825 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { | ||
826 | my $sign_offs = $hash{$line}; | ||
827 | $count++; | ||
828 | last if ($sign_offs < $email_git_min_signatures || | ||
829 | $count > $email_git_max_maintainers || | ||
830 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); | ||
831 | push_email_address($line); | ||
832 | } | ||
535 | } | 833 | } |
536 | 834 | ||
537 | sub uniq { | 835 | sub uniq { |