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