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