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