drm/panthor: Don't add write fences to the shared BOs
[drm/drm-misc.git] / scripts / get_maintainer.pl
blob5ac02e19873722d0f5bf3ac8de8374338c7bddc3
1 #!/usr/bin/env perl
2 # SPDX-License-Identifier: GPL-2.0
4 # (c) 2007, Joe Perches <joe@perches.com>
5 # created from checkpatch.pl
7 # Print selected MAINTAINERS information for
8 # the files modified in a patch or for a file
10 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
11 # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
13 use warnings;
14 use strict;
16 my $P = $0;
17 my $V = '0.26';
19 use Getopt::Long qw(:config no_auto_abbrev);
20 use Cwd;
21 use File::Find;
22 use File::Spec::Functions;
23 use open qw(:std :encoding(UTF-8));
25 my $cur_path = fastgetcwd() . '/';
26 my $lk_path = "./";
27 my $email = 1;
28 my $email_usename = 1;
29 my $email_maintainer = 1;
30 my $email_reviewer = 1;
31 my $email_fixes = 1;
32 my $email_list = 1;
33 my $email_moderated_list = 1;
34 my $email_subscriber_list = 0;
35 my $email_git_penguin_chiefs = 0;
36 my $email_git = 0;
37 my $email_git_all_signature_types = 0;
38 my $email_git_blame = 0;
39 my $email_git_blame_signatures = 1;
40 my $email_git_fallback = 1;
41 my $email_git_min_signatures = 1;
42 my $email_git_max_maintainers = 5;
43 my $email_git_min_percent = 5;
44 my $email_git_since = "1-year-ago";
45 my $email_hg_since = "-365";
46 my $interactive = 0;
47 my $email_remove_duplicates = 1;
48 my $email_use_mailmap = 1;
49 my $output_multiline = 1;
50 my $output_separator = ", ";
51 my $output_roles = 0;
52 my $output_rolestats = 1;
53 my $output_section_maxlen = 50;
54 my $scm = 0;
55 my $tree = 1;
56 my $web = 0;
57 my $bug = 0;
58 my $subsystem = 0;
59 my $status = 0;
60 my $letters = "";
61 my $keywords = 1;
62 my $keywords_in_file = 0;
63 my $sections = 0;
64 my $email_file_emails = 0;
65 my $from_filename = 0;
66 my $pattern_depth = 0;
67 my $self_test = undef;
68 my $version = 0;
69 my $help = 0;
70 my $find_maintainer_files = 0;
71 my $maintainer_path;
72 my $vcs_used = 0;
74 my $exit = 0;
76 my @files = ();
77 my @fixes = (); # If a patch description includes Fixes: lines
78 my @range = ();
79 my @keyword_tvi = ();
80 my @file_emails = ();
82 my %commit_author_hash;
83 my %commit_signer_hash;
85 my @penguin_chief = ();
86 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
87 #Andrew wants in on most everything - 2009/01/14
88 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
90 my @penguin_chief_names = ();
91 foreach my $chief (@penguin_chief) {
92 if ($chief =~ m/^(.*):(.*)/) {
93 my $chief_name = $1;
94 my $chief_addr = $2;
95 push(@penguin_chief_names, $chief_name);
98 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
100 # Signature types of people who are either
101 # a) responsible for the code in question, or
102 # b) familiar enough with it to give relevant feedback
103 my @signature_tags = ();
104 push(@signature_tags, "Signed-off-by:");
105 push(@signature_tags, "Reviewed-by:");
106 push(@signature_tags, "Acked-by:");
108 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
110 # rfc822 email address - preloaded methods go here.
111 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
112 my $rfc822_char = '[\\000-\\377]';
114 # VCS command support: class-like functions and strings
116 my %VCS_cmds;
118 my %VCS_cmds_git = (
119 "execute_cmd" => \&git_execute_cmd,
120 "available" => '(which("git") ne "") && (-e ".git")',
121 "find_signers_cmd" =>
122 "git log --no-color --follow --since=\$email_git_since " .
123 '--numstat --no-merges ' .
124 '--format="GitCommit: %H%n' .
125 'GitAuthor: %an <%ae>%n' .
126 'GitDate: %aD%n' .
127 'GitSubject: %s%n' .
128 '%b%n"' .
129 " -- \$file",
130 "find_commit_signers_cmd" =>
131 "git log --no-color " .
132 '--numstat ' .
133 '--format="GitCommit: %H%n' .
134 'GitAuthor: %an <%ae>%n' .
135 'GitDate: %aD%n' .
136 'GitSubject: %s%n' .
137 '%b%n"' .
138 " -1 \$commit",
139 "find_commit_author_cmd" =>
140 "git log --no-color " .
141 '--numstat ' .
142 '--format="GitCommit: %H%n' .
143 'GitAuthor: %an <%ae>%n' .
144 'GitDate: %aD%n' .
145 'GitSubject: %s%n"' .
146 " -1 \$commit",
147 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
148 "blame_file_cmd" => "git blame -l \$file",
149 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
150 "blame_commit_pattern" => "^([0-9a-f]+) ",
151 "author_pattern" => "^GitAuthor: (.*)",
152 "subject_pattern" => "^GitSubject: (.*)",
153 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
154 "file_exists_cmd" => "git ls-files \$file",
155 "list_files_cmd" => "git ls-files \$file",
158 my %VCS_cmds_hg = (
159 "execute_cmd" => \&hg_execute_cmd,
160 "available" => '(which("hg") ne "") && (-d ".hg")',
161 "find_signers_cmd" =>
162 "hg log --date=\$email_hg_since " .
163 "--template='HgCommit: {node}\\n" .
164 "HgAuthor: {author}\\n" .
165 "HgSubject: {desc}\\n'" .
166 " -- \$file",
167 "find_commit_signers_cmd" =>
168 "hg log " .
169 "--template='HgSubject: {desc}\\n'" .
170 " -r \$commit",
171 "find_commit_author_cmd" =>
172 "hg log " .
173 "--template='HgCommit: {node}\\n" .
174 "HgAuthor: {author}\\n" .
175 "HgSubject: {desc|firstline}\\n'" .
176 " -r \$commit",
177 "blame_range_cmd" => "", # not supported
178 "blame_file_cmd" => "hg blame -n \$file",
179 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
180 "blame_commit_pattern" => "^([ 0-9a-f]+):",
181 "author_pattern" => "^HgAuthor: (.*)",
182 "subject_pattern" => "^HgSubject: (.*)",
183 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
184 "file_exists_cmd" => "hg files \$file",
185 "list_files_cmd" => "hg manifest -R \$file",
188 my $conf = which_conf(".get_maintainer.conf");
189 if (-f $conf) {
190 my @conf_args;
191 open(my $conffile, '<', "$conf")
192 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
194 while (<$conffile>) {
195 my $line = $_;
197 $line =~ s/\s*\n?$//g;
198 $line =~ s/^\s*//g;
199 $line =~ s/\s+/ /g;
201 next if ($line =~ m/^\s*#/);
202 next if ($line =~ m/^\s*$/);
204 my @words = split(" ", $line);
205 foreach my $word (@words) {
206 last if ($word =~ m/^#/);
207 push (@conf_args, $word);
210 close($conffile);
211 unshift(@ARGV, @conf_args) if @conf_args;
214 my @ignore_emails = ();
215 my $ignore_file = which_conf(".get_maintainer.ignore");
216 if (-f $ignore_file) {
217 open(my $ignore, '<', "$ignore_file")
218 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
219 while (<$ignore>) {
220 my $line = $_;
222 $line =~ s/\s*\n?$//;
223 $line =~ s/^\s*//;
224 $line =~ s/\s+$//;
225 $line =~ s/#.*$//;
227 next if ($line =~ m/^\s*$/);
228 if (rfc822_valid($line)) {
229 push(@ignore_emails, $line);
232 close($ignore);
235 if ($#ARGV > 0) {
236 foreach (@ARGV) {
237 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
238 die "$P: using --self-test does not allow any other option or argument\n";
243 if (!GetOptions(
244 'email!' => \$email,
245 'git!' => \$email_git,
246 'git-all-signature-types!' => \$email_git_all_signature_types,
247 'git-blame!' => \$email_git_blame,
248 'git-blame-signatures!' => \$email_git_blame_signatures,
249 'git-fallback!' => \$email_git_fallback,
250 'git-chief-penguins!' => \$email_git_penguin_chiefs,
251 'git-min-signatures=i' => \$email_git_min_signatures,
252 'git-max-maintainers=i' => \$email_git_max_maintainers,
253 'git-min-percent=i' => \$email_git_min_percent,
254 'git-since=s' => \$email_git_since,
255 'hg-since=s' => \$email_hg_since,
256 'i|interactive!' => \$interactive,
257 'remove-duplicates!' => \$email_remove_duplicates,
258 'mailmap!' => \$email_use_mailmap,
259 'm!' => \$email_maintainer,
260 'r!' => \$email_reviewer,
261 'n!' => \$email_usename,
262 'l!' => \$email_list,
263 'fixes!' => \$email_fixes,
264 'moderated!' => \$email_moderated_list,
265 's!' => \$email_subscriber_list,
266 'multiline!' => \$output_multiline,
267 'roles!' => \$output_roles,
268 'rolestats!' => \$output_rolestats,
269 'separator=s' => \$output_separator,
270 'subsystem!' => \$subsystem,
271 'status!' => \$status,
272 'scm!' => \$scm,
273 'tree!' => \$tree,
274 'web!' => \$web,
275 'bug!' => \$bug,
276 'letters=s' => \$letters,
277 'pattern-depth=i' => \$pattern_depth,
278 'k|keywords!' => \$keywords,
279 'kf|keywords-in-file!' => \$keywords_in_file,
280 'sections!' => \$sections,
281 'fe|file-emails!' => \$email_file_emails,
282 'f|file' => \$from_filename,
283 'find-maintainer-files' => \$find_maintainer_files,
284 'mpath|maintainer-path=s' => \$maintainer_path,
285 'self-test:s' => \$self_test,
286 'v|version' => \$version,
287 'h|help|usage' => \$help,
288 )) {
289 die "$P: invalid argument - use --help if necessary\n";
292 if ($help != 0) {
293 usage();
294 exit 0;
297 if ($version != 0) {
298 print("${P} ${V}\n");
299 exit 0;
302 if (defined $self_test) {
303 read_all_maintainer_files();
304 self_test();
305 exit 0;
308 if (-t STDIN && !@ARGV) {
309 # We're talking to a terminal, but have no command line arguments.
310 die "$P: missing patchfile or -f file - use --help if necessary\n";
313 $output_multiline = 0 if ($output_separator ne ", ");
314 $output_rolestats = 1 if ($interactive);
315 $output_roles = 1 if ($output_rolestats);
317 if ($sections || $letters ne "") {
318 $sections = 1;
319 $email = 0;
320 $email_list = 0;
321 $scm = 0;
322 $status = 0;
323 $subsystem = 0;
324 $web = 0;
325 $bug = 0;
326 $keywords = 0;
327 $keywords_in_file = 0;
328 $interactive = 0;
329 } else {
330 my $selections = $email + $scm + $status + $subsystem + $web + $bug;
331 if ($selections == 0) {
332 die "$P: Missing required option: email, scm, status, subsystem, web or bug\n";
336 if ($email &&
337 ($email_maintainer + $email_reviewer +
338 $email_list + $email_subscriber_list +
339 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
340 die "$P: Please select at least 1 email option\n";
343 if ($tree && !top_of_kernel_tree($lk_path)) {
344 die "$P: The current directory does not appear to be "
345 . "a linux kernel source tree.\n";
348 ## Read MAINTAINERS for type/value pairs
350 my @typevalue = ();
351 my %keyword_hash;
352 my @mfiles = ();
353 my @self_test_info = ();
355 sub read_maintainer_file {
356 my ($file) = @_;
358 open (my $maint, '<', "$file")
359 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
360 my $i = 1;
361 while (<$maint>) {
362 my $line = $_;
363 chomp $line;
365 if ($line =~ m/^([A-Z]):\s*(.*)/) {
366 my $type = $1;
367 my $value = $2;
369 ##Filename pattern matching
370 if ($type eq "F" || $type eq "X") {
371 $value =~ s@\.@\\\.@g; ##Convert . to \.
372 $value =~ s/\*/\.\*/g; ##Convert * to .*
373 $value =~ s/\?/\./g; ##Convert ? to .
374 ##if pattern is a directory and it lacks a trailing slash, add one
375 if ((-d $value)) {
376 $value =~ s@([^/])$@$1/@;
378 } elsif ($type eq "K") {
379 $keyword_hash{@typevalue} = $value;
381 push(@typevalue, "$type:$value");
382 } elsif (!(/^\s*$/ || /^\s*\#/)) {
383 push(@typevalue, $line);
385 if (defined $self_test) {
386 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
388 $i++;
390 close($maint);
393 sub find_is_maintainer_file {
394 my ($file) = $_;
395 return if ($file !~ m@/MAINTAINERS$@);
396 $file = $File::Find::name;
397 return if (! -f $file);
398 push(@mfiles, $file);
401 sub find_ignore_git {
402 return grep { $_ !~ /^\.git$/; } @_;
405 read_all_maintainer_files();
407 sub read_all_maintainer_files {
408 my $path = "${lk_path}MAINTAINERS";
409 if (defined $maintainer_path) {
410 $path = $maintainer_path;
411 # Perl Cookbook tilde expansion if necessary
412 $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
415 if (-d $path) {
416 $path .= '/' if ($path !~ m@/$@);
417 if ($find_maintainer_files) {
418 find( { wanted => \&find_is_maintainer_file,
419 preprocess => \&find_ignore_git,
420 no_chdir => 1,
421 }, "$path");
422 } else {
423 opendir(DIR, "$path") or die $!;
424 my @files = readdir(DIR);
425 closedir(DIR);
426 foreach my $file (@files) {
427 push(@mfiles, "$path$file") if ($file !~ /^\./);
430 } elsif (-f "$path") {
431 push(@mfiles, "$path");
432 } else {
433 die "$P: MAINTAINER file not found '$path'\n";
435 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
436 foreach my $file (@mfiles) {
437 read_maintainer_file("$file");
441 sub maintainers_in_file {
442 my ($file) = @_;
444 return if ($file =~ m@\bMAINTAINERS$@);
446 if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) {
447 open(my $f, '<', $file)
448 or die "$P: Can't open $file: $!\n";
449 my $text = do { local($/) ; <$f> };
450 close($f);
452 my @poss_addr = $text =~ m$[\p{L}\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g;
453 push(@file_emails, clean_file_emails(@poss_addr));
458 # Read mail address map
461 my $mailmap;
463 read_mailmap();
465 sub read_mailmap {
466 $mailmap = {
467 names => {},
468 addresses => {}
471 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
473 open(my $mailmap_file, '<', "${lk_path}.mailmap")
474 or warn "$P: Can't open .mailmap: $!\n";
476 while (<$mailmap_file>) {
477 s/#.*$//; #strip comments
478 s/^\s+|\s+$//g; #trim
480 next if (/^\s*$/); #skip empty lines
481 #entries have one of the following formats:
482 # name1 <mail1>
483 # <mail1> <mail2>
484 # name1 <mail1> <mail2>
485 # name1 <mail1> name2 <mail2>
486 # (see man git-shortlog)
488 if (/^([^<]+)<([^>]+)>$/) {
489 my $real_name = $1;
490 my $address = $2;
492 $real_name =~ s/\s+$//;
493 ($real_name, $address) = parse_email("$real_name <$address>");
494 $mailmap->{names}->{$address} = $real_name;
496 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
497 my $real_address = $1;
498 my $wrong_address = $2;
500 $mailmap->{addresses}->{$wrong_address} = $real_address;
502 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
503 my $real_name = $1;
504 my $real_address = $2;
505 my $wrong_address = $3;
507 $real_name =~ s/\s+$//;
508 ($real_name, $real_address) =
509 parse_email("$real_name <$real_address>");
510 $mailmap->{names}->{$wrong_address} = $real_name;
511 $mailmap->{addresses}->{$wrong_address} = $real_address;
513 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
514 my $real_name = $1;
515 my $real_address = $2;
516 my $wrong_name = $3;
517 my $wrong_address = $4;
519 $real_name =~ s/\s+$//;
520 ($real_name, $real_address) =
521 parse_email("$real_name <$real_address>");
523 $wrong_name =~ s/\s+$//;
524 ($wrong_name, $wrong_address) =
525 parse_email("$wrong_name <$wrong_address>");
527 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
528 $mailmap->{names}->{$wrong_email} = $real_name;
529 $mailmap->{addresses}->{$wrong_email} = $real_address;
532 close($mailmap_file);
535 ## use the filenames on the command line or find the filenames in the patchfiles
537 if (!@ARGV) {
538 push(@ARGV, "&STDIN");
541 foreach my $file (@ARGV) {
542 if ($file ne "&STDIN") {
543 $file = canonpath($file);
544 ##if $file is a directory and it lacks a trailing slash, add one
545 if ((-d $file)) {
546 $file =~ s@([^/])$@$1/@;
547 } elsif (!(-f $file)) {
548 die "$P: file '${file}' not found\n";
551 if ($from_filename && (vcs_exists() && !vcs_file_exists($file))) {
552 warn "$P: file '$file' not found in version control $!\n";
554 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
555 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
556 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
557 push(@files, $file);
558 if ($file ne "MAINTAINERS" && -f $file && $keywords && $keywords_in_file) {
559 open(my $f, '<', $file)
560 or die "$P: Can't open $file: $!\n";
561 my $text = do { local($/) ; <$f> };
562 close($f);
563 foreach my $line (keys %keyword_hash) {
564 if ($text =~ m/$keyword_hash{$line}/x) {
565 push(@keyword_tvi, $line);
569 } else {
570 my $file_cnt = @files;
571 my $lastfile;
573 open(my $patch, "< $file")
574 or die "$P: Can't open $file: $!\n";
576 # We can check arbitrary information before the patch
577 # like the commit message, mail headers, etc...
578 # This allows us to match arbitrary keywords against any part
579 # of a git format-patch generated file (subject tags, etc...)
581 my $patch_prefix = ""; #Parsing the intro
583 while (<$patch>) {
584 my $patch_line = $_;
585 if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
586 my $filename = $1;
587 push(@files, $filename);
588 } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
589 my $filename = $1;
590 push(@files, $filename);
591 } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
592 my $filename1 = $1;
593 my $filename2 = $2;
594 push(@files, $filename1);
595 push(@files, $filename2);
596 } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
597 push(@fixes, $1) if ($email_fixes);
598 } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
599 my $filename = $1;
600 $filename =~ s@^[^/]*/@@;
601 $filename =~ s@\n@@;
602 $lastfile = $filename;
603 push(@files, $filename);
604 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
605 } elsif (m/^\@\@ -(\d+),(\d+)/) {
606 if ($email_git_blame) {
607 push(@range, "$lastfile:$1:$2");
609 } elsif ($keywords) {
610 foreach my $line (keys %keyword_hash) {
611 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
612 push(@keyword_tvi, $line);
617 close($patch);
619 if ($file_cnt == @files) {
620 warn "$P: file '${file}' doesn't appear to be a patch. "
621 . "Add -f to options?\n";
623 @files = sort_and_uniq(@files);
627 @file_emails = uniq(@file_emails);
628 @fixes = uniq(@fixes);
630 my %email_hash_name;
631 my %email_hash_address;
632 my @email_to = ();
633 my %hash_list_to;
634 my @list_to = ();
635 my @scm = ();
636 my @web = ();
637 my @bug = ();
638 my @subsystem = ();
639 my @status = ();
640 my %deduplicate_name_hash = ();
641 my %deduplicate_address_hash = ();
643 my @maintainers = get_maintainers();
644 if (@maintainers) {
645 @maintainers = merge_email(@maintainers);
646 output(@maintainers);
649 if ($scm) {
650 @scm = uniq(@scm);
651 output(@scm);
654 if ($status) {
655 @status = uniq(@status);
656 output(@status);
659 if ($subsystem) {
660 @subsystem = uniq(@subsystem);
661 output(@subsystem);
664 if ($web) {
665 @web = uniq(@web);
666 output(@web);
669 if ($bug) {
670 @bug = uniq(@bug);
671 output(@bug);
674 exit($exit);
676 sub self_test {
677 my @lsfiles = ();
678 my @good_links = ();
679 my @bad_links = ();
680 my @section_headers = ();
681 my $index = 0;
683 @lsfiles = vcs_list_files($lk_path);
685 for my $x (@self_test_info) {
686 $index++;
688 ## Section header duplication and missing section content
689 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
690 $x->{line} =~ /^\S[^:]/ &&
691 defined $self_test_info[$index] &&
692 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
693 my $has_S = 0;
694 my $has_F = 0;
695 my $has_ML = 0;
696 my $status = "";
697 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
698 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
699 } else {
700 push(@section_headers, $x->{line});
702 my $nextline = $index;
703 while (defined $self_test_info[$nextline] &&
704 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
705 my $type = $1;
706 my $value = $2;
707 if ($type eq "S") {
708 $has_S = 1;
709 $status = $value;
710 } elsif ($type eq "F" || $type eq "N") {
711 $has_F = 1;
712 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
713 $has_ML = 1;
715 $nextline++;
717 if (!$has_ML && $status !~ /orphan|obsolete/i) {
718 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
720 if (!$has_S) {
721 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
723 if (!$has_F) {
724 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
728 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
730 my $type = $1;
731 my $value = $2;
733 ## Filename pattern matching
734 if (($type eq "F" || $type eq "X") &&
735 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
736 $value =~ s@\.@\\\.@g; ##Convert . to \.
737 $value =~ s/\*/\.\*/g; ##Convert * to .*
738 $value =~ s/\?/\./g; ##Convert ? to .
739 ##if pattern is a directory and it lacks a trailing slash, add one
740 if ((-d $value)) {
741 $value =~ s@([^/])$@$1/@;
743 if (!grep(m@^$value@, @lsfiles)) {
744 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
747 ## Link reachability
748 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
749 $value =~ /^https?:/ &&
750 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
751 next if (grep(m@^\Q$value\E$@, @good_links));
752 my $isbad = 0;
753 if (grep(m@^\Q$value\E$@, @bad_links)) {
754 $isbad = 1;
755 } else {
756 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
757 if ($? == 0) {
758 push(@good_links, $value);
759 } else {
760 push(@bad_links, $value);
761 $isbad = 1;
764 if ($isbad) {
765 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
768 ## SCM reachability
769 } elsif ($type eq "T" &&
770 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
771 next if (grep(m@^\Q$value\E$@, @good_links));
772 my $isbad = 0;
773 if (grep(m@^\Q$value\E$@, @bad_links)) {
774 $isbad = 1;
775 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
776 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
777 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
778 my $url = $1;
779 my $branch = "";
780 $branch = $3 if $3;
781 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
782 if ($? == 0) {
783 push(@good_links, $value);
784 } else {
785 push(@bad_links, $value);
786 $isbad = 1;
788 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
789 my $url = $1;
790 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
791 if ($? == 0) {
792 push(@good_links, $value);
793 } else {
794 push(@bad_links, $value);
795 $isbad = 1;
798 if ($isbad) {
799 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
805 sub ignore_email_address {
806 my ($address) = @_;
808 foreach my $ignore (@ignore_emails) {
809 return 1 if ($ignore eq $address);
812 return 0;
815 sub range_is_maintained {
816 my ($start, $end) = @_;
818 for (my $i = $start; $i < $end; $i++) {
819 my $line = $typevalue[$i];
820 if ($line =~ m/^([A-Z]):\s*(.*)/) {
821 my $type = $1;
822 my $value = $2;
823 if ($type eq 'S') {
824 if ($value =~ /(maintain|support)/i) {
825 return 1;
830 return 0;
833 sub range_has_maintainer {
834 my ($start, $end) = @_;
836 for (my $i = $start; $i < $end; $i++) {
837 my $line = $typevalue[$i];
838 if ($line =~ m/^([A-Z]):\s*(.*)/) {
839 my $type = $1;
840 my $value = $2;
841 if ($type eq 'M') {
842 return 1;
846 return 0;
849 sub get_maintainers {
850 %email_hash_name = ();
851 %email_hash_address = ();
852 %commit_author_hash = ();
853 %commit_signer_hash = ();
854 @email_to = ();
855 %hash_list_to = ();
856 @list_to = ();
857 @scm = ();
858 @web = ();
859 @bug = ();
860 @subsystem = ();
861 @status = ();
862 %deduplicate_name_hash = ();
863 %deduplicate_address_hash = ();
864 if ($email_git_all_signature_types) {
865 $signature_pattern = "(.+?)[Bb][Yy]:";
866 } else {
867 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
870 # Find responsible parties
872 my %exact_pattern_match_hash = ();
874 foreach my $file (@files) {
876 my %hash;
877 my $tvi = find_first_section();
878 while ($tvi < @typevalue) {
879 my $start = find_starting_index($tvi);
880 my $end = find_ending_index($tvi);
881 my $exclude = 0;
882 my $i;
884 #Do not match excluded file patterns
886 for ($i = $start; $i < $end; $i++) {
887 my $line = $typevalue[$i];
888 if ($line =~ m/^([A-Z]):\s*(.*)/) {
889 my $type = $1;
890 my $value = $2;
891 if ($type eq 'X') {
892 if (file_match_pattern($file, $value)) {
893 $exclude = 1;
894 last;
900 if (!$exclude) {
901 for ($i = $start; $i < $end; $i++) {
902 my $line = $typevalue[$i];
903 if ($line =~ m/^([A-Z]):\s*(.*)/) {
904 my $type = $1;
905 my $value = $2;
906 if ($type eq 'F') {
907 if (file_match_pattern($file, $value)) {
908 my $value_pd = ($value =~ tr@/@@);
909 my $file_pd = ($file =~ tr@/@@);
910 $value_pd++ if (substr($value,-1,1) ne "/");
911 $value_pd = -1 if ($value =~ /^\.\*/);
912 if ($value_pd >= $file_pd &&
913 range_is_maintained($start, $end) &&
914 range_has_maintainer($start, $end)) {
915 $exact_pattern_match_hash{$file} = 1;
917 if ($pattern_depth == 0 ||
918 (($file_pd - $value_pd) < $pattern_depth)) {
919 $hash{$tvi} = $value_pd;
922 } elsif ($type eq 'N') {
923 if ($file =~ m/$value/x) {
924 $hash{$tvi} = 0;
930 $tvi = $end + 1;
933 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
934 add_categories($line, "");
935 if ($sections) {
936 my $i;
937 my $start = find_starting_index($line);
938 my $end = find_ending_index($line);
939 for ($i = $start; $i < $end; $i++) {
940 my $line = $typevalue[$i];
941 if ($line =~ /^[FX]:/) { ##Restore file patterns
942 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
943 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
944 $line =~ s/\\\./\./g; ##Convert \. to .
945 $line =~ s/\.\*/\*/g; ##Convert .* to *
947 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
948 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
949 print("$line\n");
952 print("\n");
956 maintainers_in_file($file);
959 if ($keywords) {
960 @keyword_tvi = sort_and_uniq(@keyword_tvi);
961 foreach my $line (@keyword_tvi) {
962 add_categories($line, ":Keyword:$keyword_hash{$line}");
966 foreach my $email (@email_to, @list_to) {
967 $email->[0] = deduplicate_email($email->[0]);
970 foreach my $file (@files) {
971 if ($email &&
972 ($email_git ||
973 ($email_git_fallback &&
974 $file !~ /MAINTAINERS$/ &&
975 !$exact_pattern_match_hash{$file}))) {
976 vcs_file_signoffs($file);
978 if ($email && $email_git_blame) {
979 vcs_file_blame($file);
983 if ($email) {
984 foreach my $chief (@penguin_chief) {
985 if ($chief =~ m/^(.*):(.*)/) {
986 my $email_address;
988 $email_address = format_email($1, $2, $email_usename);
989 if ($email_git_penguin_chiefs) {
990 push(@email_to, [$email_address, 'chief penguin']);
991 } else {
992 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
997 foreach my $email (@file_emails) {
998 $email = mailmap_email($email);
999 my ($name, $address) = parse_email($email);
1001 my $tmp_email = format_email($name, $address, $email_usename);
1002 push_email_address($tmp_email, '');
1003 add_role($tmp_email, 'in file');
1007 foreach my $fix (@fixes) {
1008 vcs_add_commit_signers($fix, "blamed_fixes");
1011 my @to = ();
1012 if ($email || $email_list) {
1013 if ($email) {
1014 @to = (@to, @email_to);
1016 if ($email_list) {
1017 @to = (@to, @list_to);
1021 if ($interactive) {
1022 @to = interactive_get_maintainers(\@to);
1025 return @to;
1028 sub file_match_pattern {
1029 my ($file, $pattern) = @_;
1030 if (substr($pattern, -1) eq "/") {
1031 if ($file =~ m@^$pattern@) {
1032 return 1;
1034 } else {
1035 if ($file =~ m@^$pattern@) {
1036 my $s1 = ($file =~ tr@/@@);
1037 my $s2 = ($pattern =~ tr@/@@);
1038 if ($s1 == $s2) {
1039 return 1;
1043 return 0;
1046 sub usage {
1047 print <<EOT;
1048 usage: $P [options] patchfile
1049 $P [options] -f file|directory
1050 version: $V
1052 MAINTAINER field selection options:
1053 --email => print email address(es) if any
1054 --git => include recent git \*-by: signers
1055 --git-all-signature-types => include signers regardless of signature type
1056 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
1057 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
1058 --git-chief-penguins => include ${penguin_chiefs}
1059 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1060 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1061 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
1062 --git-blame => use git blame to find modified commits for patch or file
1063 --git-blame-signatures => when used with --git-blame, also include all commit signers
1064 --git-since => git history to use (default: $email_git_since)
1065 --hg-since => hg history to use (default: $email_hg_since)
1066 --interactive => display a menu (mostly useful if used with the --git option)
1067 --m => include maintainer(s) if any
1068 --r => include reviewer(s) if any
1069 --n => include name 'Full Name <addr\@domain.tld>'
1070 --l => include list(s) if any
1071 --moderated => include moderated lists(s) if any (default: true)
1072 --s => include subscriber only list(s) if any (default: false)
1073 --remove-duplicates => minimize duplicate email names/addresses
1074 --roles => show roles (status:subsystem, git-signer, list, etc...)
1075 --rolestats => show roles and statistics (commits/total_commits, %)
1076 --file-emails => add email addresses found in -f file (default: 0 (off))
1077 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
1078 --scm => print SCM tree(s) if any
1079 --status => print status if any
1080 --subsystem => print subsystem name if any
1081 --web => print website(s) if any
1082 --bug => print bug reporting info if any
1084 Output type options:
1085 --separator [, ] => separator for multiple entries on 1 line
1086 using --separator also sets --nomultiline if --separator is not [, ]
1087 --multiline => print 1 entry per line
1089 Other options:
1090 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
1091 --keywords => scan patch for keywords (default: $keywords)
1092 --keywords-in-file => scan file for keywords (default: $keywords_in_file)
1093 --sections => print all of the subsystem sections with pattern matches
1094 --letters => print all matching 'letter' types from all matching sections
1095 --mailmap => use .mailmap file (default: $email_use_mailmap)
1096 --no-tree => run without a kernel tree
1097 --self-test => show potential issues with MAINTAINERS file content
1098 --version => show version
1099 --help => show this help information
1101 Default options:
1102 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1103 --pattern-depth=0 --remove-duplicates --rolestats --keywords]
1105 Notes:
1106 Using "-f directory" may give unexpected results:
1107 Used with "--git", git signators for _all_ files in and below
1108 directory are examined as git recurses directories.
1109 Any specified X: (exclude) pattern matches are _not_ ignored.
1110 Used with "--nogit", directory is used as a pattern match,
1111 no individual file within the directory or subdirectory
1112 is matched.
1113 Used with "--git-blame", does not iterate all files in directory
1114 Using "--git-blame" is slow and may add old committers and authors
1115 that are no longer active maintainers to the output.
1116 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1117 other automated tools that expect only ["name"] <email address>
1118 may not work because of additional output after <email address>.
1119 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1120 not the percentage of the entire file authored. # of commits is
1121 not a good measure of amount of code authored. 1 major commit may
1122 contain a thousand lines, 5 trivial commits may modify a single line.
1123 If git is not installed, but mercurial (hg) is installed and an .hg
1124 repository exists, the following options apply to mercurial:
1125 --git,
1126 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1127 --git-blame
1128 Use --hg-since not --git-since to control date selection
1129 File ".get_maintainer.conf", if it exists in the linux kernel source root
1130 directory, can change whatever get_maintainer defaults are desired.
1131 Entries in this file can be any command line argument.
1132 This file is prepended to any additional command line arguments.
1133 Multiple lines and # comments are allowed.
1134 Most options have both positive and negative forms.
1135 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1140 sub top_of_kernel_tree {
1141 my ($lk_path) = @_;
1143 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1144 $lk_path .= "/";
1146 if ( (-f "${lk_path}COPYING")
1147 && (-f "${lk_path}CREDITS")
1148 && (-f "${lk_path}Kbuild")
1149 && (-e "${lk_path}MAINTAINERS")
1150 && (-f "${lk_path}Makefile")
1151 && (-f "${lk_path}README")
1152 && (-d "${lk_path}Documentation")
1153 && (-d "${lk_path}arch")
1154 && (-d "${lk_path}include")
1155 && (-d "${lk_path}drivers")
1156 && (-d "${lk_path}fs")
1157 && (-d "${lk_path}init")
1158 && (-d "${lk_path}ipc")
1159 && (-d "${lk_path}kernel")
1160 && (-d "${lk_path}lib")
1161 && (-d "${lk_path}scripts")) {
1162 return 1;
1164 return 0;
1167 sub escape_name {
1168 my ($name) = @_;
1170 if ($name =~ /[^\w \-]/ai) { ##has "must quote" chars
1171 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1172 $name = "\"$name\"";
1175 return $name;
1178 sub parse_email {
1179 my ($formatted_email) = @_;
1181 my $name = "";
1182 my $address = "";
1184 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
1185 $name = $1;
1186 $address = $2;
1187 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
1188 $address = $1;
1189 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
1190 $address = $1;
1193 $name =~ s/^\s+|\s+$//g;
1194 $name =~ s/^\"|\"$//g;
1195 $name = escape_name($name);
1196 $address =~ s/^\s+|\s+$//g;
1198 return ($name, $address);
1201 sub format_email {
1202 my ($name, $address, $usename) = @_;
1204 my $formatted_email;
1206 $name =~ s/^\s+|\s+$//g;
1207 $name =~ s/^\"|\"$//g;
1208 $name = escape_name($name);
1209 $address =~ s/^\s+|\s+$//g;
1211 if ($usename) {
1212 if ("$name" eq "") {
1213 $formatted_email = "$address";
1214 } else {
1215 $formatted_email = "$name <$address>";
1217 } else {
1218 $formatted_email = $address;
1221 return $formatted_email;
1224 sub find_first_section {
1225 my $index = 0;
1227 while ($index < @typevalue) {
1228 my $tv = $typevalue[$index];
1229 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1230 last;
1232 $index++;
1235 return $index;
1238 sub find_starting_index {
1239 my ($index) = @_;
1241 while ($index > 0) {
1242 my $tv = $typevalue[$index];
1243 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1244 last;
1246 $index--;
1249 return $index;
1252 sub find_ending_index {
1253 my ($index) = @_;
1255 while ($index < @typevalue) {
1256 my $tv = $typevalue[$index];
1257 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1258 last;
1260 $index++;
1263 return $index;
1266 sub get_subsystem_name {
1267 my ($index) = @_;
1269 my $start = find_starting_index($index);
1271 my $subsystem = $typevalue[$start];
1272 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1273 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1274 $subsystem =~ s/\s*$//;
1275 $subsystem = $subsystem . "...";
1277 return $subsystem;
1280 sub get_maintainer_role {
1281 my ($index) = @_;
1283 my $i;
1284 my $start = find_starting_index($index);
1285 my $end = find_ending_index($index);
1287 my $role = "unknown";
1288 my $subsystem = get_subsystem_name($index);
1290 for ($i = $start + 1; $i < $end; $i++) {
1291 my $tv = $typevalue[$i];
1292 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1293 my $ptype = $1;
1294 my $pvalue = $2;
1295 if ($ptype eq "S") {
1296 $role = $pvalue;
1301 $role = lc($role);
1302 if ($role eq "supported") {
1303 $role = "supporter";
1304 } elsif ($role eq "maintained") {
1305 $role = "maintainer";
1306 } elsif ($role eq "odd fixes") {
1307 $role = "odd fixer";
1308 } elsif ($role eq "orphan") {
1309 $role = "orphan minder";
1310 } elsif ($role eq "obsolete") {
1311 $role = "obsolete minder";
1312 } elsif ($role eq "buried alive in reporters") {
1313 $role = "chief penguin";
1316 return $role . ":" . $subsystem;
1319 sub get_list_role {
1320 my ($index) = @_;
1322 my $subsystem = get_subsystem_name($index);
1324 if ($subsystem eq "THE REST") {
1325 $subsystem = "";
1328 return $subsystem;
1331 sub add_categories {
1332 my ($index, $suffix) = @_;
1334 my $i;
1335 my $start = find_starting_index($index);
1336 my $end = find_ending_index($index);
1338 push(@subsystem, $typevalue[$start]);
1340 for ($i = $start + 1; $i < $end; $i++) {
1341 my $tv = $typevalue[$i];
1342 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1343 my $ptype = $1;
1344 my $pvalue = $2;
1345 if ($ptype eq "L") {
1346 my $list_address = $pvalue;
1347 my $list_additional = "";
1348 my $list_role = get_list_role($i);
1350 if ($list_role ne "") {
1351 $list_role = ":" . $list_role;
1353 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1354 $list_address = $1;
1355 $list_additional = $2;
1357 if ($list_additional =~ m/subscribers-only/) {
1358 if ($email_subscriber_list) {
1359 if (!$hash_list_to{lc($list_address)}) {
1360 $hash_list_to{lc($list_address)} = 1;
1361 push(@list_to, [$list_address,
1362 "subscriber list${list_role}" . $suffix]);
1365 } else {
1366 if ($email_list) {
1367 if (!$hash_list_to{lc($list_address)}) {
1368 if ($list_additional =~ m/moderated/) {
1369 if ($email_moderated_list) {
1370 $hash_list_to{lc($list_address)} = 1;
1371 push(@list_to, [$list_address,
1372 "moderated list${list_role}" . $suffix]);
1374 } else {
1375 $hash_list_to{lc($list_address)} = 1;
1376 push(@list_to, [$list_address,
1377 "open list${list_role}" . $suffix]);
1382 } elsif ($ptype eq "M") {
1383 if ($email_maintainer) {
1384 my $role = get_maintainer_role($i);
1385 push_email_addresses($pvalue, $role . $suffix);
1387 } elsif ($ptype eq "R") {
1388 if ($email_reviewer) {
1389 my $subsystem = get_subsystem_name($i);
1390 push_email_addresses($pvalue, "reviewer:$subsystem" . $suffix);
1392 } elsif ($ptype eq "T") {
1393 push(@scm, $pvalue . $suffix);
1394 } elsif ($ptype eq "W") {
1395 push(@web, $pvalue . $suffix);
1396 } elsif ($ptype eq "B") {
1397 push(@bug, $pvalue . $suffix);
1398 } elsif ($ptype eq "S") {
1399 push(@status, $pvalue . $suffix);
1405 sub email_inuse {
1406 my ($name, $address) = @_;
1408 return 1 if (($name eq "") && ($address eq ""));
1409 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1410 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1412 return 0;
1415 sub push_email_address {
1416 my ($line, $role) = @_;
1418 my ($name, $address) = parse_email($line);
1420 if ($address eq "") {
1421 return 0;
1424 if (!$email_remove_duplicates) {
1425 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1426 } elsif (!email_inuse($name, $address)) {
1427 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1428 $email_hash_name{lc($name)}++ if ($name ne "");
1429 $email_hash_address{lc($address)}++;
1432 return 1;
1435 sub push_email_addresses {
1436 my ($address, $role) = @_;
1438 my @address_list = ();
1440 if (rfc822_valid($address)) {
1441 push_email_address($address, $role);
1442 } elsif (@address_list = rfc822_validlist($address)) {
1443 my $array_count = shift(@address_list);
1444 while (my $entry = shift(@address_list)) {
1445 push_email_address($entry, $role);
1447 } else {
1448 if (!push_email_address($address, $role)) {
1449 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1454 sub add_role {
1455 my ($line, $role) = @_;
1457 my ($name, $address) = parse_email($line);
1458 my $email = format_email($name, $address, $email_usename);
1460 foreach my $entry (@email_to) {
1461 if ($email_remove_duplicates) {
1462 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1463 if (($name eq $entry_name || $address eq $entry_address)
1464 && ($role eq "" || !($entry->[1] =~ m/$role/))
1466 if ($entry->[1] eq "") {
1467 $entry->[1] = "$role";
1468 } else {
1469 $entry->[1] = "$entry->[1],$role";
1472 } else {
1473 if ($email eq $entry->[0]
1474 && ($role eq "" || !($entry->[1] =~ m/$role/))
1476 if ($entry->[1] eq "") {
1477 $entry->[1] = "$role";
1478 } else {
1479 $entry->[1] = "$entry->[1],$role";
1486 sub which {
1487 my ($bin) = @_;
1489 foreach my $path (split(/:/, $ENV{PATH})) {
1490 if (-e "$path/$bin") {
1491 return "$path/$bin";
1495 return "";
1498 sub which_conf {
1499 my ($conf) = @_;
1501 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1502 if (-e "$path/$conf") {
1503 return "$path/$conf";
1507 return "";
1510 sub mailmap_email {
1511 my ($line) = @_;
1513 my ($name, $address) = parse_email($line);
1514 my $email = format_email($name, $address, 1);
1515 my $real_name = $name;
1516 my $real_address = $address;
1518 if (exists $mailmap->{names}->{$email} ||
1519 exists $mailmap->{addresses}->{$email}) {
1520 if (exists $mailmap->{names}->{$email}) {
1521 $real_name = $mailmap->{names}->{$email};
1523 if (exists $mailmap->{addresses}->{$email}) {
1524 $real_address = $mailmap->{addresses}->{$email};
1526 } else {
1527 if (exists $mailmap->{names}->{$address}) {
1528 $real_name = $mailmap->{names}->{$address};
1530 if (exists $mailmap->{addresses}->{$address}) {
1531 $real_address = $mailmap->{addresses}->{$address};
1534 return format_email($real_name, $real_address, 1);
1537 sub mailmap {
1538 my (@addresses) = @_;
1540 my @mapped_emails = ();
1541 foreach my $line (@addresses) {
1542 push(@mapped_emails, mailmap_email($line));
1544 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1545 return @mapped_emails;
1548 sub merge_by_realname {
1549 my %address_map;
1550 my (@emails) = @_;
1552 foreach my $email (@emails) {
1553 my ($name, $address) = parse_email($email);
1554 if (exists $address_map{$name}) {
1555 $address = $address_map{$name};
1556 $email = format_email($name, $address, 1);
1557 } else {
1558 $address_map{$name} = $address;
1563 sub git_execute_cmd {
1564 my ($cmd) = @_;
1565 my @lines = ();
1567 my $output = `$cmd`;
1568 $output =~ s/^\s*//gm;
1569 @lines = split("\n", $output);
1571 return @lines;
1574 sub hg_execute_cmd {
1575 my ($cmd) = @_;
1576 my @lines = ();
1578 my $output = `$cmd`;
1579 @lines = split("\n", $output);
1581 return @lines;
1584 sub extract_formatted_signatures {
1585 my (@signature_lines) = @_;
1587 my @type = @signature_lines;
1589 s/\s*(.*):.*/$1/ for (@type);
1591 # cut -f2- -d":"
1592 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1594 ## Reformat email addresses (with names) to avoid badly written signatures
1596 foreach my $signer (@signature_lines) {
1597 $signer = deduplicate_email($signer);
1600 return (\@type, \@signature_lines);
1603 sub vcs_find_signers {
1604 my ($cmd, $file) = @_;
1605 my $commits;
1606 my @lines = ();
1607 my @signatures = ();
1608 my @authors = ();
1609 my @stats = ();
1611 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1613 my $pattern = $VCS_cmds{"commit_pattern"};
1614 my $author_pattern = $VCS_cmds{"author_pattern"};
1615 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1617 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1619 $commits = grep(/$pattern/, @lines); # of commits
1621 @authors = grep(/$author_pattern/, @lines);
1622 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1623 @stats = grep(/$stat_pattern/, @lines);
1625 # print("stats: <@stats>\n");
1627 return (0, \@signatures, \@authors, \@stats) if !@signatures;
1629 save_commits_by_author(@lines) if ($interactive);
1630 save_commits_by_signer(@lines) if ($interactive);
1632 if (!$email_git_penguin_chiefs) {
1633 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1636 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1637 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1639 return ($commits, $signers_ref, $authors_ref, \@stats);
1642 sub vcs_find_author {
1643 my ($cmd) = @_;
1644 my @lines = ();
1646 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1648 if (!$email_git_penguin_chiefs) {
1649 @lines = grep(!/${penguin_chiefs}/i, @lines);
1652 return @lines if !@lines;
1654 my @authors = ();
1655 foreach my $line (@lines) {
1656 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1657 my $author = $1;
1658 my ($name, $address) = parse_email($author);
1659 $author = format_email($name, $address, 1);
1660 push(@authors, $author);
1664 save_commits_by_author(@lines) if ($interactive);
1665 save_commits_by_signer(@lines) if ($interactive);
1667 return @authors;
1670 sub vcs_save_commits {
1671 my ($cmd) = @_;
1672 my @lines = ();
1673 my @commits = ();
1675 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1677 foreach my $line (@lines) {
1678 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1679 push(@commits, $1);
1683 return @commits;
1686 sub vcs_blame {
1687 my ($file) = @_;
1688 my $cmd;
1689 my @commits = ();
1691 return @commits if (!(-f $file));
1693 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1694 my @all_commits = ();
1696 $cmd = $VCS_cmds{"blame_file_cmd"};
1697 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1698 @all_commits = vcs_save_commits($cmd);
1700 foreach my $file_range_diff (@range) {
1701 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1702 my $diff_file = $1;
1703 my $diff_start = $2;
1704 my $diff_length = $3;
1705 next if ("$file" ne "$diff_file");
1706 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1707 push(@commits, $all_commits[$i]);
1710 } elsif (@range) {
1711 foreach my $file_range_diff (@range) {
1712 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1713 my $diff_file = $1;
1714 my $diff_start = $2;
1715 my $diff_length = $3;
1716 next if ("$file" ne "$diff_file");
1717 $cmd = $VCS_cmds{"blame_range_cmd"};
1718 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1719 push(@commits, vcs_save_commits($cmd));
1721 } else {
1722 $cmd = $VCS_cmds{"blame_file_cmd"};
1723 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1724 @commits = vcs_save_commits($cmd);
1727 foreach my $commit (@commits) {
1728 $commit =~ s/^\^//g;
1731 return @commits;
1734 my $printed_novcs = 0;
1735 sub vcs_exists {
1736 %VCS_cmds = %VCS_cmds_git;
1737 return 1 if eval $VCS_cmds{"available"};
1738 %VCS_cmds = %VCS_cmds_hg;
1739 return 2 if eval $VCS_cmds{"available"};
1740 %VCS_cmds = ();
1741 if (!$printed_novcs && $email_git) {
1742 warn("$P: No supported VCS found. Add --nogit to options?\n");
1743 warn("Using a git repository produces better results.\n");
1744 warn("Try Linus Torvalds' latest git repository using:\n");
1745 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1746 $printed_novcs = 1;
1748 return 0;
1751 sub vcs_is_git {
1752 vcs_exists();
1753 return $vcs_used == 1;
1756 sub vcs_is_hg {
1757 return $vcs_used == 2;
1760 sub vcs_add_commit_signers {
1761 return if (!vcs_exists());
1763 my ($commit, $desc) = @_;
1764 my $commit_count = 0;
1765 my $commit_authors_ref;
1766 my $commit_signers_ref;
1767 my $stats_ref;
1768 my @commit_authors = ();
1769 my @commit_signers = ();
1770 my $cmd;
1772 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1773 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1775 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1776 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1777 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1779 foreach my $signer (@commit_signers) {
1780 $signer = deduplicate_email($signer);
1783 vcs_assign($desc, 1, @commit_signers);
1786 sub interactive_get_maintainers {
1787 my ($list_ref) = @_;
1788 my @list = @$list_ref;
1790 vcs_exists();
1792 my %selected;
1793 my %authored;
1794 my %signed;
1795 my $count = 0;
1796 my $maintained = 0;
1797 foreach my $entry (@list) {
1798 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1799 $selected{$count} = 1;
1800 $authored{$count} = 0;
1801 $signed{$count} = 0;
1802 $count++;
1805 #menu loop
1806 my $done = 0;
1807 my $print_options = 0;
1808 my $redraw = 1;
1809 while (!$done) {
1810 $count = 0;
1811 if ($redraw) {
1812 printf STDERR "\n%1s %2s %-65s",
1813 "*", "#", "email/list and role:stats";
1814 if ($email_git ||
1815 ($email_git_fallback && !$maintained) ||
1816 $email_git_blame) {
1817 print STDERR "auth sign";
1819 print STDERR "\n";
1820 foreach my $entry (@list) {
1821 my $email = $entry->[0];
1822 my $role = $entry->[1];
1823 my $sel = "";
1824 $sel = "*" if ($selected{$count});
1825 my $commit_author = $commit_author_hash{$email};
1826 my $commit_signer = $commit_signer_hash{$email};
1827 my $authored = 0;
1828 my $signed = 0;
1829 $authored++ for (@{$commit_author});
1830 $signed++ for (@{$commit_signer});
1831 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1832 printf STDERR "%4d %4d", $authored, $signed
1833 if ($authored > 0 || $signed > 0);
1834 printf STDERR "\n %s\n", $role;
1835 if ($authored{$count}) {
1836 my $commit_author = $commit_author_hash{$email};
1837 foreach my $ref (@{$commit_author}) {
1838 print STDERR " Author: @{$ref}[1]\n";
1841 if ($signed{$count}) {
1842 my $commit_signer = $commit_signer_hash{$email};
1843 foreach my $ref (@{$commit_signer}) {
1844 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1848 $count++;
1851 my $date_ref = \$email_git_since;
1852 $date_ref = \$email_hg_since if (vcs_is_hg());
1853 if ($print_options) {
1854 $print_options = 0;
1855 if (vcs_exists()) {
1856 print STDERR <<EOT
1858 Version Control options:
1859 g use git history [$email_git]
1860 gf use git-fallback [$email_git_fallback]
1861 b use git blame [$email_git_blame]
1862 bs use blame signatures [$email_git_blame_signatures]
1863 c# minimum commits [$email_git_min_signatures]
1864 %# min percent [$email_git_min_percent]
1865 d# history to use [$$date_ref]
1866 x# max maintainers [$email_git_max_maintainers]
1867 t all signature types [$email_git_all_signature_types]
1868 m use .mailmap [$email_use_mailmap]
1871 print STDERR <<EOT
1873 Additional options:
1874 0 toggle all
1875 tm toggle maintainers
1876 tg toggle git entries
1877 tl toggle open list entries
1878 ts toggle subscriber list entries
1879 f emails in file [$email_file_emails]
1880 k keywords in file [$keywords]
1881 r remove duplicates [$email_remove_duplicates]
1882 p# pattern match depth [$pattern_depth]
1885 print STDERR
1886 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1888 my $input = <STDIN>;
1889 chomp($input);
1891 $redraw = 1;
1892 my $rerun = 0;
1893 my @wish = split(/[, ]+/, $input);
1894 foreach my $nr (@wish) {
1895 $nr = lc($nr);
1896 my $sel = substr($nr, 0, 1);
1897 my $str = substr($nr, 1);
1898 my $val = 0;
1899 $val = $1 if $str =~ /^(\d+)$/;
1901 if ($sel eq "y") {
1902 $interactive = 0;
1903 $done = 1;
1904 $output_rolestats = 0;
1905 $output_roles = 0;
1906 last;
1907 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1908 $selected{$nr - 1} = !$selected{$nr - 1};
1909 } elsif ($sel eq "*" || $sel eq '^') {
1910 my $toggle = 0;
1911 $toggle = 1 if ($sel eq '*');
1912 for (my $i = 0; $i < $count; $i++) {
1913 $selected{$i} = $toggle;
1915 } elsif ($sel eq "0") {
1916 for (my $i = 0; $i < $count; $i++) {
1917 $selected{$i} = !$selected{$i};
1919 } elsif ($sel eq "t") {
1920 if (lc($str) eq "m") {
1921 for (my $i = 0; $i < $count; $i++) {
1922 $selected{$i} = !$selected{$i}
1923 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1925 } elsif (lc($str) eq "g") {
1926 for (my $i = 0; $i < $count; $i++) {
1927 $selected{$i} = !$selected{$i}
1928 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1930 } elsif (lc($str) eq "l") {
1931 for (my $i = 0; $i < $count; $i++) {
1932 $selected{$i} = !$selected{$i}
1933 if ($list[$i]->[1] =~ /^(open list)/i);
1935 } elsif (lc($str) eq "s") {
1936 for (my $i = 0; $i < $count; $i++) {
1937 $selected{$i} = !$selected{$i}
1938 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1941 } elsif ($sel eq "a") {
1942 if ($val > 0 && $val <= $count) {
1943 $authored{$val - 1} = !$authored{$val - 1};
1944 } elsif ($str eq '*' || $str eq '^') {
1945 my $toggle = 0;
1946 $toggle = 1 if ($str eq '*');
1947 for (my $i = 0; $i < $count; $i++) {
1948 $authored{$i} = $toggle;
1951 } elsif ($sel eq "s") {
1952 if ($val > 0 && $val <= $count) {
1953 $signed{$val - 1} = !$signed{$val - 1};
1954 } elsif ($str eq '*' || $str eq '^') {
1955 my $toggle = 0;
1956 $toggle = 1 if ($str eq '*');
1957 for (my $i = 0; $i < $count; $i++) {
1958 $signed{$i} = $toggle;
1961 } elsif ($sel eq "o") {
1962 $print_options = 1;
1963 $redraw = 1;
1964 } elsif ($sel eq "g") {
1965 if ($str eq "f") {
1966 bool_invert(\$email_git_fallback);
1967 } else {
1968 bool_invert(\$email_git);
1970 $rerun = 1;
1971 } elsif ($sel eq "b") {
1972 if ($str eq "s") {
1973 bool_invert(\$email_git_blame_signatures);
1974 } else {
1975 bool_invert(\$email_git_blame);
1977 $rerun = 1;
1978 } elsif ($sel eq "c") {
1979 if ($val > 0) {
1980 $email_git_min_signatures = $val;
1981 $rerun = 1;
1983 } elsif ($sel eq "x") {
1984 if ($val > 0) {
1985 $email_git_max_maintainers = $val;
1986 $rerun = 1;
1988 } elsif ($sel eq "%") {
1989 if ($str ne "" && $val >= 0) {
1990 $email_git_min_percent = $val;
1991 $rerun = 1;
1993 } elsif ($sel eq "d") {
1994 if (vcs_is_git()) {
1995 $email_git_since = $str;
1996 } elsif (vcs_is_hg()) {
1997 $email_hg_since = $str;
1999 $rerun = 1;
2000 } elsif ($sel eq "t") {
2001 bool_invert(\$email_git_all_signature_types);
2002 $rerun = 1;
2003 } elsif ($sel eq "f") {
2004 bool_invert(\$email_file_emails);
2005 $rerun = 1;
2006 } elsif ($sel eq "r") {
2007 bool_invert(\$email_remove_duplicates);
2008 $rerun = 1;
2009 } elsif ($sel eq "m") {
2010 bool_invert(\$email_use_mailmap);
2011 read_mailmap();
2012 $rerun = 1;
2013 } elsif ($sel eq "k") {
2014 bool_invert(\$keywords);
2015 $rerun = 1;
2016 } elsif ($sel eq "p") {
2017 if ($str ne "" && $val >= 0) {
2018 $pattern_depth = $val;
2019 $rerun = 1;
2021 } elsif ($sel eq "h" || $sel eq "?") {
2022 print STDERR <<EOT
2024 Interactive mode allows you to select the various maintainers, submitters,
2025 commit signers and mailing lists that could be CC'd on a patch.
2027 Any *'d entry is selected.
2029 If you have git or hg installed, you can choose to summarize the commit
2030 history of files in the patch. Also, each line of the current file can
2031 be matched to its commit author and that commits signers with blame.
2033 Various knobs exist to control the length of time for active commit
2034 tracking, the maximum number of commit authors and signers to add,
2035 and such.
2037 Enter selections at the prompt until you are satisfied that the selected
2038 maintainers are appropriate. You may enter multiple selections separated
2039 by either commas or spaces.
2042 } else {
2043 print STDERR "invalid option: '$nr'\n";
2044 $redraw = 0;
2047 if ($rerun) {
2048 print STDERR "git-blame can be very slow, please have patience..."
2049 if ($email_git_blame);
2050 goto &get_maintainers;
2054 #drop not selected entries
2055 $count = 0;
2056 my @new_emailto = ();
2057 foreach my $entry (@list) {
2058 if ($selected{$count}) {
2059 push(@new_emailto, $list[$count]);
2061 $count++;
2063 return @new_emailto;
2066 sub bool_invert {
2067 my ($bool_ref) = @_;
2069 if ($$bool_ref) {
2070 $$bool_ref = 0;
2071 } else {
2072 $$bool_ref = 1;
2076 sub deduplicate_email {
2077 my ($email) = @_;
2079 my $matched = 0;
2080 my ($name, $address) = parse_email($email);
2081 $email = format_email($name, $address, 1);
2082 $email = mailmap_email($email);
2084 return $email if (!$email_remove_duplicates);
2086 ($name, $address) = parse_email($email);
2088 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
2089 $name = $deduplicate_name_hash{lc($name)}->[0];
2090 $address = $deduplicate_name_hash{lc($name)}->[1];
2091 $matched = 1;
2092 } elsif ($deduplicate_address_hash{lc($address)}) {
2093 $name = $deduplicate_address_hash{lc($address)}->[0];
2094 $address = $deduplicate_address_hash{lc($address)}->[1];
2095 $matched = 1;
2097 if (!$matched) {
2098 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2099 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2101 $email = format_email($name, $address, 1);
2102 $email = mailmap_email($email);
2103 return $email;
2106 sub save_commits_by_author {
2107 my (@lines) = @_;
2109 my @authors = ();
2110 my @commits = ();
2111 my @subjects = ();
2113 foreach my $line (@lines) {
2114 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2115 my $author = $1;
2116 $author = deduplicate_email($author);
2117 push(@authors, $author);
2119 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2120 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2123 for (my $i = 0; $i < @authors; $i++) {
2124 my $exists = 0;
2125 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2126 if (@{$ref}[0] eq $commits[$i] &&
2127 @{$ref}[1] eq $subjects[$i]) {
2128 $exists = 1;
2129 last;
2132 if (!$exists) {
2133 push(@{$commit_author_hash{$authors[$i]}},
2134 [ ($commits[$i], $subjects[$i]) ]);
2139 sub save_commits_by_signer {
2140 my (@lines) = @_;
2142 my $commit = "";
2143 my $subject = "";
2145 foreach my $line (@lines) {
2146 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2147 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2148 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2149 my @signatures = ($line);
2150 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2151 my @types = @$types_ref;
2152 my @signers = @$signers_ref;
2154 my $type = $types[0];
2155 my $signer = $signers[0];
2157 $signer = deduplicate_email($signer);
2159 my $exists = 0;
2160 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2161 if (@{$ref}[0] eq $commit &&
2162 @{$ref}[1] eq $subject &&
2163 @{$ref}[2] eq $type) {
2164 $exists = 1;
2165 last;
2168 if (!$exists) {
2169 push(@{$commit_signer_hash{$signer}},
2170 [ ($commit, $subject, $type) ]);
2176 sub vcs_assign {
2177 my ($role, $divisor, @lines) = @_;
2179 my %hash;
2180 my $count = 0;
2182 return if (@lines <= 0);
2184 if ($divisor <= 0) {
2185 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
2186 $divisor = 1;
2189 @lines = mailmap(@lines);
2191 return if (@lines <= 0);
2193 @lines = sort(@lines);
2195 # uniq -c
2196 $hash{$_}++ for @lines;
2198 # sort -rn
2199 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2200 my $sign_offs = $hash{$line};
2201 my $percent = $sign_offs * 100 / $divisor;
2203 $percent = 100 if ($percent > 100);
2204 next if (ignore_email_address($line));
2205 $count++;
2206 last if ($sign_offs < $email_git_min_signatures ||
2207 $count > $email_git_max_maintainers ||
2208 $percent < $email_git_min_percent);
2209 push_email_address($line, '');
2210 if ($output_rolestats) {
2211 my $fmt_percent = sprintf("%.0f", $percent);
2212 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2213 } else {
2214 add_role($line, $role);
2219 sub vcs_file_signoffs {
2220 my ($file) = @_;
2222 my $authors_ref;
2223 my $signers_ref;
2224 my $stats_ref;
2225 my @authors = ();
2226 my @signers = ();
2227 my @stats = ();
2228 my $commits;
2230 $vcs_used = vcs_exists();
2231 return if (!$vcs_used);
2233 my $cmd = $VCS_cmds{"find_signers_cmd"};
2234 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2236 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2238 @signers = @{$signers_ref} if defined $signers_ref;
2239 @authors = @{$authors_ref} if defined $authors_ref;
2240 @stats = @{$stats_ref} if defined $stats_ref;
2242 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2244 foreach my $signer (@signers) {
2245 $signer = deduplicate_email($signer);
2248 vcs_assign("commit_signer", $commits, @signers);
2249 vcs_assign("authored", $commits, @authors);
2250 if ($#authors == $#stats) {
2251 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2252 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2254 my $added = 0;
2255 my $deleted = 0;
2256 for (my $i = 0; $i <= $#stats; $i++) {
2257 if ($stats[$i] =~ /$stat_pattern/) {
2258 $added += $1;
2259 $deleted += $2;
2262 my @tmp_authors = uniq(@authors);
2263 foreach my $author (@tmp_authors) {
2264 $author = deduplicate_email($author);
2266 @tmp_authors = uniq(@tmp_authors);
2267 my @list_added = ();
2268 my @list_deleted = ();
2269 foreach my $author (@tmp_authors) {
2270 my $auth_added = 0;
2271 my $auth_deleted = 0;
2272 for (my $i = 0; $i <= $#stats; $i++) {
2273 if ($author eq deduplicate_email($authors[$i]) &&
2274 $stats[$i] =~ /$stat_pattern/) {
2275 $auth_added += $1;
2276 $auth_deleted += $2;
2279 for (my $i = 0; $i < $auth_added; $i++) {
2280 push(@list_added, $author);
2282 for (my $i = 0; $i < $auth_deleted; $i++) {
2283 push(@list_deleted, $author);
2286 vcs_assign("added_lines", $added, @list_added);
2287 vcs_assign("removed_lines", $deleted, @list_deleted);
2291 sub vcs_file_blame {
2292 my ($file) = @_;
2294 my @signers = ();
2295 my @all_commits = ();
2296 my @commits = ();
2297 my $total_commits;
2298 my $total_lines;
2300 $vcs_used = vcs_exists();
2301 return if (!$vcs_used);
2303 @all_commits = vcs_blame($file);
2304 @commits = uniq(@all_commits);
2305 $total_commits = @commits;
2306 $total_lines = @all_commits;
2308 if ($email_git_blame_signatures) {
2309 if (vcs_is_hg()) {
2310 my $commit_count;
2311 my $commit_authors_ref;
2312 my $commit_signers_ref;
2313 my $stats_ref;
2314 my @commit_authors = ();
2315 my @commit_signers = ();
2316 my $commit = join(" -r ", @commits);
2317 my $cmd;
2319 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2320 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2322 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2323 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2324 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2326 push(@signers, @commit_signers);
2327 } else {
2328 foreach my $commit (@commits) {
2329 my $commit_count;
2330 my $commit_authors_ref;
2331 my $commit_signers_ref;
2332 my $stats_ref;
2333 my @commit_authors = ();
2334 my @commit_signers = ();
2335 my $cmd;
2337 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2338 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2340 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2341 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2342 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2344 push(@signers, @commit_signers);
2349 if ($from_filename) {
2350 if ($output_rolestats) {
2351 my @blame_signers;
2352 if (vcs_is_hg()) {{ # Double brace for last exit
2353 my $commit_count;
2354 my @commit_signers = ();
2355 @commits = uniq(@commits);
2356 @commits = sort(@commits);
2357 my $commit = join(" -r ", @commits);
2358 my $cmd;
2360 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2361 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2363 my @lines = ();
2365 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2367 if (!$email_git_penguin_chiefs) {
2368 @lines = grep(!/${penguin_chiefs}/i, @lines);
2371 last if !@lines;
2373 my @authors = ();
2374 foreach my $line (@lines) {
2375 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2376 my $author = $1;
2377 $author = deduplicate_email($author);
2378 push(@authors, $author);
2382 save_commits_by_author(@lines) if ($interactive);
2383 save_commits_by_signer(@lines) if ($interactive);
2385 push(@signers, @authors);
2387 else {
2388 foreach my $commit (@commits) {
2389 my $i;
2390 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2391 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2392 my @author = vcs_find_author($cmd);
2393 next if !@author;
2395 my $formatted_author = deduplicate_email($author[0]);
2397 my $count = grep(/$commit/, @all_commits);
2398 for ($i = 0; $i < $count ; $i++) {
2399 push(@blame_signers, $formatted_author);
2403 if (@blame_signers) {
2404 vcs_assign("authored lines", $total_lines, @blame_signers);
2407 foreach my $signer (@signers) {
2408 $signer = deduplicate_email($signer);
2410 vcs_assign("commits", $total_commits, @signers);
2411 } else {
2412 foreach my $signer (@signers) {
2413 $signer = deduplicate_email($signer);
2415 vcs_assign("modified commits", $total_commits, @signers);
2419 sub vcs_file_exists {
2420 my ($file) = @_;
2422 my $exists;
2424 my $vcs_used = vcs_exists();
2425 return 0 if (!$vcs_used);
2427 my $cmd = $VCS_cmds{"file_exists_cmd"};
2428 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2429 $cmd .= " 2>&1";
2430 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2432 return 0 if ($? != 0);
2434 return $exists;
2437 sub vcs_list_files {
2438 my ($file) = @_;
2440 my @lsfiles = ();
2442 my $vcs_used = vcs_exists();
2443 return 0 if (!$vcs_used);
2445 my $cmd = $VCS_cmds{"list_files_cmd"};
2446 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2447 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2449 return () if ($? != 0);
2451 return @lsfiles;
2454 sub uniq {
2455 my (@parms) = @_;
2457 my %saw;
2458 @parms = grep(!$saw{$_}++, @parms);
2459 return @parms;
2462 sub sort_and_uniq {
2463 my (@parms) = @_;
2465 my %saw;
2466 @parms = sort @parms;
2467 @parms = grep(!$saw{$_}++, @parms);
2468 return @parms;
2471 sub clean_file_emails {
2472 my (@file_emails) = @_;
2473 my @fmt_emails = ();
2475 foreach my $email (@file_emails) {
2476 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2477 my ($name, $address) = parse_email($email);
2479 # Strip quotes for easier processing, format_email will add them back
2480 $name =~ s/^"(.*)"$/$1/;
2482 # Split into name-like parts and remove stray punctuation particles
2483 my @nw = split(/[^\p{L}\'\,\.\+-]/, $name);
2484 @nw = grep(!/^[\'\,\.\+-]$/, @nw);
2486 # Make a best effort to extract the name, and only the name, by taking
2487 # only the last two names, or in the case of obvious initials, the last
2488 # three names.
2489 if (@nw > 2) {
2490 my $first = $nw[@nw - 3];
2491 my $middle = $nw[@nw - 2];
2492 my $last = $nw[@nw - 1];
2494 if (((length($first) == 1 && $first =~ m/\p{L}/) ||
2495 (length($first) == 2 && substr($first, -1) eq ".")) ||
2496 (length($middle) == 1 ||
2497 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2498 $name = "$first $middle $last";
2499 } else {
2500 $name = "$middle $last";
2502 } else {
2503 $name = "@nw";
2506 if (substr($name, -1) =~ /[,\.]/) {
2507 $name = substr($name, 0, length($name) - 1);
2510 if (substr($name, 0, 1) =~ /[,\.]/) {
2511 $name = substr($name, 1, length($name) - 1);
2514 my $fmt_email = format_email($name, $address, $email_usename);
2515 push(@fmt_emails, $fmt_email);
2517 return @fmt_emails;
2520 sub merge_email {
2521 my @lines;
2522 my %saw;
2524 for (@_) {
2525 my ($address, $role) = @$_;
2526 if (!$saw{$address}) {
2527 if ($output_roles) {
2528 push(@lines, "$address ($role)");
2529 } else {
2530 push(@lines, $address);
2532 $saw{$address} = 1;
2536 return @lines;
2539 sub output {
2540 my (@parms) = @_;
2542 if ($output_multiline) {
2543 foreach my $line (@parms) {
2544 print("${line}\n");
2546 } else {
2547 print(join($output_separator, @parms));
2548 print("\n");
2552 my $rfc822re;
2554 sub make_rfc822re {
2555 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2556 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2557 # This regexp will only work on addresses which have had comments stripped
2558 # and replaced with rfc822_lwsp.
2560 my $specials = '()<>@,;:\\\\".\\[\\]';
2561 my $controls = '\\000-\\037\\177';
2563 my $dtext = "[^\\[\\]\\r\\\\]";
2564 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2566 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2568 # Use zero-width assertion to spot the limit of an atom. A simple
2569 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2570 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2571 my $word = "(?:$atom|$quoted_string)";
2572 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2574 my $sub_domain = "(?:$atom|$domain_literal)";
2575 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2577 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2579 my $phrase = "$word*";
2580 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2581 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2582 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2584 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2585 my $address = "(?:$mailbox|$group)";
2587 return "$rfc822_lwsp*$address";
2590 sub rfc822_strip_comments {
2591 my $s = shift;
2592 # Recursively remove comments, and replace with a single space. The simpler
2593 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2594 # chars in atoms, for example.
2596 while ($s =~ s/^((?:[^"\\]|\\.)*
2597 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2598 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2599 return $s;
2602 # valid: returns true if the parameter is an RFC822 valid address
2604 sub rfc822_valid {
2605 my $s = rfc822_strip_comments(shift);
2607 if (!$rfc822re) {
2608 $rfc822re = make_rfc822re();
2611 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2614 # validlist: In scalar context, returns true if the parameter is an RFC822
2615 # valid list of addresses.
2617 # In list context, returns an empty list on failure (an invalid
2618 # address was found); otherwise a list whose first element is the
2619 # number of addresses found and whose remaining elements are the
2620 # addresses. This is needed to disambiguate failure (invalid)
2621 # from success with no addresses found, because an empty string is
2622 # a valid list.
2624 sub rfc822_validlist {
2625 my $s = rfc822_strip_comments(shift);
2627 if (!$rfc822re) {
2628 $rfc822re = make_rfc822re();
2630 # * null list items are valid according to the RFC
2631 # * the '1' business is to aid in distinguishing failure from no results
2633 my @r;
2634 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2635 $s =~ m/^$rfc822_char*$/) {
2636 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2637 push(@r, $1);
2639 return wantarray ? (scalar(@r), @r) : 1;
2641 return wantarray ? () : 0;