]> bbs.cooldavid.org Git - net-next-2.6.git/blame - scripts/get_maintainer.pl
xps: Transmit Packet Steering
[net-next-2.6.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;
fae99206 16my $V = '0.26-beta6';
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
JP
42my $output_roles = 0;
43my $output_rolestats = 0;
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:
11ecf53c 723 [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates]
3fb55652 724
870020f9
JP
725Notes:
726 Using "-f directory" may give unexpected results:
f5492666
JP
727 Used with "--git", git signators for _all_ files in and below
728 directory are examined as git recurses directories.
729 Any specified X: (exclude) pattern matches are _not_ ignored.
730 Used with "--nogit", directory is used as a pattern match,
60db31ac
JP
731 no individual file within the directory or subdirectory
732 is matched.
f5492666
JP
733 Used with "--git-blame", does not iterate all files in directory
734 Using "--git-blame" is slow and may add old committers and authors
735 that are no longer active maintainers to the output.
3c7385b8
JP
736 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
737 other automated tools that expect only ["name"] <email address>
738 may not work because of additional output after <email address>.
739 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
740 not the percentage of the entire file authored. # of commits is
741 not a good measure of amount of code authored. 1 major commit may
742 contain a thousand lines, 5 trivial commits may modify a single line.
60db31ac
JP
743 If git is not installed, but mercurial (hg) is installed and an .hg
744 repository exists, the following options apply to mercurial:
745 --git,
746 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
747 --git-blame
748 Use --hg-since not --git-since to control date selection
368669da
JP
749 File ".get_maintainer.conf", if it exists in the linux kernel source root
750 directory, can change whatever get_maintainer defaults are desired.
751 Entries in this file can be any command line argument.
752 This file is prepended to any additional command line arguments.
753 Multiple lines and # comments are allowed.
cb7301c7
JP
754EOT
755}
756
757sub top_of_kernel_tree {
47abc722 758 my ($lk_path) = @_;
cb7301c7 759
47abc722
JP
760 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
761 $lk_path .= "/";
762 }
763 if ( (-f "${lk_path}COPYING")
764 && (-f "${lk_path}CREDITS")
765 && (-f "${lk_path}Kbuild")
766 && (-f "${lk_path}MAINTAINERS")
767 && (-f "${lk_path}Makefile")
768 && (-f "${lk_path}README")
769 && (-d "${lk_path}Documentation")
770 && (-d "${lk_path}arch")
771 && (-d "${lk_path}include")
772 && (-d "${lk_path}drivers")
773 && (-d "${lk_path}fs")
774 && (-d "${lk_path}init")
775 && (-d "${lk_path}ipc")
776 && (-d "${lk_path}kernel")
777 && (-d "${lk_path}lib")
778 && (-d "${lk_path}scripts")) {
779 return 1;
780 }
781 return 0;
cb7301c7
JP
782}
783
0e70e83d
JP
784sub parse_email {
785 my ($formatted_email) = @_;
786
787 my $name = "";
788 my $address = "";
789
11ecf53c 790 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
0e70e83d
JP
791 $name = $1;
792 $address = $2;
11ecf53c 793 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
0e70e83d 794 $address = $1;
b781655a 795 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
0e70e83d
JP
796 $address = $1;
797 }
cb7301c7
JP
798
799 $name =~ s/^\s+|\s+$//g;
d789504a 800 $name =~ s/^\"|\"$//g;
0e70e83d 801 $address =~ s/^\s+|\s+$//g;
cb7301c7 802
a63ceb4c 803 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
0e70e83d
JP
804 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
805 $name = "\"$name\"";
806 }
807
808 return ($name, $address);
809}
810
811sub format_email {
a8af2430 812 my ($name, $address, $usename) = @_;
0e70e83d
JP
813
814 my $formatted_email;
815
816 $name =~ s/^\s+|\s+$//g;
817 $name =~ s/^\"|\"$//g;
818 $address =~ s/^\s+|\s+$//g;
cb7301c7 819
a63ceb4c 820 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
cb7301c7 821 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
0e70e83d
JP
822 $name = "\"$name\"";
823 }
824
a8af2430 825 if ($usename) {
0e70e83d
JP
826 if ("$name" eq "") {
827 $formatted_email = "$address";
828 } else {
a8af2430 829 $formatted_email = "$name <$address>";
0e70e83d 830 }
cb7301c7 831 } else {
0e70e83d 832 $formatted_email = $address;
cb7301c7 833 }
0e70e83d 834
cb7301c7
JP
835 return $formatted_email;
836}
837
272a8979
JP
838sub find_first_section {
839 my $index = 0;
840
841 while ($index < @typevalue) {
842 my $tv = $typevalue[$index];
843 if (($tv =~ m/^(\C):\s*(.*)/)) {
844 last;
845 }
846 $index++;
847 }
848
849 return $index;
850}
851
b781655a 852sub find_starting_index {
b781655a
JP
853 my ($index) = @_;
854
855 while ($index > 0) {
856 my $tv = $typevalue[$index];
857 if (!($tv =~ m/^(\C):\s*(.*)/)) {
858 last;
859 }
860 $index--;
861 }
862
863 return $index;
864}
865
866sub find_ending_index {
cb7301c7
JP
867 my ($index) = @_;
868
b781655a 869 while ($index < @typevalue) {
cb7301c7 870 my $tv = $typevalue[$index];
b781655a
JP
871 if (!($tv =~ m/^(\C):\s*(.*)/)) {
872 last;
873 }
874 $index++;
875 }
876
877 return $index;
878}
879
3c7385b8
JP
880sub get_maintainer_role {
881 my ($index) = @_;
882
883 my $i;
884 my $start = find_starting_index($index);
885 my $end = find_ending_index($index);
886
887 my $role;
888 my $subsystem = $typevalue[$start];
889 if (length($subsystem) > 20) {
890 $subsystem = substr($subsystem, 0, 17);
891 $subsystem =~ s/\s*$//;
892 $subsystem = $subsystem . "...";
893 }
894
895 for ($i = $start + 1; $i < $end; $i++) {
896 my $tv = $typevalue[$i];
897 if ($tv =~ m/^(\C):\s*(.*)/) {
898 my $ptype = $1;
899 my $pvalue = $2;
900 if ($ptype eq "S") {
901 $role = $pvalue;
902 }
903 }
904 }
905
906 $role = lc($role);
907 if ($role eq "supported") {
908 $role = "supporter";
909 } elsif ($role eq "maintained") {
910 $role = "maintainer";
911 } elsif ($role eq "odd fixes") {
912 $role = "odd fixer";
913 } elsif ($role eq "orphan") {
914 $role = "orphan minder";
915 } elsif ($role eq "obsolete") {
916 $role = "obsolete minder";
917 } elsif ($role eq "buried alive in reporters") {
918 $role = "chief penguin";
919 }
920
921 return $role . ":" . $subsystem;
922}
923
924sub get_list_role {
925 my ($index) = @_;
926
927 my $i;
928 my $start = find_starting_index($index);
929 my $end = find_ending_index($index);
930
931 my $subsystem = $typevalue[$start];
932 if (length($subsystem) > 20) {
933 $subsystem = substr($subsystem, 0, 17);
934 $subsystem =~ s/\s*$//;
935 $subsystem = $subsystem . "...";
936 }
937
938 if ($subsystem eq "THE REST") {
939 $subsystem = "";
940 }
941
942 return $subsystem;
943}
944
b781655a
JP
945sub add_categories {
946 my ($index) = @_;
947
948 my $i;
949 my $start = find_starting_index($index);
950 my $end = find_ending_index($index);
951
952 push(@subsystem, $typevalue[$start]);
953
954 for ($i = $start + 1; $i < $end; $i++) {
955 my $tv = $typevalue[$i];
290603c1 956 if ($tv =~ m/^(\C):\s*(.*)/) {
cb7301c7
JP
957 my $ptype = $1;
958 my $pvalue = $2;
959 if ($ptype eq "L") {
290603c1
JP
960 my $list_address = $pvalue;
961 my $list_additional = "";
3c7385b8
JP
962 my $list_role = get_list_role($i);
963
964 if ($list_role ne "") {
965 $list_role = ":" . $list_role;
966 }
290603c1
JP
967 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
968 $list_address = $1;
969 $list_additional = $2;
970 }
bdf7c685 971 if ($list_additional =~ m/subscribers-only/) {
cb7301c7 972 if ($email_subscriber_list) {
6ef1c52e
JP
973 if (!$hash_list_to{lc($list_address)}) {
974 $hash_list_to{lc($list_address)} = 1;
683c6f8f
JP
975 push(@list_to, [$list_address,
976 "subscriber list${list_role}"]);
977 }
cb7301c7
JP
978 }
979 } else {
980 if ($email_list) {
6ef1c52e
JP
981 if (!$hash_list_to{lc($list_address)}) {
982 $hash_list_to{lc($list_address)} = 1;
683c6f8f
JP
983 push(@list_to, [$list_address,
984 "open list${list_role}"]);
985 }
cb7301c7
JP
986 }
987 }
988 } elsif ($ptype eq "M") {
0e70e83d
JP
989 my ($name, $address) = parse_email($pvalue);
990 if ($name eq "") {
b781655a
JP
991 if ($i > 0) {
992 my $tv = $typevalue[$i - 1];
0e70e83d
JP
993 if ($tv =~ m/^(\C):\s*(.*)/) {
994 if ($1 eq "P") {
995 $name = $2;
a8af2430 996 $pvalue = format_email($name, $address, $email_usename);
5f2441e9
JP
997 }
998 }
999 }
1000 }
0e70e83d 1001 if ($email_maintainer) {
3c7385b8
JP
1002 my $role = get_maintainer_role($i);
1003 push_email_addresses($pvalue, $role);
cb7301c7
JP
1004 }
1005 } elsif ($ptype eq "T") {
1006 push(@scm, $pvalue);
1007 } elsif ($ptype eq "W") {
1008 push(@web, $pvalue);
1009 } elsif ($ptype eq "S") {
1010 push(@status, $pvalue);
1011 }
cb7301c7
JP
1012 }
1013 }
1014}
1015
11ecf53c
JP
1016sub email_inuse {
1017 my ($name, $address) = @_;
1018
1019 return 1 if (($name eq "") && ($address eq ""));
6ef1c52e
JP
1020 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1021 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
0e70e83d 1022
0e70e83d
JP
1023 return 0;
1024}
1025
1b5e1cf6 1026sub push_email_address {
3c7385b8 1027 my ($line, $role) = @_;
1b5e1cf6 1028
0e70e83d 1029 my ($name, $address) = parse_email($line);
1b5e1cf6 1030
b781655a
JP
1031 if ($address eq "") {
1032 return 0;
1033 }
1034
11ecf53c 1035 if (!$email_remove_duplicates) {
a8af2430 1036 push(@email_to, [format_email($name, $address, $email_usename), $role]);
11ecf53c 1037 } elsif (!email_inuse($name, $address)) {
a8af2430 1038 push(@email_to, [format_email($name, $address, $email_usename), $role]);
fae99206 1039 $email_hash_name{lc($name)}++ if ($name ne "");
6ef1c52e 1040 $email_hash_address{lc($address)}++;
1b5e1cf6 1041 }
b781655a
JP
1042
1043 return 1;
1b5e1cf6
JP
1044}
1045
1046sub push_email_addresses {
3c7385b8 1047 my ($address, $role) = @_;
1b5e1cf6
JP
1048
1049 my @address_list = ();
1050
5f2441e9 1051 if (rfc822_valid($address)) {
3c7385b8 1052 push_email_address($address, $role);
5f2441e9 1053 } elsif (@address_list = rfc822_validlist($address)) {
1b5e1cf6
JP
1054 my $array_count = shift(@address_list);
1055 while (my $entry = shift(@address_list)) {
3c7385b8 1056 push_email_address($entry, $role);
1b5e1cf6 1057 }
5f2441e9 1058 } else {
3c7385b8 1059 if (!push_email_address($address, $role)) {
b781655a
JP
1060 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1061 }
1b5e1cf6 1062 }
1b5e1cf6
JP
1063}
1064
3c7385b8
JP
1065sub add_role {
1066 my ($line, $role) = @_;
1067
1068 my ($name, $address) = parse_email($line);
a8af2430 1069 my $email = format_email($name, $address, $email_usename);
3c7385b8
JP
1070
1071 foreach my $entry (@email_to) {
1072 if ($email_remove_duplicates) {
1073 my ($entry_name, $entry_address) = parse_email($entry->[0]);
03372dbb
JP
1074 if (($name eq $entry_name || $address eq $entry_address)
1075 && ($role eq "" || !($entry->[1] =~ m/$role/))
1076 ) {
3c7385b8
JP
1077 if ($entry->[1] eq "") {
1078 $entry->[1] = "$role";
1079 } else {
1080 $entry->[1] = "$entry->[1],$role";
1081 }
1082 }
1083 } else {
03372dbb
JP
1084 if ($email eq $entry->[0]
1085 && ($role eq "" || !($entry->[1] =~ m/$role/))
1086 ) {
3c7385b8
JP
1087 if ($entry->[1] eq "") {
1088 $entry->[1] = "$role";
1089 } else {
1090 $entry->[1] = "$entry->[1],$role";
1091 }
1092 }
1093 }
1094 }
1095}
1096
cb7301c7
JP
1097sub which {
1098 my ($bin) = @_;
1099
f5f5078d 1100 foreach my $path (split(/:/, $ENV{PATH})) {
cb7301c7
JP
1101 if (-e "$path/$bin") {
1102 return "$path/$bin";
1103 }
1104 }
1105
1106 return "";
1107}
1108
bcde44ed
JP
1109sub which_conf {
1110 my ($conf) = @_;
1111
1112 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1113 if (-e "$path/$conf") {
1114 return "$path/$conf";
1115 }
1116 }
1117
1118 return "";
1119}
1120
7fa8ff2e 1121sub mailmap_email {
b9e2331d 1122 my ($line) = @_;
7fa8ff2e 1123
47abc722
JP
1124 my ($name, $address) = parse_email($line);
1125 my $email = format_email($name, $address, 1);
1126 my $real_name = $name;
1127 my $real_address = $address;
1128
1129 if (exists $mailmap->{names}->{$email} ||
1130 exists $mailmap->{addresses}->{$email}) {
1131 if (exists $mailmap->{names}->{$email}) {
1132 $real_name = $mailmap->{names}->{$email};
1133 }
1134 if (exists $mailmap->{addresses}->{$email}) {
1135 $real_address = $mailmap->{addresses}->{$email};
1136 }
1137 } else {
1138 if (exists $mailmap->{names}->{$address}) {
1139 $real_name = $mailmap->{names}->{$address};
1140 }
1141 if (exists $mailmap->{addresses}->{$address}) {
1142 $real_address = $mailmap->{addresses}->{$address};
8cbb3a77 1143 }
47abc722
JP
1144 }
1145 return format_email($real_name, $real_address, 1);
7fa8ff2e
FM
1146}
1147
1148sub mailmap {
1149 my (@addresses) = @_;
1150
b9e2331d 1151 my @mapped_emails = ();
7fa8ff2e 1152 foreach my $line (@addresses) {
b9e2331d 1153 push(@mapped_emails, mailmap_email($line));
8cbb3a77 1154 }
b9e2331d
JP
1155 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1156 return @mapped_emails;
7fa8ff2e
FM
1157}
1158
1159sub merge_by_realname {
47abc722
JP
1160 my %address_map;
1161 my (@emails) = @_;
b9e2331d 1162
47abc722
JP
1163 foreach my $email (@emails) {
1164 my ($name, $address) = parse_email($email);
b9e2331d 1165 if (exists $address_map{$name}) {
47abc722 1166 $address = $address_map{$name};
b9e2331d
JP
1167 $email = format_email($name, $address, 1);
1168 } else {
1169 $address_map{$name} = $address;
7fa8ff2e 1170 }
47abc722 1171 }
8cbb3a77
JP
1172}
1173
60db31ac
JP
1174sub git_execute_cmd {
1175 my ($cmd) = @_;
1176 my @lines = ();
cb7301c7 1177
60db31ac
JP
1178 my $output = `$cmd`;
1179 $output =~ s/^\s*//gm;
1180 @lines = split("\n", $output);
1181
1182 return @lines;
a8af2430
JP
1183}
1184
60db31ac 1185sub hg_execute_cmd {
a8af2430 1186 my ($cmd) = @_;
60db31ac
JP
1187 my @lines = ();
1188
1189 my $output = `$cmd`;
1190 @lines = split("\n", $output);
a8af2430 1191
60db31ac
JP
1192 return @lines;
1193}
1194
683c6f8f
JP
1195sub extract_formatted_signatures {
1196 my (@signature_lines) = @_;
1197
1198 my @type = @signature_lines;
1199
1200 s/\s*(.*):.*/$1/ for (@type);
1201
1202 # cut -f2- -d":"
1203 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1204
1205## Reformat email addresses (with names) to avoid badly written signatures
1206
1207 foreach my $signer (@signature_lines) {
b9e2331d 1208 $signer = deduplicate_email($signer);
683c6f8f
JP
1209 }
1210
1211 return (\@type, \@signature_lines);
1212}
1213
60db31ac
JP
1214sub vcs_find_signers {
1215 my ($cmd) = @_;
a8af2430 1216 my $commits;
683c6f8f
JP
1217 my @lines = ();
1218 my @signatures = ();
a8af2430 1219
60db31ac 1220 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
cb7301c7 1221
60db31ac 1222 my $pattern = $VCS_cmds{"commit_pattern"};
cb7301c7 1223
60db31ac 1224 $commits = grep(/$pattern/, @lines); # of commits
afa81ee1 1225
683c6f8f 1226 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
63ab52db 1227
683c6f8f 1228 return (0, @signatures) if !@signatures;
63ab52db 1229
683c6f8f
JP
1230 save_commits_by_author(@lines) if ($interactive);
1231 save_commits_by_signer(@lines) if ($interactive);
0e70e83d 1232
683c6f8f
JP
1233 if (!$email_git_penguin_chiefs) {
1234 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
a8af2430
JP
1235 }
1236
683c6f8f
JP
1237 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1238
1239 return ($commits, @$signers_ref);
a8af2430
JP
1240}
1241
63ab52db
JP
1242sub vcs_find_author {
1243 my ($cmd) = @_;
1244 my @lines = ();
1245
1246 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1247
1248 if (!$email_git_penguin_chiefs) {
1249 @lines = grep(!/${penguin_chiefs}/i, @lines);
1250 }
1251
1252 return @lines if !@lines;
1253
683c6f8f 1254 my @authors = ();
63ab52db 1255 foreach my $line (@lines) {
683c6f8f
JP
1256 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1257 my $author = $1;
1258 my ($name, $address) = parse_email($author);
1259 $author = format_email($name, $address, 1);
1260 push(@authors, $author);
1261 }
63ab52db
JP
1262 }
1263
683c6f8f
JP
1264 save_commits_by_author(@lines) if ($interactive);
1265 save_commits_by_signer(@lines) if ($interactive);
1266
1267 return @authors;
63ab52db
JP
1268}
1269
60db31ac
JP
1270sub vcs_save_commits {
1271 my ($cmd) = @_;
1272 my @lines = ();
1273 my @commits = ();
1274
1275 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1276
1277 foreach my $line (@lines) {
1278 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1279 push(@commits, $1);
1280 }
1281 }
1282
1283 return @commits;
1284}
1285
1286sub vcs_blame {
1287 my ($file) = @_;
1288 my $cmd;
1289 my @commits = ();
1290
1291 return @commits if (!(-f $file));
1292
1293 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1294 my @all_commits = ();
1295
1296 $cmd = $VCS_cmds{"blame_file_cmd"};
1297 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1298 @all_commits = vcs_save_commits($cmd);
1299
1300 foreach my $file_range_diff (@range) {
1301 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1302 my $diff_file = $1;
1303 my $diff_start = $2;
1304 my $diff_length = $3;
1305 next if ("$file" ne "$diff_file");
1306 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1307 push(@commits, $all_commits[$i]);
1308 }
1309 }
1310 } elsif (@range) {
1311 foreach my $file_range_diff (@range) {
1312 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1313 my $diff_file = $1;
1314 my $diff_start = $2;
1315 my $diff_length = $3;
1316 next if ("$file" ne "$diff_file");
1317 $cmd = $VCS_cmds{"blame_range_cmd"};
1318 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1319 push(@commits, vcs_save_commits($cmd));
1320 }
1321 } else {
1322 $cmd = $VCS_cmds{"blame_file_cmd"};
1323 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1324 @commits = vcs_save_commits($cmd);
1325 }
1326
63ab52db
JP
1327 foreach my $commit (@commits) {
1328 $commit =~ s/^\^//g;
1329 }
1330
60db31ac
JP
1331 return @commits;
1332}
1333
1334my $printed_novcs = 0;
1335sub vcs_exists {
1336 %VCS_cmds = %VCS_cmds_git;
1337 return 1 if eval $VCS_cmds{"available"};
1338 %VCS_cmds = %VCS_cmds_hg;
683c6f8f 1339 return 2 if eval $VCS_cmds{"available"};
60db31ac
JP
1340 %VCS_cmds = ();
1341 if (!$printed_novcs) {
1342 warn("$P: No supported VCS found. Add --nogit to options?\n");
1343 warn("Using a git repository produces better results.\n");
1344 warn("Try Linus Torvalds' latest git repository using:\n");
1345 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1346 $printed_novcs = 1;
1347 }
1348 return 0;
1349}
1350
683c6f8f 1351sub vcs_is_git {
b9e2331d 1352 vcs_exists();
683c6f8f
JP
1353 return $vcs_used == 1;
1354}
1355
1356sub vcs_is_hg {
1357 return $vcs_used == 2;
1358}
1359
6ef1c52e 1360sub interactive_get_maintainers {
683c6f8f 1361 my ($list_ref) = @_;
dace8e30
FM
1362 my @list = @$list_ref;
1363
683c6f8f 1364 vcs_exists();
dace8e30
FM
1365
1366 my %selected;
683c6f8f
JP
1367 my %authored;
1368 my %signed;
dace8e30 1369 my $count = 0;
6ef1c52e 1370 my $maintained = 0;
6ef1c52e 1371 foreach my $entry (@list) {
b9e2331d
JP
1372 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1373 $selected{$count} = 1;
683c6f8f
JP
1374 $authored{$count} = 0;
1375 $signed{$count} = 0;
1376 $count++;
dace8e30
FM
1377 }
1378
1379 #menu loop
683c6f8f
JP
1380 my $done = 0;
1381 my $print_options = 0;
1382 my $redraw = 1;
1383 while (!$done) {
1384 $count = 0;
1385 if ($redraw) {
6ef1c52e
JP
1386 printf STDERR "\n%1s %2s %-65s",
1387 "*", "#", "email/list and role:stats";
1388 if ($email_git ||
1389 ($email_git_fallback && !$maintained) ||
1390 $email_git_blame) {
1391 print STDERR "auth sign";
1392 }
1393 print STDERR "\n";
683c6f8f
JP
1394 foreach my $entry (@list) {
1395 my $email = $entry->[0];
1396 my $role = $entry->[1];
1397 my $sel = "";
1398 $sel = "*" if ($selected{$count});
1399 my $commit_author = $commit_author_hash{$email};
1400 my $commit_signer = $commit_signer_hash{$email};
1401 my $authored = 0;
1402 my $signed = 0;
1403 $authored++ for (@{$commit_author});
1404 $signed++ for (@{$commit_signer});
1405 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1406 printf STDERR "%4d %4d", $authored, $signed
1407 if ($authored > 0 || $signed > 0);
1408 printf STDERR "\n %s\n", $role;
1409 if ($authored{$count}) {
1410 my $commit_author = $commit_author_hash{$email};
1411 foreach my $ref (@{$commit_author}) {
1412 print STDERR " Author: @{$ref}[1]\n";
dace8e30 1413 }
dace8e30 1414 }
683c6f8f
JP
1415 if ($signed{$count}) {
1416 my $commit_signer = $commit_signer_hash{$email};
1417 foreach my $ref (@{$commit_signer}) {
1418 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1419 }
1420 }
1421
1422 $count++;
1423 }
1424 }
1425 my $date_ref = \$email_git_since;
1426 $date_ref = \$email_hg_since if (vcs_is_hg());
1427 if ($print_options) {
1428 $print_options = 0;
1429 if (vcs_exists()) {
b9e2331d
JP
1430 print STDERR <<EOT
1431
1432Version Control options:
1433g use git history [$email_git]
1434gf use git-fallback [$email_git_fallback]
1435b use git blame [$email_git_blame]
1436bs use blame signatures [$email_git_blame_signatures]
1437c# minimum commits [$email_git_min_signatures]
1438%# min percent [$email_git_min_percent]
1439d# history to use [$$date_ref]
1440x# max maintainers [$email_git_max_maintainers]
1441t all signature types [$email_git_all_signature_types]
1442m use .mailmap [$email_use_mailmap]
1443EOT
dace8e30 1444 }
b9e2331d
JP
1445 print STDERR <<EOT
1446
1447Additional options:
14480 toggle all
1449tm toggle maintainers
1450tg toggle git entries
1451tl toggle open list entries
1452ts toggle subscriber list entries
1453f emails in file [$file_emails]
1454k keywords in file [$keywords]
1455r remove duplicates [$email_remove_duplicates]
1456p# pattern match depth [$pattern_depth]
1457EOT
dace8e30 1458 }
683c6f8f
JP
1459 print STDERR
1460"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1461
1462 my $input = <STDIN>;
dace8e30
FM
1463 chomp($input);
1464
683c6f8f
JP
1465 $redraw = 1;
1466 my $rerun = 0;
1467 my @wish = split(/[, ]+/, $input);
1468 foreach my $nr (@wish) {
1469 $nr = lc($nr);
1470 my $sel = substr($nr, 0, 1);
1471 my $str = substr($nr, 1);
1472 my $val = 0;
1473 $val = $1 if $str =~ /^(\d+)$/;
1474
1475 if ($sel eq "y") {
1476 $interactive = 0;
1477 $done = 1;
1478 $output_rolestats = 0;
1479 $output_roles = 0;
1480 last;
1481 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1482 $selected{$nr - 1} = !$selected{$nr - 1};
1483 } elsif ($sel eq "*" || $sel eq '^') {
1484 my $toggle = 0;
1485 $toggle = 1 if ($sel eq '*');
1486 for (my $i = 0; $i < $count; $i++) {
1487 $selected{$i} = $toggle;
dace8e30 1488 }
683c6f8f
JP
1489 } elsif ($sel eq "0") {
1490 for (my $i = 0; $i < $count; $i++) {
1491 $selected{$i} = !$selected{$i};
1492 }
b9e2331d
JP
1493 } elsif ($sel eq "t") {
1494 if (lc($str) eq "m") {
1495 for (my $i = 0; $i < $count; $i++) {
1496 $selected{$i} = !$selected{$i}
1497 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1498 }
1499 } elsif (lc($str) eq "g") {
1500 for (my $i = 0; $i < $count; $i++) {
1501 $selected{$i} = !$selected{$i}
1502 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1503 }
1504 } elsif (lc($str) eq "l") {
1505 for (my $i = 0; $i < $count; $i++) {
1506 $selected{$i} = !$selected{$i}
1507 if ($list[$i]->[1] =~ /^(open list)/i);
1508 }
1509 } elsif (lc($str) eq "s") {
1510 for (my $i = 0; $i < $count; $i++) {
1511 $selected{$i} = !$selected{$i}
1512 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1513 }
1514 }
683c6f8f
JP
1515 } elsif ($sel eq "a") {
1516 if ($val > 0 && $val <= $count) {
1517 $authored{$val - 1} = !$authored{$val - 1};
1518 } elsif ($str eq '*' || $str eq '^') {
1519 my $toggle = 0;
1520 $toggle = 1 if ($str eq '*');
1521 for (my $i = 0; $i < $count; $i++) {
1522 $authored{$i} = $toggle;
1523 }
1524 }
1525 } elsif ($sel eq "s") {
1526 if ($val > 0 && $val <= $count) {
1527 $signed{$val - 1} = !$signed{$val - 1};
1528 } elsif ($str eq '*' || $str eq '^') {
1529 my $toggle = 0;
1530 $toggle = 1 if ($str eq '*');
1531 for (my $i = 0; $i < $count; $i++) {
1532 $signed{$i} = $toggle;
1533 }
1534 }
1535 } elsif ($sel eq "o") {
1536 $print_options = 1;
1537 $redraw = 1;
1538 } elsif ($sel eq "g") {
1539 if ($str eq "f") {
1540 bool_invert(\$email_git_fallback);
dace8e30 1541 } else {
683c6f8f
JP
1542 bool_invert(\$email_git);
1543 }
1544 $rerun = 1;
1545 } elsif ($sel eq "b") {
1546 if ($str eq "s") {
1547 bool_invert(\$email_git_blame_signatures);
1548 } else {
1549 bool_invert(\$email_git_blame);
1550 }
1551 $rerun = 1;
1552 } elsif ($sel eq "c") {
1553 if ($val > 0) {
1554 $email_git_min_signatures = $val;
1555 $rerun = 1;
1556 }
1557 } elsif ($sel eq "x") {
1558 if ($val > 0) {
1559 $email_git_max_maintainers = $val;
1560 $rerun = 1;
1561 }
1562 } elsif ($sel eq "%") {
1563 if ($str ne "" && $val >= 0) {
1564 $email_git_min_percent = $val;
1565 $rerun = 1;
dace8e30 1566 }
683c6f8f
JP
1567 } elsif ($sel eq "d") {
1568 if (vcs_is_git()) {
1569 $email_git_since = $str;
1570 } elsif (vcs_is_hg()) {
1571 $email_hg_since = $str;
1572 }
1573 $rerun = 1;
1574 } elsif ($sel eq "t") {
1575 bool_invert(\$email_git_all_signature_types);
1576 $rerun = 1;
1577 } elsif ($sel eq "f") {
1578 bool_invert(\$file_emails);
1579 $rerun = 1;
1580 } elsif ($sel eq "r") {
1581 bool_invert(\$email_remove_duplicates);
1582 $rerun = 1;
b9e2331d
JP
1583 } elsif ($sel eq "m") {
1584 bool_invert(\$email_use_mailmap);
1585 read_mailmap();
1586 $rerun = 1;
683c6f8f
JP
1587 } elsif ($sel eq "k") {
1588 bool_invert(\$keywords);
1589 $rerun = 1;
1590 } elsif ($sel eq "p") {
1591 if ($str ne "" && $val >= 0) {
1592 $pattern_depth = $val;
1593 $rerun = 1;
1594 }
6ef1c52e
JP
1595 } elsif ($sel eq "h" || $sel eq "?") {
1596 print STDERR <<EOT
1597
1598Interactive mode allows you to select the various maintainers, submitters,
1599commit signers and mailing lists that could be CC'd on a patch.
1600
1601Any *'d entry is selected.
1602
47abc722 1603If you have git or hg installed, you can choose to summarize the commit
6ef1c52e
JP
1604history of files in the patch. Also, each line of the current file can
1605be matched to its commit author and that commits signers with blame.
1606
1607Various knobs exist to control the length of time for active commit
1608tracking, the maximum number of commit authors and signers to add,
1609and such.
1610
1611Enter selections at the prompt until you are satisfied that the selected
1612maintainers are appropriate. You may enter multiple selections separated
1613by either commas or spaces.
1614
1615EOT
683c6f8f
JP
1616 } else {
1617 print STDERR "invalid option: '$nr'\n";
1618 $redraw = 0;
1619 }
1620 }
1621 if ($rerun) {
1622 print STDERR "git-blame can be very slow, please have patience..."
1623 if ($email_git_blame);
6ef1c52e 1624 goto &get_maintainers;
683c6f8f
JP
1625 }
1626 }
dace8e30
FM
1627
1628 #drop not selected entries
1629 $count = 0;
683c6f8f
JP
1630 my @new_emailto = ();
1631 foreach my $entry (@list) {
1632 if ($selected{$count}) {
1633 push(@new_emailto, $list[$count]);
dace8e30
FM
1634 }
1635 $count++;
1636 }
683c6f8f 1637 return @new_emailto;
dace8e30
FM
1638}
1639
683c6f8f
JP
1640sub bool_invert {
1641 my ($bool_ref) = @_;
1642
1643 if ($$bool_ref) {
1644 $$bool_ref = 0;
1645 } else {
1646 $$bool_ref = 1;
1647 }
dace8e30
FM
1648}
1649
b9e2331d
JP
1650sub deduplicate_email {
1651 my ($email) = @_;
1652
1653 my $matched = 0;
1654 my ($name, $address) = parse_email($email);
1655 $email = format_email($name, $address, 1);
1656 $email = mailmap_email($email);
1657
1658 return $email if (!$email_remove_duplicates);
1659
1660 ($name, $address) = parse_email($email);
1661
fae99206 1662 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
b9e2331d
JP
1663 $name = $deduplicate_name_hash{lc($name)}->[0];
1664 $address = $deduplicate_name_hash{lc($name)}->[1];
1665 $matched = 1;
1666 } elsif ($deduplicate_address_hash{lc($address)}) {
1667 $name = $deduplicate_address_hash{lc($address)}->[0];
1668 $address = $deduplicate_address_hash{lc($address)}->[1];
1669 $matched = 1;
1670 }
1671 if (!$matched) {
1672 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1673 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1674 }
1675 $email = format_email($name, $address, 1);
1676 $email = mailmap_email($email);
1677 return $email;
1678}
1679
683c6f8f
JP
1680sub save_commits_by_author {
1681 my (@lines) = @_;
1682
1683 my @authors = ();
1684 my @commits = ();
1685 my @subjects = ();
1686
1687 foreach my $line (@lines) {
1688 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1689 my $author = $1;
b9e2331d 1690 $author = deduplicate_email($author);
683c6f8f
JP
1691 push(@authors, $author);
1692 }
1693 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1694 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1695 }
1696
1697 for (my $i = 0; $i < @authors; $i++) {
1698 my $exists = 0;
1699 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1700 if (@{$ref}[0] eq $commits[$i] &&
1701 @{$ref}[1] eq $subjects[$i]) {
1702 $exists = 1;
1703 last;
1704 }
1705 }
1706 if (!$exists) {
1707 push(@{$commit_author_hash{$authors[$i]}},
1708 [ ($commits[$i], $subjects[$i]) ]);
1709 }
dace8e30 1710 }
dace8e30
FM
1711}
1712
683c6f8f
JP
1713sub save_commits_by_signer {
1714 my (@lines) = @_;
1715
1716 my $commit = "";
1717 my $subject = "";
dace8e30 1718
683c6f8f
JP
1719 foreach my $line (@lines) {
1720 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1721 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1722 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1723 my @signatures = ($line);
1724 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1725 my @types = @$types_ref;
1726 my @signers = @$signers_ref;
1727
1728 my $type = $types[0];
1729 my $signer = $signers[0];
1730
b9e2331d 1731 $signer = deduplicate_email($signer);
6ef1c52e 1732
683c6f8f
JP
1733 my $exists = 0;
1734 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1735 if (@{$ref}[0] eq $commit &&
1736 @{$ref}[1] eq $subject &&
1737 @{$ref}[2] eq $type) {
1738 $exists = 1;
1739 last;
1740 }
1741 }
1742 if (!$exists) {
1743 push(@{$commit_signer_hash{$signer}},
1744 [ ($commit, $subject, $type) ]);
1745 }
1746 }
1747 }
dace8e30
FM
1748}
1749
60db31ac 1750sub vcs_assign {
a8af2430
JP
1751 my ($role, $divisor, @lines) = @_;
1752
1753 my %hash;
1754 my $count = 0;
1755
a8af2430
JP
1756 return if (@lines <= 0);
1757
1758 if ($divisor <= 0) {
60db31ac 1759 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
a8af2430 1760 $divisor = 1;
3c7385b8 1761 }
8cbb3a77 1762
7fa8ff2e 1763 @lines = mailmap(@lines);
0e70e83d 1764
63ab52db
JP
1765 return if (@lines <= 0);
1766
0e70e83d 1767 @lines = sort(@lines);
11ecf53c 1768
0e70e83d 1769 # uniq -c
11ecf53c
JP
1770 $hash{$_}++ for @lines;
1771
0e70e83d 1772 # sort -rn
0e70e83d 1773 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
11ecf53c 1774 my $sign_offs = $hash{$line};
a8af2430 1775 my $percent = $sign_offs * 100 / $divisor;
3c7385b8 1776
a8af2430 1777 $percent = 100 if ($percent > 100);
11ecf53c
JP
1778 $count++;
1779 last if ($sign_offs < $email_git_min_signatures ||
1780 $count > $email_git_max_maintainers ||
a8af2430 1781 $percent < $email_git_min_percent);
3c7385b8 1782 push_email_address($line, '');
3c7385b8 1783 if ($output_rolestats) {
a8af2430
JP
1784 my $fmt_percent = sprintf("%.0f", $percent);
1785 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1786 } else {
1787 add_role($line, $role);
3c7385b8 1788 }
f5492666
JP
1789 }
1790}
1791
60db31ac 1792sub vcs_file_signoffs {
a8af2430
JP
1793 my ($file) = @_;
1794
1795 my @signers = ();
60db31ac 1796 my $commits;
f5492666 1797
683c6f8f
JP
1798 $vcs_used = vcs_exists();
1799 return if (!$vcs_used);
a8af2430 1800
60db31ac
JP
1801 my $cmd = $VCS_cmds{"find_signers_cmd"};
1802 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
f5492666 1803
60db31ac 1804 ($commits, @signers) = vcs_find_signers($cmd);
b9e2331d
JP
1805
1806 foreach my $signer (@signers) {
1807 $signer = deduplicate_email($signer);
1808 }
1809
60db31ac 1810 vcs_assign("commit_signer", $commits, @signers);
f5492666
JP
1811}
1812
60db31ac 1813sub vcs_file_blame {
f5492666
JP
1814 my ($file) = @_;
1815
a8af2430 1816 my @signers = ();
63ab52db 1817 my @all_commits = ();
60db31ac 1818 my @commits = ();
a8af2430 1819 my $total_commits;
63ab52db 1820 my $total_lines;
f5492666 1821
683c6f8f
JP
1822 $vcs_used = vcs_exists();
1823 return if (!$vcs_used);
f5492666 1824
63ab52db
JP
1825 @all_commits = vcs_blame($file);
1826 @commits = uniq(@all_commits);
a8af2430 1827 $total_commits = @commits;
63ab52db 1828 $total_lines = @all_commits;
8cbb3a77 1829
683c6f8f
JP
1830 if ($email_git_blame_signatures) {
1831 if (vcs_is_hg()) {
1832 my $commit_count;
1833 my @commit_signers = ();
1834 my $commit = join(" -r ", @commits);
1835 my $cmd;
8cbb3a77 1836
683c6f8f
JP
1837 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1838 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
60db31ac 1839
683c6f8f 1840 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
63ab52db 1841
683c6f8f
JP
1842 push(@signers, @commit_signers);
1843 } else {
1844 foreach my $commit (@commits) {
1845 my $commit_count;
1846 my @commit_signers = ();
1847 my $cmd;
1848
1849 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1850 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1851
1852 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1853
1854 push(@signers, @commit_signers);
1855 }
1856 }
f5492666
JP
1857 }
1858
a8af2430 1859 if ($from_filename) {
63ab52db
JP
1860 if ($output_rolestats) {
1861 my @blame_signers;
683c6f8f
JP
1862 if (vcs_is_hg()) {{ # Double brace for last exit
1863 my $commit_count;
1864 my @commit_signers = ();
1865 @commits = uniq(@commits);
1866 @commits = sort(@commits);
1867 my $commit = join(" -r ", @commits);
1868 my $cmd;
1869
1870 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1871 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1872
1873 my @lines = ();
1874
1875 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1876
1877 if (!$email_git_penguin_chiefs) {
1878 @lines = grep(!/${penguin_chiefs}/i, @lines);
1879 }
1880
1881 last if !@lines;
1882
1883 my @authors = ();
1884 foreach my $line (@lines) {
1885 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1886 my $author = $1;
b9e2331d
JP
1887 $author = deduplicate_email($author);
1888 push(@authors, $author);
683c6f8f
JP
1889 }
1890 }
1891
1892 save_commits_by_author(@lines) if ($interactive);
1893 save_commits_by_signer(@lines) if ($interactive);
1894
1895 push(@signers, @authors);
1896 }}
1897 else {
1898 foreach my $commit (@commits) {
1899 my $i;
1900 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1901 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1902 my @author = vcs_find_author($cmd);
1903 next if !@author;
b9e2331d
JP
1904
1905 my $formatted_author = deduplicate_email($author[0]);
1906
683c6f8f
JP
1907 my $count = grep(/$commit/, @all_commits);
1908 for ($i = 0; $i < $count ; $i++) {
b9e2331d 1909 push(@blame_signers, $formatted_author);
683c6f8f 1910 }
63ab52db
JP
1911 }
1912 }
1913 if (@blame_signers) {
1914 vcs_assign("authored lines", $total_lines, @blame_signers);
1915 }
1916 }
b9e2331d
JP
1917 foreach my $signer (@signers) {
1918 $signer = deduplicate_email($signer);
1919 }
60db31ac 1920 vcs_assign("commits", $total_commits, @signers);
a8af2430 1921 } else {
b9e2331d
JP
1922 foreach my $signer (@signers) {
1923 $signer = deduplicate_email($signer);
1924 }
60db31ac 1925 vcs_assign("modified commits", $total_commits, @signers);
cb7301c7 1926 }
cb7301c7
JP
1927}
1928
1929sub uniq {
a8af2430 1930 my (@parms) = @_;
cb7301c7
JP
1931
1932 my %saw;
1933 @parms = grep(!$saw{$_}++, @parms);
1934 return @parms;
1935}
1936
1937sub sort_and_uniq {
a8af2430 1938 my (@parms) = @_;
cb7301c7
JP
1939
1940 my %saw;
1941 @parms = sort @parms;
1942 @parms = grep(!$saw{$_}++, @parms);
1943 return @parms;
1944}
1945
03372dbb
JP
1946sub clean_file_emails {
1947 my (@file_emails) = @_;
1948 my @fmt_emails = ();
1949
1950 foreach my $email (@file_emails) {
1951 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1952 my ($name, $address) = parse_email($email);
1953 if ($name eq '"[,\.]"') {
1954 $name = "";
1955 }
1956
1957 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1958 if (@nw > 2) {
1959 my $first = $nw[@nw - 3];
1960 my $middle = $nw[@nw - 2];
1961 my $last = $nw[@nw - 1];
1962
1963 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
1964 (length($first) == 2 && substr($first, -1) eq ".")) ||
1965 (length($middle) == 1 ||
1966 (length($middle) == 2 && substr($middle, -1) eq "."))) {
1967 $name = "$first $middle $last";
1968 } else {
1969 $name = "$middle $last";
1970 }
1971 }
1972
1973 if (substr($name, -1) =~ /[,\.]/) {
1974 $name = substr($name, 0, length($name) - 1);
1975 } elsif (substr($name, -2) =~ /[,\.]"/) {
1976 $name = substr($name, 0, length($name) - 2) . '"';
1977 }
1978
1979 if (substr($name, 0, 1) =~ /[,\.]/) {
1980 $name = substr($name, 1, length($name) - 1);
1981 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
1982 $name = '"' . substr($name, 2, length($name) - 2);
1983 }
1984
1985 my $fmt_email = format_email($name, $address, $email_usename);
1986 push(@fmt_emails, $fmt_email);
1987 }
1988 return @fmt_emails;
1989}
1990
3c7385b8
JP
1991sub merge_email {
1992 my @lines;
1993 my %saw;
1994
1995 for (@_) {
1996 my ($address, $role) = @$_;
1997 if (!$saw{$address}) {
1998 if ($output_roles) {
60db31ac 1999 push(@lines, "$address ($role)");
3c7385b8 2000 } else {
60db31ac 2001 push(@lines, $address);
3c7385b8
JP
2002 }
2003 $saw{$address} = 1;
2004 }
2005 }
2006
2007 return @lines;
2008}
2009
cb7301c7 2010sub output {
a8af2430 2011 my (@parms) = @_;
cb7301c7
JP
2012
2013 if ($output_multiline) {
2014 foreach my $line (@parms) {
2015 print("${line}\n");
2016 }
2017 } else {
2018 print(join($output_separator, @parms));
2019 print("\n");
2020 }
2021}
1b5e1cf6
JP
2022
2023my $rfc822re;
2024
2025sub make_rfc822re {
2026# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2027# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2028# This regexp will only work on addresses which have had comments stripped
2029# and replaced with rfc822_lwsp.
2030
2031 my $specials = '()<>@,;:\\\\".\\[\\]';
2032 my $controls = '\\000-\\037\\177';
2033
2034 my $dtext = "[^\\[\\]\\r\\\\]";
2035 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2036
2037 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2038
2039# Use zero-width assertion to spot the limit of an atom. A simple
2040# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2041 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2042 my $word = "(?:$atom|$quoted_string)";
2043 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2044
2045 my $sub_domain = "(?:$atom|$domain_literal)";
2046 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2047
2048 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2049
2050 my $phrase = "$word*";
2051 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2052 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2053 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2054
2055 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2056 my $address = "(?:$mailbox|$group)";
2057
2058 return "$rfc822_lwsp*$address";
2059}
2060
2061sub rfc822_strip_comments {
2062 my $s = shift;
2063# Recursively remove comments, and replace with a single space. The simpler
2064# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2065# chars in atoms, for example.
2066
2067 while ($s =~ s/^((?:[^"\\]|\\.)*
2068 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2069 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2070 return $s;
2071}
2072
2073# valid: returns true if the parameter is an RFC822 valid address
2074#
22dd5b0c 2075sub rfc822_valid {
1b5e1cf6
JP
2076 my $s = rfc822_strip_comments(shift);
2077
2078 if (!$rfc822re) {
2079 $rfc822re = make_rfc822re();
2080 }
2081
2082 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2083}
2084
2085# validlist: In scalar context, returns true if the parameter is an RFC822
2086# valid list of addresses.
2087#
2088# In list context, returns an empty list on failure (an invalid
2089# address was found); otherwise a list whose first element is the
2090# number of addresses found and whose remaining elements are the
2091# addresses. This is needed to disambiguate failure (invalid)
2092# from success with no addresses found, because an empty string is
2093# a valid list.
2094
22dd5b0c 2095sub rfc822_validlist {
1b5e1cf6
JP
2096 my $s = rfc822_strip_comments(shift);
2097
2098 if (!$rfc822re) {
2099 $rfc822re = make_rfc822re();
2100 }
2101 # * null list items are valid according to the RFC
2102 # * the '1' business is to aid in distinguishing failure from no results
2103
2104 my @r;
2105 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2106 $s =~ m/^$rfc822_char*$/) {
5f2441e9 2107 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
60db31ac 2108 push(@r, $1);
1b5e1cf6
JP
2109 }
2110 return wantarray ? (scalar(@r), @r) : 1;
2111 }
60db31ac 2112 return wantarray ? () : 0;
1b5e1cf6 2113}