scripts/get_maintainer.pl: make --rolestats the default
[linux-2.6-block.git] / scripts / get_maintainer.pl
CommitLineData
cb7301c7
JP
1#!/usr/bin/perl -w
2# (c) 2007, Joe Perches <joe@perches.com>
3# created from checkpatch.pl
4#
5# Print selected MAINTAINERS information for
6# the files modified in a patch or for a file
7#
3bd7bf5f
RK
8# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9# perl scripts/get_maintainer.pl [OPTIONS] -f <file>
cb7301c7
JP
10#
11# Licensed under the terms of the GNU GPL License version 2
12
13use strict;
14
15my $P = $0;
7e1863af 16my $V = '0.26';
cb7301c7
JP
17
18use Getopt::Long qw(:config no_auto_abbrev);
19
20my $lk_path = "./";
21my $email = 1;
22my $email_usename = 1;
23my $email_maintainer = 1;
24my $email_list = 1;
25my $email_subscriber_list = 0;
cb7301c7 26my $email_git_penguin_chiefs = 0;
e3e9d114 27my $email_git = 0;
0fa05599 28my $email_git_all_signature_types = 0;
60db31ac 29my $email_git_blame = 0;
683c6f8f 30my $email_git_blame_signatures = 1;
e3e9d114 31my $email_git_fallback = 1;
cb7301c7
JP
32my $email_git_min_signatures = 1;
33my $email_git_max_maintainers = 5;
afa81ee1 34my $email_git_min_percent = 5;
cb7301c7 35my $email_git_since = "1-year-ago";
60db31ac 36my $email_hg_since = "-365";
dace8e30 37my $interactive = 0;
11ecf53c 38my $email_remove_duplicates = 1;
b9e2331d 39my $email_use_mailmap = 1;
cb7301c7
JP
40my $output_multiline = 1;
41my $output_separator = ", ";
3c7385b8 42my $output_roles = 0;
7e1863af 43my $output_rolestats = 1;
cb7301c7
JP
44my $scm = 0;
45my $web = 0;
46my $subsystem = 0;
47my $status = 0;
dcf36a92 48my $keywords = 1;
4b76c9da 49my $sections = 0;
03372dbb 50my $file_emails = 0;
4a7fdb5f 51my $from_filename = 0;
3fb55652 52my $pattern_depth = 0;
cb7301c7
JP
53my $version = 0;
54my $help = 0;
55
683c6f8f
JP
56my $vcs_used = 0;
57
cb7301c7
JP
58my $exit = 0;
59
683c6f8f
JP
60my %commit_author_hash;
61my %commit_signer_hash;
dace8e30 62
cb7301c7 63my @penguin_chief = ();
e4d26b02 64push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
cb7301c7 65#Andrew wants in on most everything - 2009/01/14
e4d26b02 66#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
cb7301c7
JP
67
68my @penguin_chief_names = ();
69foreach my $chief (@penguin_chief) {
70 if ($chief =~ m/^(.*):(.*)/) {
71 my $chief_name = $1;
72 my $chief_addr = $2;
73 push(@penguin_chief_names, $chief_name);
74 }
75}
e4d26b02
JP
76my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
77
78# Signature types of people who are either
79# a) responsible for the code in question, or
80# b) familiar enough with it to give relevant feedback
81my @signature_tags = ();
82push(@signature_tags, "Signed-off-by:");
83push(@signature_tags, "Reviewed-by:");
84push(@signature_tags, "Acked-by:");
cb7301c7 85
5f2441e9 86# rfc822 email address - preloaded methods go here.
1b5e1cf6 87my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
df4cc036 88my $rfc822_char = '[\\000-\\377]';
1b5e1cf6 89
60db31ac
JP
90# VCS command support: class-like functions and strings
91
92my %VCS_cmds;
93
94my %VCS_cmds_git = (
95 "execute_cmd" => \&git_execute_cmd,
96 "available" => '(which("git") ne "") && (-d ".git")',
683c6f8f
JP
97 "find_signers_cmd" =>
98 "git log --no-color --since=\$email_git_since " .
99 '--format="GitCommit: %H%n' .
100 'GitAuthor: %an <%ae>%n' .
101 'GitDate: %aD%n' .
102 'GitSubject: %s%n' .
103 '%b%n"' .
104 " -- \$file",
105 "find_commit_signers_cmd" =>
106 "git log --no-color " .
107 '--format="GitCommit: %H%n' .
108 'GitAuthor: %an <%ae>%n' .
109 'GitDate: %aD%n' .
110 'GitSubject: %s%n' .
111 '%b%n"' .
112 " -1 \$commit",
113 "find_commit_author_cmd" =>
114 "git log --no-color " .
115 '--format="GitCommit: %H%n' .
116 'GitAuthor: %an <%ae>%n' .
117 'GitDate: %aD%n' .
118 'GitSubject: %s%n"' .
119 " -1 \$commit",
60db31ac
JP
120 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
121 "blame_file_cmd" => "git blame -l \$file",
683c6f8f 122 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
dace8e30 123 "blame_commit_pattern" => "^([0-9a-f]+) ",
683c6f8f
JP
124 "author_pattern" => "^GitAuthor: (.*)",
125 "subject_pattern" => "^GitSubject: (.*)",
60db31ac
JP
126);
127
128my %VCS_cmds_hg = (
129 "execute_cmd" => \&hg_execute_cmd,
130 "available" => '(which("hg") ne "") && (-d ".hg")',
131 "find_signers_cmd" =>
683c6f8f
JP
132 "hg log --date=\$email_hg_since " .
133 "--template='HgCommit: {node}\\n" .
134 "HgAuthor: {author}\\n" .
135 "HgSubject: {desc}\\n'" .
136 " -- \$file",
137 "find_commit_signers_cmd" =>
138 "hg log " .
139 "--template='HgSubject: {desc}\\n'" .
140 " -r \$commit",
141 "find_commit_author_cmd" =>
142 "hg log " .
143 "--template='HgCommit: {node}\\n" .
144 "HgAuthor: {author}\\n" .
145 "HgSubject: {desc|firstline}\\n'" .
146 " -r \$commit",
60db31ac 147 "blame_range_cmd" => "", # not supported
683c6f8f
JP
148 "blame_file_cmd" => "hg blame -n \$file",
149 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
150 "blame_commit_pattern" => "^([ 0-9a-f]+):",
151 "author_pattern" => "^HgAuthor: (.*)",
152 "subject_pattern" => "^HgSubject: (.*)",
60db31ac
JP
153);
154
bcde44ed
JP
155my $conf = which_conf(".get_maintainer.conf");
156if (-f $conf) {
368669da 157 my @conf_args;
bcde44ed
JP
158 open(my $conffile, '<', "$conf")
159 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
160
368669da
JP
161 while (<$conffile>) {
162 my $line = $_;
163
164 $line =~ s/\s*\n?$//g;
165 $line =~ s/^\s*//g;
166 $line =~ s/\s+/ /g;
167
168 next if ($line =~ m/^\s*#/);
169 next if ($line =~ m/^\s*$/);
170
171 my @words = split(" ", $line);
172 foreach my $word (@words) {
173 last if ($word =~ m/^#/);
174 push (@conf_args, $word);
175 }
176 }
177 close($conffile);
178 unshift(@ARGV, @conf_args) if @conf_args;
179}
180
cb7301c7
JP
181if (!GetOptions(
182 'email!' => \$email,
183 'git!' => \$email_git,
e4d26b02 184 'git-all-signature-types!' => \$email_git_all_signature_types,
60db31ac 185 'git-blame!' => \$email_git_blame,
683c6f8f 186 'git-blame-signatures!' => \$email_git_blame_signatures,
e3e9d114 187 'git-fallback!' => \$email_git_fallback,
cb7301c7
JP
188 'git-chief-penguins!' => \$email_git_penguin_chiefs,
189 'git-min-signatures=i' => \$email_git_min_signatures,
190 'git-max-maintainers=i' => \$email_git_max_maintainers,
afa81ee1 191 'git-min-percent=i' => \$email_git_min_percent,
cb7301c7 192 'git-since=s' => \$email_git_since,
60db31ac 193 'hg-since=s' => \$email_hg_since,
dace8e30 194 'i|interactive!' => \$interactive,
11ecf53c 195 'remove-duplicates!' => \$email_remove_duplicates,
b9e2331d 196 'mailmap!' => \$email_use_mailmap,
cb7301c7
JP
197 'm!' => \$email_maintainer,
198 'n!' => \$email_usename,
199 'l!' => \$email_list,
200 's!' => \$email_subscriber_list,
201 'multiline!' => \$output_multiline,
3c7385b8
JP
202 'roles!' => \$output_roles,
203 'rolestats!' => \$output_rolestats,
cb7301c7
JP
204 'separator=s' => \$output_separator,
205 'subsystem!' => \$subsystem,
206 'status!' => \$status,
207 'scm!' => \$scm,
208 'web!' => \$web,
3fb55652 209 'pattern-depth=i' => \$pattern_depth,
dcf36a92 210 'k|keywords!' => \$keywords,
4b76c9da 211 'sections!' => \$sections,
03372dbb 212 'fe|file-emails!' => \$file_emails,
4a7fdb5f 213 'f|file' => \$from_filename,
cb7301c7 214 'v|version' => \$version,
64f77f31 215 'h|help|usage' => \$help,
cb7301c7 216 )) {
3c7385b8 217 die "$P: invalid argument - use --help if necessary\n";
cb7301c7
JP
218}
219
220if ($help != 0) {
221 usage();
222 exit 0;
223}
224
225if ($version != 0) {
226 print("${P} ${V}\n");
227 exit 0;
228}
229
64f77f31
JP
230if (-t STDIN && !@ARGV) {
231 # We're talking to a terminal, but have no command line arguments.
232 die "$P: missing patchfile or -f file - use --help if necessary\n";
cb7301c7
JP
233}
234
683c6f8f
JP
235$output_multiline = 0 if ($output_separator ne ", ");
236$output_rolestats = 1 if ($interactive);
237$output_roles = 1 if ($output_rolestats);
3c7385b8 238
4b76c9da
JP
239if ($sections) {
240 $email = 0;
241 $email_list = 0;
242 $scm = 0;
243 $status = 0;
244 $subsystem = 0;
245 $web = 0;
246 $keywords = 0;
6ef1c52e 247 $interactive = 0;
4b76c9da
JP
248} else {
249 my $selections = $email + $scm + $status + $subsystem + $web;
250 if ($selections == 0) {
4b76c9da
JP
251 die "$P: Missing required option: email, scm, status, subsystem or web\n";
252 }
cb7301c7
JP
253}
254
f5492666
JP
255if ($email &&
256 ($email_maintainer + $email_list + $email_subscriber_list +
257 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
cb7301c7
JP
258 die "$P: Please select at least 1 email option\n";
259}
260
261if (!top_of_kernel_tree($lk_path)) {
262 die "$P: The current directory does not appear to be "
263 . "a linux kernel source tree.\n";
264}
265
266## Read MAINTAINERS for type/value pairs
267
268my @typevalue = ();
dcf36a92
JP
269my %keyword_hash;
270
22dd5b0c
SH
271open (my $maint, '<', "${lk_path}MAINTAINERS")
272 or die "$P: Can't open MAINTAINERS: $!\n";
273while (<$maint>) {
cb7301c7
JP
274 my $line = $_;
275
276 if ($line =~ m/^(\C):\s*(.*)/) {
277 my $type = $1;
278 my $value = $2;
279
280 ##Filename pattern matching
281 if ($type eq "F" || $type eq "X") {
282 $value =~ s@\.@\\\.@g; ##Convert . to \.
283 $value =~ s/\*/\.\*/g; ##Convert * to .*
284 $value =~ s/\?/\./g; ##Convert ? to .
870020f9
JP
285 ##if pattern is a directory and it lacks a trailing slash, add one
286 if ((-d $value)) {
287 $value =~ s@([^/])$@$1/@;
288 }
dcf36a92
JP
289 } elsif ($type eq "K") {
290 $keyword_hash{@typevalue} = $value;
cb7301c7
JP
291 }
292 push(@typevalue, "$type:$value");
293 } elsif (!/^(\s)*$/) {
294 $line =~ s/\n$//g;
295 push(@typevalue, $line);
296 }
297}
22dd5b0c 298close($maint);
cb7301c7 299
8cbb3a77 300
7fa8ff2e
FM
301#
302# Read mail address map
303#
304
b9e2331d
JP
305my $mailmap;
306
307read_mailmap();
7fa8ff2e
FM
308
309sub read_mailmap {
b9e2331d 310 $mailmap = {
7fa8ff2e
FM
311 names => {},
312 addresses => {}
47abc722 313 };
7fa8ff2e 314
b9e2331d 315 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
7fa8ff2e
FM
316
317 open(my $mailmap_file, '<', "${lk_path}.mailmap")
22dd5b0c 318 or warn "$P: Can't open .mailmap: $!\n";
8cbb3a77 319
7fa8ff2e
FM
320 while (<$mailmap_file>) {
321 s/#.*$//; #strip comments
322 s/^\s+|\s+$//g; #trim
8cbb3a77 323
7fa8ff2e
FM
324 next if (/^\s*$/); #skip empty lines
325 #entries have one of the following formats:
326 # name1 <mail1>
327 # <mail1> <mail2>
328 # name1 <mail1> <mail2>
329 # name1 <mail1> name2 <mail2>
330 # (see man git-shortlog)
331 if (/^(.+)<(.+)>$/) {
47abc722
JP
332 my $real_name = $1;
333 my $address = $2;
8cbb3a77 334
47abc722 335 $real_name =~ s/\s+$//;
b9e2331d 336 ($real_name, $address) = parse_email("$real_name <$address>");
47abc722 337 $mailmap->{names}->{$address} = $real_name;
8cbb3a77 338
7fa8ff2e 339 } elsif (/^<([^\s]+)>\s*<([^\s]+)>$/) {
47abc722
JP
340 my $real_address = $1;
341 my $wrong_address = $2;
7fa8ff2e 342
47abc722 343 $mailmap->{addresses}->{$wrong_address} = $real_address;
7fa8ff2e
FM
344
345 } elsif (/^(.+)<([^\s]+)>\s*<([^\s]+)>$/) {
b9e2331d 346 my $real_name = $1;
47abc722
JP
347 my $real_address = $2;
348 my $wrong_address = $3;
7fa8ff2e 349
47abc722 350 $real_name =~ s/\s+$//;
b9e2331d
JP
351 ($real_name, $real_address) =
352 parse_email("$real_name <$real_address>");
47abc722
JP
353 $mailmap->{names}->{$wrong_address} = $real_name;
354 $mailmap->{addresses}->{$wrong_address} = $real_address;
7fa8ff2e
FM
355
356 } elsif (/^(.+)<([^\s]+)>\s*([^\s].*)<([^\s]+)>$/) {
47abc722
JP
357 my $real_name = $1;
358 my $real_address = $2;
359 my $wrong_name = $3;
360 my $wrong_address = $4;
7fa8ff2e 361
47abc722 362 $real_name =~ s/\s+$//;
b9e2331d
JP
363 ($real_name, $real_address) =
364 parse_email("$real_name <$real_address>");
365
47abc722 366 $wrong_name =~ s/\s+$//;
b9e2331d
JP
367 ($wrong_name, $wrong_address) =
368 parse_email("$wrong_name <$wrong_address>");
7fa8ff2e 369
b9e2331d
JP
370 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
371 $mailmap->{names}->{$wrong_email} = $real_name;
372 $mailmap->{addresses}->{$wrong_email} = $real_address;
11ecf53c 373 }
8cbb3a77 374 }
7fa8ff2e 375 close($mailmap_file);
8cbb3a77
JP
376}
377
4a7fdb5f 378## use the filenames on the command line or find the filenames in the patchfiles
cb7301c7
JP
379
380my @files = ();
f5492666 381my @range = ();
dcf36a92 382my @keyword_tvi = ();
03372dbb 383my @file_emails = ();
cb7301c7 384
64f77f31
JP
385if (!@ARGV) {
386 push(@ARGV, "&STDIN");
387}
388
4a7fdb5f 389foreach my $file (@ARGV) {
64f77f31
JP
390 if ($file ne "&STDIN") {
391 ##if $file is a directory and it lacks a trailing slash, add one
392 if ((-d $file)) {
393 $file =~ s@([^/])$@$1/@;
394 } elsif (!(-f $file)) {
395 die "$P: file '${file}' not found\n";
396 }
cb7301c7 397 }
4a7fdb5f
JP
398 if ($from_filename) {
399 push(@files, $file);
fab9ed12 400 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
22dd5b0c
SH
401 open(my $f, '<', $file)
402 or die "$P: Can't open $file: $!\n";
403 my $text = do { local($/) ; <$f> };
404 close($f);
03372dbb
JP
405 if ($keywords) {
406 foreach my $line (keys %keyword_hash) {
407 if ($text =~ m/$keyword_hash{$line}/x) {
408 push(@keyword_tvi, $line);
409 }
dcf36a92
JP
410 }
411 }
03372dbb
JP
412 if ($file_emails) {
413 my @poss_addr = $text =~ m$[A-Za-zÀ-ÿ\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g;
414 push(@file_emails, clean_file_emails(@poss_addr));
415 }
dcf36a92 416 }
4a7fdb5f
JP
417 } else {
418 my $file_cnt = @files;
f5492666 419 my $lastfile;
22dd5b0c 420
3a4df13d 421 open(my $patch, "< $file")
22dd5b0c
SH
422 or die "$P: Can't open $file: $!\n";
423 while (<$patch>) {
dcf36a92 424 my $patch_line = $_;
4a7fdb5f
JP
425 if (m/^\+\+\+\s+(\S+)/) {
426 my $filename = $1;
427 $filename =~ s@^[^/]*/@@;
428 $filename =~ s@\n@@;
f5492666 429 $lastfile = $filename;
4a7fdb5f 430 push(@files, $filename);
f5492666
JP
431 } elsif (m/^\@\@ -(\d+),(\d+)/) {
432 if ($email_git_blame) {
433 push(@range, "$lastfile:$1:$2");
434 }
dcf36a92
JP
435 } elsif ($keywords) {
436 foreach my $line (keys %keyword_hash) {
437 if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) {
438 push(@keyword_tvi, $line);
439 }
440 }
4a7fdb5f 441 }
cb7301c7 442 }
22dd5b0c
SH
443 close($patch);
444
4a7fdb5f 445 if ($file_cnt == @files) {
7f29fd27 446 warn "$P: file '${file}' doesn't appear to be a patch. "
4a7fdb5f
JP
447 . "Add -f to options?\n";
448 }
449 @files = sort_and_uniq(@files);
cb7301c7 450 }
cb7301c7
JP
451}
452
03372dbb
JP
453@file_emails = uniq(@file_emails);
454
683c6f8f
JP
455my %email_hash_name;
456my %email_hash_address;
cb7301c7 457my @email_to = ();
683c6f8f 458my %hash_list_to;
290603c1 459my @list_to = ();
cb7301c7
JP
460my @scm = ();
461my @web = ();
462my @subsystem = ();
463my @status = ();
b9e2331d
JP
464my %deduplicate_name_hash = ();
465my %deduplicate_address_hash = ();
683c6f8f 466my $signature_pattern;
cb7301c7 467
6ef1c52e 468my @maintainers = get_maintainers();
cb7301c7 469
6ef1c52e
JP
470if (@maintainers) {
471 @maintainers = merge_email(@maintainers);
472 output(@maintainers);
473}
683c6f8f
JP
474
475if ($scm) {
476 @scm = uniq(@scm);
477 output(@scm);
478}
479
480if ($status) {
481 @status = uniq(@status);
482 output(@status);
483}
484
485if ($subsystem) {
486 @subsystem = uniq(@subsystem);
487 output(@subsystem);
488}
489
490if ($web) {
491 @web = uniq(@web);
492 output(@web);
493}
494
495exit($exit);
496
6ef1c52e 497sub get_maintainers {
683c6f8f
JP
498 %email_hash_name = ();
499 %email_hash_address = ();
500 %commit_author_hash = ();
501 %commit_signer_hash = ();
502 @email_to = ();
503 %hash_list_to = ();
504 @list_to = ();
505 @scm = ();
506 @web = ();
507 @subsystem = ();
508 @status = ();
b9e2331d
JP
509 %deduplicate_name_hash = ();
510 %deduplicate_address_hash = ();
683c6f8f
JP
511 if ($email_git_all_signature_types) {
512 $signature_pattern = "(.+?)[Bb][Yy]:";
513 } else {
514 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
515 }
516
517 # Find responsible parties
518
b9e2331d 519 my %exact_pattern_match_hash = ();
6ef1c52e 520
683c6f8f
JP
521 foreach my $file (@files) {
522
523 my %hash;
683c6f8f
JP
524 my $tvi = find_first_section();
525 while ($tvi < @typevalue) {
526 my $start = find_starting_index($tvi);
527 my $end = find_ending_index($tvi);
528 my $exclude = 0;
529 my $i;
530
531 #Do not match excluded file patterns
272a8979 532
272a8979
JP
533 for ($i = $start; $i < $end; $i++) {
534 my $line = $typevalue[$i];
535 if ($line =~ m/^(\C):\s*(.*)/) {
536 my $type = $1;
537 my $value = $2;
683c6f8f 538 if ($type eq 'X') {
272a8979 539 if (file_match_pattern($file, $value)) {
683c6f8f
JP
540 $exclude = 1;
541 last;
542 }
543 }
544 }
545 }
546
547 if (!$exclude) {
548 for ($i = $start; $i < $end; $i++) {
549 my $line = $typevalue[$i];
550 if ($line =~ m/^(\C):\s*(.*)/) {
551 my $type = $1;
552 my $value = $2;
553 if ($type eq 'F') {
554 if (file_match_pattern($file, $value)) {
555 my $value_pd = ($value =~ tr@/@@);
556 my $file_pd = ($file =~ tr@/@@);
557 $value_pd++ if (substr($value,-1,1) ne "/");
558 $value_pd = -1 if ($value =~ /^\.\*/);
6ef1c52e
JP
559 if ($value_pd >= $file_pd) {
560 $exact_pattern_match_hash{$file} = 1;
561 }
683c6f8f
JP
562 if ($pattern_depth == 0 ||
563 (($file_pd - $value_pd) < $pattern_depth)) {
564 $hash{$tvi} = $value_pd;
565 }
272a8979
JP
566 }
567 }
568 }
569 }
570 }
683c6f8f 571 $tvi = $end + 1;
1d606b4e 572 }
272a8979 573
683c6f8f
JP
574 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
575 add_categories($line);
576 if ($sections) {
577 my $i;
578 my $start = find_starting_index($line);
579 my $end = find_ending_index($line);
580 for ($i = $start; $i < $end; $i++) {
581 my $line = $typevalue[$i];
582 if ($line =~ /^[FX]:/) { ##Restore file patterns
583 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
584 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
585 $line =~ s/\\\./\./g; ##Convert \. to .
586 $line =~ s/\.\*/\*/g; ##Convert .* to *
587 }
588 $line =~ s/^([A-Z]):/$1:\t/g;
589 print("$line\n");
4b76c9da 590 }
683c6f8f 591 print("\n");
4b76c9da 592 }
6ffd9485 593 }
dace8e30 594 }
cb7301c7 595
683c6f8f
JP
596 if ($keywords) {
597 @keyword_tvi = sort_and_uniq(@keyword_tvi);
598 foreach my $line (@keyword_tvi) {
599 add_categories($line);
600 }
dcf36a92 601 }
dcf36a92 602
b9e2331d
JP
603 foreach my $email (@email_to, @list_to) {
604 $email->[0] = deduplicate_email($email->[0]);
605 }
6ef1c52e
JP
606
607 foreach my $file (@files) {
608 if ($email &&
609 ($email_git || ($email_git_fallback &&
610 !$exact_pattern_match_hash{$file}))) {
611 vcs_file_signoffs($file);
612 }
613 if ($email && $email_git_blame) {
614 vcs_file_blame($file);
615 }
616 }
617
683c6f8f
JP
618 if ($email) {
619 foreach my $chief (@penguin_chief) {
620 if ($chief =~ m/^(.*):(.*)/) {
621 my $email_address;
0e70e83d 622
683c6f8f
JP
623 $email_address = format_email($1, $2, $email_usename);
624 if ($email_git_penguin_chiefs) {
625 push(@email_to, [$email_address, 'chief penguin']);
626 } else {
627 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
628 }
cb7301c7
JP
629 }
630 }
03372dbb 631
683c6f8f
JP
632 foreach my $email (@file_emails) {
633 my ($name, $address) = parse_email($email);
03372dbb 634
683c6f8f
JP
635 my $tmp_email = format_email($name, $address, $email_usename);
636 push_email_address($tmp_email, '');
637 add_role($tmp_email, 'in file');
638 }
03372dbb 639 }
cb7301c7 640
290603c1 641 my @to = ();
683c6f8f
JP
642 if ($email || $email_list) {
643 if ($email) {
644 @to = (@to, @email_to);
645 }
646 if ($email_list) {
647 @to = (@to, @list_to);
dace8e30 648 }
290603c1 649 }
cb7301c7 650
6ef1c52e 651 if ($interactive) {
b9e2331d 652 @to = interactive_get_maintainers(\@to);
6ef1c52e 653 }
cb7301c7 654
683c6f8f 655 return @to;
cb7301c7
JP
656}
657
cb7301c7
JP
658sub file_match_pattern {
659 my ($file, $pattern) = @_;
660 if (substr($pattern, -1) eq "/") {
661 if ($file =~ m@^$pattern@) {
662 return 1;
663 }
664 } else {
665 if ($file =~ m@^$pattern@) {
666 my $s1 = ($file =~ tr@/@@);
667 my $s2 = ($pattern =~ tr@/@@);
668 if ($s1 == $s2) {
669 return 1;
670 }
671 }
672 }
673 return 0;
674}
675
676sub usage {
677 print <<EOT;
678usage: $P [options] patchfile
870020f9 679 $P [options] -f file|directory
cb7301c7
JP
680version: $V
681
682MAINTAINER field selection options:
683 --email => print email address(es) if any
684 --git => include recent git \*-by: signers
e4d26b02 685 --git-all-signature-types => include signers regardless of signature type
683c6f8f 686 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
e3e9d114 687 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
cb7301c7 688 --git-chief-penguins => include ${penguin_chiefs}
e4d26b02
JP
689 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
690 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
691 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
f5492666 692 --git-blame => use git blame to find modified commits for patch or file
e4d26b02
JP
693 --git-since => git history to use (default: $email_git_since)
694 --hg-since => hg history to use (default: $email_hg_since)
dace8e30 695 --interactive => display a menu (mostly useful if used with the --git option)
cb7301c7
JP
696 --m => include maintainer(s) if any
697 --n => include name 'Full Name <addr\@domain.tld>'
698 --l => include list(s) if any
699 --s => include subscriber only list(s) if any
11ecf53c 700 --remove-duplicates => minimize duplicate email names/addresses
3c7385b8
JP
701 --roles => show roles (status:subsystem, git-signer, list, etc...)
702 --rolestats => show roles and statistics (commits/total_commits, %)
03372dbb 703 --file-emails => add email addresses found in -f file (default: 0 (off))
cb7301c7
JP
704 --scm => print SCM tree(s) if any
705 --status => print status if any
706 --subsystem => print subsystem name if any
707 --web => print website(s) if any
708
709Output type options:
710 --separator [, ] => separator for multiple entries on 1 line
42498316 711 using --separator also sets --nomultiline if --separator is not [, ]
cb7301c7
JP
712 --multiline => print 1 entry per line
713
cb7301c7 714Other options:
3fb55652 715 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
b9e2331d
JP
716 --keywords => scan patch for keywords (default: $keywords)
717 --sections => print all of the subsystem sections with pattern matches
718 --mailmap => use .mailmap file (default: $email_use_mailmap)
f5f5078d 719 --version => show version
cb7301c7
JP
720 --help => show this help information
721
3fb55652 722Default options:
7e1863af
JP
723 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
724 --remove-duplicates --rolestats]
3fb55652 725
870020f9
JP
726Notes:
727 Using "-f directory" may give unexpected results:
f5492666
JP
728 Used with "--git", git signators for _all_ files in and below
729 directory are examined as git recurses directories.
730 Any specified X: (exclude) pattern matches are _not_ ignored.
731 Used with "--nogit", directory is used as a pattern match,
60db31ac
JP
732 no individual file within the directory or subdirectory
733 is matched.
f5492666
JP
734 Used with "--git-blame", does not iterate all files in directory
735 Using "--git-blame" is slow and may add old committers and authors
736 that are no longer active maintainers to the output.
3c7385b8
JP
737 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
738 other automated tools that expect only ["name"] <email address>
739 may not work because of additional output after <email address>.
740 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
741 not the percentage of the entire file authored. # of commits is
742 not a good measure of amount of code authored. 1 major commit may
743 contain a thousand lines, 5 trivial commits may modify a single line.
60db31ac
JP
744 If git is not installed, but mercurial (hg) is installed and an .hg
745 repository exists, the following options apply to mercurial:
746 --git,
747 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
748 --git-blame
749 Use --hg-since not --git-since to control date selection
368669da
JP
750 File ".get_maintainer.conf", if it exists in the linux kernel source root
751 directory, can change whatever get_maintainer defaults are desired.
752 Entries in this file can be any command line argument.
753 This file is prepended to any additional command line arguments.
754 Multiple lines and # comments are allowed.
cb7301c7
JP
755EOT
756}
757
758sub top_of_kernel_tree {
47abc722 759 my ($lk_path) = @_;
cb7301c7 760
47abc722
JP
761 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
762 $lk_path .= "/";
763 }
764 if ( (-f "${lk_path}COPYING")
765 && (-f "${lk_path}CREDITS")
766 && (-f "${lk_path}Kbuild")
767 && (-f "${lk_path}MAINTAINERS")
768 && (-f "${lk_path}Makefile")
769 && (-f "${lk_path}README")
770 && (-d "${lk_path}Documentation")
771 && (-d "${lk_path}arch")
772 && (-d "${lk_path}include")
773 && (-d "${lk_path}drivers")
774 && (-d "${lk_path}fs")
775 && (-d "${lk_path}init")
776 && (-d "${lk_path}ipc")
777 && (-d "${lk_path}kernel")
778 && (-d "${lk_path}lib")
779 && (-d "${lk_path}scripts")) {
780 return 1;
781 }
782 return 0;
cb7301c7
JP
783}
784
0e70e83d
JP
785sub parse_email {
786 my ($formatted_email) = @_;
787
788 my $name = "";
789 my $address = "";
790
11ecf53c 791 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
0e70e83d
JP
792 $name = $1;
793 $address = $2;
11ecf53c 794 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
0e70e83d 795 $address = $1;
b781655a 796 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
0e70e83d
JP
797 $address = $1;
798 }
cb7301c7
JP
799
800 $name =~ s/^\s+|\s+$//g;
d789504a 801 $name =~ s/^\"|\"$//g;
0e70e83d 802 $address =~ s/^\s+|\s+$//g;
cb7301c7 803
a63ceb4c 804 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
0e70e83d
JP
805 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
806 $name = "\"$name\"";
807 }
808
809 return ($name, $address);
810}
811
812sub format_email {
a8af2430 813 my ($name, $address, $usename) = @_;
0e70e83d
JP
814
815 my $formatted_email;
816
817 $name =~ s/^\s+|\s+$//g;
818 $name =~ s/^\"|\"$//g;
819 $address =~ s/^\s+|\s+$//g;
cb7301c7 820
a63ceb4c 821 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
cb7301c7 822 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
0e70e83d
JP
823 $name = "\"$name\"";
824 }
825
a8af2430 826 if ($usename) {
0e70e83d
JP
827 if ("$name" eq "") {
828 $formatted_email = "$address";
829 } else {
a8af2430 830 $formatted_email = "$name <$address>";
0e70e83d 831 }
cb7301c7 832 } else {
0e70e83d 833 $formatted_email = $address;
cb7301c7 834 }
0e70e83d 835
cb7301c7
JP
836 return $formatted_email;
837}
838
272a8979
JP
839sub find_first_section {
840 my $index = 0;
841
842 while ($index < @typevalue) {
843 my $tv = $typevalue[$index];
844 if (($tv =~ m/^(\C):\s*(.*)/)) {
845 last;
846 }
847 $index++;
848 }
849
850 return $index;
851}
852
b781655a 853sub find_starting_index {
b781655a
JP
854 my ($index) = @_;
855
856 while ($index > 0) {
857 my $tv = $typevalue[$index];
858 if (!($tv =~ m/^(\C):\s*(.*)/)) {
859 last;
860 }
861 $index--;
862 }
863
864 return $index;
865}
866
867sub find_ending_index {
cb7301c7
JP
868 my ($index) = @_;
869
b781655a 870 while ($index < @typevalue) {
cb7301c7 871 my $tv = $typevalue[$index];
b781655a
JP
872 if (!($tv =~ m/^(\C):\s*(.*)/)) {
873 last;
874 }
875 $index++;
876 }
877
878 return $index;
879}
880
3c7385b8
JP
881sub get_maintainer_role {
882 my ($index) = @_;
883
884 my $i;
885 my $start = find_starting_index($index);
886 my $end = find_ending_index($index);
887
888 my $role;
889 my $subsystem = $typevalue[$start];
890 if (length($subsystem) > 20) {
891 $subsystem = substr($subsystem, 0, 17);
892 $subsystem =~ s/\s*$//;
893 $subsystem = $subsystem . "...";
894 }
895
896 for ($i = $start + 1; $i < $end; $i++) {
897 my $tv = $typevalue[$i];
898 if ($tv =~ m/^(\C):\s*(.*)/) {
899 my $ptype = $1;
900 my $pvalue = $2;
901 if ($ptype eq "S") {
902 $role = $pvalue;
903 }
904 }
905 }
906
907 $role = lc($role);
908 if ($role eq "supported") {
909 $role = "supporter";
910 } elsif ($role eq "maintained") {
911 $role = "maintainer";
912 } elsif ($role eq "odd fixes") {
913 $role = "odd fixer";
914 } elsif ($role eq "orphan") {
915 $role = "orphan minder";
916 } elsif ($role eq "obsolete") {
917 $role = "obsolete minder";
918 } elsif ($role eq "buried alive in reporters") {
919 $role = "chief penguin";
920 }
921
922 return $role . ":" . $subsystem;
923}
924
925sub get_list_role {
926 my ($index) = @_;
927
928 my $i;
929 my $start = find_starting_index($index);
930 my $end = find_ending_index($index);
931
932 my $subsystem = $typevalue[$start];
933 if (length($subsystem) > 20) {
934 $subsystem = substr($subsystem, 0, 17);
935 $subsystem =~ s/\s*$//;
936 $subsystem = $subsystem . "...";
937 }
938
939 if ($subsystem eq "THE REST") {
940 $subsystem = "";
941 }
942
943 return $subsystem;
944}
945
b781655a
JP
946sub add_categories {
947 my ($index) = @_;
948
949 my $i;
950 my $start = find_starting_index($index);
951 my $end = find_ending_index($index);
952
953 push(@subsystem, $typevalue[$start]);
954
955 for ($i = $start + 1; $i < $end; $i++) {
956 my $tv = $typevalue[$i];
290603c1 957 if ($tv =~ m/^(\C):\s*(.*)/) {
cb7301c7
JP
958 my $ptype = $1;
959 my $pvalue = $2;
960 if ($ptype eq "L") {
290603c1
JP
961 my $list_address = $pvalue;
962 my $list_additional = "";
3c7385b8
JP
963 my $list_role = get_list_role($i);
964
965 if ($list_role ne "") {
966 $list_role = ":" . $list_role;
967 }
290603c1
JP
968 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
969 $list_address = $1;
970 $list_additional = $2;
971 }
bdf7c685 972 if ($list_additional =~ m/subscribers-only/) {
cb7301c7 973 if ($email_subscriber_list) {
6ef1c52e
JP
974 if (!$hash_list_to{lc($list_address)}) {
975 $hash_list_to{lc($list_address)} = 1;
683c6f8f
JP
976 push(@list_to, [$list_address,
977 "subscriber list${list_role}"]);
978 }
cb7301c7
JP
979 }
980 } else {
981 if ($email_list) {
6ef1c52e
JP
982 if (!$hash_list_to{lc($list_address)}) {
983 $hash_list_to{lc($list_address)} = 1;
683c6f8f
JP
984 push(@list_to, [$list_address,
985 "open list${list_role}"]);
986 }
cb7301c7
JP
987 }
988 }
989 } elsif ($ptype eq "M") {
0e70e83d
JP
990 my ($name, $address) = parse_email($pvalue);
991 if ($name eq "") {
b781655a
JP
992 if ($i > 0) {
993 my $tv = $typevalue[$i - 1];
0e70e83d
JP
994 if ($tv =~ m/^(\C):\s*(.*)/) {
995 if ($1 eq "P") {
996 $name = $2;
a8af2430 997 $pvalue = format_email($name, $address, $email_usename);
5f2441e9
JP
998 }
999 }
1000 }
1001 }
0e70e83d 1002 if ($email_maintainer) {
3c7385b8
JP
1003 my $role = get_maintainer_role($i);
1004 push_email_addresses($pvalue, $role);
cb7301c7
JP
1005 }
1006 } elsif ($ptype eq "T") {
1007 push(@scm, $pvalue);
1008 } elsif ($ptype eq "W") {
1009 push(@web, $pvalue);
1010 } elsif ($ptype eq "S") {
1011 push(@status, $pvalue);
1012 }
cb7301c7
JP
1013 }
1014 }
1015}
1016
11ecf53c
JP
1017sub email_inuse {
1018 my ($name, $address) = @_;
1019
1020 return 1 if (($name eq "") && ($address eq ""));
6ef1c52e
JP
1021 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1022 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
0e70e83d 1023
0e70e83d
JP
1024 return 0;
1025}
1026
1b5e1cf6 1027sub push_email_address {
3c7385b8 1028 my ($line, $role) = @_;
1b5e1cf6 1029
0e70e83d 1030 my ($name, $address) = parse_email($line);
1b5e1cf6 1031
b781655a
JP
1032 if ($address eq "") {
1033 return 0;
1034 }
1035
11ecf53c 1036 if (!$email_remove_duplicates) {
a8af2430 1037 push(@email_to, [format_email($name, $address, $email_usename), $role]);
11ecf53c 1038 } elsif (!email_inuse($name, $address)) {
a8af2430 1039 push(@email_to, [format_email($name, $address, $email_usename), $role]);
fae99206 1040 $email_hash_name{lc($name)}++ if ($name ne "");
6ef1c52e 1041 $email_hash_address{lc($address)}++;
1b5e1cf6 1042 }
b781655a
JP
1043
1044 return 1;
1b5e1cf6
JP
1045}
1046
1047sub push_email_addresses {
3c7385b8 1048 my ($address, $role) = @_;
1b5e1cf6
JP
1049
1050 my @address_list = ();
1051
5f2441e9 1052 if (rfc822_valid($address)) {
3c7385b8 1053 push_email_address($address, $role);
5f2441e9 1054 } elsif (@address_list = rfc822_validlist($address)) {
1b5e1cf6
JP
1055 my $array_count = shift(@address_list);
1056 while (my $entry = shift(@address_list)) {
3c7385b8 1057 push_email_address($entry, $role);
1b5e1cf6 1058 }
5f2441e9 1059 } else {
3c7385b8 1060 if (!push_email_address($address, $role)) {
b781655a
JP
1061 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1062 }
1b5e1cf6 1063 }
1b5e1cf6
JP
1064}
1065
3c7385b8
JP
1066sub add_role {
1067 my ($line, $role) = @_;
1068
1069 my ($name, $address) = parse_email($line);
a8af2430 1070 my $email = format_email($name, $address, $email_usename);
3c7385b8
JP
1071
1072 foreach my $entry (@email_to) {
1073 if ($email_remove_duplicates) {
1074 my ($entry_name, $entry_address) = parse_email($entry->[0]);
03372dbb
JP
1075 if (($name eq $entry_name || $address eq $entry_address)
1076 && ($role eq "" || !($entry->[1] =~ m/$role/))
1077 ) {
3c7385b8
JP
1078 if ($entry->[1] eq "") {
1079 $entry->[1] = "$role";
1080 } else {
1081 $entry->[1] = "$entry->[1],$role";
1082 }
1083 }
1084 } else {
03372dbb
JP
1085 if ($email eq $entry->[0]
1086 && ($role eq "" || !($entry->[1] =~ m/$role/))
1087 ) {
3c7385b8
JP
1088 if ($entry->[1] eq "") {
1089 $entry->[1] = "$role";
1090 } else {
1091 $entry->[1] = "$entry->[1],$role";
1092 }
1093 }
1094 }
1095 }
1096}
1097
cb7301c7
JP
1098sub which {
1099 my ($bin) = @_;
1100
f5f5078d 1101 foreach my $path (split(/:/, $ENV{PATH})) {
cb7301c7
JP
1102 if (-e "$path/$bin") {
1103 return "$path/$bin";
1104 }
1105 }
1106
1107 return "";
1108}
1109
bcde44ed
JP
1110sub which_conf {
1111 my ($conf) = @_;
1112
1113 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1114 if (-e "$path/$conf") {
1115 return "$path/$conf";
1116 }
1117 }
1118
1119 return "";
1120}
1121
7fa8ff2e 1122sub mailmap_email {
b9e2331d 1123 my ($line) = @_;
7fa8ff2e 1124
47abc722
JP
1125 my ($name, $address) = parse_email($line);
1126 my $email = format_email($name, $address, 1);
1127 my $real_name = $name;
1128 my $real_address = $address;
1129
1130 if (exists $mailmap->{names}->{$email} ||
1131 exists $mailmap->{addresses}->{$email}) {
1132 if (exists $mailmap->{names}->{$email}) {
1133 $real_name = $mailmap->{names}->{$email};
1134 }
1135 if (exists $mailmap->{addresses}->{$email}) {
1136 $real_address = $mailmap->{addresses}->{$email};
1137 }
1138 } else {
1139 if (exists $mailmap->{names}->{$address}) {
1140 $real_name = $mailmap->{names}->{$address};
1141 }
1142 if (exists $mailmap->{addresses}->{$address}) {
1143 $real_address = $mailmap->{addresses}->{$address};
8cbb3a77 1144 }
47abc722
JP
1145 }
1146 return format_email($real_name, $real_address, 1);
7fa8ff2e
FM
1147}
1148
1149sub mailmap {
1150 my (@addresses) = @_;
1151
b9e2331d 1152 my @mapped_emails = ();
7fa8ff2e 1153 foreach my $line (@addresses) {
b9e2331d 1154 push(@mapped_emails, mailmap_email($line));
8cbb3a77 1155 }
b9e2331d
JP
1156 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1157 return @mapped_emails;
7fa8ff2e
FM
1158}
1159
1160sub merge_by_realname {
47abc722
JP
1161 my %address_map;
1162 my (@emails) = @_;
b9e2331d 1163
47abc722
JP
1164 foreach my $email (@emails) {
1165 my ($name, $address) = parse_email($email);
b9e2331d 1166 if (exists $address_map{$name}) {
47abc722 1167 $address = $address_map{$name};
b9e2331d
JP
1168 $email = format_email($name, $address, 1);
1169 } else {
1170 $address_map{$name} = $address;
7fa8ff2e 1171 }
47abc722 1172 }
8cbb3a77
JP
1173}
1174
60db31ac
JP
1175sub git_execute_cmd {
1176 my ($cmd) = @_;
1177 my @lines = ();
cb7301c7 1178
60db31ac
JP
1179 my $output = `$cmd`;
1180 $output =~ s/^\s*//gm;
1181 @lines = split("\n", $output);
1182
1183 return @lines;
a8af2430
JP
1184}
1185
60db31ac 1186sub hg_execute_cmd {
a8af2430 1187 my ($cmd) = @_;
60db31ac
JP
1188 my @lines = ();
1189
1190 my $output = `$cmd`;
1191 @lines = split("\n", $output);
a8af2430 1192
60db31ac
JP
1193 return @lines;
1194}
1195
683c6f8f
JP
1196sub extract_formatted_signatures {
1197 my (@signature_lines) = @_;
1198
1199 my @type = @signature_lines;
1200
1201 s/\s*(.*):.*/$1/ for (@type);
1202
1203 # cut -f2- -d":"
1204 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1205
1206## Reformat email addresses (with names) to avoid badly written signatures
1207
1208 foreach my $signer (@signature_lines) {
b9e2331d 1209 $signer = deduplicate_email($signer);
683c6f8f
JP
1210 }
1211
1212 return (\@type, \@signature_lines);
1213}
1214
60db31ac
JP
1215sub vcs_find_signers {
1216 my ($cmd) = @_;
a8af2430 1217 my $commits;
683c6f8f
JP
1218 my @lines = ();
1219 my @signatures = ();
a8af2430 1220
60db31ac 1221 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
cb7301c7 1222
60db31ac 1223 my $pattern = $VCS_cmds{"commit_pattern"};
cb7301c7 1224
60db31ac 1225 $commits = grep(/$pattern/, @lines); # of commits
afa81ee1 1226
683c6f8f 1227 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
63ab52db 1228
683c6f8f 1229 return (0, @signatures) if !@signatures;
63ab52db 1230
683c6f8f
JP
1231 save_commits_by_author(@lines) if ($interactive);
1232 save_commits_by_signer(@lines) if ($interactive);
0e70e83d 1233
683c6f8f
JP
1234 if (!$email_git_penguin_chiefs) {
1235 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
a8af2430
JP
1236 }
1237
683c6f8f
JP
1238 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1239
1240 return ($commits, @$signers_ref);
a8af2430
JP
1241}
1242
63ab52db
JP
1243sub vcs_find_author {
1244 my ($cmd) = @_;
1245 my @lines = ();
1246
1247 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1248
1249 if (!$email_git_penguin_chiefs) {
1250 @lines = grep(!/${penguin_chiefs}/i, @lines);
1251 }
1252
1253 return @lines if !@lines;
1254
683c6f8f 1255 my @authors = ();
63ab52db 1256 foreach my $line (@lines) {
683c6f8f
JP
1257 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1258 my $author = $1;
1259 my ($name, $address) = parse_email($author);
1260 $author = format_email($name, $address, 1);
1261 push(@authors, $author);
1262 }
63ab52db
JP
1263 }
1264
683c6f8f
JP
1265 save_commits_by_author(@lines) if ($interactive);
1266 save_commits_by_signer(@lines) if ($interactive);
1267
1268 return @authors;
63ab52db
JP
1269}
1270
60db31ac
JP
1271sub vcs_save_commits {
1272 my ($cmd) = @_;
1273 my @lines = ();
1274 my @commits = ();
1275
1276 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1277
1278 foreach my $line (@lines) {
1279 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1280 push(@commits, $1);
1281 }
1282 }
1283
1284 return @commits;
1285}
1286
1287sub vcs_blame {
1288 my ($file) = @_;
1289 my $cmd;
1290 my @commits = ();
1291
1292 return @commits if (!(-f $file));
1293
1294 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1295 my @all_commits = ();
1296
1297 $cmd = $VCS_cmds{"blame_file_cmd"};
1298 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1299 @all_commits = vcs_save_commits($cmd);
1300
1301 foreach my $file_range_diff (@range) {
1302 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1303 my $diff_file = $1;
1304 my $diff_start = $2;
1305 my $diff_length = $3;
1306 next if ("$file" ne "$diff_file");
1307 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1308 push(@commits, $all_commits[$i]);
1309 }
1310 }
1311 } elsif (@range) {
1312 foreach my $file_range_diff (@range) {
1313 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1314 my $diff_file = $1;
1315 my $diff_start = $2;
1316 my $diff_length = $3;
1317 next if ("$file" ne "$diff_file");
1318 $cmd = $VCS_cmds{"blame_range_cmd"};
1319 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1320 push(@commits, vcs_save_commits($cmd));
1321 }
1322 } else {
1323 $cmd = $VCS_cmds{"blame_file_cmd"};
1324 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1325 @commits = vcs_save_commits($cmd);
1326 }
1327
63ab52db
JP
1328 foreach my $commit (@commits) {
1329 $commit =~ s/^\^//g;
1330 }
1331
60db31ac
JP
1332 return @commits;
1333}
1334
1335my $printed_novcs = 0;
1336sub vcs_exists {
1337 %VCS_cmds = %VCS_cmds_git;
1338 return 1 if eval $VCS_cmds{"available"};
1339 %VCS_cmds = %VCS_cmds_hg;
683c6f8f 1340 return 2 if eval $VCS_cmds{"available"};
60db31ac
JP
1341 %VCS_cmds = ();
1342 if (!$printed_novcs) {
1343 warn("$P: No supported VCS found. Add --nogit to options?\n");
1344 warn("Using a git repository produces better results.\n");
1345 warn("Try Linus Torvalds' latest git repository using:\n");
1346 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1347 $printed_novcs = 1;
1348 }
1349 return 0;
1350}
1351
683c6f8f 1352sub vcs_is_git {
b9e2331d 1353 vcs_exists();
683c6f8f
JP
1354 return $vcs_used == 1;
1355}
1356
1357sub vcs_is_hg {
1358 return $vcs_used == 2;
1359}
1360
6ef1c52e 1361sub interactive_get_maintainers {
683c6f8f 1362 my ($list_ref) = @_;
dace8e30
FM
1363 my @list = @$list_ref;
1364
683c6f8f 1365 vcs_exists();
dace8e30
FM
1366
1367 my %selected;
683c6f8f
JP
1368 my %authored;
1369 my %signed;
dace8e30 1370 my $count = 0;
6ef1c52e 1371 my $maintained = 0;
6ef1c52e 1372 foreach my $entry (@list) {
b9e2331d
JP
1373 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1374 $selected{$count} = 1;
683c6f8f
JP
1375 $authored{$count} = 0;
1376 $signed{$count} = 0;
1377 $count++;
dace8e30
FM
1378 }
1379
1380 #menu loop
683c6f8f
JP
1381 my $done = 0;
1382 my $print_options = 0;
1383 my $redraw = 1;
1384 while (!$done) {
1385 $count = 0;
1386 if ($redraw) {
6ef1c52e
JP
1387 printf STDERR "\n%1s %2s %-65s",
1388 "*", "#", "email/list and role:stats";
1389 if ($email_git ||
1390 ($email_git_fallback && !$maintained) ||
1391 $email_git_blame) {
1392 print STDERR "auth sign";
1393 }
1394 print STDERR "\n";
683c6f8f
JP
1395 foreach my $entry (@list) {
1396 my $email = $entry->[0];
1397 my $role = $entry->[1];
1398 my $sel = "";
1399 $sel = "*" if ($selected{$count});
1400 my $commit_author = $commit_author_hash{$email};
1401 my $commit_signer = $commit_signer_hash{$email};
1402 my $authored = 0;
1403 my $signed = 0;
1404 $authored++ for (@{$commit_author});
1405 $signed++ for (@{$commit_signer});
1406 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1407 printf STDERR "%4d %4d", $authored, $signed
1408 if ($authored > 0 || $signed > 0);
1409 printf STDERR "\n %s\n", $role;
1410 if ($authored{$count}) {
1411 my $commit_author = $commit_author_hash{$email};
1412 foreach my $ref (@{$commit_author}) {
1413 print STDERR " Author: @{$ref}[1]\n";
dace8e30 1414 }
dace8e30 1415 }
683c6f8f
JP
1416 if ($signed{$count}) {
1417 my $commit_signer = $commit_signer_hash{$email};
1418 foreach my $ref (@{$commit_signer}) {
1419 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1420 }
1421 }
1422
1423 $count++;
1424 }
1425 }
1426 my $date_ref = \$email_git_since;
1427 $date_ref = \$email_hg_since if (vcs_is_hg());
1428 if ($print_options) {
1429 $print_options = 0;
1430 if (vcs_exists()) {
b9e2331d
JP
1431 print STDERR <<EOT
1432
1433Version Control options:
1434g use git history [$email_git]
1435gf use git-fallback [$email_git_fallback]
1436b use git blame [$email_git_blame]
1437bs use blame signatures [$email_git_blame_signatures]
1438c# minimum commits [$email_git_min_signatures]
1439%# min percent [$email_git_min_percent]
1440d# history to use [$$date_ref]
1441x# max maintainers [$email_git_max_maintainers]
1442t all signature types [$email_git_all_signature_types]
1443m use .mailmap [$email_use_mailmap]
1444EOT
dace8e30 1445 }
b9e2331d
JP
1446 print STDERR <<EOT
1447
1448Additional options:
14490 toggle all
1450tm toggle maintainers
1451tg toggle git entries
1452tl toggle open list entries
1453ts toggle subscriber list entries
1454f emails in file [$file_emails]
1455k keywords in file [$keywords]
1456r remove duplicates [$email_remove_duplicates]
1457p# pattern match depth [$pattern_depth]
1458EOT
dace8e30 1459 }
683c6f8f
JP
1460 print STDERR
1461"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1462
1463 my $input = <STDIN>;
dace8e30
FM
1464 chomp($input);
1465
683c6f8f
JP
1466 $redraw = 1;
1467 my $rerun = 0;
1468 my @wish = split(/[, ]+/, $input);
1469 foreach my $nr (@wish) {
1470 $nr = lc($nr);
1471 my $sel = substr($nr, 0, 1);
1472 my $str = substr($nr, 1);
1473 my $val = 0;
1474 $val = $1 if $str =~ /^(\d+)$/;
1475
1476 if ($sel eq "y") {
1477 $interactive = 0;
1478 $done = 1;
1479 $output_rolestats = 0;
1480 $output_roles = 0;
1481 last;
1482 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1483 $selected{$nr - 1} = !$selected{$nr - 1};
1484 } elsif ($sel eq "*" || $sel eq '^') {
1485 my $toggle = 0;
1486 $toggle = 1 if ($sel eq '*');
1487 for (my $i = 0; $i < $count; $i++) {
1488 $selected{$i} = $toggle;
dace8e30 1489 }
683c6f8f
JP
1490 } elsif ($sel eq "0") {
1491 for (my $i = 0; $i < $count; $i++) {
1492 $selected{$i} = !$selected{$i};
1493 }
b9e2331d
JP
1494 } elsif ($sel eq "t") {
1495 if (lc($str) eq "m") {
1496 for (my $i = 0; $i < $count; $i++) {
1497 $selected{$i} = !$selected{$i}
1498 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1499 }
1500 } elsif (lc($str) eq "g") {
1501 for (my $i = 0; $i < $count; $i++) {
1502 $selected{$i} = !$selected{$i}
1503 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1504 }
1505 } elsif (lc($str) eq "l") {
1506 for (my $i = 0; $i < $count; $i++) {
1507 $selected{$i} = !$selected{$i}
1508 if ($list[$i]->[1] =~ /^(open list)/i);
1509 }
1510 } elsif (lc($str) eq "s") {
1511 for (my $i = 0; $i < $count; $i++) {
1512 $selected{$i} = !$selected{$i}
1513 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1514 }
1515 }
683c6f8f
JP
1516 } elsif ($sel eq "a") {
1517 if ($val > 0 && $val <= $count) {
1518 $authored{$val - 1} = !$authored{$val - 1};
1519 } elsif ($str eq '*' || $str eq '^') {
1520 my $toggle = 0;
1521 $toggle = 1 if ($str eq '*');
1522 for (my $i = 0; $i < $count; $i++) {
1523 $authored{$i} = $toggle;
1524 }
1525 }
1526 } elsif ($sel eq "s") {
1527 if ($val > 0 && $val <= $count) {
1528 $signed{$val - 1} = !$signed{$val - 1};
1529 } elsif ($str eq '*' || $str eq '^') {
1530 my $toggle = 0;
1531 $toggle = 1 if ($str eq '*');
1532 for (my $i = 0; $i < $count; $i++) {
1533 $signed{$i} = $toggle;
1534 }
1535 }
1536 } elsif ($sel eq "o") {
1537 $print_options = 1;
1538 $redraw = 1;
1539 } elsif ($sel eq "g") {
1540 if ($str eq "f") {
1541 bool_invert(\$email_git_fallback);
dace8e30 1542 } else {
683c6f8f
JP
1543 bool_invert(\$email_git);
1544 }
1545 $rerun = 1;
1546 } elsif ($sel eq "b") {
1547 if ($str eq "s") {
1548 bool_invert(\$email_git_blame_signatures);
1549 } else {
1550 bool_invert(\$email_git_blame);
1551 }
1552 $rerun = 1;
1553 } elsif ($sel eq "c") {
1554 if ($val > 0) {
1555 $email_git_min_signatures = $val;
1556 $rerun = 1;
1557 }
1558 } elsif ($sel eq "x") {
1559 if ($val > 0) {
1560 $email_git_max_maintainers = $val;
1561 $rerun = 1;
1562 }
1563 } elsif ($sel eq "%") {
1564 if ($str ne "" && $val >= 0) {
1565 $email_git_min_percent = $val;
1566 $rerun = 1;
dace8e30 1567 }
683c6f8f
JP
1568 } elsif ($sel eq "d") {
1569 if (vcs_is_git()) {
1570 $email_git_since = $str;
1571 } elsif (vcs_is_hg()) {
1572 $email_hg_since = $str;
1573 }
1574 $rerun = 1;
1575 } elsif ($sel eq "t") {
1576 bool_invert(\$email_git_all_signature_types);
1577 $rerun = 1;
1578 } elsif ($sel eq "f") {
1579 bool_invert(\$file_emails);
1580 $rerun = 1;
1581 } elsif ($sel eq "r") {
1582 bool_invert(\$email_remove_duplicates);
1583 $rerun = 1;
b9e2331d
JP
1584 } elsif ($sel eq "m") {
1585 bool_invert(\$email_use_mailmap);
1586 read_mailmap();
1587 $rerun = 1;
683c6f8f
JP
1588 } elsif ($sel eq "k") {
1589 bool_invert(\$keywords);
1590 $rerun = 1;
1591 } elsif ($sel eq "p") {
1592 if ($str ne "" && $val >= 0) {
1593 $pattern_depth = $val;
1594 $rerun = 1;
1595 }
6ef1c52e
JP
1596 } elsif ($sel eq "h" || $sel eq "?") {
1597 print STDERR <<EOT
1598
1599Interactive mode allows you to select the various maintainers, submitters,
1600commit signers and mailing lists that could be CC'd on a patch.
1601
1602Any *'d entry is selected.
1603
47abc722 1604If you have git or hg installed, you can choose to summarize the commit
6ef1c52e
JP
1605history of files in the patch. Also, each line of the current file can
1606be matched to its commit author and that commits signers with blame.
1607
1608Various knobs exist to control the length of time for active commit
1609tracking, the maximum number of commit authors and signers to add,
1610and such.
1611
1612Enter selections at the prompt until you are satisfied that the selected
1613maintainers are appropriate. You may enter multiple selections separated
1614by either commas or spaces.
1615
1616EOT
683c6f8f
JP
1617 } else {
1618 print STDERR "invalid option: '$nr'\n";
1619 $redraw = 0;
1620 }
1621 }
1622 if ($rerun) {
1623 print STDERR "git-blame can be very slow, please have patience..."
1624 if ($email_git_blame);
6ef1c52e 1625 goto &get_maintainers;
683c6f8f
JP
1626 }
1627 }
dace8e30
FM
1628
1629 #drop not selected entries
1630 $count = 0;
683c6f8f
JP
1631 my @new_emailto = ();
1632 foreach my $entry (@list) {
1633 if ($selected{$count}) {
1634 push(@new_emailto, $list[$count]);
dace8e30
FM
1635 }
1636 $count++;
1637 }
683c6f8f 1638 return @new_emailto;
dace8e30
FM
1639}
1640
683c6f8f
JP
1641sub bool_invert {
1642 my ($bool_ref) = @_;
1643
1644 if ($$bool_ref) {
1645 $$bool_ref = 0;
1646 } else {
1647 $$bool_ref = 1;
1648 }
dace8e30
FM
1649}
1650
b9e2331d
JP
1651sub deduplicate_email {
1652 my ($email) = @_;
1653
1654 my $matched = 0;
1655 my ($name, $address) = parse_email($email);
1656 $email = format_email($name, $address, 1);
1657 $email = mailmap_email($email);
1658
1659 return $email if (!$email_remove_duplicates);
1660
1661 ($name, $address) = parse_email($email);
1662
fae99206 1663 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
b9e2331d
JP
1664 $name = $deduplicate_name_hash{lc($name)}->[0];
1665 $address = $deduplicate_name_hash{lc($name)}->[1];
1666 $matched = 1;
1667 } elsif ($deduplicate_address_hash{lc($address)}) {
1668 $name = $deduplicate_address_hash{lc($address)}->[0];
1669 $address = $deduplicate_address_hash{lc($address)}->[1];
1670 $matched = 1;
1671 }
1672 if (!$matched) {
1673 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1674 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1675 }
1676 $email = format_email($name, $address, 1);
1677 $email = mailmap_email($email);
1678 return $email;
1679}
1680
683c6f8f
JP
1681sub save_commits_by_author {
1682 my (@lines) = @_;
1683
1684 my @authors = ();
1685 my @commits = ();
1686 my @subjects = ();
1687
1688 foreach my $line (@lines) {
1689 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1690 my $author = $1;
b9e2331d 1691 $author = deduplicate_email($author);
683c6f8f
JP
1692 push(@authors, $author);
1693 }
1694 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1695 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1696 }
1697
1698 for (my $i = 0; $i < @authors; $i++) {
1699 my $exists = 0;
1700 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1701 if (@{$ref}[0] eq $commits[$i] &&
1702 @{$ref}[1] eq $subjects[$i]) {
1703 $exists = 1;
1704 last;
1705 }
1706 }
1707 if (!$exists) {
1708 push(@{$commit_author_hash{$authors[$i]}},
1709 [ ($commits[$i], $subjects[$i]) ]);
1710 }
dace8e30 1711 }
dace8e30
FM
1712}
1713
683c6f8f
JP
1714sub save_commits_by_signer {
1715 my (@lines) = @_;
1716
1717 my $commit = "";
1718 my $subject = "";
dace8e30 1719
683c6f8f
JP
1720 foreach my $line (@lines) {
1721 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1722 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1723 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1724 my @signatures = ($line);
1725 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1726 my @types = @$types_ref;
1727 my @signers = @$signers_ref;
1728
1729 my $type = $types[0];
1730 my $signer = $signers[0];
1731
b9e2331d 1732 $signer = deduplicate_email($signer);
6ef1c52e 1733
683c6f8f
JP
1734 my $exists = 0;
1735 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1736 if (@{$ref}[0] eq $commit &&
1737 @{$ref}[1] eq $subject &&
1738 @{$ref}[2] eq $type) {
1739 $exists = 1;
1740 last;
1741 }
1742 }
1743 if (!$exists) {
1744 push(@{$commit_signer_hash{$signer}},
1745 [ ($commit, $subject, $type) ]);
1746 }
1747 }
1748 }
dace8e30
FM
1749}
1750
60db31ac 1751sub vcs_assign {
a8af2430
JP
1752 my ($role, $divisor, @lines) = @_;
1753
1754 my %hash;
1755 my $count = 0;
1756
a8af2430
JP
1757 return if (@lines <= 0);
1758
1759 if ($divisor <= 0) {
60db31ac 1760 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
a8af2430 1761 $divisor = 1;
3c7385b8 1762 }
8cbb3a77 1763
7fa8ff2e 1764 @lines = mailmap(@lines);
0e70e83d 1765
63ab52db
JP
1766 return if (@lines <= 0);
1767
0e70e83d 1768 @lines = sort(@lines);
11ecf53c 1769
0e70e83d 1770 # uniq -c
11ecf53c
JP
1771 $hash{$_}++ for @lines;
1772
0e70e83d 1773 # sort -rn
0e70e83d 1774 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
11ecf53c 1775 my $sign_offs = $hash{$line};
a8af2430 1776 my $percent = $sign_offs * 100 / $divisor;
3c7385b8 1777
a8af2430 1778 $percent = 100 if ($percent > 100);
11ecf53c
JP
1779 $count++;
1780 last if ($sign_offs < $email_git_min_signatures ||
1781 $count > $email_git_max_maintainers ||
a8af2430 1782 $percent < $email_git_min_percent);
3c7385b8 1783 push_email_address($line, '');
3c7385b8 1784 if ($output_rolestats) {
a8af2430
JP
1785 my $fmt_percent = sprintf("%.0f", $percent);
1786 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1787 } else {
1788 add_role($line, $role);
3c7385b8 1789 }
f5492666
JP
1790 }
1791}
1792
60db31ac 1793sub vcs_file_signoffs {
a8af2430
JP
1794 my ($file) = @_;
1795
1796 my @signers = ();
60db31ac 1797 my $commits;
f5492666 1798
683c6f8f
JP
1799 $vcs_used = vcs_exists();
1800 return if (!$vcs_used);
a8af2430 1801
60db31ac
JP
1802 my $cmd = $VCS_cmds{"find_signers_cmd"};
1803 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
f5492666 1804
60db31ac 1805 ($commits, @signers) = vcs_find_signers($cmd);
b9e2331d
JP
1806
1807 foreach my $signer (@signers) {
1808 $signer = deduplicate_email($signer);
1809 }
1810
60db31ac 1811 vcs_assign("commit_signer", $commits, @signers);
f5492666
JP
1812}
1813
60db31ac 1814sub vcs_file_blame {
f5492666
JP
1815 my ($file) = @_;
1816
a8af2430 1817 my @signers = ();
63ab52db 1818 my @all_commits = ();
60db31ac 1819 my @commits = ();
a8af2430 1820 my $total_commits;
63ab52db 1821 my $total_lines;
f5492666 1822
683c6f8f
JP
1823 $vcs_used = vcs_exists();
1824 return if (!$vcs_used);
f5492666 1825
63ab52db
JP
1826 @all_commits = vcs_blame($file);
1827 @commits = uniq(@all_commits);
a8af2430 1828 $total_commits = @commits;
63ab52db 1829 $total_lines = @all_commits;
8cbb3a77 1830
683c6f8f
JP
1831 if ($email_git_blame_signatures) {
1832 if (vcs_is_hg()) {
1833 my $commit_count;
1834 my @commit_signers = ();
1835 my $commit = join(" -r ", @commits);
1836 my $cmd;
8cbb3a77 1837
683c6f8f
JP
1838 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1839 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
60db31ac 1840
683c6f8f 1841 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
63ab52db 1842
683c6f8f
JP
1843 push(@signers, @commit_signers);
1844 } else {
1845 foreach my $commit (@commits) {
1846 my $commit_count;
1847 my @commit_signers = ();
1848 my $cmd;
1849
1850 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1851 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1852
1853 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1854
1855 push(@signers, @commit_signers);
1856 }
1857 }
f5492666
JP
1858 }
1859
a8af2430 1860 if ($from_filename) {
63ab52db
JP
1861 if ($output_rolestats) {
1862 my @blame_signers;
683c6f8f
JP
1863 if (vcs_is_hg()) {{ # Double brace for last exit
1864 my $commit_count;
1865 my @commit_signers = ();
1866 @commits = uniq(@commits);
1867 @commits = sort(@commits);
1868 my $commit = join(" -r ", @commits);
1869 my $cmd;
1870
1871 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1872 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1873
1874 my @lines = ();
1875
1876 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1877
1878 if (!$email_git_penguin_chiefs) {
1879 @lines = grep(!/${penguin_chiefs}/i, @lines);
1880 }
1881
1882 last if !@lines;
1883
1884 my @authors = ();
1885 foreach my $line (@lines) {
1886 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1887 my $author = $1;
b9e2331d
JP
1888 $author = deduplicate_email($author);
1889 push(@authors, $author);
683c6f8f
JP
1890 }
1891 }
1892
1893 save_commits_by_author(@lines) if ($interactive);
1894 save_commits_by_signer(@lines) if ($interactive);
1895
1896 push(@signers, @authors);
1897 }}
1898 else {
1899 foreach my $commit (@commits) {
1900 my $i;
1901 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1902 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1903 my @author = vcs_find_author($cmd);
1904 next if !@author;
b9e2331d
JP
1905
1906 my $formatted_author = deduplicate_email($author[0]);
1907
683c6f8f
JP
1908 my $count = grep(/$commit/, @all_commits);
1909 for ($i = 0; $i < $count ; $i++) {
b9e2331d 1910 push(@blame_signers, $formatted_author);
683c6f8f 1911 }
63ab52db
JP
1912 }
1913 }
1914 if (@blame_signers) {
1915 vcs_assign("authored lines", $total_lines, @blame_signers);
1916 }
1917 }
b9e2331d
JP
1918 foreach my $signer (@signers) {
1919 $signer = deduplicate_email($signer);
1920 }
60db31ac 1921 vcs_assign("commits", $total_commits, @signers);
a8af2430 1922 } else {
b9e2331d
JP
1923 foreach my $signer (@signers) {
1924 $signer = deduplicate_email($signer);
1925 }
60db31ac 1926 vcs_assign("modified commits", $total_commits, @signers);
cb7301c7 1927 }
cb7301c7
JP
1928}
1929
1930sub uniq {
a8af2430 1931 my (@parms) = @_;
cb7301c7
JP
1932
1933 my %saw;
1934 @parms = grep(!$saw{$_}++, @parms);
1935 return @parms;
1936}
1937
1938sub sort_and_uniq {
a8af2430 1939 my (@parms) = @_;
cb7301c7
JP
1940
1941 my %saw;
1942 @parms = sort @parms;
1943 @parms = grep(!$saw{$_}++, @parms);
1944 return @parms;
1945}
1946
03372dbb
JP
1947sub clean_file_emails {
1948 my (@file_emails) = @_;
1949 my @fmt_emails = ();
1950
1951 foreach my $email (@file_emails) {
1952 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1953 my ($name, $address) = parse_email($email);
1954 if ($name eq '"[,\.]"') {
1955 $name = "";
1956 }
1957
1958 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1959 if (@nw > 2) {
1960 my $first = $nw[@nw - 3];
1961 my $middle = $nw[@nw - 2];
1962 my $last = $nw[@nw - 1];
1963
1964 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
1965 (length($first) == 2 && substr($first, -1) eq ".")) ||
1966 (length($middle) == 1 ||
1967 (length($middle) == 2 && substr($middle, -1) eq "."))) {
1968 $name = "$first $middle $last";
1969 } else {
1970 $name = "$middle $last";
1971 }
1972 }
1973
1974 if (substr($name, -1) =~ /[,\.]/) {
1975 $name = substr($name, 0, length($name) - 1);
1976 } elsif (substr($name, -2) =~ /[,\.]"/) {
1977 $name = substr($name, 0, length($name) - 2) . '"';
1978 }
1979
1980 if (substr($name, 0, 1) =~ /[,\.]/) {
1981 $name = substr($name, 1, length($name) - 1);
1982 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
1983 $name = '"' . substr($name, 2, length($name) - 2);
1984 }
1985
1986 my $fmt_email = format_email($name, $address, $email_usename);
1987 push(@fmt_emails, $fmt_email);
1988 }
1989 return @fmt_emails;
1990}
1991
3c7385b8
JP
1992sub merge_email {
1993 my @lines;
1994 my %saw;
1995
1996 for (@_) {
1997 my ($address, $role) = @$_;
1998 if (!$saw{$address}) {
1999 if ($output_roles) {
60db31ac 2000 push(@lines, "$address ($role)");
3c7385b8 2001 } else {
60db31ac 2002 push(@lines, $address);
3c7385b8
JP
2003 }
2004 $saw{$address} = 1;
2005 }
2006 }
2007
2008 return @lines;
2009}
2010
cb7301c7 2011sub output {
a8af2430 2012 my (@parms) = @_;
cb7301c7
JP
2013
2014 if ($output_multiline) {
2015 foreach my $line (@parms) {
2016 print("${line}\n");
2017 }
2018 } else {
2019 print(join($output_separator, @parms));
2020 print("\n");
2021 }
2022}
1b5e1cf6
JP
2023
2024my $rfc822re;
2025
2026sub make_rfc822re {
2027# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2028# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2029# This regexp will only work on addresses which have had comments stripped
2030# and replaced with rfc822_lwsp.
2031
2032 my $specials = '()<>@,;:\\\\".\\[\\]';
2033 my $controls = '\\000-\\037\\177';
2034
2035 my $dtext = "[^\\[\\]\\r\\\\]";
2036 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2037
2038 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2039
2040# Use zero-width assertion to spot the limit of an atom. A simple
2041# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2042 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2043 my $word = "(?:$atom|$quoted_string)";
2044 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2045
2046 my $sub_domain = "(?:$atom|$domain_literal)";
2047 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2048
2049 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2050
2051 my $phrase = "$word*";
2052 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2053 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2054 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2055
2056 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2057 my $address = "(?:$mailbox|$group)";
2058
2059 return "$rfc822_lwsp*$address";
2060}
2061
2062sub rfc822_strip_comments {
2063 my $s = shift;
2064# Recursively remove comments, and replace with a single space. The simpler
2065# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2066# chars in atoms, for example.
2067
2068 while ($s =~ s/^((?:[^"\\]|\\.)*
2069 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2070 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2071 return $s;
2072}
2073
2074# valid: returns true if the parameter is an RFC822 valid address
2075#
22dd5b0c 2076sub rfc822_valid {
1b5e1cf6
JP
2077 my $s = rfc822_strip_comments(shift);
2078
2079 if (!$rfc822re) {
2080 $rfc822re = make_rfc822re();
2081 }
2082
2083 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2084}
2085
2086# validlist: In scalar context, returns true if the parameter is an RFC822
2087# valid list of addresses.
2088#
2089# In list context, returns an empty list on failure (an invalid
2090# address was found); otherwise a list whose first element is the
2091# number of addresses found and whose remaining elements are the
2092# addresses. This is needed to disambiguate failure (invalid)
2093# from success with no addresses found, because an empty string is
2094# a valid list.
2095
22dd5b0c 2096sub rfc822_validlist {
1b5e1cf6
JP
2097 my $s = rfc822_strip_comments(shift);
2098
2099 if (!$rfc822re) {
2100 $rfc822re = make_rfc822re();
2101 }
2102 # * null list items are valid according to the RFC
2103 # * the '1' business is to aid in distinguishing failure from no results
2104
2105 my @r;
2106 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2107 $s =~ m/^$rfc822_char*$/) {
5f2441e9 2108 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
60db31ac 2109 push(@r, $1);
1b5e1cf6
JP
2110 }
2111 return wantarray ? (scalar(@r), @r) : 1;
2112 }
60db31ac 2113 return wantarray ? () : 0;
1b5e1cf6 2114}