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