Merge branch 'v6v7' into devel
[linux/fpc-iii.git] / scripts / get_maintainer.pl
blob139e0fff8e3131159d8d16046d2cf9bc72503aaa
1 #!/usr/bin/perl -w
2 # (c) 2007, Joe Perches <joe@perches.com>
3 # created from checkpatch.pl
5 # Print selected MAINTAINERS information for
6 # the files modified in a patch or for a file
8 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9 # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
11 # Licensed under the terms of the GNU GPL License version 2
13 use strict;
15 my $P = $0;
16 my $V = '0.26';
18 use Getopt::Long qw(:config no_auto_abbrev);
20 my $lk_path = "./";
21 my $email = 1;
22 my $email_usename = 1;
23 my $email_maintainer = 1;
24 my $email_list = 1;
25 my $email_subscriber_list = 0;
26 my $email_git_penguin_chiefs = 0;
27 my $email_git = 0;
28 my $email_git_all_signature_types = 0;
29 my $email_git_blame = 0;
30 my $email_git_blame_signatures = 1;
31 my $email_git_fallback = 1;
32 my $email_git_min_signatures = 1;
33 my $email_git_max_maintainers = 5;
34 my $email_git_min_percent = 5;
35 my $email_git_since = "1-year-ago";
36 my $email_hg_since = "-365";
37 my $interactive = 0;
38 my $email_remove_duplicates = 1;
39 my $email_use_mailmap = 1;
40 my $output_multiline = 1;
41 my $output_separator = ", ";
42 my $output_roles = 0;
43 my $output_rolestats = 1;
44 my $scm = 0;
45 my $web = 0;
46 my $subsystem = 0;
47 my $status = 0;
48 my $keywords = 1;
49 my $sections = 0;
50 my $file_emails = 0;
51 my $from_filename = 0;
52 my $pattern_depth = 0;
53 my $version = 0;
54 my $help = 0;
56 my $vcs_used = 0;
58 my $exit = 0;
60 my %commit_author_hash;
61 my %commit_signer_hash;
63 my @penguin_chief = ();
64 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
65 #Andrew wants in on most everything - 2009/01/14
66 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
68 my @penguin_chief_names = ();
69 foreach my $chief (@penguin_chief) {
70 if ($chief =~ m/^(.*):(.*)/) {
71 my $chief_name = $1;
72 my $chief_addr = $2;
73 push(@penguin_chief_names, $chief_name);
76 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
78 # Signature types of people who are either
79 # a) responsible for the code in question, or
80 # b) familiar enough with it to give relevant feedback
81 my @signature_tags = ();
82 push(@signature_tags, "Signed-off-by:");
83 push(@signature_tags, "Reviewed-by:");
84 push(@signature_tags, "Acked-by:");
86 # rfc822 email address - preloaded methods go here.
87 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
88 my $rfc822_char = '[\\000-\\377]';
90 # VCS command support: class-like functions and strings
92 my %VCS_cmds;
94 my %VCS_cmds_git = (
95 "execute_cmd" => \&git_execute_cmd,
96 "available" => '(which("git") ne "") && (-d ".git")',
97 "find_signers_cmd" =>
98 "git log --no-color --since=\$email_git_since " .
99 '--format="GitCommit: %H%n' .
100 'GitAuthor: %an <%ae>%n' .
101 'GitDate: %aD%n' .
102 'GitSubject: %s%n' .
103 '%b%n"' .
104 " -- \$file",
105 "find_commit_signers_cmd" =>
106 "git log --no-color " .
107 '--format="GitCommit: %H%n' .
108 'GitAuthor: %an <%ae>%n' .
109 'GitDate: %aD%n' .
110 'GitSubject: %s%n' .
111 '%b%n"' .
112 " -1 \$commit",
113 "find_commit_author_cmd" =>
114 "git log --no-color " .
115 '--format="GitCommit: %H%n' .
116 'GitAuthor: %an <%ae>%n' .
117 'GitDate: %aD%n' .
118 'GitSubject: %s%n"' .
119 " -1 \$commit",
120 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
121 "blame_file_cmd" => "git blame -l \$file",
122 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
123 "blame_commit_pattern" => "^([0-9a-f]+) ",
124 "author_pattern" => "^GitAuthor: (.*)",
125 "subject_pattern" => "^GitSubject: (.*)",
128 my %VCS_cmds_hg = (
129 "execute_cmd" => \&hg_execute_cmd,
130 "available" => '(which("hg") ne "") && (-d ".hg")',
131 "find_signers_cmd" =>
132 "hg log --date=\$email_hg_since " .
133 "--template='HgCommit: {node}\\n" .
134 "HgAuthor: {author}\\n" .
135 "HgSubject: {desc}\\n'" .
136 " -- \$file",
137 "find_commit_signers_cmd" =>
138 "hg log " .
139 "--template='HgSubject: {desc}\\n'" .
140 " -r \$commit",
141 "find_commit_author_cmd" =>
142 "hg log " .
143 "--template='HgCommit: {node}\\n" .
144 "HgAuthor: {author}\\n" .
145 "HgSubject: {desc|firstline}\\n'" .
146 " -r \$commit",
147 "blame_range_cmd" => "", # not supported
148 "blame_file_cmd" => "hg blame -n \$file",
149 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
150 "blame_commit_pattern" => "^([ 0-9a-f]+):",
151 "author_pattern" => "^HgAuthor: (.*)",
152 "subject_pattern" => "^HgSubject: (.*)",
155 my $conf = which_conf(".get_maintainer.conf");
156 if (-f $conf) {
157 my @conf_args;
158 open(my $conffile, '<', "$conf")
159 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
161 while (<$conffile>) {
162 my $line = $_;
164 $line =~ s/\s*\n?$//g;
165 $line =~ s/^\s*//g;
166 $line =~ s/\s+/ /g;
168 next if ($line =~ m/^\s*#/);
169 next if ($line =~ m/^\s*$/);
171 my @words = split(" ", $line);
172 foreach my $word (@words) {
173 last if ($word =~ m/^#/);
174 push (@conf_args, $word);
177 close($conffile);
178 unshift(@ARGV, @conf_args) if @conf_args;
181 if (!GetOptions(
182 'email!' => \$email,
183 'git!' => \$email_git,
184 'git-all-signature-types!' => \$email_git_all_signature_types,
185 'git-blame!' => \$email_git_blame,
186 'git-blame-signatures!' => \$email_git_blame_signatures,
187 'git-fallback!' => \$email_git_fallback,
188 'git-chief-penguins!' => \$email_git_penguin_chiefs,
189 'git-min-signatures=i' => \$email_git_min_signatures,
190 'git-max-maintainers=i' => \$email_git_max_maintainers,
191 'git-min-percent=i' => \$email_git_min_percent,
192 'git-since=s' => \$email_git_since,
193 'hg-since=s' => \$email_hg_since,
194 'i|interactive!' => \$interactive,
195 'remove-duplicates!' => \$email_remove_duplicates,
196 'mailmap!' => \$email_use_mailmap,
197 'm!' => \$email_maintainer,
198 'n!' => \$email_usename,
199 'l!' => \$email_list,
200 's!' => \$email_subscriber_list,
201 'multiline!' => \$output_multiline,
202 'roles!' => \$output_roles,
203 'rolestats!' => \$output_rolestats,
204 'separator=s' => \$output_separator,
205 'subsystem!' => \$subsystem,
206 'status!' => \$status,
207 'scm!' => \$scm,
208 'web!' => \$web,
209 'pattern-depth=i' => \$pattern_depth,
210 'k|keywords!' => \$keywords,
211 'sections!' => \$sections,
212 'fe|file-emails!' => \$file_emails,
213 'f|file' => \$from_filename,
214 'v|version' => \$version,
215 'h|help|usage' => \$help,
216 )) {
217 die "$P: invalid argument - use --help if necessary\n";
220 if ($help != 0) {
221 usage();
222 exit 0;
225 if ($version != 0) {
226 print("${P} ${V}\n");
227 exit 0;
230 if (-t STDIN && !@ARGV) {
231 # We're talking to a terminal, but have no command line arguments.
232 die "$P: missing patchfile or -f file - use --help if necessary\n";
235 $output_multiline = 0 if ($output_separator ne ", ");
236 $output_rolestats = 1 if ($interactive);
237 $output_roles = 1 if ($output_rolestats);
239 if ($sections) {
240 $email = 0;
241 $email_list = 0;
242 $scm = 0;
243 $status = 0;
244 $subsystem = 0;
245 $web = 0;
246 $keywords = 0;
247 $interactive = 0;
248 } else {
249 my $selections = $email + $scm + $status + $subsystem + $web;
250 if ($selections == 0) {
251 die "$P: Missing required option: email, scm, status, subsystem or web\n";
255 if ($email &&
256 ($email_maintainer + $email_list + $email_subscriber_list +
257 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
258 die "$P: Please select at least 1 email option\n";
261 if (!top_of_kernel_tree($lk_path)) {
262 die "$P: The current directory does not appear to be "
263 . "a linux kernel source tree.\n";
266 ## Read MAINTAINERS for type/value pairs
268 my @typevalue = ();
269 my %keyword_hash;
271 open (my $maint, '<', "${lk_path}MAINTAINERS")
272 or die "$P: Can't open MAINTAINERS: $!\n";
273 while (<$maint>) {
274 my $line = $_;
276 if ($line =~ m/^(\C):\s*(.*)/) {
277 my $type = $1;
278 my $value = $2;
280 ##Filename pattern matching
281 if ($type eq "F" || $type eq "X") {
282 $value =~ s@\.@\\\.@g; ##Convert . to \.
283 $value =~ s/\*/\.\*/g; ##Convert * to .*
284 $value =~ s/\?/\./g; ##Convert ? to .
285 ##if pattern is a directory and it lacks a trailing slash, add one
286 if ((-d $value)) {
287 $value =~ s@([^/])$@$1/@;
289 } elsif ($type eq "K") {
290 $keyword_hash{@typevalue} = $value;
292 push(@typevalue, "$type:$value");
293 } elsif (!/^(\s)*$/) {
294 $line =~ s/\n$//g;
295 push(@typevalue, $line);
298 close($maint);
302 # Read mail address map
305 my $mailmap;
307 read_mailmap();
309 sub read_mailmap {
310 $mailmap = {
311 names => {},
312 addresses => {}
315 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
317 open(my $mailmap_file, '<', "${lk_path}.mailmap")
318 or warn "$P: Can't open .mailmap: $!\n";
320 while (<$mailmap_file>) {
321 s/#.*$//; #strip comments
322 s/^\s+|\s+$//g; #trim
324 next if (/^\s*$/); #skip empty lines
325 #entries have one of the following formats:
326 # name1 <mail1>
327 # <mail1> <mail2>
328 # name1 <mail1> <mail2>
329 # name1 <mail1> name2 <mail2>
330 # (see man git-shortlog)
331 if (/^(.+)<(.+)>$/) {
332 my $real_name = $1;
333 my $address = $2;
335 $real_name =~ s/\s+$//;
336 ($real_name, $address) = parse_email("$real_name <$address>");
337 $mailmap->{names}->{$address} = $real_name;
339 } elsif (/^<([^\s]+)>\s*<([^\s]+)>$/) {
340 my $real_address = $1;
341 my $wrong_address = $2;
343 $mailmap->{addresses}->{$wrong_address} = $real_address;
345 } elsif (/^(.+)<([^\s]+)>\s*<([^\s]+)>$/) {
346 my $real_name = $1;
347 my $real_address = $2;
348 my $wrong_address = $3;
350 $real_name =~ s/\s+$//;
351 ($real_name, $real_address) =
352 parse_email("$real_name <$real_address>");
353 $mailmap->{names}->{$wrong_address} = $real_name;
354 $mailmap->{addresses}->{$wrong_address} = $real_address;
356 } elsif (/^(.+)<([^\s]+)>\s*([^\s].*)<([^\s]+)>$/) {
357 my $real_name = $1;
358 my $real_address = $2;
359 my $wrong_name = $3;
360 my $wrong_address = $4;
362 $real_name =~ s/\s+$//;
363 ($real_name, $real_address) =
364 parse_email("$real_name <$real_address>");
366 $wrong_name =~ s/\s+$//;
367 ($wrong_name, $wrong_address) =
368 parse_email("$wrong_name <$wrong_address>");
370 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
371 $mailmap->{names}->{$wrong_email} = $real_name;
372 $mailmap->{addresses}->{$wrong_email} = $real_address;
375 close($mailmap_file);
378 ## use the filenames on the command line or find the filenames in the patchfiles
380 my @files = ();
381 my @range = ();
382 my @keyword_tvi = ();
383 my @file_emails = ();
385 if (!@ARGV) {
386 push(@ARGV, "&STDIN");
389 foreach my $file (@ARGV) {
390 if ($file ne "&STDIN") {
391 ##if $file is a directory and it lacks a trailing slash, add one
392 if ((-d $file)) {
393 $file =~ s@([^/])$@$1/@;
394 } elsif (!(-f $file)) {
395 die "$P: file '${file}' not found\n";
398 if ($from_filename) {
399 push(@files, $file);
400 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
401 open(my $f, '<', $file)
402 or die "$P: Can't open $file: $!\n";
403 my $text = do { local($/) ; <$f> };
404 close($f);
405 if ($keywords) {
406 foreach my $line (keys %keyword_hash) {
407 if ($text =~ m/$keyword_hash{$line}/x) {
408 push(@keyword_tvi, $line);
412 if ($file_emails) {
413 my @poss_addr = $text =~ m$[A-Za--ÿ\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g;
414 push(@file_emails, clean_file_emails(@poss_addr));
417 } else {
418 my $file_cnt = @files;
419 my $lastfile;
421 open(my $patch, "< $file")
422 or die "$P: Can't open $file: $!\n";
423 while (<$patch>) {
424 my $patch_line = $_;
425 if (m/^\+\+\+\s+(\S+)/) {
426 my $filename = $1;
427 $filename =~ s@^[^/]*/@@;
428 $filename =~ s@\n@@;
429 $lastfile = $filename;
430 push(@files, $filename);
431 } elsif (m/^\@\@ -(\d+),(\d+)/) {
432 if ($email_git_blame) {
433 push(@range, "$lastfile:$1:$2");
435 } elsif ($keywords) {
436 foreach my $line (keys %keyword_hash) {
437 if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) {
438 push(@keyword_tvi, $line);
443 close($patch);
445 if ($file_cnt == @files) {
446 warn "$P: file '${file}' doesn't appear to be a patch. "
447 . "Add -f to options?\n";
449 @files = sort_and_uniq(@files);
453 @file_emails = uniq(@file_emails);
455 my %email_hash_name;
456 my %email_hash_address;
457 my @email_to = ();
458 my %hash_list_to;
459 my @list_to = ();
460 my @scm = ();
461 my @web = ();
462 my @subsystem = ();
463 my @status = ();
464 my %deduplicate_name_hash = ();
465 my %deduplicate_address_hash = ();
466 my $signature_pattern;
468 my @maintainers = get_maintainers();
470 if (@maintainers) {
471 @maintainers = merge_email(@maintainers);
472 output(@maintainers);
475 if ($scm) {
476 @scm = uniq(@scm);
477 output(@scm);
480 if ($status) {
481 @status = uniq(@status);
482 output(@status);
485 if ($subsystem) {
486 @subsystem = uniq(@subsystem);
487 output(@subsystem);
490 if ($web) {
491 @web = uniq(@web);
492 output(@web);
495 exit($exit);
497 sub range_is_maintained {
498 my ($start, $end) = @_;
500 for (my $i = $start; $i < $end; $i++) {
501 my $line = $typevalue[$i];
502 if ($line =~ m/^(\C):\s*(.*)/) {
503 my $type = $1;
504 my $value = $2;
505 if ($type eq 'S') {
506 if ($value =~ /(maintain|support)/i) {
507 return 1;
512 return 0;
515 sub range_has_maintainer {
516 my ($start, $end) = @_;
518 for (my $i = $start; $i < $end; $i++) {
519 my $line = $typevalue[$i];
520 if ($line =~ m/^(\C):\s*(.*)/) {
521 my $type = $1;
522 my $value = $2;
523 if ($type eq 'M') {
524 return 1;
528 return 0;
531 sub get_maintainers {
532 %email_hash_name = ();
533 %email_hash_address = ();
534 %commit_author_hash = ();
535 %commit_signer_hash = ();
536 @email_to = ();
537 %hash_list_to = ();
538 @list_to = ();
539 @scm = ();
540 @web = ();
541 @subsystem = ();
542 @status = ();
543 %deduplicate_name_hash = ();
544 %deduplicate_address_hash = ();
545 if ($email_git_all_signature_types) {
546 $signature_pattern = "(.+?)[Bb][Yy]:";
547 } else {
548 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
551 # Find responsible parties
553 my %exact_pattern_match_hash = ();
555 foreach my $file (@files) {
557 my %hash;
558 my $tvi = find_first_section();
559 while ($tvi < @typevalue) {
560 my $start = find_starting_index($tvi);
561 my $end = find_ending_index($tvi);
562 my $exclude = 0;
563 my $i;
565 #Do not match excluded file patterns
567 for ($i = $start; $i < $end; $i++) {
568 my $line = $typevalue[$i];
569 if ($line =~ m/^(\C):\s*(.*)/) {
570 my $type = $1;
571 my $value = $2;
572 if ($type eq 'X') {
573 if (file_match_pattern($file, $value)) {
574 $exclude = 1;
575 last;
581 if (!$exclude) {
582 for ($i = $start; $i < $end; $i++) {
583 my $line = $typevalue[$i];
584 if ($line =~ m/^(\C):\s*(.*)/) {
585 my $type = $1;
586 my $value = $2;
587 if ($type eq 'F') {
588 if (file_match_pattern($file, $value)) {
589 my $value_pd = ($value =~ tr@/@@);
590 my $file_pd = ($file =~ tr@/@@);
591 $value_pd++ if (substr($value,-1,1) ne "/");
592 $value_pd = -1 if ($value =~ /^\.\*/);
593 if ($value_pd >= $file_pd &&
594 range_is_maintained($start, $end) &&
595 range_has_maintainer($start, $end)) {
596 $exact_pattern_match_hash{$file} = 1;
598 if ($pattern_depth == 0 ||
599 (($file_pd - $value_pd) < $pattern_depth)) {
600 $hash{$tvi} = $value_pd;
607 $tvi = $end + 1;
610 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
611 add_categories($line);
612 if ($sections) {
613 my $i;
614 my $start = find_starting_index($line);
615 my $end = find_ending_index($line);
616 for ($i = $start; $i < $end; $i++) {
617 my $line = $typevalue[$i];
618 if ($line =~ /^[FX]:/) { ##Restore file patterns
619 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
620 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
621 $line =~ s/\\\./\./g; ##Convert \. to .
622 $line =~ s/\.\*/\*/g; ##Convert .* to *
624 $line =~ s/^([A-Z]):/$1:\t/g;
625 print("$line\n");
627 print("\n");
632 if ($keywords) {
633 @keyword_tvi = sort_and_uniq(@keyword_tvi);
634 foreach my $line (@keyword_tvi) {
635 add_categories($line);
639 foreach my $email (@email_to, @list_to) {
640 $email->[0] = deduplicate_email($email->[0]);
643 foreach my $file (@files) {
644 if ($email &&
645 ($email_git || ($email_git_fallback &&
646 !$exact_pattern_match_hash{$file}))) {
647 vcs_file_signoffs($file);
649 if ($email && $email_git_blame) {
650 vcs_file_blame($file);
654 if ($email) {
655 foreach my $chief (@penguin_chief) {
656 if ($chief =~ m/^(.*):(.*)/) {
657 my $email_address;
659 $email_address = format_email($1, $2, $email_usename);
660 if ($email_git_penguin_chiefs) {
661 push(@email_to, [$email_address, 'chief penguin']);
662 } else {
663 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
668 foreach my $email (@file_emails) {
669 my ($name, $address) = parse_email($email);
671 my $tmp_email = format_email($name, $address, $email_usename);
672 push_email_address($tmp_email, '');
673 add_role($tmp_email, 'in file');
677 my @to = ();
678 if ($email || $email_list) {
679 if ($email) {
680 @to = (@to, @email_to);
682 if ($email_list) {
683 @to = (@to, @list_to);
687 if ($interactive) {
688 @to = interactive_get_maintainers(\@to);
691 return @to;
694 sub file_match_pattern {
695 my ($file, $pattern) = @_;
696 if (substr($pattern, -1) eq "/") {
697 if ($file =~ m@^$pattern@) {
698 return 1;
700 } else {
701 if ($file =~ m@^$pattern@) {
702 my $s1 = ($file =~ tr@/@@);
703 my $s2 = ($pattern =~ tr@/@@);
704 if ($s1 == $s2) {
705 return 1;
709 return 0;
712 sub usage {
713 print <<EOT;
714 usage: $P [options] patchfile
715 $P [options] -f file|directory
716 version: $V
718 MAINTAINER field selection options:
719 --email => print email address(es) if any
720 --git => include recent git \*-by: signers
721 --git-all-signature-types => include signers regardless of signature type
722 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
723 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
724 --git-chief-penguins => include ${penguin_chiefs}
725 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
726 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
727 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
728 --git-blame => use git blame to find modified commits for patch or file
729 --git-since => git history to use (default: $email_git_since)
730 --hg-since => hg history to use (default: $email_hg_since)
731 --interactive => display a menu (mostly useful if used with the --git option)
732 --m => include maintainer(s) if any
733 --n => include name 'Full Name <addr\@domain.tld>'
734 --l => include list(s) if any
735 --s => include subscriber only list(s) if any
736 --remove-duplicates => minimize duplicate email names/addresses
737 --roles => show roles (status:subsystem, git-signer, list, etc...)
738 --rolestats => show roles and statistics (commits/total_commits, %)
739 --file-emails => add email addresses found in -f file (default: 0 (off))
740 --scm => print SCM tree(s) if any
741 --status => print status if any
742 --subsystem => print subsystem name if any
743 --web => print website(s) if any
745 Output type options:
746 --separator [, ] => separator for multiple entries on 1 line
747 using --separator also sets --nomultiline if --separator is not [, ]
748 --multiline => print 1 entry per line
750 Other options:
751 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
752 --keywords => scan patch for keywords (default: $keywords)
753 --sections => print all of the subsystem sections with pattern matches
754 --mailmap => use .mailmap file (default: $email_use_mailmap)
755 --version => show version
756 --help => show this help information
758 Default options:
759 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
760 --remove-duplicates --rolestats]
762 Notes:
763 Using "-f directory" may give unexpected results:
764 Used with "--git", git signators for _all_ files in and below
765 directory are examined as git recurses directories.
766 Any specified X: (exclude) pattern matches are _not_ ignored.
767 Used with "--nogit", directory is used as a pattern match,
768 no individual file within the directory or subdirectory
769 is matched.
770 Used with "--git-blame", does not iterate all files in directory
771 Using "--git-blame" is slow and may add old committers and authors
772 that are no longer active maintainers to the output.
773 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
774 other automated tools that expect only ["name"] <email address>
775 may not work because of additional output after <email address>.
776 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
777 not the percentage of the entire file authored. # of commits is
778 not a good measure of amount of code authored. 1 major commit may
779 contain a thousand lines, 5 trivial commits may modify a single line.
780 If git is not installed, but mercurial (hg) is installed and an .hg
781 repository exists, the following options apply to mercurial:
782 --git,
783 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
784 --git-blame
785 Use --hg-since not --git-since to control date selection
786 File ".get_maintainer.conf", if it exists in the linux kernel source root
787 directory, can change whatever get_maintainer defaults are desired.
788 Entries in this file can be any command line argument.
789 This file is prepended to any additional command line arguments.
790 Multiple lines and # comments are allowed.
794 sub top_of_kernel_tree {
795 my ($lk_path) = @_;
797 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
798 $lk_path .= "/";
800 if ( (-f "${lk_path}COPYING")
801 && (-f "${lk_path}CREDITS")
802 && (-f "${lk_path}Kbuild")
803 && (-f "${lk_path}MAINTAINERS")
804 && (-f "${lk_path}Makefile")
805 && (-f "${lk_path}README")
806 && (-d "${lk_path}Documentation")
807 && (-d "${lk_path}arch")
808 && (-d "${lk_path}include")
809 && (-d "${lk_path}drivers")
810 && (-d "${lk_path}fs")
811 && (-d "${lk_path}init")
812 && (-d "${lk_path}ipc")
813 && (-d "${lk_path}kernel")
814 && (-d "${lk_path}lib")
815 && (-d "${lk_path}scripts")) {
816 return 1;
818 return 0;
821 sub parse_email {
822 my ($formatted_email) = @_;
824 my $name = "";
825 my $address = "";
827 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
828 $name = $1;
829 $address = $2;
830 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
831 $address = $1;
832 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
833 $address = $1;
836 $name =~ s/^\s+|\s+$//g;
837 $name =~ s/^\"|\"$//g;
838 $address =~ s/^\s+|\s+$//g;
840 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
841 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
842 $name = "\"$name\"";
845 return ($name, $address);
848 sub format_email {
849 my ($name, $address, $usename) = @_;
851 my $formatted_email;
853 $name =~ s/^\s+|\s+$//g;
854 $name =~ s/^\"|\"$//g;
855 $address =~ s/^\s+|\s+$//g;
857 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
858 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
859 $name = "\"$name\"";
862 if ($usename) {
863 if ("$name" eq "") {
864 $formatted_email = "$address";
865 } else {
866 $formatted_email = "$name <$address>";
868 } else {
869 $formatted_email = $address;
872 return $formatted_email;
875 sub find_first_section {
876 my $index = 0;
878 while ($index < @typevalue) {
879 my $tv = $typevalue[$index];
880 if (($tv =~ m/^(\C):\s*(.*)/)) {
881 last;
883 $index++;
886 return $index;
889 sub find_starting_index {
890 my ($index) = @_;
892 while ($index > 0) {
893 my $tv = $typevalue[$index];
894 if (!($tv =~ m/^(\C):\s*(.*)/)) {
895 last;
897 $index--;
900 return $index;
903 sub find_ending_index {
904 my ($index) = @_;
906 while ($index < @typevalue) {
907 my $tv = $typevalue[$index];
908 if (!($tv =~ m/^(\C):\s*(.*)/)) {
909 last;
911 $index++;
914 return $index;
917 sub get_maintainer_role {
918 my ($index) = @_;
920 my $i;
921 my $start = find_starting_index($index);
922 my $end = find_ending_index($index);
924 my $role;
925 my $subsystem = $typevalue[$start];
926 if (length($subsystem) > 20) {
927 $subsystem = substr($subsystem, 0, 17);
928 $subsystem =~ s/\s*$//;
929 $subsystem = $subsystem . "...";
932 for ($i = $start + 1; $i < $end; $i++) {
933 my $tv = $typevalue[$i];
934 if ($tv =~ m/^(\C):\s*(.*)/) {
935 my $ptype = $1;
936 my $pvalue = $2;
937 if ($ptype eq "S") {
938 $role = $pvalue;
943 $role = lc($role);
944 if ($role eq "supported") {
945 $role = "supporter";
946 } elsif ($role eq "maintained") {
947 $role = "maintainer";
948 } elsif ($role eq "odd fixes") {
949 $role = "odd fixer";
950 } elsif ($role eq "orphan") {
951 $role = "orphan minder";
952 } elsif ($role eq "obsolete") {
953 $role = "obsolete minder";
954 } elsif ($role eq "buried alive in reporters") {
955 $role = "chief penguin";
958 return $role . ":" . $subsystem;
961 sub get_list_role {
962 my ($index) = @_;
964 my $i;
965 my $start = find_starting_index($index);
966 my $end = find_ending_index($index);
968 my $subsystem = $typevalue[$start];
969 if (length($subsystem) > 20) {
970 $subsystem = substr($subsystem, 0, 17);
971 $subsystem =~ s/\s*$//;
972 $subsystem = $subsystem . "...";
975 if ($subsystem eq "THE REST") {
976 $subsystem = "";
979 return $subsystem;
982 sub add_categories {
983 my ($index) = @_;
985 my $i;
986 my $start = find_starting_index($index);
987 my $end = find_ending_index($index);
989 push(@subsystem, $typevalue[$start]);
991 for ($i = $start + 1; $i < $end; $i++) {
992 my $tv = $typevalue[$i];
993 if ($tv =~ m/^(\C):\s*(.*)/) {
994 my $ptype = $1;
995 my $pvalue = $2;
996 if ($ptype eq "L") {
997 my $list_address = $pvalue;
998 my $list_additional = "";
999 my $list_role = get_list_role($i);
1001 if ($list_role ne "") {
1002 $list_role = ":" . $list_role;
1004 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1005 $list_address = $1;
1006 $list_additional = $2;
1008 if ($list_additional =~ m/subscribers-only/) {
1009 if ($email_subscriber_list) {
1010 if (!$hash_list_to{lc($list_address)}) {
1011 $hash_list_to{lc($list_address)} = 1;
1012 push(@list_to, [$list_address,
1013 "subscriber list${list_role}"]);
1016 } else {
1017 if ($email_list) {
1018 if (!$hash_list_to{lc($list_address)}) {
1019 $hash_list_to{lc($list_address)} = 1;
1020 push(@list_to, [$list_address,
1021 "open list${list_role}"]);
1025 } elsif ($ptype eq "M") {
1026 my ($name, $address) = parse_email($pvalue);
1027 if ($name eq "") {
1028 if ($i > 0) {
1029 my $tv = $typevalue[$i - 1];
1030 if ($tv =~ m/^(\C):\s*(.*)/) {
1031 if ($1 eq "P") {
1032 $name = $2;
1033 $pvalue = format_email($name, $address, $email_usename);
1038 if ($email_maintainer) {
1039 my $role = get_maintainer_role($i);
1040 push_email_addresses($pvalue, $role);
1042 } elsif ($ptype eq "T") {
1043 push(@scm, $pvalue);
1044 } elsif ($ptype eq "W") {
1045 push(@web, $pvalue);
1046 } elsif ($ptype eq "S") {
1047 push(@status, $pvalue);
1053 sub email_inuse {
1054 my ($name, $address) = @_;
1056 return 1 if (($name eq "") && ($address eq ""));
1057 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1058 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1060 return 0;
1063 sub push_email_address {
1064 my ($line, $role) = @_;
1066 my ($name, $address) = parse_email($line);
1068 if ($address eq "") {
1069 return 0;
1072 if (!$email_remove_duplicates) {
1073 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1074 } elsif (!email_inuse($name, $address)) {
1075 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1076 $email_hash_name{lc($name)}++ if ($name ne "");
1077 $email_hash_address{lc($address)}++;
1080 return 1;
1083 sub push_email_addresses {
1084 my ($address, $role) = @_;
1086 my @address_list = ();
1088 if (rfc822_valid($address)) {
1089 push_email_address($address, $role);
1090 } elsif (@address_list = rfc822_validlist($address)) {
1091 my $array_count = shift(@address_list);
1092 while (my $entry = shift(@address_list)) {
1093 push_email_address($entry, $role);
1095 } else {
1096 if (!push_email_address($address, $role)) {
1097 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1102 sub add_role {
1103 my ($line, $role) = @_;
1105 my ($name, $address) = parse_email($line);
1106 my $email = format_email($name, $address, $email_usename);
1108 foreach my $entry (@email_to) {
1109 if ($email_remove_duplicates) {
1110 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1111 if (($name eq $entry_name || $address eq $entry_address)
1112 && ($role eq "" || !($entry->[1] =~ m/$role/))
1114 if ($entry->[1] eq "") {
1115 $entry->[1] = "$role";
1116 } else {
1117 $entry->[1] = "$entry->[1],$role";
1120 } else {
1121 if ($email eq $entry->[0]
1122 && ($role eq "" || !($entry->[1] =~ m/$role/))
1124 if ($entry->[1] eq "") {
1125 $entry->[1] = "$role";
1126 } else {
1127 $entry->[1] = "$entry->[1],$role";
1134 sub which {
1135 my ($bin) = @_;
1137 foreach my $path (split(/:/, $ENV{PATH})) {
1138 if (-e "$path/$bin") {
1139 return "$path/$bin";
1143 return "";
1146 sub which_conf {
1147 my ($conf) = @_;
1149 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1150 if (-e "$path/$conf") {
1151 return "$path/$conf";
1155 return "";
1158 sub mailmap_email {
1159 my ($line) = @_;
1161 my ($name, $address) = parse_email($line);
1162 my $email = format_email($name, $address, 1);
1163 my $real_name = $name;
1164 my $real_address = $address;
1166 if (exists $mailmap->{names}->{$email} ||
1167 exists $mailmap->{addresses}->{$email}) {
1168 if (exists $mailmap->{names}->{$email}) {
1169 $real_name = $mailmap->{names}->{$email};
1171 if (exists $mailmap->{addresses}->{$email}) {
1172 $real_address = $mailmap->{addresses}->{$email};
1174 } else {
1175 if (exists $mailmap->{names}->{$address}) {
1176 $real_name = $mailmap->{names}->{$address};
1178 if (exists $mailmap->{addresses}->{$address}) {
1179 $real_address = $mailmap->{addresses}->{$address};
1182 return format_email($real_name, $real_address, 1);
1185 sub mailmap {
1186 my (@addresses) = @_;
1188 my @mapped_emails = ();
1189 foreach my $line (@addresses) {
1190 push(@mapped_emails, mailmap_email($line));
1192 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1193 return @mapped_emails;
1196 sub merge_by_realname {
1197 my %address_map;
1198 my (@emails) = @_;
1200 foreach my $email (@emails) {
1201 my ($name, $address) = parse_email($email);
1202 if (exists $address_map{$name}) {
1203 $address = $address_map{$name};
1204 $email = format_email($name, $address, 1);
1205 } else {
1206 $address_map{$name} = $address;
1211 sub git_execute_cmd {
1212 my ($cmd) = @_;
1213 my @lines = ();
1215 my $output = `$cmd`;
1216 $output =~ s/^\s*//gm;
1217 @lines = split("\n", $output);
1219 return @lines;
1222 sub hg_execute_cmd {
1223 my ($cmd) = @_;
1224 my @lines = ();
1226 my $output = `$cmd`;
1227 @lines = split("\n", $output);
1229 return @lines;
1232 sub extract_formatted_signatures {
1233 my (@signature_lines) = @_;
1235 my @type = @signature_lines;
1237 s/\s*(.*):.*/$1/ for (@type);
1239 # cut -f2- -d":"
1240 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1242 ## Reformat email addresses (with names) to avoid badly written signatures
1244 foreach my $signer (@signature_lines) {
1245 $signer = deduplicate_email($signer);
1248 return (\@type, \@signature_lines);
1251 sub vcs_find_signers {
1252 my ($cmd) = @_;
1253 my $commits;
1254 my @lines = ();
1255 my @signatures = ();
1257 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1259 my $pattern = $VCS_cmds{"commit_pattern"};
1261 $commits = grep(/$pattern/, @lines); # of commits
1263 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1265 return (0, @signatures) if !@signatures;
1267 save_commits_by_author(@lines) if ($interactive);
1268 save_commits_by_signer(@lines) if ($interactive);
1270 if (!$email_git_penguin_chiefs) {
1271 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1274 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1276 return ($commits, @$signers_ref);
1279 sub vcs_find_author {
1280 my ($cmd) = @_;
1281 my @lines = ();
1283 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1285 if (!$email_git_penguin_chiefs) {
1286 @lines = grep(!/${penguin_chiefs}/i, @lines);
1289 return @lines if !@lines;
1291 my @authors = ();
1292 foreach my $line (@lines) {
1293 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1294 my $author = $1;
1295 my ($name, $address) = parse_email($author);
1296 $author = format_email($name, $address, 1);
1297 push(@authors, $author);
1301 save_commits_by_author(@lines) if ($interactive);
1302 save_commits_by_signer(@lines) if ($interactive);
1304 return @authors;
1307 sub vcs_save_commits {
1308 my ($cmd) = @_;
1309 my @lines = ();
1310 my @commits = ();
1312 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1314 foreach my $line (@lines) {
1315 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1316 push(@commits, $1);
1320 return @commits;
1323 sub vcs_blame {
1324 my ($file) = @_;
1325 my $cmd;
1326 my @commits = ();
1328 return @commits if (!(-f $file));
1330 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1331 my @all_commits = ();
1333 $cmd = $VCS_cmds{"blame_file_cmd"};
1334 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1335 @all_commits = vcs_save_commits($cmd);
1337 foreach my $file_range_diff (@range) {
1338 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1339 my $diff_file = $1;
1340 my $diff_start = $2;
1341 my $diff_length = $3;
1342 next if ("$file" ne "$diff_file");
1343 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1344 push(@commits, $all_commits[$i]);
1347 } elsif (@range) {
1348 foreach my $file_range_diff (@range) {
1349 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1350 my $diff_file = $1;
1351 my $diff_start = $2;
1352 my $diff_length = $3;
1353 next if ("$file" ne "$diff_file");
1354 $cmd = $VCS_cmds{"blame_range_cmd"};
1355 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1356 push(@commits, vcs_save_commits($cmd));
1358 } else {
1359 $cmd = $VCS_cmds{"blame_file_cmd"};
1360 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1361 @commits = vcs_save_commits($cmd);
1364 foreach my $commit (@commits) {
1365 $commit =~ s/^\^//g;
1368 return @commits;
1371 my $printed_novcs = 0;
1372 sub vcs_exists {
1373 %VCS_cmds = %VCS_cmds_git;
1374 return 1 if eval $VCS_cmds{"available"};
1375 %VCS_cmds = %VCS_cmds_hg;
1376 return 2 if eval $VCS_cmds{"available"};
1377 %VCS_cmds = ();
1378 if (!$printed_novcs) {
1379 warn("$P: No supported VCS found. Add --nogit to options?\n");
1380 warn("Using a git repository produces better results.\n");
1381 warn("Try Linus Torvalds' latest git repository using:\n");
1382 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1383 $printed_novcs = 1;
1385 return 0;
1388 sub vcs_is_git {
1389 vcs_exists();
1390 return $vcs_used == 1;
1393 sub vcs_is_hg {
1394 return $vcs_used == 2;
1397 sub interactive_get_maintainers {
1398 my ($list_ref) = @_;
1399 my @list = @$list_ref;
1401 vcs_exists();
1403 my %selected;
1404 my %authored;
1405 my %signed;
1406 my $count = 0;
1407 my $maintained = 0;
1408 foreach my $entry (@list) {
1409 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1410 $selected{$count} = 1;
1411 $authored{$count} = 0;
1412 $signed{$count} = 0;
1413 $count++;
1416 #menu loop
1417 my $done = 0;
1418 my $print_options = 0;
1419 my $redraw = 1;
1420 while (!$done) {
1421 $count = 0;
1422 if ($redraw) {
1423 printf STDERR "\n%1s %2s %-65s",
1424 "*", "#", "email/list and role:stats";
1425 if ($email_git ||
1426 ($email_git_fallback && !$maintained) ||
1427 $email_git_blame) {
1428 print STDERR "auth sign";
1430 print STDERR "\n";
1431 foreach my $entry (@list) {
1432 my $email = $entry->[0];
1433 my $role = $entry->[1];
1434 my $sel = "";
1435 $sel = "*" if ($selected{$count});
1436 my $commit_author = $commit_author_hash{$email};
1437 my $commit_signer = $commit_signer_hash{$email};
1438 my $authored = 0;
1439 my $signed = 0;
1440 $authored++ for (@{$commit_author});
1441 $signed++ for (@{$commit_signer});
1442 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1443 printf STDERR "%4d %4d", $authored, $signed
1444 if ($authored > 0 || $signed > 0);
1445 printf STDERR "\n %s\n", $role;
1446 if ($authored{$count}) {
1447 my $commit_author = $commit_author_hash{$email};
1448 foreach my $ref (@{$commit_author}) {
1449 print STDERR " Author: @{$ref}[1]\n";
1452 if ($signed{$count}) {
1453 my $commit_signer = $commit_signer_hash{$email};
1454 foreach my $ref (@{$commit_signer}) {
1455 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1459 $count++;
1462 my $date_ref = \$email_git_since;
1463 $date_ref = \$email_hg_since if (vcs_is_hg());
1464 if ($print_options) {
1465 $print_options = 0;
1466 if (vcs_exists()) {
1467 print STDERR <<EOT
1469 Version Control options:
1470 g use git history [$email_git]
1471 gf use git-fallback [$email_git_fallback]
1472 b use git blame [$email_git_blame]
1473 bs use blame signatures [$email_git_blame_signatures]
1474 c# minimum commits [$email_git_min_signatures]
1475 %# min percent [$email_git_min_percent]
1476 d# history to use [$$date_ref]
1477 x# max maintainers [$email_git_max_maintainers]
1478 t all signature types [$email_git_all_signature_types]
1479 m use .mailmap [$email_use_mailmap]
1482 print STDERR <<EOT
1484 Additional options:
1485 0 toggle all
1486 tm toggle maintainers
1487 tg toggle git entries
1488 tl toggle open list entries
1489 ts toggle subscriber list entries
1490 f emails in file [$file_emails]
1491 k keywords in file [$keywords]
1492 r remove duplicates [$email_remove_duplicates]
1493 p# pattern match depth [$pattern_depth]
1496 print STDERR
1497 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1499 my $input = <STDIN>;
1500 chomp($input);
1502 $redraw = 1;
1503 my $rerun = 0;
1504 my @wish = split(/[, ]+/, $input);
1505 foreach my $nr (@wish) {
1506 $nr = lc($nr);
1507 my $sel = substr($nr, 0, 1);
1508 my $str = substr($nr, 1);
1509 my $val = 0;
1510 $val = $1 if $str =~ /^(\d+)$/;
1512 if ($sel eq "y") {
1513 $interactive = 0;
1514 $done = 1;
1515 $output_rolestats = 0;
1516 $output_roles = 0;
1517 last;
1518 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1519 $selected{$nr - 1} = !$selected{$nr - 1};
1520 } elsif ($sel eq "*" || $sel eq '^') {
1521 my $toggle = 0;
1522 $toggle = 1 if ($sel eq '*');
1523 for (my $i = 0; $i < $count; $i++) {
1524 $selected{$i} = $toggle;
1526 } elsif ($sel eq "0") {
1527 for (my $i = 0; $i < $count; $i++) {
1528 $selected{$i} = !$selected{$i};
1530 } elsif ($sel eq "t") {
1531 if (lc($str) eq "m") {
1532 for (my $i = 0; $i < $count; $i++) {
1533 $selected{$i} = !$selected{$i}
1534 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1536 } elsif (lc($str) eq "g") {
1537 for (my $i = 0; $i < $count; $i++) {
1538 $selected{$i} = !$selected{$i}
1539 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1541 } elsif (lc($str) eq "l") {
1542 for (my $i = 0; $i < $count; $i++) {
1543 $selected{$i} = !$selected{$i}
1544 if ($list[$i]->[1] =~ /^(open list)/i);
1546 } elsif (lc($str) eq "s") {
1547 for (my $i = 0; $i < $count; $i++) {
1548 $selected{$i} = !$selected{$i}
1549 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1552 } elsif ($sel eq "a") {
1553 if ($val > 0 && $val <= $count) {
1554 $authored{$val - 1} = !$authored{$val - 1};
1555 } elsif ($str eq '*' || $str eq '^') {
1556 my $toggle = 0;
1557 $toggle = 1 if ($str eq '*');
1558 for (my $i = 0; $i < $count; $i++) {
1559 $authored{$i} = $toggle;
1562 } elsif ($sel eq "s") {
1563 if ($val > 0 && $val <= $count) {
1564 $signed{$val - 1} = !$signed{$val - 1};
1565 } elsif ($str eq '*' || $str eq '^') {
1566 my $toggle = 0;
1567 $toggle = 1 if ($str eq '*');
1568 for (my $i = 0; $i < $count; $i++) {
1569 $signed{$i} = $toggle;
1572 } elsif ($sel eq "o") {
1573 $print_options = 1;
1574 $redraw = 1;
1575 } elsif ($sel eq "g") {
1576 if ($str eq "f") {
1577 bool_invert(\$email_git_fallback);
1578 } else {
1579 bool_invert(\$email_git);
1581 $rerun = 1;
1582 } elsif ($sel eq "b") {
1583 if ($str eq "s") {
1584 bool_invert(\$email_git_blame_signatures);
1585 } else {
1586 bool_invert(\$email_git_blame);
1588 $rerun = 1;
1589 } elsif ($sel eq "c") {
1590 if ($val > 0) {
1591 $email_git_min_signatures = $val;
1592 $rerun = 1;
1594 } elsif ($sel eq "x") {
1595 if ($val > 0) {
1596 $email_git_max_maintainers = $val;
1597 $rerun = 1;
1599 } elsif ($sel eq "%") {
1600 if ($str ne "" && $val >= 0) {
1601 $email_git_min_percent = $val;
1602 $rerun = 1;
1604 } elsif ($sel eq "d") {
1605 if (vcs_is_git()) {
1606 $email_git_since = $str;
1607 } elsif (vcs_is_hg()) {
1608 $email_hg_since = $str;
1610 $rerun = 1;
1611 } elsif ($sel eq "t") {
1612 bool_invert(\$email_git_all_signature_types);
1613 $rerun = 1;
1614 } elsif ($sel eq "f") {
1615 bool_invert(\$file_emails);
1616 $rerun = 1;
1617 } elsif ($sel eq "r") {
1618 bool_invert(\$email_remove_duplicates);
1619 $rerun = 1;
1620 } elsif ($sel eq "m") {
1621 bool_invert(\$email_use_mailmap);
1622 read_mailmap();
1623 $rerun = 1;
1624 } elsif ($sel eq "k") {
1625 bool_invert(\$keywords);
1626 $rerun = 1;
1627 } elsif ($sel eq "p") {
1628 if ($str ne "" && $val >= 0) {
1629 $pattern_depth = $val;
1630 $rerun = 1;
1632 } elsif ($sel eq "h" || $sel eq "?") {
1633 print STDERR <<EOT
1635 Interactive mode allows you to select the various maintainers, submitters,
1636 commit signers and mailing lists that could be CC'd on a patch.
1638 Any *'d entry is selected.
1640 If you have git or hg installed, you can choose to summarize the commit
1641 history of files in the patch. Also, each line of the current file can
1642 be matched to its commit author and that commits signers with blame.
1644 Various knobs exist to control the length of time for active commit
1645 tracking, the maximum number of commit authors and signers to add,
1646 and such.
1648 Enter selections at the prompt until you are satisfied that the selected
1649 maintainers are appropriate. You may enter multiple selections separated
1650 by either commas or spaces.
1653 } else {
1654 print STDERR "invalid option: '$nr'\n";
1655 $redraw = 0;
1658 if ($rerun) {
1659 print STDERR "git-blame can be very slow, please have patience..."
1660 if ($email_git_blame);
1661 goto &get_maintainers;
1665 #drop not selected entries
1666 $count = 0;
1667 my @new_emailto = ();
1668 foreach my $entry (@list) {
1669 if ($selected{$count}) {
1670 push(@new_emailto, $list[$count]);
1672 $count++;
1674 return @new_emailto;
1677 sub bool_invert {
1678 my ($bool_ref) = @_;
1680 if ($$bool_ref) {
1681 $$bool_ref = 0;
1682 } else {
1683 $$bool_ref = 1;
1687 sub deduplicate_email {
1688 my ($email) = @_;
1690 my $matched = 0;
1691 my ($name, $address) = parse_email($email);
1692 $email = format_email($name, $address, 1);
1693 $email = mailmap_email($email);
1695 return $email if (!$email_remove_duplicates);
1697 ($name, $address) = parse_email($email);
1699 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1700 $name = $deduplicate_name_hash{lc($name)}->[0];
1701 $address = $deduplicate_name_hash{lc($name)}->[1];
1702 $matched = 1;
1703 } elsif ($deduplicate_address_hash{lc($address)}) {
1704 $name = $deduplicate_address_hash{lc($address)}->[0];
1705 $address = $deduplicate_address_hash{lc($address)}->[1];
1706 $matched = 1;
1708 if (!$matched) {
1709 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1710 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1712 $email = format_email($name, $address, 1);
1713 $email = mailmap_email($email);
1714 return $email;
1717 sub save_commits_by_author {
1718 my (@lines) = @_;
1720 my @authors = ();
1721 my @commits = ();
1722 my @subjects = ();
1724 foreach my $line (@lines) {
1725 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1726 my $author = $1;
1727 $author = deduplicate_email($author);
1728 push(@authors, $author);
1730 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1731 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1734 for (my $i = 0; $i < @authors; $i++) {
1735 my $exists = 0;
1736 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1737 if (@{$ref}[0] eq $commits[$i] &&
1738 @{$ref}[1] eq $subjects[$i]) {
1739 $exists = 1;
1740 last;
1743 if (!$exists) {
1744 push(@{$commit_author_hash{$authors[$i]}},
1745 [ ($commits[$i], $subjects[$i]) ]);
1750 sub save_commits_by_signer {
1751 my (@lines) = @_;
1753 my $commit = "";
1754 my $subject = "";
1756 foreach my $line (@lines) {
1757 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1758 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1759 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1760 my @signatures = ($line);
1761 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1762 my @types = @$types_ref;
1763 my @signers = @$signers_ref;
1765 my $type = $types[0];
1766 my $signer = $signers[0];
1768 $signer = deduplicate_email($signer);
1770 my $exists = 0;
1771 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1772 if (@{$ref}[0] eq $commit &&
1773 @{$ref}[1] eq $subject &&
1774 @{$ref}[2] eq $type) {
1775 $exists = 1;
1776 last;
1779 if (!$exists) {
1780 push(@{$commit_signer_hash{$signer}},
1781 [ ($commit, $subject, $type) ]);
1787 sub vcs_assign {
1788 my ($role, $divisor, @lines) = @_;
1790 my %hash;
1791 my $count = 0;
1793 return if (@lines <= 0);
1795 if ($divisor <= 0) {
1796 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1797 $divisor = 1;
1800 @lines = mailmap(@lines);
1802 return if (@lines <= 0);
1804 @lines = sort(@lines);
1806 # uniq -c
1807 $hash{$_}++ for @lines;
1809 # sort -rn
1810 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1811 my $sign_offs = $hash{$line};
1812 my $percent = $sign_offs * 100 / $divisor;
1814 $percent = 100 if ($percent > 100);
1815 $count++;
1816 last if ($sign_offs < $email_git_min_signatures ||
1817 $count > $email_git_max_maintainers ||
1818 $percent < $email_git_min_percent);
1819 push_email_address($line, '');
1820 if ($output_rolestats) {
1821 my $fmt_percent = sprintf("%.0f", $percent);
1822 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1823 } else {
1824 add_role($line, $role);
1829 sub vcs_file_signoffs {
1830 my ($file) = @_;
1832 my @signers = ();
1833 my $commits;
1835 $vcs_used = vcs_exists();
1836 return if (!$vcs_used);
1838 my $cmd = $VCS_cmds{"find_signers_cmd"};
1839 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1841 ($commits, @signers) = vcs_find_signers($cmd);
1843 foreach my $signer (@signers) {
1844 $signer = deduplicate_email($signer);
1847 vcs_assign("commit_signer", $commits, @signers);
1850 sub vcs_file_blame {
1851 my ($file) = @_;
1853 my @signers = ();
1854 my @all_commits = ();
1855 my @commits = ();
1856 my $total_commits;
1857 my $total_lines;
1859 $vcs_used = vcs_exists();
1860 return if (!$vcs_used);
1862 @all_commits = vcs_blame($file);
1863 @commits = uniq(@all_commits);
1864 $total_commits = @commits;
1865 $total_lines = @all_commits;
1867 if ($email_git_blame_signatures) {
1868 if (vcs_is_hg()) {
1869 my $commit_count;
1870 my @commit_signers = ();
1871 my $commit = join(" -r ", @commits);
1872 my $cmd;
1874 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1875 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1877 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1879 push(@signers, @commit_signers);
1880 } else {
1881 foreach my $commit (@commits) {
1882 my $commit_count;
1883 my @commit_signers = ();
1884 my $cmd;
1886 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1887 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1889 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1891 push(@signers, @commit_signers);
1896 if ($from_filename) {
1897 if ($output_rolestats) {
1898 my @blame_signers;
1899 if (vcs_is_hg()) {{ # Double brace for last exit
1900 my $commit_count;
1901 my @commit_signers = ();
1902 @commits = uniq(@commits);
1903 @commits = sort(@commits);
1904 my $commit = join(" -r ", @commits);
1905 my $cmd;
1907 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1908 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1910 my @lines = ();
1912 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1914 if (!$email_git_penguin_chiefs) {
1915 @lines = grep(!/${penguin_chiefs}/i, @lines);
1918 last if !@lines;
1920 my @authors = ();
1921 foreach my $line (@lines) {
1922 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1923 my $author = $1;
1924 $author = deduplicate_email($author);
1925 push(@authors, $author);
1929 save_commits_by_author(@lines) if ($interactive);
1930 save_commits_by_signer(@lines) if ($interactive);
1932 push(@signers, @authors);
1934 else {
1935 foreach my $commit (@commits) {
1936 my $i;
1937 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1938 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1939 my @author = vcs_find_author($cmd);
1940 next if !@author;
1942 my $formatted_author = deduplicate_email($author[0]);
1944 my $count = grep(/$commit/, @all_commits);
1945 for ($i = 0; $i < $count ; $i++) {
1946 push(@blame_signers, $formatted_author);
1950 if (@blame_signers) {
1951 vcs_assign("authored lines", $total_lines, @blame_signers);
1954 foreach my $signer (@signers) {
1955 $signer = deduplicate_email($signer);
1957 vcs_assign("commits", $total_commits, @signers);
1958 } else {
1959 foreach my $signer (@signers) {
1960 $signer = deduplicate_email($signer);
1962 vcs_assign("modified commits", $total_commits, @signers);
1966 sub uniq {
1967 my (@parms) = @_;
1969 my %saw;
1970 @parms = grep(!$saw{$_}++, @parms);
1971 return @parms;
1974 sub sort_and_uniq {
1975 my (@parms) = @_;
1977 my %saw;
1978 @parms = sort @parms;
1979 @parms = grep(!$saw{$_}++, @parms);
1980 return @parms;
1983 sub clean_file_emails {
1984 my (@file_emails) = @_;
1985 my @fmt_emails = ();
1987 foreach my $email (@file_emails) {
1988 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1989 my ($name, $address) = parse_email($email);
1990 if ($name eq '"[,\.]"') {
1991 $name = "";
1994 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1995 if (@nw > 2) {
1996 my $first = $nw[@nw - 3];
1997 my $middle = $nw[@nw - 2];
1998 my $last = $nw[@nw - 1];
2000 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2001 (length($first) == 2 && substr($first, -1) eq ".")) ||
2002 (length($middle) == 1 ||
2003 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2004 $name = "$first $middle $last";
2005 } else {
2006 $name = "$middle $last";
2010 if (substr($name, -1) =~ /[,\.]/) {
2011 $name = substr($name, 0, length($name) - 1);
2012 } elsif (substr($name, -2) =~ /[,\.]"/) {
2013 $name = substr($name, 0, length($name) - 2) . '"';
2016 if (substr($name, 0, 1) =~ /[,\.]/) {
2017 $name = substr($name, 1, length($name) - 1);
2018 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2019 $name = '"' . substr($name, 2, length($name) - 2);
2022 my $fmt_email = format_email($name, $address, $email_usename);
2023 push(@fmt_emails, $fmt_email);
2025 return @fmt_emails;
2028 sub merge_email {
2029 my @lines;
2030 my %saw;
2032 for (@_) {
2033 my ($address, $role) = @$_;
2034 if (!$saw{$address}) {
2035 if ($output_roles) {
2036 push(@lines, "$address ($role)");
2037 } else {
2038 push(@lines, $address);
2040 $saw{$address} = 1;
2044 return @lines;
2047 sub output {
2048 my (@parms) = @_;
2050 if ($output_multiline) {
2051 foreach my $line (@parms) {
2052 print("${line}\n");
2054 } else {
2055 print(join($output_separator, @parms));
2056 print("\n");
2060 my $rfc822re;
2062 sub make_rfc822re {
2063 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2064 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2065 # This regexp will only work on addresses which have had comments stripped
2066 # and replaced with rfc822_lwsp.
2068 my $specials = '()<>@,;:\\\\".\\[\\]';
2069 my $controls = '\\000-\\037\\177';
2071 my $dtext = "[^\\[\\]\\r\\\\]";
2072 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2074 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2076 # Use zero-width assertion to spot the limit of an atom. A simple
2077 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2078 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2079 my $word = "(?:$atom|$quoted_string)";
2080 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2082 my $sub_domain = "(?:$atom|$domain_literal)";
2083 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2085 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2087 my $phrase = "$word*";
2088 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2089 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2090 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2092 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2093 my $address = "(?:$mailbox|$group)";
2095 return "$rfc822_lwsp*$address";
2098 sub rfc822_strip_comments {
2099 my $s = shift;
2100 # Recursively remove comments, and replace with a single space. The simpler
2101 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2102 # chars in atoms, for example.
2104 while ($s =~ s/^((?:[^"\\]|\\.)*
2105 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2106 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2107 return $s;
2110 # valid: returns true if the parameter is an RFC822 valid address
2112 sub rfc822_valid {
2113 my $s = rfc822_strip_comments(shift);
2115 if (!$rfc822re) {
2116 $rfc822re = make_rfc822re();
2119 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2122 # validlist: In scalar context, returns true if the parameter is an RFC822
2123 # valid list of addresses.
2125 # In list context, returns an empty list on failure (an invalid
2126 # address was found); otherwise a list whose first element is the
2127 # number of addresses found and whose remaining elements are the
2128 # addresses. This is needed to disambiguate failure (invalid)
2129 # from success with no addresses found, because an empty string is
2130 # a valid list.
2132 sub rfc822_validlist {
2133 my $s = rfc822_strip_comments(shift);
2135 if (!$rfc822re) {
2136 $rfc822re = make_rfc822re();
2138 # * null list items are valid according to the RFC
2139 # * the '1' business is to aid in distinguishing failure from no results
2141 my @r;
2142 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2143 $s =~ m/^$rfc822_char*$/) {
2144 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2145 push(@r, $1);
2147 return wantarray ? (scalar(@r), @r) : 1;
2149 return wantarray ? () : 0;