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