xen/gntdev: add VM_PFNMAP to vma
[zen-stable.git] / scripts / get_maintainer.pl
blobd21ec3a89603b0fa8155671587deaebaa52b6163
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-beta6';
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 = 0;
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 get_maintainers {
498 %email_hash_name = ();
499 %email_hash_address = ();
500 %commit_author_hash = ();
501 %commit_signer_hash = ();
502 @email_to = ();
503 %hash_list_to = ();
504 @list_to = ();
505 @scm = ();
506 @web = ();
507 @subsystem = ();
508 @status = ();
509 %deduplicate_name_hash = ();
510 %deduplicate_address_hash = ();
511 if ($email_git_all_signature_types) {
512 $signature_pattern = "(.+?)[Bb][Yy]:";
513 } else {
514 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
517 # Find responsible parties
519 my %exact_pattern_match_hash = ();
521 foreach my $file (@files) {
523 my %hash;
524 my $tvi = find_first_section();
525 while ($tvi < @typevalue) {
526 my $start = find_starting_index($tvi);
527 my $end = find_ending_index($tvi);
528 my $exclude = 0;
529 my $i;
531 #Do not match excluded file patterns
533 for ($i = $start; $i < $end; $i++) {
534 my $line = $typevalue[$i];
535 if ($line =~ m/^(\C):\s*(.*)/) {
536 my $type = $1;
537 my $value = $2;
538 if ($type eq 'X') {
539 if (file_match_pattern($file, $value)) {
540 $exclude = 1;
541 last;
547 if (!$exclude) {
548 for ($i = $start; $i < $end; $i++) {
549 my $line = $typevalue[$i];
550 if ($line =~ m/^(\C):\s*(.*)/) {
551 my $type = $1;
552 my $value = $2;
553 if ($type eq 'F') {
554 if (file_match_pattern($file, $value)) {
555 my $value_pd = ($value =~ tr@/@@);
556 my $file_pd = ($file =~ tr@/@@);
557 $value_pd++ if (substr($value,-1,1) ne "/");
558 $value_pd = -1 if ($value =~ /^\.\*/);
559 if ($value_pd >= $file_pd) {
560 $exact_pattern_match_hash{$file} = 1;
562 if ($pattern_depth == 0 ||
563 (($file_pd - $value_pd) < $pattern_depth)) {
564 $hash{$tvi} = $value_pd;
571 $tvi = $end + 1;
574 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
575 add_categories($line);
576 if ($sections) {
577 my $i;
578 my $start = find_starting_index($line);
579 my $end = find_ending_index($line);
580 for ($i = $start; $i < $end; $i++) {
581 my $line = $typevalue[$i];
582 if ($line =~ /^[FX]:/) { ##Restore file patterns
583 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
584 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
585 $line =~ s/\\\./\./g; ##Convert \. to .
586 $line =~ s/\.\*/\*/g; ##Convert .* to *
588 $line =~ s/^([A-Z]):/$1:\t/g;
589 print("$line\n");
591 print("\n");
596 if ($keywords) {
597 @keyword_tvi = sort_and_uniq(@keyword_tvi);
598 foreach my $line (@keyword_tvi) {
599 add_categories($line);
603 foreach my $email (@email_to, @list_to) {
604 $email->[0] = deduplicate_email($email->[0]);
607 foreach my $file (@files) {
608 if ($email &&
609 ($email_git || ($email_git_fallback &&
610 !$exact_pattern_match_hash{$file}))) {
611 vcs_file_signoffs($file);
613 if ($email && $email_git_blame) {
614 vcs_file_blame($file);
618 if ($email) {
619 foreach my $chief (@penguin_chief) {
620 if ($chief =~ m/^(.*):(.*)/) {
621 my $email_address;
623 $email_address = format_email($1, $2, $email_usename);
624 if ($email_git_penguin_chiefs) {
625 push(@email_to, [$email_address, 'chief penguin']);
626 } else {
627 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
632 foreach my $email (@file_emails) {
633 my ($name, $address) = parse_email($email);
635 my $tmp_email = format_email($name, $address, $email_usename);
636 push_email_address($tmp_email, '');
637 add_role($tmp_email, 'in file');
641 my @to = ();
642 if ($email || $email_list) {
643 if ($email) {
644 @to = (@to, @email_to);
646 if ($email_list) {
647 @to = (@to, @list_to);
651 if ($interactive) {
652 @to = interactive_get_maintainers(\@to);
655 return @to;
658 sub file_match_pattern {
659 my ($file, $pattern) = @_;
660 if (substr($pattern, -1) eq "/") {
661 if ($file =~ m@^$pattern@) {
662 return 1;
664 } else {
665 if ($file =~ m@^$pattern@) {
666 my $s1 = ($file =~ tr@/@@);
667 my $s2 = ($pattern =~ tr@/@@);
668 if ($s1 == $s2) {
669 return 1;
673 return 0;
676 sub usage {
677 print <<EOT;
678 usage: $P [options] patchfile
679 $P [options] -f file|directory
680 version: $V
682 MAINTAINER field selection options:
683 --email => print email address(es) if any
684 --git => include recent git \*-by: signers
685 --git-all-signature-types => include signers regardless of signature type
686 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
687 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
688 --git-chief-penguins => include ${penguin_chiefs}
689 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
690 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
691 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
692 --git-blame => use git blame to find modified commits for patch or file
693 --git-since => git history to use (default: $email_git_since)
694 --hg-since => hg history to use (default: $email_hg_since)
695 --interactive => display a menu (mostly useful if used with the --git option)
696 --m => include maintainer(s) if any
697 --n => include name 'Full Name <addr\@domain.tld>'
698 --l => include list(s) if any
699 --s => include subscriber only list(s) if any
700 --remove-duplicates => minimize duplicate email names/addresses
701 --roles => show roles (status:subsystem, git-signer, list, etc...)
702 --rolestats => show roles and statistics (commits/total_commits, %)
703 --file-emails => add email addresses found in -f file (default: 0 (off))
704 --scm => print SCM tree(s) if any
705 --status => print status if any
706 --subsystem => print subsystem name if any
707 --web => print website(s) if any
709 Output type options:
710 --separator [, ] => separator for multiple entries on 1 line
711 using --separator also sets --nomultiline if --separator is not [, ]
712 --multiline => print 1 entry per line
714 Other options:
715 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
716 --keywords => scan patch for keywords (default: $keywords)
717 --sections => print all of the subsystem sections with pattern matches
718 --mailmap => use .mailmap file (default: $email_use_mailmap)
719 --version => show version
720 --help => show this help information
722 Default options:
723 [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates]
725 Notes:
726 Using "-f directory" may give unexpected results:
727 Used with "--git", git signators for _all_ files in and below
728 directory are examined as git recurses directories.
729 Any specified X: (exclude) pattern matches are _not_ ignored.
730 Used with "--nogit", directory is used as a pattern match,
731 no individual file within the directory or subdirectory
732 is matched.
733 Used with "--git-blame", does not iterate all files in directory
734 Using "--git-blame" is slow and may add old committers and authors
735 that are no longer active maintainers to the output.
736 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
737 other automated tools that expect only ["name"] <email address>
738 may not work because of additional output after <email address>.
739 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
740 not the percentage of the entire file authored. # of commits is
741 not a good measure of amount of code authored. 1 major commit may
742 contain a thousand lines, 5 trivial commits may modify a single line.
743 If git is not installed, but mercurial (hg) is installed and an .hg
744 repository exists, the following options apply to mercurial:
745 --git,
746 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
747 --git-blame
748 Use --hg-since not --git-since to control date selection
749 File ".get_maintainer.conf", if it exists in the linux kernel source root
750 directory, can change whatever get_maintainer defaults are desired.
751 Entries in this file can be any command line argument.
752 This file is prepended to any additional command line arguments.
753 Multiple lines and # comments are allowed.
757 sub top_of_kernel_tree {
758 my ($lk_path) = @_;
760 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
761 $lk_path .= "/";
763 if ( (-f "${lk_path}COPYING")
764 && (-f "${lk_path}CREDITS")
765 && (-f "${lk_path}Kbuild")
766 && (-f "${lk_path}MAINTAINERS")
767 && (-f "${lk_path}Makefile")
768 && (-f "${lk_path}README")
769 && (-d "${lk_path}Documentation")
770 && (-d "${lk_path}arch")
771 && (-d "${lk_path}include")
772 && (-d "${lk_path}drivers")
773 && (-d "${lk_path}fs")
774 && (-d "${lk_path}init")
775 && (-d "${lk_path}ipc")
776 && (-d "${lk_path}kernel")
777 && (-d "${lk_path}lib")
778 && (-d "${lk_path}scripts")) {
779 return 1;
781 return 0;
784 sub parse_email {
785 my ($formatted_email) = @_;
787 my $name = "";
788 my $address = "";
790 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
791 $name = $1;
792 $address = $2;
793 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
794 $address = $1;
795 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
796 $address = $1;
799 $name =~ s/^\s+|\s+$//g;
800 $name =~ s/^\"|\"$//g;
801 $address =~ s/^\s+|\s+$//g;
803 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
804 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
805 $name = "\"$name\"";
808 return ($name, $address);
811 sub format_email {
812 my ($name, $address, $usename) = @_;
814 my $formatted_email;
816 $name =~ s/^\s+|\s+$//g;
817 $name =~ s/^\"|\"$//g;
818 $address =~ s/^\s+|\s+$//g;
820 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
821 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
822 $name = "\"$name\"";
825 if ($usename) {
826 if ("$name" eq "") {
827 $formatted_email = "$address";
828 } else {
829 $formatted_email = "$name <$address>";
831 } else {
832 $formatted_email = $address;
835 return $formatted_email;
838 sub find_first_section {
839 my $index = 0;
841 while ($index < @typevalue) {
842 my $tv = $typevalue[$index];
843 if (($tv =~ m/^(\C):\s*(.*)/)) {
844 last;
846 $index++;
849 return $index;
852 sub find_starting_index {
853 my ($index) = @_;
855 while ($index > 0) {
856 my $tv = $typevalue[$index];
857 if (!($tv =~ m/^(\C):\s*(.*)/)) {
858 last;
860 $index--;
863 return $index;
866 sub find_ending_index {
867 my ($index) = @_;
869 while ($index < @typevalue) {
870 my $tv = $typevalue[$index];
871 if (!($tv =~ m/^(\C):\s*(.*)/)) {
872 last;
874 $index++;
877 return $index;
880 sub get_maintainer_role {
881 my ($index) = @_;
883 my $i;
884 my $start = find_starting_index($index);
885 my $end = find_ending_index($index);
887 my $role;
888 my $subsystem = $typevalue[$start];
889 if (length($subsystem) > 20) {
890 $subsystem = substr($subsystem, 0, 17);
891 $subsystem =~ s/\s*$//;
892 $subsystem = $subsystem . "...";
895 for ($i = $start + 1; $i < $end; $i++) {
896 my $tv = $typevalue[$i];
897 if ($tv =~ m/^(\C):\s*(.*)/) {
898 my $ptype = $1;
899 my $pvalue = $2;
900 if ($ptype eq "S") {
901 $role = $pvalue;
906 $role = lc($role);
907 if ($role eq "supported") {
908 $role = "supporter";
909 } elsif ($role eq "maintained") {
910 $role = "maintainer";
911 } elsif ($role eq "odd fixes") {
912 $role = "odd fixer";
913 } elsif ($role eq "orphan") {
914 $role = "orphan minder";
915 } elsif ($role eq "obsolete") {
916 $role = "obsolete minder";
917 } elsif ($role eq "buried alive in reporters") {
918 $role = "chief penguin";
921 return $role . ":" . $subsystem;
924 sub get_list_role {
925 my ($index) = @_;
927 my $i;
928 my $start = find_starting_index($index);
929 my $end = find_ending_index($index);
931 my $subsystem = $typevalue[$start];
932 if (length($subsystem) > 20) {
933 $subsystem = substr($subsystem, 0, 17);
934 $subsystem =~ s/\s*$//;
935 $subsystem = $subsystem . "...";
938 if ($subsystem eq "THE REST") {
939 $subsystem = "";
942 return $subsystem;
945 sub add_categories {
946 my ($index) = @_;
948 my $i;
949 my $start = find_starting_index($index);
950 my $end = find_ending_index($index);
952 push(@subsystem, $typevalue[$start]);
954 for ($i = $start + 1; $i < $end; $i++) {
955 my $tv = $typevalue[$i];
956 if ($tv =~ m/^(\C):\s*(.*)/) {
957 my $ptype = $1;
958 my $pvalue = $2;
959 if ($ptype eq "L") {
960 my $list_address = $pvalue;
961 my $list_additional = "";
962 my $list_role = get_list_role($i);
964 if ($list_role ne "") {
965 $list_role = ":" . $list_role;
967 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
968 $list_address = $1;
969 $list_additional = $2;
971 if ($list_additional =~ m/subscribers-only/) {
972 if ($email_subscriber_list) {
973 if (!$hash_list_to{lc($list_address)}) {
974 $hash_list_to{lc($list_address)} = 1;
975 push(@list_to, [$list_address,
976 "subscriber list${list_role}"]);
979 } else {
980 if ($email_list) {
981 if (!$hash_list_to{lc($list_address)}) {
982 $hash_list_to{lc($list_address)} = 1;
983 push(@list_to, [$list_address,
984 "open list${list_role}"]);
988 } elsif ($ptype eq "M") {
989 my ($name, $address) = parse_email($pvalue);
990 if ($name eq "") {
991 if ($i > 0) {
992 my $tv = $typevalue[$i - 1];
993 if ($tv =~ m/^(\C):\s*(.*)/) {
994 if ($1 eq "P") {
995 $name = $2;
996 $pvalue = format_email($name, $address, $email_usename);
1001 if ($email_maintainer) {
1002 my $role = get_maintainer_role($i);
1003 push_email_addresses($pvalue, $role);
1005 } elsif ($ptype eq "T") {
1006 push(@scm, $pvalue);
1007 } elsif ($ptype eq "W") {
1008 push(@web, $pvalue);
1009 } elsif ($ptype eq "S") {
1010 push(@status, $pvalue);
1016 sub email_inuse {
1017 my ($name, $address) = @_;
1019 return 1 if (($name eq "") && ($address eq ""));
1020 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1021 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1023 return 0;
1026 sub push_email_address {
1027 my ($line, $role) = @_;
1029 my ($name, $address) = parse_email($line);
1031 if ($address eq "") {
1032 return 0;
1035 if (!$email_remove_duplicates) {
1036 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1037 } elsif (!email_inuse($name, $address)) {
1038 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1039 $email_hash_name{lc($name)}++ if ($name ne "");
1040 $email_hash_address{lc($address)}++;
1043 return 1;
1046 sub push_email_addresses {
1047 my ($address, $role) = @_;
1049 my @address_list = ();
1051 if (rfc822_valid($address)) {
1052 push_email_address($address, $role);
1053 } elsif (@address_list = rfc822_validlist($address)) {
1054 my $array_count = shift(@address_list);
1055 while (my $entry = shift(@address_list)) {
1056 push_email_address($entry, $role);
1058 } else {
1059 if (!push_email_address($address, $role)) {
1060 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1065 sub add_role {
1066 my ($line, $role) = @_;
1068 my ($name, $address) = parse_email($line);
1069 my $email = format_email($name, $address, $email_usename);
1071 foreach my $entry (@email_to) {
1072 if ($email_remove_duplicates) {
1073 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1074 if (($name eq $entry_name || $address eq $entry_address)
1075 && ($role eq "" || !($entry->[1] =~ m/$role/))
1077 if ($entry->[1] eq "") {
1078 $entry->[1] = "$role";
1079 } else {
1080 $entry->[1] = "$entry->[1],$role";
1083 } else {
1084 if ($email eq $entry->[0]
1085 && ($role eq "" || !($entry->[1] =~ m/$role/))
1087 if ($entry->[1] eq "") {
1088 $entry->[1] = "$role";
1089 } else {
1090 $entry->[1] = "$entry->[1],$role";
1097 sub which {
1098 my ($bin) = @_;
1100 foreach my $path (split(/:/, $ENV{PATH})) {
1101 if (-e "$path/$bin") {
1102 return "$path/$bin";
1106 return "";
1109 sub which_conf {
1110 my ($conf) = @_;
1112 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1113 if (-e "$path/$conf") {
1114 return "$path/$conf";
1118 return "";
1121 sub mailmap_email {
1122 my ($line) = @_;
1124 my ($name, $address) = parse_email($line);
1125 my $email = format_email($name, $address, 1);
1126 my $real_name = $name;
1127 my $real_address = $address;
1129 if (exists $mailmap->{names}->{$email} ||
1130 exists $mailmap->{addresses}->{$email}) {
1131 if (exists $mailmap->{names}->{$email}) {
1132 $real_name = $mailmap->{names}->{$email};
1134 if (exists $mailmap->{addresses}->{$email}) {
1135 $real_address = $mailmap->{addresses}->{$email};
1137 } else {
1138 if (exists $mailmap->{names}->{$address}) {
1139 $real_name = $mailmap->{names}->{$address};
1141 if (exists $mailmap->{addresses}->{$address}) {
1142 $real_address = $mailmap->{addresses}->{$address};
1145 return format_email($real_name, $real_address, 1);
1148 sub mailmap {
1149 my (@addresses) = @_;
1151 my @mapped_emails = ();
1152 foreach my $line (@addresses) {
1153 push(@mapped_emails, mailmap_email($line));
1155 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1156 return @mapped_emails;
1159 sub merge_by_realname {
1160 my %address_map;
1161 my (@emails) = @_;
1163 foreach my $email (@emails) {
1164 my ($name, $address) = parse_email($email);
1165 if (exists $address_map{$name}) {
1166 $address = $address_map{$name};
1167 $email = format_email($name, $address, 1);
1168 } else {
1169 $address_map{$name} = $address;
1174 sub git_execute_cmd {
1175 my ($cmd) = @_;
1176 my @lines = ();
1178 my $output = `$cmd`;
1179 $output =~ s/^\s*//gm;
1180 @lines = split("\n", $output);
1182 return @lines;
1185 sub hg_execute_cmd {
1186 my ($cmd) = @_;
1187 my @lines = ();
1189 my $output = `$cmd`;
1190 @lines = split("\n", $output);
1192 return @lines;
1195 sub extract_formatted_signatures {
1196 my (@signature_lines) = @_;
1198 my @type = @signature_lines;
1200 s/\s*(.*):.*/$1/ for (@type);
1202 # cut -f2- -d":"
1203 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1205 ## Reformat email addresses (with names) to avoid badly written signatures
1207 foreach my $signer (@signature_lines) {
1208 $signer = deduplicate_email($signer);
1211 return (\@type, \@signature_lines);
1214 sub vcs_find_signers {
1215 my ($cmd) = @_;
1216 my $commits;
1217 my @lines = ();
1218 my @signatures = ();
1220 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1222 my $pattern = $VCS_cmds{"commit_pattern"};
1224 $commits = grep(/$pattern/, @lines); # of commits
1226 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1228 return (0, @signatures) if !@signatures;
1230 save_commits_by_author(@lines) if ($interactive);
1231 save_commits_by_signer(@lines) if ($interactive);
1233 if (!$email_git_penguin_chiefs) {
1234 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1237 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1239 return ($commits, @$signers_ref);
1242 sub vcs_find_author {
1243 my ($cmd) = @_;
1244 my @lines = ();
1246 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1248 if (!$email_git_penguin_chiefs) {
1249 @lines = grep(!/${penguin_chiefs}/i, @lines);
1252 return @lines if !@lines;
1254 my @authors = ();
1255 foreach my $line (@lines) {
1256 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1257 my $author = $1;
1258 my ($name, $address) = parse_email($author);
1259 $author = format_email($name, $address, 1);
1260 push(@authors, $author);
1264 save_commits_by_author(@lines) if ($interactive);
1265 save_commits_by_signer(@lines) if ($interactive);
1267 return @authors;
1270 sub vcs_save_commits {
1271 my ($cmd) = @_;
1272 my @lines = ();
1273 my @commits = ();
1275 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1277 foreach my $line (@lines) {
1278 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1279 push(@commits, $1);
1283 return @commits;
1286 sub vcs_blame {
1287 my ($file) = @_;
1288 my $cmd;
1289 my @commits = ();
1291 return @commits if (!(-f $file));
1293 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1294 my @all_commits = ();
1296 $cmd = $VCS_cmds{"blame_file_cmd"};
1297 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1298 @all_commits = vcs_save_commits($cmd);
1300 foreach my $file_range_diff (@range) {
1301 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1302 my $diff_file = $1;
1303 my $diff_start = $2;
1304 my $diff_length = $3;
1305 next if ("$file" ne "$diff_file");
1306 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1307 push(@commits, $all_commits[$i]);
1310 } elsif (@range) {
1311 foreach my $file_range_diff (@range) {
1312 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1313 my $diff_file = $1;
1314 my $diff_start = $2;
1315 my $diff_length = $3;
1316 next if ("$file" ne "$diff_file");
1317 $cmd = $VCS_cmds{"blame_range_cmd"};
1318 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1319 push(@commits, vcs_save_commits($cmd));
1321 } else {
1322 $cmd = $VCS_cmds{"blame_file_cmd"};
1323 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1324 @commits = vcs_save_commits($cmd);
1327 foreach my $commit (@commits) {
1328 $commit =~ s/^\^//g;
1331 return @commits;
1334 my $printed_novcs = 0;
1335 sub vcs_exists {
1336 %VCS_cmds = %VCS_cmds_git;
1337 return 1 if eval $VCS_cmds{"available"};
1338 %VCS_cmds = %VCS_cmds_hg;
1339 return 2 if eval $VCS_cmds{"available"};
1340 %VCS_cmds = ();
1341 if (!$printed_novcs) {
1342 warn("$P: No supported VCS found. Add --nogit to options?\n");
1343 warn("Using a git repository produces better results.\n");
1344 warn("Try Linus Torvalds' latest git repository using:\n");
1345 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1346 $printed_novcs = 1;
1348 return 0;
1351 sub vcs_is_git {
1352 vcs_exists();
1353 return $vcs_used == 1;
1356 sub vcs_is_hg {
1357 return $vcs_used == 2;
1360 sub interactive_get_maintainers {
1361 my ($list_ref) = @_;
1362 my @list = @$list_ref;
1364 vcs_exists();
1366 my %selected;
1367 my %authored;
1368 my %signed;
1369 my $count = 0;
1370 my $maintained = 0;
1371 foreach my $entry (@list) {
1372 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1373 $selected{$count} = 1;
1374 $authored{$count} = 0;
1375 $signed{$count} = 0;
1376 $count++;
1379 #menu loop
1380 my $done = 0;
1381 my $print_options = 0;
1382 my $redraw = 1;
1383 while (!$done) {
1384 $count = 0;
1385 if ($redraw) {
1386 printf STDERR "\n%1s %2s %-65s",
1387 "*", "#", "email/list and role:stats";
1388 if ($email_git ||
1389 ($email_git_fallback && !$maintained) ||
1390 $email_git_blame) {
1391 print STDERR "auth sign";
1393 print STDERR "\n";
1394 foreach my $entry (@list) {
1395 my $email = $entry->[0];
1396 my $role = $entry->[1];
1397 my $sel = "";
1398 $sel = "*" if ($selected{$count});
1399 my $commit_author = $commit_author_hash{$email};
1400 my $commit_signer = $commit_signer_hash{$email};
1401 my $authored = 0;
1402 my $signed = 0;
1403 $authored++ for (@{$commit_author});
1404 $signed++ for (@{$commit_signer});
1405 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1406 printf STDERR "%4d %4d", $authored, $signed
1407 if ($authored > 0 || $signed > 0);
1408 printf STDERR "\n %s\n", $role;
1409 if ($authored{$count}) {
1410 my $commit_author = $commit_author_hash{$email};
1411 foreach my $ref (@{$commit_author}) {
1412 print STDERR " Author: @{$ref}[1]\n";
1415 if ($signed{$count}) {
1416 my $commit_signer = $commit_signer_hash{$email};
1417 foreach my $ref (@{$commit_signer}) {
1418 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1422 $count++;
1425 my $date_ref = \$email_git_since;
1426 $date_ref = \$email_hg_since if (vcs_is_hg());
1427 if ($print_options) {
1428 $print_options = 0;
1429 if (vcs_exists()) {
1430 print STDERR <<EOT
1432 Version Control options:
1433 g use git history [$email_git]
1434 gf use git-fallback [$email_git_fallback]
1435 b use git blame [$email_git_blame]
1436 bs use blame signatures [$email_git_blame_signatures]
1437 c# minimum commits [$email_git_min_signatures]
1438 %# min percent [$email_git_min_percent]
1439 d# history to use [$$date_ref]
1440 x# max maintainers [$email_git_max_maintainers]
1441 t all signature types [$email_git_all_signature_types]
1442 m use .mailmap [$email_use_mailmap]
1445 print STDERR <<EOT
1447 Additional options:
1448 0 toggle all
1449 tm toggle maintainers
1450 tg toggle git entries
1451 tl toggle open list entries
1452 ts toggle subscriber list entries
1453 f emails in file [$file_emails]
1454 k keywords in file [$keywords]
1455 r remove duplicates [$email_remove_duplicates]
1456 p# pattern match depth [$pattern_depth]
1459 print STDERR
1460 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1462 my $input = <STDIN>;
1463 chomp($input);
1465 $redraw = 1;
1466 my $rerun = 0;
1467 my @wish = split(/[, ]+/, $input);
1468 foreach my $nr (@wish) {
1469 $nr = lc($nr);
1470 my $sel = substr($nr, 0, 1);
1471 my $str = substr($nr, 1);
1472 my $val = 0;
1473 $val = $1 if $str =~ /^(\d+)$/;
1475 if ($sel eq "y") {
1476 $interactive = 0;
1477 $done = 1;
1478 $output_rolestats = 0;
1479 $output_roles = 0;
1480 last;
1481 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1482 $selected{$nr - 1} = !$selected{$nr - 1};
1483 } elsif ($sel eq "*" || $sel eq '^') {
1484 my $toggle = 0;
1485 $toggle = 1 if ($sel eq '*');
1486 for (my $i = 0; $i < $count; $i++) {
1487 $selected{$i} = $toggle;
1489 } elsif ($sel eq "0") {
1490 for (my $i = 0; $i < $count; $i++) {
1491 $selected{$i} = !$selected{$i};
1493 } elsif ($sel eq "t") {
1494 if (lc($str) eq "m") {
1495 for (my $i = 0; $i < $count; $i++) {
1496 $selected{$i} = !$selected{$i}
1497 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1499 } elsif (lc($str) eq "g") {
1500 for (my $i = 0; $i < $count; $i++) {
1501 $selected{$i} = !$selected{$i}
1502 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1504 } elsif (lc($str) eq "l") {
1505 for (my $i = 0; $i < $count; $i++) {
1506 $selected{$i} = !$selected{$i}
1507 if ($list[$i]->[1] =~ /^(open list)/i);
1509 } elsif (lc($str) eq "s") {
1510 for (my $i = 0; $i < $count; $i++) {
1511 $selected{$i} = !$selected{$i}
1512 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1515 } elsif ($sel eq "a") {
1516 if ($val > 0 && $val <= $count) {
1517 $authored{$val - 1} = !$authored{$val - 1};
1518 } elsif ($str eq '*' || $str eq '^') {
1519 my $toggle = 0;
1520 $toggle = 1 if ($str eq '*');
1521 for (my $i = 0; $i < $count; $i++) {
1522 $authored{$i} = $toggle;
1525 } elsif ($sel eq "s") {
1526 if ($val > 0 && $val <= $count) {
1527 $signed{$val - 1} = !$signed{$val - 1};
1528 } elsif ($str eq '*' || $str eq '^') {
1529 my $toggle = 0;
1530 $toggle = 1 if ($str eq '*');
1531 for (my $i = 0; $i < $count; $i++) {
1532 $signed{$i} = $toggle;
1535 } elsif ($sel eq "o") {
1536 $print_options = 1;
1537 $redraw = 1;
1538 } elsif ($sel eq "g") {
1539 if ($str eq "f") {
1540 bool_invert(\$email_git_fallback);
1541 } else {
1542 bool_invert(\$email_git);
1544 $rerun = 1;
1545 } elsif ($sel eq "b") {
1546 if ($str eq "s") {
1547 bool_invert(\$email_git_blame_signatures);
1548 } else {
1549 bool_invert(\$email_git_blame);
1551 $rerun = 1;
1552 } elsif ($sel eq "c") {
1553 if ($val > 0) {
1554 $email_git_min_signatures = $val;
1555 $rerun = 1;
1557 } elsif ($sel eq "x") {
1558 if ($val > 0) {
1559 $email_git_max_maintainers = $val;
1560 $rerun = 1;
1562 } elsif ($sel eq "%") {
1563 if ($str ne "" && $val >= 0) {
1564 $email_git_min_percent = $val;
1565 $rerun = 1;
1567 } elsif ($sel eq "d") {
1568 if (vcs_is_git()) {
1569 $email_git_since = $str;
1570 } elsif (vcs_is_hg()) {
1571 $email_hg_since = $str;
1573 $rerun = 1;
1574 } elsif ($sel eq "t") {
1575 bool_invert(\$email_git_all_signature_types);
1576 $rerun = 1;
1577 } elsif ($sel eq "f") {
1578 bool_invert(\$file_emails);
1579 $rerun = 1;
1580 } elsif ($sel eq "r") {
1581 bool_invert(\$email_remove_duplicates);
1582 $rerun = 1;
1583 } elsif ($sel eq "m") {
1584 bool_invert(\$email_use_mailmap);
1585 read_mailmap();
1586 $rerun = 1;
1587 } elsif ($sel eq "k") {
1588 bool_invert(\$keywords);
1589 $rerun = 1;
1590 } elsif ($sel eq "p") {
1591 if ($str ne "" && $val >= 0) {
1592 $pattern_depth = $val;
1593 $rerun = 1;
1595 } elsif ($sel eq "h" || $sel eq "?") {
1596 print STDERR <<EOT
1598 Interactive mode allows you to select the various maintainers, submitters,
1599 commit signers and mailing lists that could be CC'd on a patch.
1601 Any *'d entry is selected.
1603 If you have git or hg installed, you can choose to summarize the commit
1604 history of files in the patch. Also, each line of the current file can
1605 be matched to its commit author and that commits signers with blame.
1607 Various knobs exist to control the length of time for active commit
1608 tracking, the maximum number of commit authors and signers to add,
1609 and such.
1611 Enter selections at the prompt until you are satisfied that the selected
1612 maintainers are appropriate. You may enter multiple selections separated
1613 by either commas or spaces.
1616 } else {
1617 print STDERR "invalid option: '$nr'\n";
1618 $redraw = 0;
1621 if ($rerun) {
1622 print STDERR "git-blame can be very slow, please have patience..."
1623 if ($email_git_blame);
1624 goto &get_maintainers;
1628 #drop not selected entries
1629 $count = 0;
1630 my @new_emailto = ();
1631 foreach my $entry (@list) {
1632 if ($selected{$count}) {
1633 push(@new_emailto, $list[$count]);
1635 $count++;
1637 return @new_emailto;
1640 sub bool_invert {
1641 my ($bool_ref) = @_;
1643 if ($$bool_ref) {
1644 $$bool_ref = 0;
1645 } else {
1646 $$bool_ref = 1;
1650 sub deduplicate_email {
1651 my ($email) = @_;
1653 my $matched = 0;
1654 my ($name, $address) = parse_email($email);
1655 $email = format_email($name, $address, 1);
1656 $email = mailmap_email($email);
1658 return $email if (!$email_remove_duplicates);
1660 ($name, $address) = parse_email($email);
1662 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1663 $name = $deduplicate_name_hash{lc($name)}->[0];
1664 $address = $deduplicate_name_hash{lc($name)}->[1];
1665 $matched = 1;
1666 } elsif ($deduplicate_address_hash{lc($address)}) {
1667 $name = $deduplicate_address_hash{lc($address)}->[0];
1668 $address = $deduplicate_address_hash{lc($address)}->[1];
1669 $matched = 1;
1671 if (!$matched) {
1672 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1673 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1675 $email = format_email($name, $address, 1);
1676 $email = mailmap_email($email);
1677 return $email;
1680 sub save_commits_by_author {
1681 my (@lines) = @_;
1683 my @authors = ();
1684 my @commits = ();
1685 my @subjects = ();
1687 foreach my $line (@lines) {
1688 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1689 my $author = $1;
1690 $author = deduplicate_email($author);
1691 push(@authors, $author);
1693 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1694 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1697 for (my $i = 0; $i < @authors; $i++) {
1698 my $exists = 0;
1699 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1700 if (@{$ref}[0] eq $commits[$i] &&
1701 @{$ref}[1] eq $subjects[$i]) {
1702 $exists = 1;
1703 last;
1706 if (!$exists) {
1707 push(@{$commit_author_hash{$authors[$i]}},
1708 [ ($commits[$i], $subjects[$i]) ]);
1713 sub save_commits_by_signer {
1714 my (@lines) = @_;
1716 my $commit = "";
1717 my $subject = "";
1719 foreach my $line (@lines) {
1720 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1721 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1722 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1723 my @signatures = ($line);
1724 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1725 my @types = @$types_ref;
1726 my @signers = @$signers_ref;
1728 my $type = $types[0];
1729 my $signer = $signers[0];
1731 $signer = deduplicate_email($signer);
1733 my $exists = 0;
1734 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1735 if (@{$ref}[0] eq $commit &&
1736 @{$ref}[1] eq $subject &&
1737 @{$ref}[2] eq $type) {
1738 $exists = 1;
1739 last;
1742 if (!$exists) {
1743 push(@{$commit_signer_hash{$signer}},
1744 [ ($commit, $subject, $type) ]);
1750 sub vcs_assign {
1751 my ($role, $divisor, @lines) = @_;
1753 my %hash;
1754 my $count = 0;
1756 return if (@lines <= 0);
1758 if ($divisor <= 0) {
1759 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1760 $divisor = 1;
1763 @lines = mailmap(@lines);
1765 return if (@lines <= 0);
1767 @lines = sort(@lines);
1769 # uniq -c
1770 $hash{$_}++ for @lines;
1772 # sort -rn
1773 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1774 my $sign_offs = $hash{$line};
1775 my $percent = $sign_offs * 100 / $divisor;
1777 $percent = 100 if ($percent > 100);
1778 $count++;
1779 last if ($sign_offs < $email_git_min_signatures ||
1780 $count > $email_git_max_maintainers ||
1781 $percent < $email_git_min_percent);
1782 push_email_address($line, '');
1783 if ($output_rolestats) {
1784 my $fmt_percent = sprintf("%.0f", $percent);
1785 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1786 } else {
1787 add_role($line, $role);
1792 sub vcs_file_signoffs {
1793 my ($file) = @_;
1795 my @signers = ();
1796 my $commits;
1798 $vcs_used = vcs_exists();
1799 return if (!$vcs_used);
1801 my $cmd = $VCS_cmds{"find_signers_cmd"};
1802 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1804 ($commits, @signers) = vcs_find_signers($cmd);
1806 foreach my $signer (@signers) {
1807 $signer = deduplicate_email($signer);
1810 vcs_assign("commit_signer", $commits, @signers);
1813 sub vcs_file_blame {
1814 my ($file) = @_;
1816 my @signers = ();
1817 my @all_commits = ();
1818 my @commits = ();
1819 my $total_commits;
1820 my $total_lines;
1822 $vcs_used = vcs_exists();
1823 return if (!$vcs_used);
1825 @all_commits = vcs_blame($file);
1826 @commits = uniq(@all_commits);
1827 $total_commits = @commits;
1828 $total_lines = @all_commits;
1830 if ($email_git_blame_signatures) {
1831 if (vcs_is_hg()) {
1832 my $commit_count;
1833 my @commit_signers = ();
1834 my $commit = join(" -r ", @commits);
1835 my $cmd;
1837 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1838 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1840 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1842 push(@signers, @commit_signers);
1843 } else {
1844 foreach my $commit (@commits) {
1845 my $commit_count;
1846 my @commit_signers = ();
1847 my $cmd;
1849 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1850 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1852 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1854 push(@signers, @commit_signers);
1859 if ($from_filename) {
1860 if ($output_rolestats) {
1861 my @blame_signers;
1862 if (vcs_is_hg()) {{ # Double brace for last exit
1863 my $commit_count;
1864 my @commit_signers = ();
1865 @commits = uniq(@commits);
1866 @commits = sort(@commits);
1867 my $commit = join(" -r ", @commits);
1868 my $cmd;
1870 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1871 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1873 my @lines = ();
1875 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1877 if (!$email_git_penguin_chiefs) {
1878 @lines = grep(!/${penguin_chiefs}/i, @lines);
1881 last if !@lines;
1883 my @authors = ();
1884 foreach my $line (@lines) {
1885 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1886 my $author = $1;
1887 $author = deduplicate_email($author);
1888 push(@authors, $author);
1892 save_commits_by_author(@lines) if ($interactive);
1893 save_commits_by_signer(@lines) if ($interactive);
1895 push(@signers, @authors);
1897 else {
1898 foreach my $commit (@commits) {
1899 my $i;
1900 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1901 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1902 my @author = vcs_find_author($cmd);
1903 next if !@author;
1905 my $formatted_author = deduplicate_email($author[0]);
1907 my $count = grep(/$commit/, @all_commits);
1908 for ($i = 0; $i < $count ; $i++) {
1909 push(@blame_signers, $formatted_author);
1913 if (@blame_signers) {
1914 vcs_assign("authored lines", $total_lines, @blame_signers);
1917 foreach my $signer (@signers) {
1918 $signer = deduplicate_email($signer);
1920 vcs_assign("commits", $total_commits, @signers);
1921 } else {
1922 foreach my $signer (@signers) {
1923 $signer = deduplicate_email($signer);
1925 vcs_assign("modified commits", $total_commits, @signers);
1929 sub uniq {
1930 my (@parms) = @_;
1932 my %saw;
1933 @parms = grep(!$saw{$_}++, @parms);
1934 return @parms;
1937 sub sort_and_uniq {
1938 my (@parms) = @_;
1940 my %saw;
1941 @parms = sort @parms;
1942 @parms = grep(!$saw{$_}++, @parms);
1943 return @parms;
1946 sub clean_file_emails {
1947 my (@file_emails) = @_;
1948 my @fmt_emails = ();
1950 foreach my $email (@file_emails) {
1951 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1952 my ($name, $address) = parse_email($email);
1953 if ($name eq '"[,\.]"') {
1954 $name = "";
1957 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1958 if (@nw > 2) {
1959 my $first = $nw[@nw - 3];
1960 my $middle = $nw[@nw - 2];
1961 my $last = $nw[@nw - 1];
1963 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
1964 (length($first) == 2 && substr($first, -1) eq ".")) ||
1965 (length($middle) == 1 ||
1966 (length($middle) == 2 && substr($middle, -1) eq "."))) {
1967 $name = "$first $middle $last";
1968 } else {
1969 $name = "$middle $last";
1973 if (substr($name, -1) =~ /[,\.]/) {
1974 $name = substr($name, 0, length($name) - 1);
1975 } elsif (substr($name, -2) =~ /[,\.]"/) {
1976 $name = substr($name, 0, length($name) - 2) . '"';
1979 if (substr($name, 0, 1) =~ /[,\.]/) {
1980 $name = substr($name, 1, length($name) - 1);
1981 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
1982 $name = '"' . substr($name, 2, length($name) - 2);
1985 my $fmt_email = format_email($name, $address, $email_usename);
1986 push(@fmt_emails, $fmt_email);
1988 return @fmt_emails;
1991 sub merge_email {
1992 my @lines;
1993 my %saw;
1995 for (@_) {
1996 my ($address, $role) = @$_;
1997 if (!$saw{$address}) {
1998 if ($output_roles) {
1999 push(@lines, "$address ($role)");
2000 } else {
2001 push(@lines, $address);
2003 $saw{$address} = 1;
2007 return @lines;
2010 sub output {
2011 my (@parms) = @_;
2013 if ($output_multiline) {
2014 foreach my $line (@parms) {
2015 print("${line}\n");
2017 } else {
2018 print(join($output_separator, @parms));
2019 print("\n");
2023 my $rfc822re;
2025 sub make_rfc822re {
2026 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2027 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2028 # This regexp will only work on addresses which have had comments stripped
2029 # and replaced with rfc822_lwsp.
2031 my $specials = '()<>@,;:\\\\".\\[\\]';
2032 my $controls = '\\000-\\037\\177';
2034 my $dtext = "[^\\[\\]\\r\\\\]";
2035 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2037 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2039 # Use zero-width assertion to spot the limit of an atom. A simple
2040 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2041 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2042 my $word = "(?:$atom|$quoted_string)";
2043 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2045 my $sub_domain = "(?:$atom|$domain_literal)";
2046 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2048 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2050 my $phrase = "$word*";
2051 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2052 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2053 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2055 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2056 my $address = "(?:$mailbox|$group)";
2058 return "$rfc822_lwsp*$address";
2061 sub rfc822_strip_comments {
2062 my $s = shift;
2063 # Recursively remove comments, and replace with a single space. The simpler
2064 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2065 # chars in atoms, for example.
2067 while ($s =~ s/^((?:[^"\\]|\\.)*
2068 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2069 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2070 return $s;
2073 # valid: returns true if the parameter is an RFC822 valid address
2075 sub rfc822_valid {
2076 my $s = rfc822_strip_comments(shift);
2078 if (!$rfc822re) {
2079 $rfc822re = make_rfc822re();
2082 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2085 # validlist: In scalar context, returns true if the parameter is an RFC822
2086 # valid list of addresses.
2088 # In list context, returns an empty list on failure (an invalid
2089 # address was found); otherwise a list whose first element is the
2090 # number of addresses found and whose remaining elements are the
2091 # addresses. This is needed to disambiguate failure (invalid)
2092 # from success with no addresses found, because an empty string is
2093 # a valid list.
2095 sub rfc822_validlist {
2096 my $s = rfc822_strip_comments(shift);
2098 if (!$rfc822re) {
2099 $rfc822re = make_rfc822re();
2101 # * null list items are valid according to the RFC
2102 # * the '1' business is to aid in distinguishing failure from no results
2104 my @r;
2105 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2106 $s =~ m/^$rfc822_char*$/) {
2107 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2108 push(@r, $1);
2110 return wantarray ? (scalar(@r), @r) : 1;
2112 return wantarray ? () : 0;