diff options
Diffstat (limited to 'scripts/get_maintainer.pl')
-rwxr-xr-x | scripts/get_maintainer.pl | 421 |
1 files changed, 329 insertions, 92 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl index 278a45bd45a5..cdb44b63342e 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.17'; | 16 | my $V = '0.20'; |
17 | 17 | ||
18 | use Getopt::Long qw(:config no_auto_abbrev); | 18 | use Getopt::Long qw(:config no_auto_abbrev); |
19 | 19 | ||
@@ -29,6 +29,8 @@ 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_min_percent = 5; |
31 | 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; | ||
32 | my $output_multiline = 1; | 34 | my $output_multiline = 1; |
33 | my $output_separator = ", "; | 35 | my $output_separator = ", "; |
34 | my $scm = 0; | 36 | my $scm = 0; |
@@ -36,6 +38,7 @@ my $web = 0; | |||
36 | my $subsystem = 0; | 38 | my $subsystem = 0; |
37 | my $status = 0; | 39 | my $status = 0; |
38 | my $from_filename = 0; | 40 | my $from_filename = 0; |
41 | my $pattern_depth = 0; | ||
39 | my $version = 0; | 42 | my $version = 0; |
40 | my $help = 0; | 43 | my $help = 0; |
41 | 44 | ||
@@ -68,6 +71,8 @@ if (!GetOptions( | |||
68 | 'git-max-maintainers=i' => \$email_git_max_maintainers, | 71 | 'git-max-maintainers=i' => \$email_git_max_maintainers, |
69 | 'git-min-percent=i' => \$email_git_min_percent, | 72 | 'git-min-percent=i' => \$email_git_min_percent, |
70 | 'git-since=s' => \$email_git_since, | 73 | 'git-since=s' => \$email_git_since, |
74 | 'git-blame!' => \$email_git_blame, | ||
75 | 'remove-duplicates!' => \$email_remove_duplicates, | ||
71 | 'm!' => \$email_maintainer, | 76 | 'm!' => \$email_maintainer, |
72 | 'n!' => \$email_usename, | 77 | 'n!' => \$email_usename, |
73 | 'l!' => \$email_list, | 78 | 'l!' => \$email_list, |
@@ -78,6 +83,7 @@ if (!GetOptions( | |||
78 | 'status!' => \$status, | 83 | 'status!' => \$status, |
79 | 'scm!' => \$scm, | 84 | 'scm!' => \$scm, |
80 | 'web!' => \$web, | 85 | 'web!' => \$web, |
86 | 'pattern-depth=i' => \$pattern_depth, | ||
81 | 'f|file' => \$from_filename, | 87 | 'f|file' => \$from_filename, |
82 | 'v|version' => \$version, | 88 | 'v|version' => \$version, |
83 | 'h|help' => \$help, | 89 | 'h|help' => \$help, |
@@ -101,14 +107,19 @@ if ($#ARGV < 0) { | |||
101 | die "$P: argument missing: patchfile or -f file please\n"; | 107 | die "$P: argument missing: patchfile or -f file please\n"; |
102 | } | 108 | } |
103 | 109 | ||
110 | if ($output_separator ne ", ") { | ||
111 | $output_multiline = 0; | ||
112 | } | ||
113 | |||
104 | my $selections = $email + $scm + $status + $subsystem + $web; | 114 | my $selections = $email + $scm + $status + $subsystem + $web; |
105 | if ($selections == 0) { | 115 | if ($selections == 0) { |
106 | usage(); | 116 | usage(); |
107 | die "$P: Missing required option: email, scm, status, subsystem or web\n"; | 117 | die "$P: Missing required option: email, scm, status, subsystem or web\n"; |
108 | } | 118 | } |
109 | 119 | ||
110 | if ($email && ($email_maintainer + $email_list + $email_subscriber_list | 120 | if ($email && |
111 | + $email_git + $email_git_penguin_chiefs) == 0) { | 121 | ($email_maintainer + $email_list + $email_subscriber_list + |
122 | $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) { | ||
112 | usage(); | 123 | usage(); |
113 | die "$P: Please select at least 1 email option\n"; | 124 | die "$P: Please select at least 1 email option\n"; |
114 | } | 125 | } |
@@ -147,9 +158,36 @@ while (<MAINT>) { | |||
147 | } | 158 | } |
148 | close(MAINT); | 159 | close(MAINT); |
149 | 160 | ||
161 | my %mailmap; | ||
162 | |||
163 | if ($email_remove_duplicates) { | ||
164 | open(MAILMAP, "<${lk_path}.mailmap") || warn "$P: Can't open .mailmap\n"; | ||
165 | while (<MAILMAP>) { | ||
166 | my $line = $_; | ||
167 | |||
168 | next if ($line =~ m/^\s*#/); | ||
169 | next if ($line =~ m/^\s*$/); | ||
170 | |||
171 | my ($name, $address) = parse_email($line); | ||
172 | $line = format_email($name, $address); | ||
173 | |||
174 | next if ($line =~ m/^\s*$/); | ||
175 | |||
176 | if (exists($mailmap{$name})) { | ||
177 | my $obj = $mailmap{$name}; | ||
178 | push(@$obj, $address); | ||
179 | } else { | ||
180 | my @arr = ($address); | ||
181 | $mailmap{$name} = \@arr; | ||
182 | } | ||
183 | } | ||
184 | close(MAILMAP); | ||
185 | } | ||
186 | |||
150 | ## use the filenames on the command line or find the filenames in the patchfiles | 187 | ## use the filenames on the command line or find the filenames in the patchfiles |
151 | 188 | ||
152 | my @files = (); | 189 | my @files = (); |
190 | my @range = (); | ||
153 | 191 | ||
154 | foreach my $file (@ARGV) { | 192 | foreach my $file (@ARGV) { |
155 | ##if $file is a directory and it lacks a trailing slash, add one | 193 | ##if $file is a directory and it lacks a trailing slash, add one |
@@ -162,13 +200,19 @@ foreach my $file (@ARGV) { | |||
162 | push(@files, $file); | 200 | push(@files, $file); |
163 | } else { | 201 | } else { |
164 | my $file_cnt = @files; | 202 | my $file_cnt = @files; |
203 | my $lastfile; | ||
165 | open(PATCH, "<$file") or die "$P: Can't open ${file}\n"; | 204 | open(PATCH, "<$file") or die "$P: Can't open ${file}\n"; |
166 | while (<PATCH>) { | 205 | while (<PATCH>) { |
167 | if (m/^\+\+\+\s+(\S+)/) { | 206 | if (m/^\+\+\+\s+(\S+)/) { |
168 | my $filename = $1; | 207 | my $filename = $1; |
169 | $filename =~ s@^[^/]*/@@; | 208 | $filename =~ s@^[^/]*/@@; |
170 | $filename =~ s@\n@@; | 209 | $filename =~ s@\n@@; |
210 | $lastfile = $filename; | ||
171 | push(@files, $filename); | 211 | push(@files, $filename); |
212 | } elsif (m/^\@\@ -(\d+),(\d+)/) { | ||
213 | if ($email_git_blame) { | ||
214 | push(@range, "$lastfile:$1:$2"); | ||
215 | } | ||
172 | } | 216 | } |
173 | } | 217 | } |
174 | close(PATCH); | 218 | close(PATCH); |
@@ -201,6 +245,7 @@ foreach my $file (@files) { | |||
201 | if ($type eq 'X') { | 245 | if ($type eq 'X') { |
202 | if (file_match_pattern($file, $value)) { | 246 | if (file_match_pattern($file, $value)) { |
203 | $exclude = 1; | 247 | $exclude = 1; |
248 | last; | ||
204 | } | 249 | } |
205 | } | 250 | } |
206 | } | 251 | } |
@@ -208,35 +253,45 @@ foreach my $file (@files) { | |||
208 | 253 | ||
209 | if (!$exclude) { | 254 | if (!$exclude) { |
210 | my $tvi = 0; | 255 | my $tvi = 0; |
256 | my %hash; | ||
211 | foreach my $line (@typevalue) { | 257 | foreach my $line (@typevalue) { |
212 | if ($line =~ m/^(\C):\s*(.*)/) { | 258 | if ($line =~ m/^(\C):\s*(.*)/) { |
213 | my $type = $1; | 259 | my $type = $1; |
214 | my $value = $2; | 260 | my $value = $2; |
215 | if ($type eq 'F') { | 261 | if ($type eq 'F') { |
216 | if (file_match_pattern($file, $value)) { | 262 | if (file_match_pattern($file, $value)) { |
217 | add_categories($tvi); | 263 | my $value_pd = ($value =~ tr@/@@); |
264 | my $file_pd = ($file =~ tr@/@@); | ||
265 | $value_pd++ if (substr($value,-1,1) ne "/"); | ||
266 | if ($pattern_depth == 0 || | ||
267 | (($file_pd - $value_pd) < $pattern_depth)) { | ||
268 | $hash{$tvi} = $value_pd; | ||
269 | } | ||
218 | } | 270 | } |
219 | } | 271 | } |
220 | } | 272 | } |
221 | $tvi++; | 273 | $tvi++; |
222 | } | 274 | } |
275 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { | ||
276 | add_categories($line); | ||
277 | } | ||
223 | } | 278 | } |
224 | 279 | ||
225 | if ($email && $email_git) { | 280 | if ($email && $email_git) { |
226 | recent_git_signoffs($file); | 281 | recent_git_signoffs($file); |
227 | } | 282 | } |
228 | 283 | ||
284 | if ($email && $email_git_blame) { | ||
285 | git_assign_blame($file); | ||
286 | } | ||
229 | } | 287 | } |
230 | 288 | ||
231 | if ($email) { | 289 | if ($email) { |
232 | foreach my $chief (@penguin_chief) { | 290 | foreach my $chief (@penguin_chief) { |
233 | if ($chief =~ m/^(.*):(.*)/) { | 291 | if ($chief =~ m/^(.*):(.*)/) { |
234 | my $email_address; | 292 | my $email_address; |
235 | if ($email_usename) { | 293 | |
236 | $email_address = format_email($1, $2); | 294 | $email_address = format_email($1, $2); |
237 | } else { | ||
238 | $email_address = $2; | ||
239 | } | ||
240 | if ($email_git_penguin_chiefs) { | 295 | if ($email_git_penguin_chiefs) { |
241 | push(@email_to, $email_address); | 296 | push(@email_to, $email_address); |
242 | } else { | 297 | } else { |
@@ -258,22 +313,22 @@ if ($email || $email_list) { | |||
258 | } | 313 | } |
259 | 314 | ||
260 | if ($scm) { | 315 | if ($scm) { |
261 | @scm = sort_and_uniq(@scm); | 316 | @scm = uniq(@scm); |
262 | output(@scm); | 317 | output(@scm); |
263 | } | 318 | } |
264 | 319 | ||
265 | if ($status) { | 320 | if ($status) { |
266 | @status = sort_and_uniq(@status); | 321 | @status = uniq(@status); |
267 | output(@status); | 322 | output(@status); |
268 | } | 323 | } |
269 | 324 | ||
270 | if ($subsystem) { | 325 | if ($subsystem) { |
271 | @subsystem = sort_and_uniq(@subsystem); | 326 | @subsystem = uniq(@subsystem); |
272 | output(@subsystem); | 327 | output(@subsystem); |
273 | } | 328 | } |
274 | 329 | ||
275 | if ($web) { | 330 | if ($web) { |
276 | @web = sort_and_uniq(@web); | 331 | @web = uniq(@web); |
277 | output(@web); | 332 | output(@web); |
278 | } | 333 | } |
279 | 334 | ||
@@ -311,10 +366,12 @@ MAINTAINER field selection options: | |||
311 | --git-max-maintainers => maximum maintainers to add (default: 5) | 366 | --git-max-maintainers => maximum maintainers to add (default: 5) |
312 | --git-min-percent => minimum percentage of commits required (default: 5) | 367 | --git-min-percent => minimum percentage of commits required (default: 5) |
313 | --git-since => git history to use (default: 1-year-ago) | 368 | --git-since => git history to use (default: 1-year-ago) |
369 | --git-blame => use git blame to find modified commits for patch or file | ||
314 | --m => include maintainer(s) if any | 370 | --m => include maintainer(s) if any |
315 | --n => include name 'Full Name <addr\@domain.tld>' | 371 | --n => include name 'Full Name <addr\@domain.tld>' |
316 | --l => include list(s) if any | 372 | --l => include list(s) if any |
317 | --s => include subscriber only list(s) if any | 373 | --s => include subscriber only list(s) if any |
374 | --remove-duplicates => minimize duplicate email names/addresses | ||
318 | --scm => print SCM tree(s) if any | 375 | --scm => print SCM tree(s) if any |
319 | --status => print status if any | 376 | --status => print status if any |
320 | --subsystem => print subsystem name if any | 377 | --subsystem => print subsystem name if any |
@@ -322,24 +379,28 @@ MAINTAINER field selection options: | |||
322 | 379 | ||
323 | Output type options: | 380 | Output type options: |
324 | --separator [, ] => separator for multiple entries on 1 line | 381 | --separator [, ] => separator for multiple entries on 1 line |
382 | using --separator also sets --nomultiline if --separator is not [, ] | ||
325 | --multiline => print 1 entry per line | 383 | --multiline => print 1 entry per line |
326 | 384 | ||
327 | Default options: | ||
328 | [--email --git --m --n --l --multiline] | ||
329 | |||
330 | Other options: | 385 | Other options: |
386 | --pattern-depth => Number of pattern directory traversals (default: 0 (all)) | ||
331 | --version => show version | 387 | --version => show version |
332 | --help => show this help information | 388 | --help => show this help information |
333 | 389 | ||
390 | Default options: | ||
391 | [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates] | ||
392 | |||
334 | Notes: | 393 | Notes: |
335 | Using "-f directory" may give unexpected results: | 394 | Using "-f directory" may give unexpected results: |
336 | 395 | Used with "--git", git signators for _all_ files in and below | |
337 | Used with "--git", git signators for _all_ files in and below | 396 | directory are examined as git recurses directories. |
338 | directory are examined as git recurses directories. | 397 | Any specified X: (exclude) pattern matches are _not_ ignored. |
339 | Any specified X: (exclude) pattern matches are _not_ ignored. | 398 | Used with "--nogit", directory is used as a pattern match, |
340 | Used with "--nogit", directory is used as a pattern match, | 399 | no individual file within the directory or subdirectory |
341 | no individual file within the directory or subdirectory | 400 | is matched. |
342 | is matched. | 401 | Used with "--git-blame", does not iterate all files in directory |
402 | Using "--git-blame" is slow and may add old committers and authors | ||
403 | that are no longer active maintainers to the output. | ||
343 | EOT | 404 | EOT |
344 | } | 405 | } |
345 | 406 | ||
@@ -370,30 +431,100 @@ sub top_of_kernel_tree { | |||
370 | return 0; | 431 | return 0; |
371 | } | 432 | } |
372 | 433 | ||
373 | sub format_email { | 434 | sub parse_email { |
374 | my ($name, $email) = @_; | 435 | my ($formatted_email) = @_; |
436 | |||
437 | my $name = ""; | ||
438 | my $address = ""; | ||
439 | |||
440 | if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) { | ||
441 | $name = $1; | ||
442 | $address = $2; | ||
443 | } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) { | ||
444 | $address = $1; | ||
445 | } elsif ($formatted_email =~ /^(.+\@\S*).*$/) { | ||
446 | $address = $1; | ||
447 | } | ||
375 | 448 | ||
376 | $name =~ s/^\s+|\s+$//g; | 449 | $name =~ s/^\s+|\s+$//g; |
377 | $name =~ s/^\"|\"$//g; | 450 | $name =~ s/^\"|\"$//g; |
378 | $email =~ s/^\s+|\s+$//g; | 451 | $address =~ s/^\s+|\s+$//g; |
452 | |||
453 | if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars | ||
454 | $name =~ s/(?<!\\)"/\\"/g; ##escape quotes | ||
455 | $name = "\"$name\""; | ||
456 | } | ||
457 | |||
458 | return ($name, $address); | ||
459 | } | ||
460 | |||
461 | sub format_email { | ||
462 | my ($name, $address) = @_; | ||
379 | 463 | ||
380 | my $formatted_email = ""; | 464 | my $formatted_email; |
465 | |||
466 | $name =~ s/^\s+|\s+$//g; | ||
467 | $name =~ s/^\"|\"$//g; | ||
468 | $address =~ s/^\s+|\s+$//g; | ||
381 | 469 | ||
382 | if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars | 470 | if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars |
383 | $name =~ s/(?<!\\)"/\\"/g; ##escape quotes | 471 | $name =~ s/(?<!\\)"/\\"/g; ##escape quotes |
384 | $formatted_email = "\"${name}\"\ \<${email}\>"; | 472 | $name = "\"$name\""; |
473 | } | ||
474 | |||
475 | if ($email_usename) { | ||
476 | if ("$name" eq "") { | ||
477 | $formatted_email = "$address"; | ||
478 | } else { | ||
479 | $formatted_email = "$name <${address}>"; | ||
480 | } | ||
385 | } else { | 481 | } else { |
386 | $formatted_email = "${name} \<${email}\>"; | 482 | $formatted_email = $address; |
387 | } | 483 | } |
484 | |||
388 | return $formatted_email; | 485 | return $formatted_email; |
389 | } | 486 | } |
390 | 487 | ||
391 | sub add_categories { | 488 | sub find_starting_index { |
489 | |||
392 | my ($index) = @_; | 490 | my ($index) = @_; |
393 | 491 | ||
394 | $index = $index - 1; | 492 | while ($index > 0) { |
395 | while ($index >= 0) { | ||
396 | my $tv = $typevalue[$index]; | 493 | my $tv = $typevalue[$index]; |
494 | if (!($tv =~ m/^(\C):\s*(.*)/)) { | ||
495 | last; | ||
496 | } | ||
497 | $index--; | ||
498 | } | ||
499 | |||
500 | return $index; | ||
501 | } | ||
502 | |||
503 | sub find_ending_index { | ||
504 | my ($index) = @_; | ||
505 | |||
506 | while ($index < @typevalue) { | ||
507 | my $tv = $typevalue[$index]; | ||
508 | if (!($tv =~ m/^(\C):\s*(.*)/)) { | ||
509 | last; | ||
510 | } | ||
511 | $index++; | ||
512 | } | ||
513 | |||
514 | return $index; | ||
515 | } | ||
516 | |||
517 | sub add_categories { | ||
518 | my ($index) = @_; | ||
519 | |||
520 | my $i; | ||
521 | my $start = find_starting_index($index); | ||
522 | my $end = find_ending_index($index); | ||
523 | |||
524 | push(@subsystem, $typevalue[$start]); | ||
525 | |||
526 | for ($i = $start + 1; $i < $end; $i++) { | ||
527 | my $tv = $typevalue[$i]; | ||
397 | if ($tv =~ m/^(\C):\s*(.*)/) { | 528 | if ($tv =~ m/^(\C):\s*(.*)/) { |
398 | my $ptype = $1; | 529 | my $ptype = $1; |
399 | my $pvalue = $2; | 530 | my $pvalue = $2; |
@@ -414,19 +545,19 @@ sub add_categories { | |||
414 | } | 545 | } |
415 | } | 546 | } |
416 | } elsif ($ptype eq "M") { | 547 | } elsif ($ptype eq "M") { |
417 | my $p_used = 0; | 548 | my ($name, $address) = parse_email($pvalue); |
418 | if ($index >= 0) { | 549 | if ($name eq "") { |
419 | my $tv = $typevalue[$index - 1]; | 550 | if ($i > 0) { |
420 | if ($tv =~ m/^(\C):\s*(.*)/) { | 551 | my $tv = $typevalue[$i - 1]; |
421 | if ($1 eq "P") { | 552 | if ($tv =~ m/^(\C):\s*(.*)/) { |
422 | if ($email_usename) { | 553 | if ($1 eq "P") { |
423 | push_email_address(format_email($2, $pvalue)); | 554 | $name = $2; |
424 | $p_used = 1; | 555 | $pvalue = format_email($name, $address); |
425 | } | 556 | } |
426 | } | 557 | } |
427 | } | 558 | } |
428 | } | 559 | } |
429 | if (!$p_used) { | 560 | if ($email_maintainer) { |
430 | push_email_addresses($pvalue); | 561 | push_email_addresses($pvalue); |
431 | } | 562 | } |
432 | } elsif ($ptype eq "T") { | 563 | } elsif ($ptype eq "T") { |
@@ -436,31 +567,41 @@ sub add_categories { | |||
436 | } elsif ($ptype eq "S") { | 567 | } elsif ($ptype eq "S") { |
437 | push(@status, $pvalue); | 568 | push(@status, $pvalue); |
438 | } | 569 | } |
439 | |||
440 | $index--; | ||
441 | } else { | ||
442 | push(@subsystem,$tv); | ||
443 | $index = -1; | ||
444 | } | 570 | } |
445 | } | 571 | } |
446 | } | 572 | } |
447 | 573 | ||
574 | my %email_hash_name; | ||
575 | my %email_hash_address; | ||
576 | |||
577 | sub email_inuse { | ||
578 | my ($name, $address) = @_; | ||
579 | |||
580 | return 1 if (($name eq "") && ($address eq "")); | ||
581 | return 1 if (($name ne "") && exists($email_hash_name{$name})); | ||
582 | return 1 if (($address ne "") && exists($email_hash_address{$address})); | ||
583 | |||
584 | return 0; | ||
585 | } | ||
586 | |||
448 | sub push_email_address { | 587 | sub push_email_address { |
449 | my ($email_address) = @_; | 588 | my ($line) = @_; |
589 | |||
590 | my ($name, $address) = parse_email($line); | ||
450 | 591 | ||
451 | my $email_name = ""; | 592 | if ($address eq "") { |
452 | if ($email_address =~ m/([^<]+)<(.*\@.*)>$/) { | 593 | return 0; |
453 | $email_name = $1; | ||
454 | $email_address = $2; | ||
455 | } | 594 | } |
456 | 595 | ||
457 | if ($email_maintainer) { | 596 | if (!$email_remove_duplicates) { |
458 | if ($email_usename && $email_name) { | 597 | push(@email_to, format_email($name, $address)); |
459 | push(@email_to, format_email($email_name, $email_address)); | 598 | } elsif (!email_inuse($name, $address)) { |
460 | } else { | 599 | push(@email_to, format_email($name, $address)); |
461 | push(@email_to, $email_address); | 600 | $email_hash_name{$name}++; |
462 | } | 601 | $email_hash_address{$address}++; |
463 | } | 602 | } |
603 | |||
604 | return 1; | ||
464 | } | 605 | } |
465 | 606 | ||
466 | sub push_email_addresses { | 607 | sub push_email_addresses { |
@@ -476,7 +617,9 @@ sub push_email_addresses { | |||
476 | push_email_address($entry); | 617 | push_email_address($entry); |
477 | } | 618 | } |
478 | } else { | 619 | } else { |
479 | warn("Invalid MAINTAINERS address: '" . $address . "'\n"); | 620 | if (!push_email_address($address)) { |
621 | warn("Invalid MAINTAINERS address: '" . $address . "'\n"); | ||
622 | } | ||
480 | } | 623 | } |
481 | } | 624 | } |
482 | 625 | ||
@@ -492,6 +635,32 @@ sub which { | |||
492 | return ""; | 635 | return ""; |
493 | } | 636 | } |
494 | 637 | ||
638 | sub mailmap { | ||
639 | my @lines = @_; | ||
640 | my %hash; | ||
641 | |||
642 | foreach my $line (@lines) { | ||
643 | my ($name, $address) = parse_email($line); | ||
644 | if (!exists($hash{$name})) { | ||
645 | $hash{$name} = $address; | ||
646 | } elsif ($address ne $hash{$name}) { | ||
647 | $address = $hash{$name}; | ||
648 | $line = format_email($name, $address); | ||
649 | } | ||
650 | if (exists($mailmap{$name})) { | ||
651 | my $obj = $mailmap{$name}; | ||
652 | foreach my $map_address (@$obj) { | ||
653 | if (($map_address eq $address) && | ||
654 | ($map_address ne $hash{$name})) { | ||
655 | $line = format_email($name, $hash{$name}); | ||
656 | } | ||
657 | } | ||
658 | } | ||
659 | } | ||
660 | |||
661 | return @lines; | ||
662 | } | ||
663 | |||
495 | sub recent_git_signoffs { | 664 | sub recent_git_signoffs { |
496 | my ($file) = @_; | 665 | my ($file) = @_; |
497 | 666 | ||
@@ -500,6 +669,7 @@ sub recent_git_signoffs { | |||
500 | my $output = ""; | 669 | my $output = ""; |
501 | my $count = 0; | 670 | my $count = 0; |
502 | my @lines = (); | 671 | my @lines = (); |
672 | my %hash; | ||
503 | my $total_sign_offs; | 673 | my $total_sign_offs; |
504 | 674 | ||
505 | if (which("git") eq "") { | 675 | if (which("git") eq "") { |
@@ -513,52 +683,119 @@ sub recent_git_signoffs { | |||
513 | } | 683 | } |
514 | 684 | ||
515 | $cmd = "git log --since=${email_git_since} -- ${file}"; | 685 | $cmd = "git log --since=${email_git_since} -- ${file}"; |
516 | $cmd .= " | grep -Ei \"^[-_ a-z]+by:.*\\\@.*\$\""; | ||
517 | if (!$email_git_penguin_chiefs) { | ||
518 | $cmd .= " | grep -Ev \"${penguin_chiefs}\""; | ||
519 | } | ||
520 | $cmd .= " | cut -f2- -d\":\""; | ||
521 | $cmd .= " | sort | uniq -c | sort -rn"; | ||
522 | 686 | ||
523 | $output = `${cmd}`; | 687 | $output = `${cmd}`; |
524 | $output =~ s/^\s*//gm; | 688 | $output =~ s/^\s*//gm; |
525 | 689 | ||
526 | @lines = split("\n", $output); | 690 | @lines = split("\n", $output); |
527 | 691 | ||
528 | $total_sign_offs = 0; | 692 | @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); |
693 | if (!$email_git_penguin_chiefs) { | ||
694 | @lines = grep(!/${penguin_chiefs}/i, @lines); | ||
695 | } | ||
696 | # cut -f2- -d":" | ||
697 | s/.*:\s*(.+)\s*/$1/ for (@lines); | ||
698 | |||
699 | $total_sign_offs = @lines; | ||
700 | |||
701 | if ($email_remove_duplicates) { | ||
702 | @lines = mailmap(@lines); | ||
703 | } | ||
704 | |||
705 | @lines = sort(@lines); | ||
706 | |||
707 | # uniq -c | ||
708 | $hash{$_}++ for @lines; | ||
709 | |||
710 | # sort -rn | ||
711 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { | ||
712 | my $sign_offs = $hash{$line}; | ||
713 | $count++; | ||
714 | last if ($sign_offs < $email_git_min_signatures || | ||
715 | $count > $email_git_max_maintainers || | ||
716 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); | ||
717 | push_email_address($line); | ||
718 | } | ||
719 | } | ||
720 | |||
721 | sub save_commits { | ||
722 | my ($cmd, @commits) = @_; | ||
723 | my $output; | ||
724 | my @lines = (); | ||
725 | |||
726 | $output = `${cmd}`; | ||
727 | |||
728 | @lines = split("\n", $output); | ||
529 | foreach my $line (@lines) { | 729 | foreach my $line (@lines) { |
530 | if ($line =~ m/([0-9]+)\s+(.*)/) { | 730 | if ($line =~ m/^(\w+) /) { |
531 | $total_sign_offs += $1; | 731 | push (@commits, $1); |
532 | } else { | ||
533 | die("$P: Unexpected git output: ${line}\n"); | ||
534 | } | 732 | } |
535 | } | 733 | } |
734 | return @commits; | ||
735 | } | ||
536 | 736 | ||
537 | foreach my $line (@lines) { | 737 | sub git_assign_blame { |
538 | if ($line =~ m/([0-9]+)\s+(.*)/) { | 738 | my ($file) = @_; |
539 | my $sign_offs = $1; | 739 | |
540 | $line = $2; | 740 | my @lines = (); |
541 | $count++; | 741 | my @commits = (); |
542 | if ($sign_offs < $email_git_min_signatures || | 742 | my $cmd; |
543 | $count > $email_git_max_maintainers || | 743 | my $output; |
544 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent) { | 744 | my %hash; |
545 | last; | 745 | my $total_sign_offs; |
546 | } | 746 | my $count; |
747 | |||
748 | if (@range) { | ||
749 | foreach my $file_range_diff (@range) { | ||
750 | next if (!($file_range_diff =~ m/(.+):(.+):(.+)/)); | ||
751 | my $diff_file = $1; | ||
752 | my $diff_start = $2; | ||
753 | my $diff_length = $3; | ||
754 | next if (!("$file" eq "$diff_file")); | ||
755 | $cmd = "git blame -l -L $diff_start,+$diff_length $file"; | ||
756 | @commits = save_commits($cmd, @commits); | ||
547 | } | 757 | } |
548 | if ($line =~ m/(.+)<(.+)>/) { | 758 | } else { |
549 | my $git_name = $1; | 759 | if (-f $file) { |
550 | my $git_addr = $2; | 760 | $cmd = "git blame -l $file"; |
551 | if ($email_usename) { | 761 | @commits = save_commits($cmd, @commits); |
552 | push(@email_to, format_email($git_name, $git_addr)); | 762 | } |
553 | } else { | 763 | } |
554 | push(@email_to, $git_addr); | 764 | |
555 | } | 765 | $total_sign_offs = 0; |
556 | } elsif ($line =~ m/<(.+)>/) { | 766 | @commits = uniq(@commits); |
557 | my $git_addr = $1; | 767 | foreach my $commit (@commits) { |
558 | push(@email_to, $git_addr); | 768 | $cmd = "git log -1 ${commit}"; |
559 | } else { | 769 | |
560 | push(@email_to, $line); | 770 | $output = `${cmd}`; |
771 | $output =~ s/^\s*//gm; | ||
772 | @lines = split("\n", $output); | ||
773 | |||
774 | @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); | ||
775 | if (!$email_git_penguin_chiefs) { | ||
776 | @lines = grep(!/${penguin_chiefs}/i, @lines); | ||
777 | } | ||
778 | |||
779 | # cut -f2- -d":" | ||
780 | s/.*:\s*(.+)\s*/$1/ for (@lines); | ||
781 | |||
782 | $total_sign_offs += @lines; | ||
783 | |||
784 | if ($email_remove_duplicates) { | ||
785 | @lines = mailmap(@lines); | ||
561 | } | 786 | } |
787 | |||
788 | $hash{$_}++ for @lines; | ||
789 | } | ||
790 | |||
791 | $count = 0; | ||
792 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { | ||
793 | my $sign_offs = $hash{$line}; | ||
794 | $count++; | ||
795 | last if ($sign_offs < $email_git_min_signatures || | ||
796 | $count > $email_git_max_maintainers || | ||
797 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); | ||
798 | push_email_address($line); | ||
562 | } | 799 | } |
563 | } | 800 | } |
564 | 801 | ||