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