blob: 6d973f3685f977b58c7180be0595c0f1b6c5cffc [file] [log] [blame]
Kamil Rytarowskicb77f0d2017-05-07 23:25:26 +02001#!/usr/bin/env perl
Joe Perches882ea1d2018-06-07 17:04:33 -07002# SPDX-License-Identifier: GPL-2.0
3#
Joe Perchescb7301c2009-04-07 20:40:12 -07004# (c) 2007, Joe Perches <joe@perches.com>
5# created from checkpatch.pl
6#
7# Print selected MAINTAINERS information for
8# the files modified in a patch or for a file
9#
Roel Kluin3bd7bf52009-11-11 14:26:13 -080010# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
11# perl scripts/get_maintainer.pl [OPTIONS] -f <file>
Joe Perchescb7301c2009-04-07 20:40:12 -070012
Kamil Rytarowskicb77f0d2017-05-07 23:25:26 +020013use warnings;
Joe Perchescb7301c2009-04-07 20:40:12 -070014use strict;
15
16my $P = $0;
Joe Perches7e1863a2011-01-12 16:59:49 -080017my $V = '0.26';
Joe Perchescb7301c2009-04-07 20:40:12 -070018
19use Getopt::Long qw(:config no_auto_abbrev);
Joe Perchesbe17bdd2016-01-20 14:58:24 -080020use Cwd;
Joe Perches6f7d98e2017-08-04 21:45:48 -070021use File::Find;
Joe Perchescb7301c2009-04-07 20:40:12 -070022
Joe Perchesbe17bdd2016-01-20 14:58:24 -080023my $cur_path = fastgetcwd() . '/';
Joe Perchescb7301c2009-04-07 20:40:12 -070024my $lk_path = "./";
25my $email = 1;
26my $email_usename = 1;
27my $email_maintainer = 1;
Joe Perchesc1c3f2c2014-06-02 12:05:17 -070028my $email_reviewer = 1;
Joe Perches2f5bd3432019-12-04 16:50:29 -080029my $email_fixes = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070030my $email_list = 1;
Joe Perches49662502019-07-16 16:27:09 -070031my $email_moderated_list = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070032my $email_subscriber_list = 0;
Joe Perchescb7301c2009-04-07 20:40:12 -070033my $email_git_penguin_chiefs = 0;
Joe Perchese3e9d112010-10-26 14:22:53 -070034my $email_git = 0;
Florian Mickler0fa05592010-05-24 14:33:20 -070035my $email_git_all_signature_types = 0;
Joe Perches60db31a2009-12-14 18:00:50 -080036my $email_git_blame = 0;
Joe Perches683c6f82010-10-26 14:22:55 -070037my $email_git_blame_signatures = 1;
Joe Perchese3e9d112010-10-26 14:22:53 -070038my $email_git_fallback = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070039my $email_git_min_signatures = 1;
40my $email_git_max_maintainers = 5;
Joe Perchesafa81ee2009-07-29 15:04:28 -070041my $email_git_min_percent = 5;
Joe Perchescb7301c2009-04-07 20:40:12 -070042my $email_git_since = "1-year-ago";
Joe Perches60db31a2009-12-14 18:00:50 -080043my $email_hg_since = "-365";
Florian Micklerdace8e32010-10-26 14:22:54 -070044my $interactive = 0;
Joe Perches11ecf532009-09-21 17:04:22 -070045my $email_remove_duplicates = 1;
Joe Perchesb9e23312010-10-26 14:22:58 -070046my $email_use_mailmap = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070047my $output_multiline = 1;
48my $output_separator = ", ";
Joe Perches3c7385b2009-12-14 18:00:46 -080049my $output_roles = 0;
Joe Perches7e1863a2011-01-12 16:59:49 -080050my $output_rolestats = 1;
Joe Perches364f68d2015-06-25 15:01:52 -070051my $output_section_maxlen = 50;
Joe Perchescb7301c2009-04-07 20:40:12 -070052my $scm = 0;
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -070053my $tree = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070054my $web = 0;
55my $subsystem = 0;
56my $status = 0;
Joe Perches03aed212016-12-12 16:45:59 -080057my $letters = "";
Joe Perchesdcf36a92009-10-26 16:49:47 -070058my $keywords = 1;
Joe Perches4b76c9d2010-03-05 13:43:03 -080059my $sections = 0;
Joe Perches0c78c012020-06-04 16:50:01 -070060my $email_file_emails = 0;
Joe Perches4a7fdb52009-04-10 12:28:57 -070061my $from_filename = 0;
Joe Perches3fb55652009-09-21 17:04:17 -070062my $pattern_depth = 0;
Joe Perches083bf9c2017-11-17 15:27:46 -080063my $self_test = undef;
Joe Perchescb7301c2009-04-07 20:40:12 -070064my $version = 0;
65my $help = 0;
Joe Perches6f7d98e2017-08-04 21:45:48 -070066my $find_maintainer_files = 0;
Joe Perches5f0baf92018-08-21 21:56:52 -070067my $maintainer_path;
Joe Perches683c6f82010-10-26 14:22:55 -070068my $vcs_used = 0;
69
Joe Perchescb7301c2009-04-07 20:40:12 -070070my $exit = 0;
71
Joe Perches0c78c012020-06-04 16:50:01 -070072my @files = ();
73my @fixes = (); # If a patch description includes Fixes: lines
74my @range = ();
75my @keyword_tvi = ();
76my @file_emails = ();
77
Joe Perches683c6f82010-10-26 14:22:55 -070078my %commit_author_hash;
79my %commit_signer_hash;
Florian Micklerdace8e32010-10-26 14:22:54 -070080
Joe Perchescb7301c2009-04-07 20:40:12 -070081my @penguin_chief = ();
Joe Perchese4d26b02010-05-24 14:33:17 -070082push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
Joe Perchescb7301c2009-04-07 20:40:12 -070083#Andrew wants in on most everything - 2009/01/14
Joe Perchese4d26b02010-05-24 14:33:17 -070084#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
Joe Perchescb7301c2009-04-07 20:40:12 -070085
86my @penguin_chief_names = ();
87foreach my $chief (@penguin_chief) {
88 if ($chief =~ m/^(.*):(.*)/) {
89 my $chief_name = $1;
90 my $chief_addr = $2;
91 push(@penguin_chief_names, $chief_name);
92 }
93}
Joe Perchese4d26b02010-05-24 14:33:17 -070094my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
95
96# Signature types of people who are either
97# a) responsible for the code in question, or
98# b) familiar enough with it to give relevant feedback
99my @signature_tags = ();
100push(@signature_tags, "Signed-off-by:");
101push(@signature_tags, "Reviewed-by:");
102push(@signature_tags, "Acked-by:");
Joe Perchescb7301c2009-04-07 20:40:12 -0700103
Joe Perches7dea2682012-06-20 12:53:02 -0700104my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
105
Joe Perches5f2441e2009-06-16 15:34:02 -0700106# rfc822 email address - preloaded methods go here.
Joe Perches1b5e1cf2009-06-16 15:34:01 -0700107my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
Joe Perchesdf4cc032009-06-16 15:34:04 -0700108my $rfc822_char = '[\\000-\\377]';
Joe Perches1b5e1cf2009-06-16 15:34:01 -0700109
Joe Perches60db31a2009-12-14 18:00:50 -0800110# VCS command support: class-like functions and strings
111
112my %VCS_cmds;
113
114my %VCS_cmds_git = (
115 "execute_cmd" => \&git_execute_cmd,
Richard Genoudec83b612014-02-10 14:25:31 -0800116 "available" => '(which("git") ne "") && (-e ".git")',
Joe Perches683c6f82010-10-26 14:22:55 -0700117 "find_signers_cmd" =>
Ian Campbelled128fea2012-01-10 15:08:41 -0800118 "git log --no-color --follow --since=\$email_git_since " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800119 '--numstat --no-merges ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700120 '--format="GitCommit: %H%n' .
121 'GitAuthor: %an <%ae>%n' .
122 'GitDate: %aD%n' .
123 'GitSubject: %s%n' .
124 '%b%n"' .
125 " -- \$file",
126 "find_commit_signers_cmd" =>
127 "git log --no-color " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800128 '--numstat ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700129 '--format="GitCommit: %H%n' .
130 'GitAuthor: %an <%ae>%n' .
131 'GitDate: %aD%n' .
132 'GitSubject: %s%n' .
133 '%b%n"' .
134 " -1 \$commit",
135 "find_commit_author_cmd" =>
136 "git log --no-color " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800137 '--numstat ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700138 '--format="GitCommit: %H%n' .
139 'GitAuthor: %an <%ae>%n' .
140 'GitDate: %aD%n' .
141 'GitSubject: %s%n"' .
142 " -1 \$commit",
Joe Perches60db31a2009-12-14 18:00:50 -0800143 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
144 "blame_file_cmd" => "git blame -l \$file",
Joe Perches683c6f82010-10-26 14:22:55 -0700145 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
Florian Micklerdace8e32010-10-26 14:22:54 -0700146 "blame_commit_pattern" => "^([0-9a-f]+) ",
Joe Perches683c6f82010-10-26 14:22:55 -0700147 "author_pattern" => "^GitAuthor: (.*)",
148 "subject_pattern" => "^GitSubject: (.*)",
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800149 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
Joe Perches4cad35a2016-08-02 14:04:10 -0700150 "file_exists_cmd" => "git ls-files \$file",
Tom Saegere1f75902017-11-17 15:27:42 -0800151 "list_files_cmd" => "git ls-files \$file",
Joe Perches60db31a2009-12-14 18:00:50 -0800152);
153
154my %VCS_cmds_hg = (
155 "execute_cmd" => \&hg_execute_cmd,
156 "available" => '(which("hg") ne "") && (-d ".hg")',
157 "find_signers_cmd" =>
Joe Perches683c6f82010-10-26 14:22:55 -0700158 "hg log --date=\$email_hg_since " .
159 "--template='HgCommit: {node}\\n" .
160 "HgAuthor: {author}\\n" .
161 "HgSubject: {desc}\\n'" .
162 " -- \$file",
163 "find_commit_signers_cmd" =>
164 "hg log " .
165 "--template='HgSubject: {desc}\\n'" .
166 " -r \$commit",
167 "find_commit_author_cmd" =>
168 "hg log " .
169 "--template='HgCommit: {node}\\n" .
170 "HgAuthor: {author}\\n" .
171 "HgSubject: {desc|firstline}\\n'" .
172 " -r \$commit",
Joe Perches60db31a2009-12-14 18:00:50 -0800173 "blame_range_cmd" => "", # not supported
Joe Perches683c6f82010-10-26 14:22:55 -0700174 "blame_file_cmd" => "hg blame -n \$file",
175 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
176 "blame_commit_pattern" => "^([ 0-9a-f]+):",
177 "author_pattern" => "^HgAuthor: (.*)",
178 "subject_pattern" => "^HgSubject: (.*)",
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800179 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
Joe Perches4cad35a2016-08-02 14:04:10 -0700180 "file_exists_cmd" => "hg files \$file",
Tom Saegere1f75902017-11-17 15:27:42 -0800181 "list_files_cmd" => "hg manifest -R \$file",
Joe Perches60db31a2009-12-14 18:00:50 -0800182);
183
Joe Perchesbcde44e2010-10-26 14:22:53 -0700184my $conf = which_conf(".get_maintainer.conf");
185if (-f $conf) {
Joe Perches368669d2010-05-24 14:33:19 -0700186 my @conf_args;
Joe Perchesbcde44e2010-10-26 14:22:53 -0700187 open(my $conffile, '<', "$conf")
188 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
189
Joe Perches368669d2010-05-24 14:33:19 -0700190 while (<$conffile>) {
191 my $line = $_;
192
193 $line =~ s/\s*\n?$//g;
194 $line =~ s/^\s*//g;
195 $line =~ s/\s+/ /g;
196
197 next if ($line =~ m/^\s*#/);
198 next if ($line =~ m/^\s*$/);
199
200 my @words = split(" ", $line);
201 foreach my $word (@words) {
202 last if ($word =~ m/^#/);
203 push (@conf_args, $word);
204 }
205 }
206 close($conffile);
207 unshift(@ARGV, @conf_args) if @conf_args;
208}
209
Joe Perches435de072015-06-25 15:01:50 -0700210my @ignore_emails = ();
211my $ignore_file = which_conf(".get_maintainer.ignore");
212if (-f $ignore_file) {
213 open(my $ignore, '<', "$ignore_file")
214 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
215 while (<$ignore>) {
216 my $line = $_;
217
218 $line =~ s/\s*\n?$//;
219 $line =~ s/^\s*//;
220 $line =~ s/\s+$//;
221 $line =~ s/#.*$//;
222
223 next if ($line =~ m/^\s*$/);
224 if (rfc822_valid($line)) {
225 push(@ignore_emails, $line);
226 }
227 }
228 close($ignore);
229}
230
Tom Saegere1f75902017-11-17 15:27:42 -0800231if ($#ARGV > 0) {
232 foreach (@ARGV) {
Joe Perches083bf9c2017-11-17 15:27:46 -0800233 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
Tom Saegere1f75902017-11-17 15:27:42 -0800234 die "$P: using --self-test does not allow any other option or argument\n";
235 }
236 }
237}
238
Joe Perchescb7301c2009-04-07 20:40:12 -0700239if (!GetOptions(
240 'email!' => \$email,
241 'git!' => \$email_git,
Joe Perchese4d26b02010-05-24 14:33:17 -0700242 'git-all-signature-types!' => \$email_git_all_signature_types,
Joe Perches60db31a2009-12-14 18:00:50 -0800243 'git-blame!' => \$email_git_blame,
Joe Perches683c6f82010-10-26 14:22:55 -0700244 'git-blame-signatures!' => \$email_git_blame_signatures,
Joe Perchese3e9d112010-10-26 14:22:53 -0700245 'git-fallback!' => \$email_git_fallback,
Joe Perchescb7301c2009-04-07 20:40:12 -0700246 'git-chief-penguins!' => \$email_git_penguin_chiefs,
247 'git-min-signatures=i' => \$email_git_min_signatures,
248 'git-max-maintainers=i' => \$email_git_max_maintainers,
Joe Perchesafa81ee2009-07-29 15:04:28 -0700249 'git-min-percent=i' => \$email_git_min_percent,
Joe Perchescb7301c2009-04-07 20:40:12 -0700250 'git-since=s' => \$email_git_since,
Joe Perches60db31a2009-12-14 18:00:50 -0800251 'hg-since=s' => \$email_hg_since,
Florian Micklerdace8e32010-10-26 14:22:54 -0700252 'i|interactive!' => \$interactive,
Joe Perches11ecf532009-09-21 17:04:22 -0700253 'remove-duplicates!' => \$email_remove_duplicates,
Joe Perchesb9e23312010-10-26 14:22:58 -0700254 'mailmap!' => \$email_use_mailmap,
Joe Perchescb7301c2009-04-07 20:40:12 -0700255 'm!' => \$email_maintainer,
Joe Perchesc1c3f2c2014-06-02 12:05:17 -0700256 'r!' => \$email_reviewer,
Joe Perchescb7301c2009-04-07 20:40:12 -0700257 'n!' => \$email_usename,
258 'l!' => \$email_list,
Joe Perches2f5bd3432019-12-04 16:50:29 -0800259 'fixes!' => \$email_fixes,
Joe Perches49662502019-07-16 16:27:09 -0700260 'moderated!' => \$email_moderated_list,
Joe Perchescb7301c2009-04-07 20:40:12 -0700261 's!' => \$email_subscriber_list,
262 'multiline!' => \$output_multiline,
Joe Perches3c7385b2009-12-14 18:00:46 -0800263 'roles!' => \$output_roles,
264 'rolestats!' => \$output_rolestats,
Joe Perchescb7301c2009-04-07 20:40:12 -0700265 'separator=s' => \$output_separator,
266 'subsystem!' => \$subsystem,
267 'status!' => \$status,
268 'scm!' => \$scm,
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -0700269 'tree!' => \$tree,
Joe Perchescb7301c2009-04-07 20:40:12 -0700270 'web!' => \$web,
Joe Perches03aed212016-12-12 16:45:59 -0800271 'letters=s' => \$letters,
Joe Perches3fb55652009-09-21 17:04:17 -0700272 'pattern-depth=i' => \$pattern_depth,
Joe Perchesdcf36a92009-10-26 16:49:47 -0700273 'k|keywords!' => \$keywords,
Joe Perches4b76c9d2010-03-05 13:43:03 -0800274 'sections!' => \$sections,
Joe Perches0c78c012020-06-04 16:50:01 -0700275 'fe|file-emails!' => \$email_file_emails,
Joe Perches4a7fdb52009-04-10 12:28:57 -0700276 'f|file' => \$from_filename,
Joe Perches6f7d98e2017-08-04 21:45:48 -0700277 'find-maintainer-files' => \$find_maintainer_files,
Joe Perches5f0baf92018-08-21 21:56:52 -0700278 'mpath|maintainer-path=s' => \$maintainer_path,
Joe Perches083bf9c2017-11-17 15:27:46 -0800279 'self-test:s' => \$self_test,
Joe Perchescb7301c2009-04-07 20:40:12 -0700280 'v|version' => \$version,
Joe Perches64f77f32010-03-05 13:43:04 -0800281 'h|help|usage' => \$help,
Joe Perchescb7301c2009-04-07 20:40:12 -0700282 )) {
Joe Perches3c7385b2009-12-14 18:00:46 -0800283 die "$P: invalid argument - use --help if necessary\n";
Joe Perchescb7301c2009-04-07 20:40:12 -0700284}
285
286if ($help != 0) {
287 usage();
288 exit 0;
289}
290
291if ($version != 0) {
292 print("${P} ${V}\n");
293 exit 0;
294}
295
Joe Perches083bf9c2017-11-17 15:27:46 -0800296if (defined $self_test) {
Tom Saegere1f75902017-11-17 15:27:42 -0800297 read_all_maintainer_files();
Joe Perches083bf9c2017-11-17 15:27:46 -0800298 self_test();
Tom Saegere1f75902017-11-17 15:27:42 -0800299 exit 0;
300}
301
Joe Perches64f77f32010-03-05 13:43:04 -0800302if (-t STDIN && !@ARGV) {
303 # We're talking to a terminal, but have no command line arguments.
304 die "$P: missing patchfile or -f file - use --help if necessary\n";
Joe Perchescb7301c2009-04-07 20:40:12 -0700305}
306
Joe Perches683c6f82010-10-26 14:22:55 -0700307$output_multiline = 0 if ($output_separator ne ", ");
308$output_rolestats = 1 if ($interactive);
309$output_roles = 1 if ($output_rolestats);
Joe Perches3c7385b2009-12-14 18:00:46 -0800310
Joe Perches03aed212016-12-12 16:45:59 -0800311if ($sections || $letters ne "") {
312 $sections = 1;
Joe Perches4b76c9d2010-03-05 13:43:03 -0800313 $email = 0;
314 $email_list = 0;
315 $scm = 0;
316 $status = 0;
317 $subsystem = 0;
318 $web = 0;
319 $keywords = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -0700320 $interactive = 0;
Joe Perches4b76c9d2010-03-05 13:43:03 -0800321} else {
322 my $selections = $email + $scm + $status + $subsystem + $web;
323 if ($selections == 0) {
Joe Perches4b76c9d2010-03-05 13:43:03 -0800324 die "$P: Missing required option: email, scm, status, subsystem or web\n";
325 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700326}
327
Joe Perchesf5492662009-09-21 17:04:13 -0700328if ($email &&
Joe Perchesc1c3f2c2014-06-02 12:05:17 -0700329 ($email_maintainer + $email_reviewer +
330 $email_list + $email_subscriber_list +
Joe Perchesf5492662009-09-21 17:04:13 -0700331 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
Joe Perchescb7301c2009-04-07 20:40:12 -0700332 die "$P: Please select at least 1 email option\n";
333}
334
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -0700335if ($tree && !top_of_kernel_tree($lk_path)) {
Joe Perchescb7301c2009-04-07 20:40:12 -0700336 die "$P: The current directory does not appear to be "
337 . "a linux kernel source tree.\n";
338}
339
340## Read MAINTAINERS for type/value pairs
341
342my @typevalue = ();
Joe Perchesdcf36a92009-10-26 16:49:47 -0700343my %keyword_hash;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700344my @mfiles = ();
Joe Perches083bf9c2017-11-17 15:27:46 -0800345my @self_test_info = ();
Joe Perchesdcf36a92009-10-26 16:49:47 -0700346
Joe Perches6f7d98e2017-08-04 21:45:48 -0700347sub read_maintainer_file {
348 my ($file) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -0700349
Joe Perches6f7d98e2017-08-04 21:45:48 -0700350 open (my $maint, '<', "$file")
351 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
Tom Saegere1f75902017-11-17 15:27:42 -0800352 my $i = 1;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700353 while (<$maint>) {
354 my $line = $_;
Joe Perches083bf9c2017-11-17 15:27:46 -0800355 chomp $line;
Joe Perchescb7301c2009-04-07 20:40:12 -0700356
Joe Perches6f7d98e2017-08-04 21:45:48 -0700357 if ($line =~ m/^([A-Z]):\s*(.*)/) {
358 my $type = $1;
359 my $value = $2;
360
361 ##Filename pattern matching
362 if ($type eq "F" || $type eq "X") {
363 $value =~ s@\.@\\\.@g; ##Convert . to \.
364 $value =~ s/\*/\.\*/g; ##Convert * to .*
365 $value =~ s/\?/\./g; ##Convert ? to .
366 ##if pattern is a directory and it lacks a trailing slash, add one
367 if ((-d $value)) {
368 $value =~ s@([^/])$@$1/@;
369 }
370 } elsif ($type eq "K") {
371 $keyword_hash{@typevalue} = $value;
Joe Perches870020f2009-07-29 15:04:28 -0700372 }
Joe Perches6f7d98e2017-08-04 21:45:48 -0700373 push(@typevalue, "$type:$value");
374 } elsif (!(/^\s*$/ || /^\s*\#/)) {
Joe Perches6f7d98e2017-08-04 21:45:48 -0700375 push(@typevalue, $line);
Joe Perchescb7301c2009-04-07 20:40:12 -0700376 }
Joe Perches083bf9c2017-11-17 15:27:46 -0800377 if (defined $self_test) {
378 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
379 }
Tom Saegere1f75902017-11-17 15:27:42 -0800380 $i++;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700381 }
382 close($maint);
383}
384
385sub find_is_maintainer_file {
386 my ($file) = $_;
387 return if ($file !~ m@/MAINTAINERS$@);
388 $file = $File::Find::name;
389 return if (! -f $file);
390 push(@mfiles, $file);
391}
392
393sub find_ignore_git {
394 return grep { $_ !~ /^\.git$/; } @_;
395}
396
Tom Saegere1f75902017-11-17 15:27:42 -0800397read_all_maintainer_files();
398
399sub read_all_maintainer_files {
Joe Perches5f0baf92018-08-21 21:56:52 -0700400 my $path = "${lk_path}MAINTAINERS";
401 if (defined $maintainer_path) {
402 $path = $maintainer_path;
403 # Perl Cookbook tilde expansion if necessary
404 $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
Joe Perchescb7301c2009-04-07 20:40:12 -0700405 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700406
Joe Perches5f0baf92018-08-21 21:56:52 -0700407 if (-d $path) {
408 $path .= '/' if ($path !~ m@/$@);
Joe Perches0fbd75f2018-08-21 21:56:55 -0700409 if ($find_maintainer_files) {
410 find( { wanted => \&find_is_maintainer_file,
411 preprocess => \&find_ignore_git,
412 no_chdir => 1,
413 }, "$path");
414 } else {
Joe Perches5f0baf92018-08-21 21:56:52 -0700415 opendir(DIR, "$path") or die $!;
416 my @files = readdir(DIR);
417 closedir(DIR);
418 foreach my $file (@files) {
419 push(@mfiles, "$path$file") if ($file !~ /^\./);
420 }
421 }
Joe Perches5f0baf92018-08-21 21:56:52 -0700422 } elsif (-f "$path") {
423 push(@mfiles, "$path");
Tom Saegere1f75902017-11-17 15:27:42 -0800424 } else {
Joe Perches5f0baf92018-08-21 21:56:52 -0700425 die "$P: MAINTAINER file not found '$path'\n";
Tom Saegere1f75902017-11-17 15:27:42 -0800426 }
Joe Perches5f0baf92018-08-21 21:56:52 -0700427 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
Tom Saegere1f75902017-11-17 15:27:42 -0800428 foreach my $file (@mfiles) {
Joe Perches5f0baf92018-08-21 21:56:52 -0700429 read_maintainer_file("$file");
Tom Saegere1f75902017-11-17 15:27:42 -0800430 }
Joe Perches6f7d98e2017-08-04 21:45:48 -0700431}
Joe Perches8cbb3a72009-09-21 17:04:21 -0700432
Joe Perches0c78c012020-06-04 16:50:01 -0700433sub maintainers_in_file {
434 my ($file) = @_;
435
436 return if ($file =~ m@\bMAINTAINERS$@);
437
438 if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) {
439 open(my $f, '<', $file)
440 or die "$P: Can't open $file: $!\n";
441 my $text = do { local($/) ; <$f> };
442 close($f);
443
444 my @poss_addr = $text =~ m$[A-Za-zÀ-ÿ\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g;
445 push(@file_emails, clean_file_emails(@poss_addr));
446 }
447}
448
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700449#
450# Read mail address map
451#
452
Joe Perchesb9e23312010-10-26 14:22:58 -0700453my $mailmap;
454
455read_mailmap();
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700456
457sub read_mailmap {
Joe Perchesb9e23312010-10-26 14:22:58 -0700458 $mailmap = {
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700459 names => {},
460 addresses => {}
Joe Perches47abc722010-10-26 14:22:57 -0700461 };
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700462
Joe Perchesb9e23312010-10-26 14:22:58 -0700463 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700464
465 open(my $mailmap_file, '<', "${lk_path}.mailmap")
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800466 or warn "$P: Can't open .mailmap: $!\n";
Joe Perches8cbb3a72009-09-21 17:04:21 -0700467
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700468 while (<$mailmap_file>) {
469 s/#.*$//; #strip comments
470 s/^\s+|\s+$//g; #trim
Joe Perches8cbb3a72009-09-21 17:04:21 -0700471
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700472 next if (/^\s*$/); #skip empty lines
473 #entries have one of the following formats:
474 # name1 <mail1>
475 # <mail1> <mail2>
476 # name1 <mail1> <mail2>
477 # name1 <mail1> name2 <mail2>
478 # (see man git-shortlog)
Joe Perches0334b382011-07-25 17:13:13 -0700479
480 if (/^([^<]+)<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700481 my $real_name = $1;
482 my $address = $2;
Joe Perches8cbb3a72009-09-21 17:04:21 -0700483
Joe Perches47abc722010-10-26 14:22:57 -0700484 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700485 ($real_name, $address) = parse_email("$real_name <$address>");
Joe Perches47abc722010-10-26 14:22:57 -0700486 $mailmap->{names}->{$address} = $real_name;
Joe Perches8cbb3a72009-09-21 17:04:21 -0700487
Joe Perches0334b382011-07-25 17:13:13 -0700488 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700489 my $real_address = $1;
490 my $wrong_address = $2;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700491
Joe Perches47abc722010-10-26 14:22:57 -0700492 $mailmap->{addresses}->{$wrong_address} = $real_address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700493
Joe Perches0334b382011-07-25 17:13:13 -0700494 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
Joe Perchesb9e23312010-10-26 14:22:58 -0700495 my $real_name = $1;
Joe Perches47abc722010-10-26 14:22:57 -0700496 my $real_address = $2;
497 my $wrong_address = $3;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700498
Joe Perches47abc722010-10-26 14:22:57 -0700499 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700500 ($real_name, $real_address) =
501 parse_email("$real_name <$real_address>");
Joe Perches47abc722010-10-26 14:22:57 -0700502 $mailmap->{names}->{$wrong_address} = $real_name;
503 $mailmap->{addresses}->{$wrong_address} = $real_address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700504
Joe Perches0334b382011-07-25 17:13:13 -0700505 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700506 my $real_name = $1;
507 my $real_address = $2;
508 my $wrong_name = $3;
509 my $wrong_address = $4;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700510
Joe Perches47abc722010-10-26 14:22:57 -0700511 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700512 ($real_name, $real_address) =
513 parse_email("$real_name <$real_address>");
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700514
Joe Perchesb9e23312010-10-26 14:22:58 -0700515 $wrong_name =~ s/\s+$//;
516 ($wrong_name, $wrong_address) =
517 parse_email("$wrong_name <$wrong_address>");
518
519 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
520 $mailmap->{names}->{$wrong_email} = $real_name;
521 $mailmap->{addresses}->{$wrong_email} = $real_address;
Joe Perches11ecf532009-09-21 17:04:22 -0700522 }
Joe Perches8cbb3a72009-09-21 17:04:21 -0700523 }
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700524 close($mailmap_file);
Joe Perches8cbb3a72009-09-21 17:04:21 -0700525}
526
Joe Perches4a7fdb52009-04-10 12:28:57 -0700527## use the filenames on the command line or find the filenames in the patchfiles
Joe Perchescb7301c2009-04-07 20:40:12 -0700528
Joe Perches64f77f32010-03-05 13:43:04 -0800529if (!@ARGV) {
530 push(@ARGV, "&STDIN");
531}
532
Joe Perches4a7fdb52009-04-10 12:28:57 -0700533foreach my $file (@ARGV) {
Joe Perches64f77f32010-03-05 13:43:04 -0800534 if ($file ne "&STDIN") {
535 ##if $file is a directory and it lacks a trailing slash, add one
536 if ((-d $file)) {
537 $file =~ s@([^/])$@$1/@;
538 } elsif (!(-f $file)) {
539 die "$P: file '${file}' not found\n";
540 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700541 }
Joe Perchesaec742e2016-08-10 08:45:11 -0700542 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
Joe Perchesbe17bdd2016-01-20 14:58:24 -0800543 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
544 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
Joe Perches4a7fdb52009-04-10 12:28:57 -0700545 push(@files, $file);
Joe Perches0c78c012020-06-04 16:50:01 -0700546 if ($file ne "MAINTAINERS" && -f $file && $keywords) {
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800547 open(my $f, '<', $file)
548 or die "$P: Can't open $file: $!\n";
549 my $text = do { local($/) ; <$f> };
550 close($f);
Joe Perches03372db2010-03-05 13:43:00 -0800551 if ($keywords) {
552 foreach my $line (keys %keyword_hash) {
553 if ($text =~ m/$keyword_hash{$line}/x) {
554 push(@keyword_tvi, $line);
555 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700556 }
557 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700558 }
Joe Perches4a7fdb52009-04-10 12:28:57 -0700559 } else {
560 my $file_cnt = @files;
Joe Perchesf5492662009-09-21 17:04:13 -0700561 my $lastfile;
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800562
Wolfram Sang3a4df132010-03-23 13:35:18 -0700563 open(my $patch, "< $file")
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800564 or die "$P: Can't open $file: $!\n";
Joe Perches7764dcb2011-03-22 16:34:24 -0700565
566 # We can check arbitrary information before the patch
567 # like the commit message, mail headers, etc...
568 # This allows us to match arbitrary keywords against any part
569 # of a git format-patch generated file (subject tags, etc...)
570
571 my $patch_prefix = ""; #Parsing the intro
572
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800573 while (<$patch>) {
Joe Perchesdcf36a92009-10-26 16:49:47 -0700574 my $patch_line = $_;
Joe Perches0455c742018-06-07 17:10:38 -0700575 if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
576 my $filename = $1;
577 push(@files, $filename);
578 } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
579 my $filename = $1;
580 push(@files, $filename);
581 } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
582 my $filename1 = $1;
583 my $filename2 = $2;
584 push(@files, $filename1);
585 push(@files, $filename2);
Joe Perches2f5bd3432019-12-04 16:50:29 -0800586 } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
587 push(@fixes, $1) if ($email_fixes);
Joe Perches0455c742018-06-07 17:10:38 -0700588 } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
Joe Perches4a7fdb52009-04-10 12:28:57 -0700589 my $filename = $1;
590 $filename =~ s@^[^/]*/@@;
591 $filename =~ s@\n@@;
Joe Perchesf5492662009-09-21 17:04:13 -0700592 $lastfile = $filename;
Joe Perches4a7fdb52009-04-10 12:28:57 -0700593 push(@files, $filename);
Joe Perches7764dcb2011-03-22 16:34:24 -0700594 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
Joe Perchesf5492662009-09-21 17:04:13 -0700595 } elsif (m/^\@\@ -(\d+),(\d+)/) {
596 if ($email_git_blame) {
597 push(@range, "$lastfile:$1:$2");
598 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700599 } elsif ($keywords) {
600 foreach my $line (keys %keyword_hash) {
Joe Perches7764dcb2011-03-22 16:34:24 -0700601 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
Joe Perchesdcf36a92009-10-26 16:49:47 -0700602 push(@keyword_tvi, $line);
603 }
604 }
Joe Perches4a7fdb52009-04-10 12:28:57 -0700605 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700606 }
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800607 close($patch);
608
Joe Perches4a7fdb52009-04-10 12:28:57 -0700609 if ($file_cnt == @files) {
Joe Perches7f29fd272009-06-16 15:34:04 -0700610 warn "$P: file '${file}' doesn't appear to be a patch. "
Joe Perches4a7fdb52009-04-10 12:28:57 -0700611 . "Add -f to options?\n";
612 }
613 @files = sort_and_uniq(@files);
Joe Perchescb7301c2009-04-07 20:40:12 -0700614 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700615}
616
Joe Perches03372db2010-03-05 13:43:00 -0800617@file_emails = uniq(@file_emails);
Joe Perches2f5bd3432019-12-04 16:50:29 -0800618@fixes = uniq(@fixes);
Joe Perches03372db2010-03-05 13:43:00 -0800619
Joe Perches683c6f82010-10-26 14:22:55 -0700620my %email_hash_name;
621my %email_hash_address;
Joe Perchescb7301c2009-04-07 20:40:12 -0700622my @email_to = ();
Joe Perches683c6f82010-10-26 14:22:55 -0700623my %hash_list_to;
Joe Perches290603c2009-06-16 15:33:58 -0700624my @list_to = ();
Joe Perchescb7301c2009-04-07 20:40:12 -0700625my @scm = ();
626my @web = ();
627my @subsystem = ();
628my @status = ();
Joe Perchesb9e23312010-10-26 14:22:58 -0700629my %deduplicate_name_hash = ();
630my %deduplicate_address_hash = ();
Joe Perchescb7301c2009-04-07 20:40:12 -0700631
Joe Perches6ef1c522010-10-26 14:22:56 -0700632my @maintainers = get_maintainers();
Joe Perches6ef1c522010-10-26 14:22:56 -0700633if (@maintainers) {
634 @maintainers = merge_email(@maintainers);
635 output(@maintainers);
636}
Joe Perchescb7301c2009-04-07 20:40:12 -0700637
638if ($scm) {
Joe Perchesb7816552009-09-21 17:04:24 -0700639 @scm = uniq(@scm);
Joe Perchescb7301c2009-04-07 20:40:12 -0700640 output(@scm);
641}
Joe Perches683c6f82010-10-26 14:22:55 -0700642
Joe Perchescb7301c2009-04-07 20:40:12 -0700643if ($status) {
Joe Perchesb7816552009-09-21 17:04:24 -0700644 @status = uniq(@status);
Joe Perchescb7301c2009-04-07 20:40:12 -0700645 output(@status);
646}
647
648if ($subsystem) {
Joe Perchesb7816552009-09-21 17:04:24 -0700649 @subsystem = uniq(@subsystem);
Joe Perchescb7301c2009-04-07 20:40:12 -0700650 output(@subsystem);
651}
652
653if ($web) {
Joe Perchesb7816552009-09-21 17:04:24 -0700654 @web = uniq(@web);
Joe Perchescb7301c2009-04-07 20:40:12 -0700655 output(@web);
656}
657
658exit($exit);
659
Joe Perches083bf9c2017-11-17 15:27:46 -0800660sub self_test {
Tom Saegere1f75902017-11-17 15:27:42 -0800661 my @lsfiles = ();
Joe Perches083bf9c2017-11-17 15:27:46 -0800662 my @good_links = ();
663 my @bad_links = ();
664 my @section_headers = ();
665 my $index = 0;
Tom Saegere1f75902017-11-17 15:27:42 -0800666
667 @lsfiles = vcs_list_files($lk_path);
668
Joe Perches083bf9c2017-11-17 15:27:46 -0800669 for my $x (@self_test_info) {
670 $index++;
671
672 ## Section header duplication and missing section content
673 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
674 $x->{line} =~ /^\S[^:]/ &&
675 defined $self_test_info[$index] &&
676 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
677 my $has_S = 0;
678 my $has_F = 0;
679 my $has_ML = 0;
680 my $status = "";
681 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
682 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
683 } else {
684 push(@section_headers, $x->{line});
685 }
686 my $nextline = $index;
687 while (defined $self_test_info[$nextline] &&
688 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
689 my $type = $1;
690 my $value = $2;
691 if ($type eq "S") {
692 $has_S = 1;
693 $status = $value;
694 } elsif ($type eq "F" || $type eq "N") {
695 $has_F = 1;
696 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
697 $has_ML = 1;
698 }
699 $nextline++;
700 }
701 if (!$has_ML && $status !~ /orphan|obsolete/i) {
702 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
703 }
704 if (!$has_S) {
705 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
706 }
707 if (!$has_F) {
708 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
709 }
710 }
711
712 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
713
714 my $type = $1;
715 my $value = $2;
716
717 ## Filename pattern matching
718 if (($type eq "F" || $type eq "X") &&
719 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
720 $value =~ s@\.@\\\.@g; ##Convert . to \.
721 $value =~ s/\*/\.\*/g; ##Convert * to .*
722 $value =~ s/\?/\./g; ##Convert ? to .
723 ##if pattern is a directory and it lacks a trailing slash, add one
724 if ((-d $value)) {
725 $value =~ s@([^/])$@$1/@;
726 }
727 if (!grep(m@^$value@, @lsfiles)) {
728 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
729 }
730
731 ## Link reachability
732 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
733 $value =~ /^https?:/ &&
734 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
735 next if (grep(m@^\Q$value\E$@, @good_links));
736 my $isbad = 0;
737 if (grep(m@^\Q$value\E$@, @bad_links)) {
738 $isbad = 1;
739 } else {
740 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
741 if ($? == 0) {
742 push(@good_links, $value);
743 } else {
744 push(@bad_links, $value);
745 $isbad = 1;
746 }
747 }
748 if ($isbad) {
749 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
750 }
751
752 ## SCM reachability
753 } elsif ($type eq "T" &&
754 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
755 next if (grep(m@^\Q$value\E$@, @good_links));
756 my $isbad = 0;
757 if (grep(m@^\Q$value\E$@, @bad_links)) {
758 $isbad = 1;
759 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
760 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
761 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
762 my $url = $1;
763 my $branch = "";
764 $branch = $3 if $3;
765 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
766 if ($? == 0) {
767 push(@good_links, $value);
768 } else {
769 push(@bad_links, $value);
770 $isbad = 1;
771 }
772 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
773 my $url = $1;
774 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
775 if ($? == 0) {
776 push(@good_links, $value);
777 } else {
778 push(@bad_links, $value);
779 $isbad = 1;
780 }
781 }
782 if ($isbad) {
783 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
784 }
785 }
Tom Saegere1f75902017-11-17 15:27:42 -0800786 }
787}
788
Joe Perches435de072015-06-25 15:01:50 -0700789sub ignore_email_address {
790 my ($address) = @_;
791
792 foreach my $ignore (@ignore_emails) {
793 return 1 if ($ignore eq $address);
794 }
795
796 return 0;
797}
798
Joe Perchesab6c9372011-01-12 16:59:50 -0800799sub range_is_maintained {
800 my ($start, $end) = @_;
801
802 for (my $i = $start; $i < $end; $i++) {
803 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700804 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchesab6c9372011-01-12 16:59:50 -0800805 my $type = $1;
806 my $value = $2;
807 if ($type eq 'S') {
808 if ($value =~ /(maintain|support)/i) {
809 return 1;
810 }
811 }
812 }
813 }
814 return 0;
815}
816
817sub range_has_maintainer {
818 my ($start, $end) = @_;
819
820 for (my $i = $start; $i < $end; $i++) {
821 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700822 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchesab6c9372011-01-12 16:59:50 -0800823 my $type = $1;
824 my $value = $2;
825 if ($type eq 'M') {
826 return 1;
827 }
828 }
829 }
830 return 0;
831}
832
Joe Perches6ef1c522010-10-26 14:22:56 -0700833sub get_maintainers {
Joe Perches683c6f82010-10-26 14:22:55 -0700834 %email_hash_name = ();
835 %email_hash_address = ();
836 %commit_author_hash = ();
837 %commit_signer_hash = ();
838 @email_to = ();
839 %hash_list_to = ();
840 @list_to = ();
841 @scm = ();
842 @web = ();
843 @subsystem = ();
844 @status = ();
Joe Perchesb9e23312010-10-26 14:22:58 -0700845 %deduplicate_name_hash = ();
846 %deduplicate_address_hash = ();
Joe Perches683c6f82010-10-26 14:22:55 -0700847 if ($email_git_all_signature_types) {
848 $signature_pattern = "(.+?)[Bb][Yy]:";
849 } else {
850 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
851 }
852
853 # Find responsible parties
854
Joe Perchesb9e23312010-10-26 14:22:58 -0700855 my %exact_pattern_match_hash = ();
Joe Perches6ef1c522010-10-26 14:22:56 -0700856
Joe Perches683c6f82010-10-26 14:22:55 -0700857 foreach my $file (@files) {
858
859 my %hash;
Joe Perches683c6f82010-10-26 14:22:55 -0700860 my $tvi = find_first_section();
861 while ($tvi < @typevalue) {
862 my $start = find_starting_index($tvi);
863 my $end = find_ending_index($tvi);
864 my $exclude = 0;
865 my $i;
866
867 #Do not match excluded file patterns
868
869 for ($i = $start; $i < $end; $i++) {
870 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700871 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches683c6f82010-10-26 14:22:55 -0700872 my $type = $1;
873 my $value = $2;
874 if ($type eq 'X') {
875 if (file_match_pattern($file, $value)) {
876 $exclude = 1;
877 last;
878 }
879 }
880 }
881 }
882
883 if (!$exclude) {
884 for ($i = $start; $i < $end; $i++) {
885 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700886 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches683c6f82010-10-26 14:22:55 -0700887 my $type = $1;
888 my $value = $2;
889 if ($type eq 'F') {
890 if (file_match_pattern($file, $value)) {
891 my $value_pd = ($value =~ tr@/@@);
892 my $file_pd = ($file =~ tr@/@@);
893 $value_pd++ if (substr($value,-1,1) ne "/");
894 $value_pd = -1 if ($value =~ /^\.\*/);
Joe Perchesab6c9372011-01-12 16:59:50 -0800895 if ($value_pd >= $file_pd &&
896 range_is_maintained($start, $end) &&
897 range_has_maintainer($start, $end)) {
Joe Perches6ef1c522010-10-26 14:22:56 -0700898 $exact_pattern_match_hash{$file} = 1;
899 }
Joe Perches683c6f82010-10-26 14:22:55 -0700900 if ($pattern_depth == 0 ||
901 (($file_pd - $value_pd) < $pattern_depth)) {
902 $hash{$tvi} = $value_pd;
903 }
904 }
Stephen Warrenbbbe96e2013-04-29 16:17:23 -0700905 } elsif ($type eq 'N') {
Stephen Warreneb90d082013-02-27 17:02:53 -0800906 if ($file =~ m/$value/x) {
907 $hash{$tvi} = 0;
908 }
Joe Perches683c6f82010-10-26 14:22:55 -0700909 }
910 }
911 }
912 }
913 $tvi = $end + 1;
914 }
915
916 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
917 add_categories($line);
918 if ($sections) {
919 my $i;
920 my $start = find_starting_index($line);
921 my $end = find_ending_index($line);
922 for ($i = $start; $i < $end; $i++) {
923 my $line = $typevalue[$i];
924 if ($line =~ /^[FX]:/) { ##Restore file patterns
925 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
926 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
927 $line =~ s/\\\./\./g; ##Convert \. to .
928 $line =~ s/\.\*/\*/g; ##Convert .* to *
929 }
Joe Perches03aed212016-12-12 16:45:59 -0800930 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
931 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
932 print("$line\n");
933 }
Joe Perches683c6f82010-10-26 14:22:55 -0700934 }
935 print("\n");
936 }
937 }
Joe Perches0c78c012020-06-04 16:50:01 -0700938
939 maintainers_in_file($file);
Joe Perches683c6f82010-10-26 14:22:55 -0700940 }
941
942 if ($keywords) {
943 @keyword_tvi = sort_and_uniq(@keyword_tvi);
944 foreach my $line (@keyword_tvi) {
945 add_categories($line);
946 }
947 }
948
Joe Perchesb9e23312010-10-26 14:22:58 -0700949 foreach my $email (@email_to, @list_to) {
950 $email->[0] = deduplicate_email($email->[0]);
951 }
Joe Perches6ef1c522010-10-26 14:22:56 -0700952
953 foreach my $file (@files) {
954 if ($email &&
955 ($email_git || ($email_git_fallback &&
956 !$exact_pattern_match_hash{$file}))) {
957 vcs_file_signoffs($file);
958 }
959 if ($email && $email_git_blame) {
960 vcs_file_blame($file);
961 }
962 }
963
Joe Perches683c6f82010-10-26 14:22:55 -0700964 if ($email) {
965 foreach my $chief (@penguin_chief) {
966 if ($chief =~ m/^(.*):(.*)/) {
967 my $email_address;
968
969 $email_address = format_email($1, $2, $email_usename);
970 if ($email_git_penguin_chiefs) {
971 push(@email_to, [$email_address, 'chief penguin']);
972 } else {
973 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
974 }
975 }
976 }
977
978 foreach my $email (@file_emails) {
979 my ($name, $address) = parse_email($email);
980
981 my $tmp_email = format_email($name, $address, $email_usename);
982 push_email_address($tmp_email, '');
983 add_role($tmp_email, 'in file');
984 }
985 }
986
Douglas Anderson0ef82fc2020-02-20 20:04:12 -0800987 foreach my $fix (@fixes) {
988 vcs_add_commit_signers($fix, "blamed_fixes");
989 }
990
Joe Perches683c6f82010-10-26 14:22:55 -0700991 my @to = ();
992 if ($email || $email_list) {
993 if ($email) {
994 @to = (@to, @email_to);
995 }
996 if ($email_list) {
997 @to = (@to, @list_to);
998 }
999 }
1000
Joe Perches6ef1c522010-10-26 14:22:56 -07001001 if ($interactive) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001002 @to = interactive_get_maintainers(\@to);
Joe Perches6ef1c522010-10-26 14:22:56 -07001003 }
Joe Perches683c6f82010-10-26 14:22:55 -07001004
1005 return @to;
1006}
1007
Joe Perchescb7301c2009-04-07 20:40:12 -07001008sub file_match_pattern {
1009 my ($file, $pattern) = @_;
1010 if (substr($pattern, -1) eq "/") {
1011 if ($file =~ m@^$pattern@) {
1012 return 1;
1013 }
1014 } else {
1015 if ($file =~ m@^$pattern@) {
1016 my $s1 = ($file =~ tr@/@@);
1017 my $s2 = ($pattern =~ tr@/@@);
1018 if ($s1 == $s2) {
1019 return 1;
1020 }
1021 }
1022 }
1023 return 0;
1024}
1025
1026sub usage {
1027 print <<EOT;
1028usage: $P [options] patchfile
Joe Perches870020f2009-07-29 15:04:28 -07001029 $P [options] -f file|directory
Joe Perchescb7301c2009-04-07 20:40:12 -07001030version: $V
1031
1032MAINTAINER field selection options:
1033 --email => print email address(es) if any
1034 --git => include recent git \*-by: signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001035 --git-all-signature-types => include signers regardless of signature type
Joe Perches683c6f82010-10-26 14:22:55 -07001036 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
Joe Perchese3e9d112010-10-26 14:22:53 -07001037 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
Joe Perchescb7301c2009-04-07 20:40:12 -07001038 --git-chief-penguins => include ${penguin_chiefs}
Joe Perchese4d26b02010-05-24 14:33:17 -07001039 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1040 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1041 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
Joe Perchesf5492662009-09-21 17:04:13 -07001042 --git-blame => use git blame to find modified commits for patch or file
Brian Norris3cbcca82015-11-06 16:30:41 -08001043 --git-blame-signatures => when used with --git-blame, also include all commit signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001044 --git-since => git history to use (default: $email_git_since)
1045 --hg-since => hg history to use (default: $email_hg_since)
Florian Micklerdace8e32010-10-26 14:22:54 -07001046 --interactive => display a menu (mostly useful if used with the --git option)
Joe Perchescb7301c2009-04-07 20:40:12 -07001047 --m => include maintainer(s) if any
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001048 --r => include reviewer(s) if any
Joe Perchescb7301c2009-04-07 20:40:12 -07001049 --n => include name 'Full Name <addr\@domain.tld>'
1050 --l => include list(s) if any
Joe Perches49662502019-07-16 16:27:09 -07001051 --moderated => include moderated lists(s) if any (default: true)
1052 --s => include subscriber only list(s) if any (default: false)
Joe Perches11ecf532009-09-21 17:04:22 -07001053 --remove-duplicates => minimize duplicate email names/addresses
Joe Perches3c7385b2009-12-14 18:00:46 -08001054 --roles => show roles (status:subsystem, git-signer, list, etc...)
1055 --rolestats => show roles and statistics (commits/total_commits, %)
Joe Perches03372db2010-03-05 13:43:00 -08001056 --file-emails => add email addresses found in -f file (default: 0 (off))
Joe Perches2f5bd3432019-12-04 16:50:29 -08001057 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
Joe Perchescb7301c2009-04-07 20:40:12 -07001058 --scm => print SCM tree(s) if any
1059 --status => print status if any
1060 --subsystem => print subsystem name if any
1061 --web => print website(s) if any
1062
1063Output type options:
1064 --separator [, ] => separator for multiple entries on 1 line
Joe Perches42498312009-09-21 17:04:21 -07001065 using --separator also sets --nomultiline if --separator is not [, ]
Joe Perchescb7301c2009-04-07 20:40:12 -07001066 --multiline => print 1 entry per line
1067
Joe Perchescb7301c2009-04-07 20:40:12 -07001068Other options:
Joe Perches3fb55652009-09-21 17:04:17 -07001069 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
Joe Perchesb9e23312010-10-26 14:22:58 -07001070 --keywords => scan patch for keywords (default: $keywords)
1071 --sections => print all of the subsystem sections with pattern matches
Joe Perches03aed212016-12-12 16:45:59 -08001072 --letters => print all matching 'letter' types from all matching sections
Joe Perchesb9e23312010-10-26 14:22:58 -07001073 --mailmap => use .mailmap file (default: $email_use_mailmap)
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001074 --no-tree => run without a kernel tree
Tom Saegere1f75902017-11-17 15:27:42 -08001075 --self-test => show potential issues with MAINTAINERS file content
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001076 --version => show version
Joe Perchescb7301c2009-04-07 20:40:12 -07001077 --help => show this help information
1078
Joe Perches3fb55652009-09-21 17:04:17 -07001079Default options:
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001080 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1081 --pattern-depth=0 --remove-duplicates --rolestats]
Joe Perches3fb55652009-09-21 17:04:17 -07001082
Joe Perches870020f2009-07-29 15:04:28 -07001083Notes:
1084 Using "-f directory" may give unexpected results:
Joe Perchesf5492662009-09-21 17:04:13 -07001085 Used with "--git", git signators for _all_ files in and below
1086 directory are examined as git recurses directories.
1087 Any specified X: (exclude) pattern matches are _not_ ignored.
1088 Used with "--nogit", directory is used as a pattern match,
Joe Perches60db31a2009-12-14 18:00:50 -08001089 no individual file within the directory or subdirectory
1090 is matched.
Joe Perchesf5492662009-09-21 17:04:13 -07001091 Used with "--git-blame", does not iterate all files in directory
1092 Using "--git-blame" is slow and may add old committers and authors
1093 that are no longer active maintainers to the output.
Joe Perches3c7385b2009-12-14 18:00:46 -08001094 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1095 other automated tools that expect only ["name"] <email address>
1096 may not work because of additional output after <email address>.
1097 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1098 not the percentage of the entire file authored. # of commits is
1099 not a good measure of amount of code authored. 1 major commit may
1100 contain a thousand lines, 5 trivial commits may modify a single line.
Joe Perches60db31a2009-12-14 18:00:50 -08001101 If git is not installed, but mercurial (hg) is installed and an .hg
1102 repository exists, the following options apply to mercurial:
1103 --git,
1104 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1105 --git-blame
1106 Use --hg-since not --git-since to control date selection
Joe Perches368669d2010-05-24 14:33:19 -07001107 File ".get_maintainer.conf", if it exists in the linux kernel source root
1108 directory, can change whatever get_maintainer defaults are desired.
1109 Entries in this file can be any command line argument.
1110 This file is prepended to any additional command line arguments.
1111 Multiple lines and # comments are allowed.
Brian Norrisb1312bf2015-11-06 16:30:46 -08001112 Most options have both positive and negative forms.
1113 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1114
Joe Perchescb7301c2009-04-07 20:40:12 -07001115EOT
1116}
1117
1118sub top_of_kernel_tree {
Joe Perches47abc722010-10-26 14:22:57 -07001119 my ($lk_path) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07001120
Joe Perches47abc722010-10-26 14:22:57 -07001121 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1122 $lk_path .= "/";
1123 }
1124 if ( (-f "${lk_path}COPYING")
1125 && (-f "${lk_path}CREDITS")
1126 && (-f "${lk_path}Kbuild")
Joe Perches6f7d98e2017-08-04 21:45:48 -07001127 && (-e "${lk_path}MAINTAINERS")
Joe Perches47abc722010-10-26 14:22:57 -07001128 && (-f "${lk_path}Makefile")
1129 && (-f "${lk_path}README")
1130 && (-d "${lk_path}Documentation")
1131 && (-d "${lk_path}arch")
1132 && (-d "${lk_path}include")
1133 && (-d "${lk_path}drivers")
1134 && (-d "${lk_path}fs")
1135 && (-d "${lk_path}init")
1136 && (-d "${lk_path}ipc")
1137 && (-d "${lk_path}kernel")
1138 && (-d "${lk_path}lib")
1139 && (-d "${lk_path}scripts")) {
1140 return 1;
1141 }
1142 return 0;
Joe Perchescb7301c2009-04-07 20:40:12 -07001143}
1144
Joe Perches0e70e832009-09-21 17:04:20 -07001145sub parse_email {
1146 my ($formatted_email) = @_;
1147
1148 my $name = "";
1149 my $address = "";
1150
Joe Perches11ecf532009-09-21 17:04:22 -07001151 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001152 $name = $1;
1153 $address = $2;
Joe Perches11ecf532009-09-21 17:04:22 -07001154 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001155 $address = $1;
Joe Perchesb7816552009-09-21 17:04:24 -07001156 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001157 $address = $1;
1158 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001159
1160 $name =~ s/^\s+|\s+$//g;
Joe Perchesd7895042009-06-16 15:34:02 -07001161 $name =~ s/^\"|\"$//g;
Joe Perches0e70e832009-09-21 17:04:20 -07001162 $address =~ s/^\s+|\s+$//g;
Joe Perchescb7301c2009-04-07 20:40:12 -07001163
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001164 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perchescb7301c2009-04-07 20:40:12 -07001165 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
Joe Perches0e70e832009-09-21 17:04:20 -07001166 $name = "\"$name\"";
Joe Perchescb7301c2009-04-07 20:40:12 -07001167 }
Joe Perches0e70e832009-09-21 17:04:20 -07001168
1169 return ($name, $address);
1170}
1171
1172sub format_email {
Joe Perchesa8af2432009-12-14 18:00:49 -08001173 my ($name, $address, $usename) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001174
1175 my $formatted_email;
1176
1177 $name =~ s/^\s+|\s+$//g;
1178 $name =~ s/^\"|\"$//g;
1179 $address =~ s/^\s+|\s+$//g;
1180
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001181 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perches0e70e832009-09-21 17:04:20 -07001182 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1183 $name = "\"$name\"";
1184 }
1185
Joe Perchesa8af2432009-12-14 18:00:49 -08001186 if ($usename) {
Joe Perches0e70e832009-09-21 17:04:20 -07001187 if ("$name" eq "") {
1188 $formatted_email = "$address";
1189 } else {
Joe Perchesa8af2432009-12-14 18:00:49 -08001190 $formatted_email = "$name <$address>";
Joe Perches0e70e832009-09-21 17:04:20 -07001191 }
1192 } else {
1193 $formatted_email = $address;
1194 }
1195
Joe Perchescb7301c2009-04-07 20:40:12 -07001196 return $formatted_email;
1197}
1198
Joe Perches272a8972010-01-08 14:42:48 -08001199sub find_first_section {
1200 my $index = 0;
1201
1202 while ($index < @typevalue) {
1203 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001204 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perches272a8972010-01-08 14:42:48 -08001205 last;
1206 }
1207 $index++;
1208 }
1209
1210 return $index;
1211}
1212
Joe Perchesb7816552009-09-21 17:04:24 -07001213sub find_starting_index {
Joe Perchesb7816552009-09-21 17:04:24 -07001214 my ($index) = @_;
1215
1216 while ($index > 0) {
1217 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001218 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001219 last;
1220 }
1221 $index--;
1222 }
1223
1224 return $index;
1225}
1226
1227sub find_ending_index {
1228 my ($index) = @_;
1229
1230 while ($index < @typevalue) {
1231 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001232 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001233 last;
1234 }
1235 $index++;
1236 }
1237
1238 return $index;
1239}
1240
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001241sub get_subsystem_name {
1242 my ($index) = @_;
1243
1244 my $start = find_starting_index($index);
1245
1246 my $subsystem = $typevalue[$start];
1247 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1248 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1249 $subsystem =~ s/\s*$//;
1250 $subsystem = $subsystem . "...";
1251 }
1252 return $subsystem;
1253}
1254
Joe Perches3c7385b2009-12-14 18:00:46 -08001255sub get_maintainer_role {
1256 my ($index) = @_;
1257
1258 my $i;
1259 my $start = find_starting_index($index);
1260 my $end = find_ending_index($index);
1261
Joe Perches0ede2742012-03-23 15:01:56 -07001262 my $role = "unknown";
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001263 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001264
1265 for ($i = $start + 1; $i < $end; $i++) {
1266 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001267 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001268 my $ptype = $1;
1269 my $pvalue = $2;
1270 if ($ptype eq "S") {
1271 $role = $pvalue;
1272 }
1273 }
1274 }
1275
1276 $role = lc($role);
1277 if ($role eq "supported") {
1278 $role = "supporter";
1279 } elsif ($role eq "maintained") {
1280 $role = "maintainer";
1281 } elsif ($role eq "odd fixes") {
1282 $role = "odd fixer";
1283 } elsif ($role eq "orphan") {
1284 $role = "orphan minder";
1285 } elsif ($role eq "obsolete") {
1286 $role = "obsolete minder";
1287 } elsif ($role eq "buried alive in reporters") {
1288 $role = "chief penguin";
1289 }
1290
1291 return $role . ":" . $subsystem;
1292}
1293
1294sub get_list_role {
1295 my ($index) = @_;
1296
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001297 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001298
1299 if ($subsystem eq "THE REST") {
1300 $subsystem = "";
1301 }
1302
1303 return $subsystem;
1304}
1305
Joe Perchescb7301c2009-04-07 20:40:12 -07001306sub add_categories {
1307 my ($index) = @_;
1308
Joe Perchesb7816552009-09-21 17:04:24 -07001309 my $i;
1310 my $start = find_starting_index($index);
1311 my $end = find_ending_index($index);
1312
1313 push(@subsystem, $typevalue[$start]);
1314
1315 for ($i = $start + 1; $i < $end; $i++) {
1316 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001317 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001318 my $ptype = $1;
1319 my $pvalue = $2;
1320 if ($ptype eq "L") {
Joe Perches290603c2009-06-16 15:33:58 -07001321 my $list_address = $pvalue;
1322 my $list_additional = "";
Joe Perches3c7385b2009-12-14 18:00:46 -08001323 my $list_role = get_list_role($i);
1324
1325 if ($list_role ne "") {
1326 $list_role = ":" . $list_role;
1327 }
Joe Perches290603c2009-06-16 15:33:58 -07001328 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1329 $list_address = $1;
1330 $list_additional = $2;
1331 }
Joe Perchesbdf7c682009-06-16 15:33:59 -07001332 if ($list_additional =~ m/subscribers-only/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001333 if ($email_subscriber_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001334 if (!$hash_list_to{lc($list_address)}) {
1335 $hash_list_to{lc($list_address)} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001336 push(@list_to, [$list_address,
1337 "subscriber list${list_role}"]);
1338 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001339 }
1340 } else {
1341 if ($email_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001342 if (!$hash_list_to{lc($list_address)}) {
Richard Weinberger728f5a92012-03-23 15:01:56 -07001343 if ($list_additional =~ m/moderated/) {
Joe Perches49662502019-07-16 16:27:09 -07001344 if ($email_moderated_list) {
1345 $hash_list_to{lc($list_address)} = 1;
1346 push(@list_to, [$list_address,
1347 "moderated list${list_role}"]);
1348 }
Richard Weinberger728f5a92012-03-23 15:01:56 -07001349 } else {
Joe Perches49662502019-07-16 16:27:09 -07001350 $hash_list_to{lc($list_address)} = 1;
Richard Weinberger728f5a92012-03-23 15:01:56 -07001351 push(@list_to, [$list_address,
1352 "open list${list_role}"]);
1353 }
Joe Perches683c6f82010-10-26 14:22:55 -07001354 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001355 }
1356 }
1357 } elsif ($ptype eq "M") {
Joe Perches0e70e832009-09-21 17:04:20 -07001358 if ($email_maintainer) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001359 my $role = get_maintainer_role($i);
1360 push_email_addresses($pvalue, $role);
Joe Perchescb7301c2009-04-07 20:40:12 -07001361 }
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001362 } elsif ($ptype eq "R") {
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001363 if ($email_reviewer) {
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001364 my $subsystem = get_subsystem_name($i);
1365 push_email_addresses($pvalue, "reviewer:$subsystem");
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001366 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001367 } elsif ($ptype eq "T") {
1368 push(@scm, $pvalue);
1369 } elsif ($ptype eq "W") {
1370 push(@web, $pvalue);
1371 } elsif ($ptype eq "S") {
1372 push(@status, $pvalue);
1373 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001374 }
1375 }
1376}
1377
Joe Perches11ecf532009-09-21 17:04:22 -07001378sub email_inuse {
1379 my ($name, $address) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001380
Joe Perches11ecf532009-09-21 17:04:22 -07001381 return 1 if (($name eq "") && ($address eq ""));
Joe Perches6ef1c522010-10-26 14:22:56 -07001382 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1383 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
Joe Perches11ecf532009-09-21 17:04:22 -07001384
Joe Perches0e70e832009-09-21 17:04:20 -07001385 return 0;
1386}
1387
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001388sub push_email_address {
Joe Perches3c7385b2009-12-14 18:00:46 -08001389 my ($line, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001390
Joe Perches0e70e832009-09-21 17:04:20 -07001391 my ($name, $address) = parse_email($line);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001392
Joe Perchesb7816552009-09-21 17:04:24 -07001393 if ($address eq "") {
1394 return 0;
1395 }
1396
Joe Perches11ecf532009-09-21 17:04:22 -07001397 if (!$email_remove_duplicates) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001398 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perches11ecf532009-09-21 17:04:22 -07001399 } elsif (!email_inuse($name, $address)) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001400 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perchesfae99202010-10-26 14:22:58 -07001401 $email_hash_name{lc($name)}++ if ($name ne "");
Joe Perches6ef1c522010-10-26 14:22:56 -07001402 $email_hash_address{lc($address)}++;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001403 }
Joe Perchesb7816552009-09-21 17:04:24 -07001404
1405 return 1;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001406}
1407
1408sub push_email_addresses {
Joe Perches3c7385b2009-12-14 18:00:46 -08001409 my ($address, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001410
1411 my @address_list = ();
1412
Joe Perches5f2441e2009-06-16 15:34:02 -07001413 if (rfc822_valid($address)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001414 push_email_address($address, $role);
Joe Perches5f2441e2009-06-16 15:34:02 -07001415 } elsif (@address_list = rfc822_validlist($address)) {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001416 my $array_count = shift(@address_list);
1417 while (my $entry = shift(@address_list)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001418 push_email_address($entry, $role);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001419 }
Joe Perches5f2441e2009-06-16 15:34:02 -07001420 } else {
Joe Perches3c7385b2009-12-14 18:00:46 -08001421 if (!push_email_address($address, $role)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001422 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1423 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001424 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001425}
1426
Joe Perches3c7385b2009-12-14 18:00:46 -08001427sub add_role {
1428 my ($line, $role) = @_;
1429
1430 my ($name, $address) = parse_email($line);
Joe Perchesa8af2432009-12-14 18:00:49 -08001431 my $email = format_email($name, $address, $email_usename);
Joe Perches3c7385b2009-12-14 18:00:46 -08001432
1433 foreach my $entry (@email_to) {
1434 if ($email_remove_duplicates) {
1435 my ($entry_name, $entry_address) = parse_email($entry->[0]);
Joe Perches03372db2010-03-05 13:43:00 -08001436 if (($name eq $entry_name || $address eq $entry_address)
1437 && ($role eq "" || !($entry->[1] =~ m/$role/))
1438 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001439 if ($entry->[1] eq "") {
1440 $entry->[1] = "$role";
1441 } else {
1442 $entry->[1] = "$entry->[1],$role";
1443 }
1444 }
1445 } else {
Joe Perches03372db2010-03-05 13:43:00 -08001446 if ($email eq $entry->[0]
1447 && ($role eq "" || !($entry->[1] =~ m/$role/))
1448 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001449 if ($entry->[1] eq "") {
1450 $entry->[1] = "$role";
1451 } else {
1452 $entry->[1] = "$entry->[1],$role";
1453 }
1454 }
1455 }
1456 }
1457}
1458
Joe Perchescb7301c2009-04-07 20:40:12 -07001459sub which {
1460 my ($bin) = @_;
1461
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001462 foreach my $path (split(/:/, $ENV{PATH})) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001463 if (-e "$path/$bin") {
1464 return "$path/$bin";
1465 }
1466 }
1467
1468 return "";
1469}
1470
Joe Perchesbcde44e2010-10-26 14:22:53 -07001471sub which_conf {
1472 my ($conf) = @_;
1473
1474 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1475 if (-e "$path/$conf") {
1476 return "$path/$conf";
1477 }
1478 }
1479
1480 return "";
1481}
1482
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001483sub mailmap_email {
Joe Perchesb9e23312010-10-26 14:22:58 -07001484 my ($line) = @_;
Joe Perches8cbb3a72009-09-21 17:04:21 -07001485
Joe Perches47abc722010-10-26 14:22:57 -07001486 my ($name, $address) = parse_email($line);
1487 my $email = format_email($name, $address, 1);
1488 my $real_name = $name;
1489 my $real_address = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001490
Joe Perches47abc722010-10-26 14:22:57 -07001491 if (exists $mailmap->{names}->{$email} ||
1492 exists $mailmap->{addresses}->{$email}) {
1493 if (exists $mailmap->{names}->{$email}) {
1494 $real_name = $mailmap->{names}->{$email};
Joe Perches8cbb3a72009-09-21 17:04:21 -07001495 }
Joe Perches47abc722010-10-26 14:22:57 -07001496 if (exists $mailmap->{addresses}->{$email}) {
1497 $real_address = $mailmap->{addresses}->{$email};
1498 }
1499 } else {
1500 if (exists $mailmap->{names}->{$address}) {
1501 $real_name = $mailmap->{names}->{$address};
1502 }
1503 if (exists $mailmap->{addresses}->{$address}) {
1504 $real_address = $mailmap->{addresses}->{$address};
1505 }
1506 }
1507 return format_email($real_name, $real_address, 1);
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001508}
1509
1510sub mailmap {
1511 my (@addresses) = @_;
1512
Joe Perchesb9e23312010-10-26 14:22:58 -07001513 my @mapped_emails = ();
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001514 foreach my $line (@addresses) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001515 push(@mapped_emails, mailmap_email($line));
Joe Perches8cbb3a72009-09-21 17:04:21 -07001516 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001517 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1518 return @mapped_emails;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001519}
1520
1521sub merge_by_realname {
Joe Perches47abc722010-10-26 14:22:57 -07001522 my %address_map;
1523 my (@emails) = @_;
Joe Perchesb9e23312010-10-26 14:22:58 -07001524
Joe Perches47abc722010-10-26 14:22:57 -07001525 foreach my $email (@emails) {
1526 my ($name, $address) = parse_email($email);
Joe Perchesb9e23312010-10-26 14:22:58 -07001527 if (exists $address_map{$name}) {
Joe Perches47abc722010-10-26 14:22:57 -07001528 $address = $address_map{$name};
Joe Perchesb9e23312010-10-26 14:22:58 -07001529 $email = format_email($name, $address, 1);
1530 } else {
1531 $address_map{$name} = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001532 }
Joe Perches47abc722010-10-26 14:22:57 -07001533 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07001534}
1535
Joe Perches60db31a2009-12-14 18:00:50 -08001536sub git_execute_cmd {
1537 my ($cmd) = @_;
1538 my @lines = ();
Joe Perchescb7301c2009-04-07 20:40:12 -07001539
Joe Perches60db31a2009-12-14 18:00:50 -08001540 my $output = `$cmd`;
1541 $output =~ s/^\s*//gm;
1542 @lines = split("\n", $output);
1543
1544 return @lines;
Joe Perchesa8af2432009-12-14 18:00:49 -08001545}
1546
Joe Perches60db31a2009-12-14 18:00:50 -08001547sub hg_execute_cmd {
Joe Perchesa8af2432009-12-14 18:00:49 -08001548 my ($cmd) = @_;
Joe Perches60db31a2009-12-14 18:00:50 -08001549 my @lines = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001550
Joe Perches60db31a2009-12-14 18:00:50 -08001551 my $output = `$cmd`;
1552 @lines = split("\n", $output);
1553
1554 return @lines;
1555}
1556
Joe Perches683c6f82010-10-26 14:22:55 -07001557sub extract_formatted_signatures {
1558 my (@signature_lines) = @_;
1559
1560 my @type = @signature_lines;
1561
1562 s/\s*(.*):.*/$1/ for (@type);
1563
1564 # cut -f2- -d":"
1565 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1566
1567## Reformat email addresses (with names) to avoid badly written signatures
1568
1569 foreach my $signer (@signature_lines) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001570 $signer = deduplicate_email($signer);
Joe Perches683c6f82010-10-26 14:22:55 -07001571 }
1572
1573 return (\@type, \@signature_lines);
1574}
1575
Joe Perches60db31a2009-12-14 18:00:50 -08001576sub vcs_find_signers {
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001577 my ($cmd, $file) = @_;
Joe Perchesa8af2432009-12-14 18:00:49 -08001578 my $commits;
Joe Perches683c6f82010-10-26 14:22:55 -07001579 my @lines = ();
1580 my @signatures = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001581 my @authors = ();
1582 my @stats = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001583
Joe Perches60db31a2009-12-14 18:00:50 -08001584 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
Joe Perchescb7301c2009-04-07 20:40:12 -07001585
Joe Perches60db31a2009-12-14 18:00:50 -08001586 my $pattern = $VCS_cmds{"commit_pattern"};
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001587 my $author_pattern = $VCS_cmds{"author_pattern"};
1588 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1589
1590 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
Joe Perchescb7301c2009-04-07 20:40:12 -07001591
Joe Perches60db31a2009-12-14 18:00:50 -08001592 $commits = grep(/$pattern/, @lines); # of commits
Joe Perchesafa81ee2009-07-29 15:04:28 -07001593
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001594 @authors = grep(/$author_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001595 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001596 @stats = grep(/$stat_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001597
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001598# print("stats: <@stats>\n");
1599
1600 return (0, \@signatures, \@authors, \@stats) if !@signatures;
Joe Perches683c6f82010-10-26 14:22:55 -07001601
1602 save_commits_by_author(@lines) if ($interactive);
1603 save_commits_by_signer(@lines) if ($interactive);
1604
Joe Perches0e70e832009-09-21 17:04:20 -07001605 if (!$email_git_penguin_chiefs) {
Joe Perches683c6f82010-10-26 14:22:55 -07001606 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
Joe Perches0e70e832009-09-21 17:04:20 -07001607 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001608
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001609 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
Joe Perches683c6f82010-10-26 14:22:55 -07001610 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
Joe Perches63ab52d2010-10-26 14:22:51 -07001611
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001612 return ($commits, $signers_ref, $authors_ref, \@stats);
Joe Perchesa8af2432009-12-14 18:00:49 -08001613}
1614
Joe Perches63ab52d2010-10-26 14:22:51 -07001615sub vcs_find_author {
1616 my ($cmd) = @_;
1617 my @lines = ();
1618
1619 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1620
1621 if (!$email_git_penguin_chiefs) {
1622 @lines = grep(!/${penguin_chiefs}/i, @lines);
1623 }
1624
1625 return @lines if !@lines;
1626
Joe Perches683c6f82010-10-26 14:22:55 -07001627 my @authors = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07001628 foreach my $line (@lines) {
Joe Perches683c6f82010-10-26 14:22:55 -07001629 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1630 my $author = $1;
1631 my ($name, $address) = parse_email($author);
1632 $author = format_email($name, $address, 1);
1633 push(@authors, $author);
1634 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001635 }
1636
Joe Perches683c6f82010-10-26 14:22:55 -07001637 save_commits_by_author(@lines) if ($interactive);
1638 save_commits_by_signer(@lines) if ($interactive);
1639
1640 return @authors;
Joe Perches63ab52d2010-10-26 14:22:51 -07001641}
1642
Joe Perches60db31a2009-12-14 18:00:50 -08001643sub vcs_save_commits {
1644 my ($cmd) = @_;
1645 my @lines = ();
1646 my @commits = ();
1647
1648 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1649
1650 foreach my $line (@lines) {
1651 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1652 push(@commits, $1);
1653 }
1654 }
1655
1656 return @commits;
1657}
1658
1659sub vcs_blame {
1660 my ($file) = @_;
1661 my $cmd;
1662 my @commits = ();
1663
1664 return @commits if (!(-f $file));
1665
1666 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1667 my @all_commits = ();
1668
1669 $cmd = $VCS_cmds{"blame_file_cmd"};
1670 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1671 @all_commits = vcs_save_commits($cmd);
1672
1673 foreach my $file_range_diff (@range) {
1674 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1675 my $diff_file = $1;
1676 my $diff_start = $2;
1677 my $diff_length = $3;
1678 next if ("$file" ne "$diff_file");
1679 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1680 push(@commits, $all_commits[$i]);
1681 }
1682 }
1683 } elsif (@range) {
1684 foreach my $file_range_diff (@range) {
1685 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1686 my $diff_file = $1;
1687 my $diff_start = $2;
1688 my $diff_length = $3;
1689 next if ("$file" ne "$diff_file");
1690 $cmd = $VCS_cmds{"blame_range_cmd"};
1691 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1692 push(@commits, vcs_save_commits($cmd));
1693 }
1694 } else {
1695 $cmd = $VCS_cmds{"blame_file_cmd"};
1696 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1697 @commits = vcs_save_commits($cmd);
1698 }
1699
Joe Perches63ab52d2010-10-26 14:22:51 -07001700 foreach my $commit (@commits) {
1701 $commit =~ s/^\^//g;
1702 }
1703
Joe Perches60db31a2009-12-14 18:00:50 -08001704 return @commits;
1705}
1706
1707my $printed_novcs = 0;
1708sub vcs_exists {
1709 %VCS_cmds = %VCS_cmds_git;
1710 return 1 if eval $VCS_cmds{"available"};
1711 %VCS_cmds = %VCS_cmds_hg;
Joe Perches683c6f82010-10-26 14:22:55 -07001712 return 2 if eval $VCS_cmds{"available"};
Joe Perches60db31a2009-12-14 18:00:50 -08001713 %VCS_cmds = ();
1714 if (!$printed_novcs) {
1715 warn("$P: No supported VCS found. Add --nogit to options?\n");
1716 warn("Using a git repository produces better results.\n");
1717 warn("Try Linus Torvalds' latest git repository using:\n");
Ralf Thielow3d1c2f72011-08-25 15:59:07 -07001718 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
Joe Perches60db31a2009-12-14 18:00:50 -08001719 $printed_novcs = 1;
1720 }
1721 return 0;
1722}
1723
Joe Perches683c6f82010-10-26 14:22:55 -07001724sub vcs_is_git {
Joe Perchesb9e23312010-10-26 14:22:58 -07001725 vcs_exists();
Joe Perches683c6f82010-10-26 14:22:55 -07001726 return $vcs_used == 1;
1727}
1728
1729sub vcs_is_hg {
1730 return $vcs_used == 2;
1731}
1732
Joe Perches2f5bd3432019-12-04 16:50:29 -08001733sub vcs_add_commit_signers {
1734 return if (!vcs_exists());
1735
1736 my ($commit, $desc) = @_;
1737 my $commit_count = 0;
1738 my $commit_authors_ref;
1739 my $commit_signers_ref;
1740 my $stats_ref;
1741 my @commit_authors = ();
1742 my @commit_signers = ();
1743 my $cmd;
1744
1745 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1746 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1747
1748 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1749 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1750 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1751
1752 foreach my $signer (@commit_signers) {
1753 $signer = deduplicate_email($signer);
1754 }
1755
1756 vcs_assign($desc, 1, @commit_signers);
1757}
1758
Joe Perches6ef1c522010-10-26 14:22:56 -07001759sub interactive_get_maintainers {
Joe Perches683c6f82010-10-26 14:22:55 -07001760 my ($list_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07001761 my @list = @$list_ref;
1762
Joe Perches683c6f82010-10-26 14:22:55 -07001763 vcs_exists();
Florian Micklerdace8e32010-10-26 14:22:54 -07001764
1765 my %selected;
Joe Perches683c6f82010-10-26 14:22:55 -07001766 my %authored;
1767 my %signed;
Florian Micklerdace8e32010-10-26 14:22:54 -07001768 my $count = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001769 my $maintained = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001770 foreach my $entry (@list) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001771 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1772 $selected{$count} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001773 $authored{$count} = 0;
1774 $signed{$count} = 0;
1775 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001776 }
1777
1778 #menu loop
Joe Perches683c6f82010-10-26 14:22:55 -07001779 my $done = 0;
1780 my $print_options = 0;
1781 my $redraw = 1;
1782 while (!$done) {
1783 $count = 0;
1784 if ($redraw) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001785 printf STDERR "\n%1s %2s %-65s",
1786 "*", "#", "email/list and role:stats";
1787 if ($email_git ||
1788 ($email_git_fallback && !$maintained) ||
1789 $email_git_blame) {
1790 print STDERR "auth sign";
1791 }
1792 print STDERR "\n";
Joe Perches683c6f82010-10-26 14:22:55 -07001793 foreach my $entry (@list) {
1794 my $email = $entry->[0];
1795 my $role = $entry->[1];
1796 my $sel = "";
1797 $sel = "*" if ($selected{$count});
1798 my $commit_author = $commit_author_hash{$email};
1799 my $commit_signer = $commit_signer_hash{$email};
1800 my $authored = 0;
1801 my $signed = 0;
1802 $authored++ for (@{$commit_author});
1803 $signed++ for (@{$commit_signer});
1804 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1805 printf STDERR "%4d %4d", $authored, $signed
1806 if ($authored > 0 || $signed > 0);
1807 printf STDERR "\n %s\n", $role;
1808 if ($authored{$count}) {
1809 my $commit_author = $commit_author_hash{$email};
1810 foreach my $ref (@{$commit_author}) {
1811 print STDERR " Author: @{$ref}[1]\n";
Florian Micklerdace8e32010-10-26 14:22:54 -07001812 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001813 }
Joe Perches683c6f82010-10-26 14:22:55 -07001814 if ($signed{$count}) {
1815 my $commit_signer = $commit_signer_hash{$email};
1816 foreach my $ref (@{$commit_signer}) {
1817 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1818 }
1819 }
1820
1821 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001822 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001823 }
Joe Perches683c6f82010-10-26 14:22:55 -07001824 my $date_ref = \$email_git_since;
1825 $date_ref = \$email_hg_since if (vcs_is_hg());
1826 if ($print_options) {
1827 $print_options = 0;
1828 if (vcs_exists()) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001829 print STDERR <<EOT
1830
1831Version Control options:
1832g use git history [$email_git]
1833gf use git-fallback [$email_git_fallback]
1834b use git blame [$email_git_blame]
1835bs use blame signatures [$email_git_blame_signatures]
1836c# minimum commits [$email_git_min_signatures]
1837%# min percent [$email_git_min_percent]
1838d# history to use [$$date_ref]
1839x# max maintainers [$email_git_max_maintainers]
1840t all signature types [$email_git_all_signature_types]
1841m use .mailmap [$email_use_mailmap]
1842EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001843 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001844 print STDERR <<EOT
1845
1846Additional options:
18470 toggle all
1848tm toggle maintainers
1849tg toggle git entries
1850tl toggle open list entries
1851ts toggle subscriber list entries
Joe Perches0c78c012020-06-04 16:50:01 -07001852f emails in file [$email_file_emails]
Joe Perchesb9e23312010-10-26 14:22:58 -07001853k keywords in file [$keywords]
1854r remove duplicates [$email_remove_duplicates]
1855p# pattern match depth [$pattern_depth]
1856EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001857 }
1858 print STDERR
1859"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1860
1861 my $input = <STDIN>;
Florian Micklerdace8e32010-10-26 14:22:54 -07001862 chomp($input);
1863
Joe Perches683c6f82010-10-26 14:22:55 -07001864 $redraw = 1;
1865 my $rerun = 0;
1866 my @wish = split(/[, ]+/, $input);
1867 foreach my $nr (@wish) {
1868 $nr = lc($nr);
1869 my $sel = substr($nr, 0, 1);
1870 my $str = substr($nr, 1);
1871 my $val = 0;
1872 $val = $1 if $str =~ /^(\d+)$/;
1873
1874 if ($sel eq "y") {
1875 $interactive = 0;
1876 $done = 1;
1877 $output_rolestats = 0;
1878 $output_roles = 0;
1879 last;
1880 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1881 $selected{$nr - 1} = !$selected{$nr - 1};
1882 } elsif ($sel eq "*" || $sel eq '^') {
1883 my $toggle = 0;
1884 $toggle = 1 if ($sel eq '*');
1885 for (my $i = 0; $i < $count; $i++) {
1886 $selected{$i} = $toggle;
Florian Micklerdace8e32010-10-26 14:22:54 -07001887 }
Joe Perches683c6f82010-10-26 14:22:55 -07001888 } elsif ($sel eq "0") {
1889 for (my $i = 0; $i < $count; $i++) {
1890 $selected{$i} = !$selected{$i};
1891 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001892 } elsif ($sel eq "t") {
1893 if (lc($str) eq "m") {
1894 for (my $i = 0; $i < $count; $i++) {
1895 $selected{$i} = !$selected{$i}
1896 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1897 }
1898 } elsif (lc($str) eq "g") {
1899 for (my $i = 0; $i < $count; $i++) {
1900 $selected{$i} = !$selected{$i}
1901 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1902 }
1903 } elsif (lc($str) eq "l") {
1904 for (my $i = 0; $i < $count; $i++) {
1905 $selected{$i} = !$selected{$i}
1906 if ($list[$i]->[1] =~ /^(open list)/i);
1907 }
1908 } elsif (lc($str) eq "s") {
1909 for (my $i = 0; $i < $count; $i++) {
1910 $selected{$i} = !$selected{$i}
1911 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1912 }
1913 }
Joe Perches683c6f82010-10-26 14:22:55 -07001914 } elsif ($sel eq "a") {
1915 if ($val > 0 && $val <= $count) {
1916 $authored{$val - 1} = !$authored{$val - 1};
1917 } elsif ($str eq '*' || $str eq '^') {
1918 my $toggle = 0;
1919 $toggle = 1 if ($str eq '*');
1920 for (my $i = 0; $i < $count; $i++) {
1921 $authored{$i} = $toggle;
1922 }
1923 }
1924 } elsif ($sel eq "s") {
1925 if ($val > 0 && $val <= $count) {
1926 $signed{$val - 1} = !$signed{$val - 1};
1927 } elsif ($str eq '*' || $str eq '^') {
1928 my $toggle = 0;
1929 $toggle = 1 if ($str eq '*');
1930 for (my $i = 0; $i < $count; $i++) {
1931 $signed{$i} = $toggle;
1932 }
1933 }
1934 } elsif ($sel eq "o") {
1935 $print_options = 1;
1936 $redraw = 1;
1937 } elsif ($sel eq "g") {
1938 if ($str eq "f") {
1939 bool_invert(\$email_git_fallback);
Florian Micklerdace8e32010-10-26 14:22:54 -07001940 } else {
Joe Perches683c6f82010-10-26 14:22:55 -07001941 bool_invert(\$email_git);
Florian Micklerdace8e32010-10-26 14:22:54 -07001942 }
Joe Perches683c6f82010-10-26 14:22:55 -07001943 $rerun = 1;
1944 } elsif ($sel eq "b") {
1945 if ($str eq "s") {
1946 bool_invert(\$email_git_blame_signatures);
1947 } else {
1948 bool_invert(\$email_git_blame);
1949 }
1950 $rerun = 1;
1951 } elsif ($sel eq "c") {
1952 if ($val > 0) {
1953 $email_git_min_signatures = $val;
1954 $rerun = 1;
1955 }
1956 } elsif ($sel eq "x") {
1957 if ($val > 0) {
1958 $email_git_max_maintainers = $val;
1959 $rerun = 1;
1960 }
1961 } elsif ($sel eq "%") {
1962 if ($str ne "" && $val >= 0) {
1963 $email_git_min_percent = $val;
1964 $rerun = 1;
1965 }
1966 } elsif ($sel eq "d") {
1967 if (vcs_is_git()) {
1968 $email_git_since = $str;
1969 } elsif (vcs_is_hg()) {
1970 $email_hg_since = $str;
1971 }
1972 $rerun = 1;
1973 } elsif ($sel eq "t") {
1974 bool_invert(\$email_git_all_signature_types);
1975 $rerun = 1;
1976 } elsif ($sel eq "f") {
Joe Perches0c78c012020-06-04 16:50:01 -07001977 bool_invert(\$email_file_emails);
Joe Perches683c6f82010-10-26 14:22:55 -07001978 $rerun = 1;
1979 } elsif ($sel eq "r") {
1980 bool_invert(\$email_remove_duplicates);
1981 $rerun = 1;
Joe Perchesb9e23312010-10-26 14:22:58 -07001982 } elsif ($sel eq "m") {
1983 bool_invert(\$email_use_mailmap);
1984 read_mailmap();
1985 $rerun = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001986 } elsif ($sel eq "k") {
1987 bool_invert(\$keywords);
1988 $rerun = 1;
1989 } elsif ($sel eq "p") {
1990 if ($str ne "" && $val >= 0) {
1991 $pattern_depth = $val;
1992 $rerun = 1;
1993 }
Joe Perches6ef1c522010-10-26 14:22:56 -07001994 } elsif ($sel eq "h" || $sel eq "?") {
1995 print STDERR <<EOT
1996
1997Interactive mode allows you to select the various maintainers, submitters,
1998commit signers and mailing lists that could be CC'd on a patch.
1999
2000Any *'d entry is selected.
2001
Joe Perches47abc722010-10-26 14:22:57 -07002002If you have git or hg installed, you can choose to summarize the commit
Joe Perches6ef1c522010-10-26 14:22:56 -07002003history of files in the patch. Also, each line of the current file can
2004be matched to its commit author and that commits signers with blame.
2005
2006Various knobs exist to control the length of time for active commit
2007tracking, the maximum number of commit authors and signers to add,
2008and such.
2009
2010Enter selections at the prompt until you are satisfied that the selected
2011maintainers are appropriate. You may enter multiple selections separated
2012by either commas or spaces.
2013
2014EOT
Joe Perches683c6f82010-10-26 14:22:55 -07002015 } else {
2016 print STDERR "invalid option: '$nr'\n";
2017 $redraw = 0;
2018 }
2019 }
2020 if ($rerun) {
2021 print STDERR "git-blame can be very slow, please have patience..."
2022 if ($email_git_blame);
Joe Perches6ef1c522010-10-26 14:22:56 -07002023 goto &get_maintainers;
Joe Perches683c6f82010-10-26 14:22:55 -07002024 }
2025 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002026
2027 #drop not selected entries
2028 $count = 0;
Joe Perches683c6f82010-10-26 14:22:55 -07002029 my @new_emailto = ();
2030 foreach my $entry (@list) {
2031 if ($selected{$count}) {
2032 push(@new_emailto, $list[$count]);
Florian Micklerdace8e32010-10-26 14:22:54 -07002033 }
2034 $count++;
2035 }
Joe Perches683c6f82010-10-26 14:22:55 -07002036 return @new_emailto;
Florian Micklerdace8e32010-10-26 14:22:54 -07002037}
2038
Joe Perches683c6f82010-10-26 14:22:55 -07002039sub bool_invert {
2040 my ($bool_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002041
Joe Perches683c6f82010-10-26 14:22:55 -07002042 if ($$bool_ref) {
2043 $$bool_ref = 0;
2044 } else {
2045 $$bool_ref = 1;
Florian Micklerdace8e32010-10-26 14:22:54 -07002046 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002047}
2048
Joe Perchesb9e23312010-10-26 14:22:58 -07002049sub deduplicate_email {
2050 my ($email) = @_;
2051
2052 my $matched = 0;
2053 my ($name, $address) = parse_email($email);
2054 $email = format_email($name, $address, 1);
2055 $email = mailmap_email($email);
2056
2057 return $email if (!$email_remove_duplicates);
2058
2059 ($name, $address) = parse_email($email);
2060
Joe Perchesfae99202010-10-26 14:22:58 -07002061 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002062 $name = $deduplicate_name_hash{lc($name)}->[0];
2063 $address = $deduplicate_name_hash{lc($name)}->[1];
2064 $matched = 1;
2065 } elsif ($deduplicate_address_hash{lc($address)}) {
2066 $name = $deduplicate_address_hash{lc($address)}->[0];
2067 $address = $deduplicate_address_hash{lc($address)}->[1];
2068 $matched = 1;
2069 }
2070 if (!$matched) {
2071 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2072 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2073 }
2074 $email = format_email($name, $address, 1);
2075 $email = mailmap_email($email);
2076 return $email;
2077}
2078
Joe Perches683c6f82010-10-26 14:22:55 -07002079sub save_commits_by_author {
2080 my (@lines) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002081
Joe Perches683c6f82010-10-26 14:22:55 -07002082 my @authors = ();
2083 my @commits = ();
2084 my @subjects = ();
2085
2086 foreach my $line (@lines) {
2087 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2088 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002089 $author = deduplicate_email($author);
Joe Perches683c6f82010-10-26 14:22:55 -07002090 push(@authors, $author);
2091 }
2092 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2093 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2094 }
2095
2096 for (my $i = 0; $i < @authors; $i++) {
2097 my $exists = 0;
2098 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2099 if (@{$ref}[0] eq $commits[$i] &&
2100 @{$ref}[1] eq $subjects[$i]) {
2101 $exists = 1;
2102 last;
2103 }
2104 }
2105 if (!$exists) {
2106 push(@{$commit_author_hash{$authors[$i]}},
2107 [ ($commits[$i], $subjects[$i]) ]);
2108 }
2109 }
2110}
2111
2112sub save_commits_by_signer {
2113 my (@lines) = @_;
2114
2115 my $commit = "";
2116 my $subject = "";
2117
2118 foreach my $line (@lines) {
2119 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2120 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2121 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2122 my @signatures = ($line);
2123 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2124 my @types = @$types_ref;
2125 my @signers = @$signers_ref;
2126
2127 my $type = $types[0];
2128 my $signer = $signers[0];
2129
Joe Perchesb9e23312010-10-26 14:22:58 -07002130 $signer = deduplicate_email($signer);
Joe Perches6ef1c522010-10-26 14:22:56 -07002131
Joe Perches683c6f82010-10-26 14:22:55 -07002132 my $exists = 0;
2133 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2134 if (@{$ref}[0] eq $commit &&
2135 @{$ref}[1] eq $subject &&
2136 @{$ref}[2] eq $type) {
2137 $exists = 1;
2138 last;
2139 }
2140 }
2141 if (!$exists) {
2142 push(@{$commit_signer_hash{$signer}},
2143 [ ($commit, $subject, $type) ]);
2144 }
2145 }
2146 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002147}
2148
Joe Perches60db31a2009-12-14 18:00:50 -08002149sub vcs_assign {
Joe Perchesa8af2432009-12-14 18:00:49 -08002150 my ($role, $divisor, @lines) = @_;
2151
2152 my %hash;
2153 my $count = 0;
2154
Joe Perchesa8af2432009-12-14 18:00:49 -08002155 return if (@lines <= 0);
2156
2157 if ($divisor <= 0) {
Joe Perches60db31a2009-12-14 18:00:50 -08002158 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
Joe Perchesa8af2432009-12-14 18:00:49 -08002159 $divisor = 1;
Joe Perches3c7385b2009-12-14 18:00:46 -08002160 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07002161
Florian Mickler7fa8ff22010-10-26 14:22:56 -07002162 @lines = mailmap(@lines);
Joe Perches0e70e832009-09-21 17:04:20 -07002163
Joe Perches63ab52d2010-10-26 14:22:51 -07002164 return if (@lines <= 0);
2165
Joe Perches0e70e832009-09-21 17:04:20 -07002166 @lines = sort(@lines);
Joe Perchesafa81ee2009-07-29 15:04:28 -07002167
Joe Perches11ecf532009-09-21 17:04:22 -07002168 # uniq -c
2169 $hash{$_}++ for @lines;
2170
2171 # sort -rn
2172 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2173 my $sign_offs = $hash{$line};
Joe Perchesa8af2432009-12-14 18:00:49 -08002174 my $percent = $sign_offs * 100 / $divisor;
Joe Perches3c7385b2009-12-14 18:00:46 -08002175
Joe Perchesa8af2432009-12-14 18:00:49 -08002176 $percent = 100 if ($percent > 100);
Joe Perches435de072015-06-25 15:01:50 -07002177 next if (ignore_email_address($line));
Joe Perches11ecf532009-09-21 17:04:22 -07002178 $count++;
2179 last if ($sign_offs < $email_git_min_signatures ||
2180 $count > $email_git_max_maintainers ||
Joe Perchesa8af2432009-12-14 18:00:49 -08002181 $percent < $email_git_min_percent);
Joe Perches3c7385b2009-12-14 18:00:46 -08002182 push_email_address($line, '');
Joe Perches3c7385b2009-12-14 18:00:46 -08002183 if ($output_rolestats) {
Joe Perchesa8af2432009-12-14 18:00:49 -08002184 my $fmt_percent = sprintf("%.0f", $percent);
2185 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2186 } else {
2187 add_role($line, $role);
Joe Perches3c7385b2009-12-14 18:00:46 -08002188 }
Joe Perchesf5492662009-09-21 17:04:13 -07002189 }
2190}
2191
Joe Perches60db31a2009-12-14 18:00:50 -08002192sub vcs_file_signoffs {
Joe Perchesa8af2432009-12-14 18:00:49 -08002193 my ($file) = @_;
2194
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002195 my $authors_ref;
2196 my $signers_ref;
2197 my $stats_ref;
2198 my @authors = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002199 my @signers = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002200 my @stats = ();
Joe Perches60db31a2009-12-14 18:00:50 -08002201 my $commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002202
Joe Perches683c6f82010-10-26 14:22:55 -07002203 $vcs_used = vcs_exists();
2204 return if (!$vcs_used);
Joe Perchesa8af2432009-12-14 18:00:49 -08002205
Joe Perches60db31a2009-12-14 18:00:50 -08002206 my $cmd = $VCS_cmds{"find_signers_cmd"};
2207 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2208
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002209 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2210
2211 @signers = @{$signers_ref} if defined $signers_ref;
2212 @authors = @{$authors_ref} if defined $authors_ref;
2213 @stats = @{$stats_ref} if defined $stats_ref;
2214
2215# print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
Joe Perchesb9e23312010-10-26 14:22:58 -07002216
2217 foreach my $signer (@signers) {
2218 $signer = deduplicate_email($signer);
2219 }
2220
Joe Perches60db31a2009-12-14 18:00:50 -08002221 vcs_assign("commit_signer", $commits, @signers);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002222 vcs_assign("authored", $commits, @authors);
2223 if ($#authors == $#stats) {
2224 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2225 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2226
2227 my $added = 0;
2228 my $deleted = 0;
2229 for (my $i = 0; $i <= $#stats; $i++) {
2230 if ($stats[$i] =~ /$stat_pattern/) {
2231 $added += $1;
2232 $deleted += $2;
2233 }
2234 }
2235 my @tmp_authors = uniq(@authors);
2236 foreach my $author (@tmp_authors) {
2237 $author = deduplicate_email($author);
2238 }
2239 @tmp_authors = uniq(@tmp_authors);
2240 my @list_added = ();
2241 my @list_deleted = ();
2242 foreach my $author (@tmp_authors) {
2243 my $auth_added = 0;
2244 my $auth_deleted = 0;
2245 for (my $i = 0; $i <= $#stats; $i++) {
2246 if ($author eq deduplicate_email($authors[$i]) &&
2247 $stats[$i] =~ /$stat_pattern/) {
2248 $auth_added += $1;
2249 $auth_deleted += $2;
2250 }
2251 }
2252 for (my $i = 0; $i < $auth_added; $i++) {
2253 push(@list_added, $author);
2254 }
2255 for (my $i = 0; $i < $auth_deleted; $i++) {
2256 push(@list_deleted, $author);
2257 }
2258 }
2259 vcs_assign("added_lines", $added, @list_added);
2260 vcs_assign("removed_lines", $deleted, @list_deleted);
2261 }
Joe Perchesa8af2432009-12-14 18:00:49 -08002262}
2263
Joe Perches60db31a2009-12-14 18:00:50 -08002264sub vcs_file_blame {
Joe Perchesf5492662009-09-21 17:04:13 -07002265 my ($file) = @_;
2266
Joe Perches60db31a2009-12-14 18:00:50 -08002267 my @signers = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07002268 my @all_commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002269 my @commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002270 my $total_commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002271 my $total_lines;
Joe Perchesf5492662009-09-21 17:04:13 -07002272
Joe Perches683c6f82010-10-26 14:22:55 -07002273 $vcs_used = vcs_exists();
2274 return if (!$vcs_used);
Joe Perchesf5492662009-09-21 17:04:13 -07002275
Joe Perches63ab52d2010-10-26 14:22:51 -07002276 @all_commits = vcs_blame($file);
2277 @commits = uniq(@all_commits);
Joe Perchesa8af2432009-12-14 18:00:49 -08002278 $total_commits = @commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002279 $total_lines = @all_commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002280
Joe Perches683c6f82010-10-26 14:22:55 -07002281 if ($email_git_blame_signatures) {
2282 if (vcs_is_hg()) {
2283 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002284 my $commit_authors_ref;
2285 my $commit_signers_ref;
2286 my $stats_ref;
2287 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002288 my @commit_signers = ();
2289 my $commit = join(" -r ", @commits);
2290 my $cmd;
Joe Perchesf5492662009-09-21 17:04:13 -07002291
Joe Perches683c6f82010-10-26 14:22:55 -07002292 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2293 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
Joe Perches60db31a2009-12-14 18:00:50 -08002294
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002295 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2296 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2297 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches63ab52d2010-10-26 14:22:51 -07002298
Joe Perches683c6f82010-10-26 14:22:55 -07002299 push(@signers, @commit_signers);
2300 } else {
2301 foreach my $commit (@commits) {
2302 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002303 my $commit_authors_ref;
2304 my $commit_signers_ref;
2305 my $stats_ref;
2306 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002307 my @commit_signers = ();
2308 my $cmd;
2309
2310 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2311 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2312
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002313 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2314 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2315 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches683c6f82010-10-26 14:22:55 -07002316
2317 push(@signers, @commit_signers);
2318 }
2319 }
Joe Perchesf5492662009-09-21 17:04:13 -07002320 }
2321
Joe Perchesa8af2432009-12-14 18:00:49 -08002322 if ($from_filename) {
Joe Perches63ab52d2010-10-26 14:22:51 -07002323 if ($output_rolestats) {
2324 my @blame_signers;
Joe Perches683c6f82010-10-26 14:22:55 -07002325 if (vcs_is_hg()) {{ # Double brace for last exit
2326 my $commit_count;
2327 my @commit_signers = ();
2328 @commits = uniq(@commits);
2329 @commits = sort(@commits);
2330 my $commit = join(" -r ", @commits);
2331 my $cmd;
2332
2333 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2334 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2335
2336 my @lines = ();
2337
2338 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2339
2340 if (!$email_git_penguin_chiefs) {
2341 @lines = grep(!/${penguin_chiefs}/i, @lines);
2342 }
2343
2344 last if !@lines;
2345
2346 my @authors = ();
2347 foreach my $line (@lines) {
2348 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2349 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002350 $author = deduplicate_email($author);
2351 push(@authors, $author);
Joe Perches683c6f82010-10-26 14:22:55 -07002352 }
2353 }
2354
2355 save_commits_by_author(@lines) if ($interactive);
2356 save_commits_by_signer(@lines) if ($interactive);
2357
2358 push(@signers, @authors);
2359 }}
2360 else {
2361 foreach my $commit (@commits) {
2362 my $i;
2363 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2364 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2365 my @author = vcs_find_author($cmd);
2366 next if !@author;
Joe Perchesb9e23312010-10-26 14:22:58 -07002367
2368 my $formatted_author = deduplicate_email($author[0]);
2369
Joe Perches683c6f82010-10-26 14:22:55 -07002370 my $count = grep(/$commit/, @all_commits);
2371 for ($i = 0; $i < $count ; $i++) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002372 push(@blame_signers, $formatted_author);
Joe Perches683c6f82010-10-26 14:22:55 -07002373 }
Joe Perches63ab52d2010-10-26 14:22:51 -07002374 }
2375 }
2376 if (@blame_signers) {
2377 vcs_assign("authored lines", $total_lines, @blame_signers);
2378 }
2379 }
Joe Perchesb9e23312010-10-26 14:22:58 -07002380 foreach my $signer (@signers) {
2381 $signer = deduplicate_email($signer);
2382 }
Joe Perches60db31a2009-12-14 18:00:50 -08002383 vcs_assign("commits", $total_commits, @signers);
Joe Perchesa8af2432009-12-14 18:00:49 -08002384 } else {
Joe Perchesb9e23312010-10-26 14:22:58 -07002385 foreach my $signer (@signers) {
2386 $signer = deduplicate_email($signer);
2387 }
Joe Perches60db31a2009-12-14 18:00:50 -08002388 vcs_assign("modified commits", $total_commits, @signers);
Joe Perchesf5492662009-09-21 17:04:13 -07002389 }
Joe Perchescb7301c2009-04-07 20:40:12 -07002390}
2391
Joe Perches4cad35a2016-08-02 14:04:10 -07002392sub vcs_file_exists {
2393 my ($file) = @_;
2394
2395 my $exists;
2396
2397 my $vcs_used = vcs_exists();
2398 return 0 if (!$vcs_used);
2399
2400 my $cmd = $VCS_cmds{"file_exists_cmd"};
2401 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
Joe Perches8582fb52016-08-25 15:16:48 -07002402 $cmd .= " 2>&1";
Joe Perches4cad35a2016-08-02 14:04:10 -07002403 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2404
Joe Perches8582fb52016-08-25 15:16:48 -07002405 return 0 if ($? != 0);
2406
Joe Perches4cad35a2016-08-02 14:04:10 -07002407 return $exists;
2408}
2409
Tom Saegere1f75902017-11-17 15:27:42 -08002410sub vcs_list_files {
2411 my ($file) = @_;
2412
2413 my @lsfiles = ();
2414
2415 my $vcs_used = vcs_exists();
2416 return 0 if (!$vcs_used);
2417
2418 my $cmd = $VCS_cmds{"list_files_cmd"};
2419 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2420 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2421
2422 return () if ($? != 0);
2423
2424 return @lsfiles;
2425}
2426
Joe Perchescb7301c2009-04-07 20:40:12 -07002427sub uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002428 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002429
2430 my %saw;
2431 @parms = grep(!$saw{$_}++, @parms);
2432 return @parms;
2433}
2434
2435sub sort_and_uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002436 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002437
2438 my %saw;
2439 @parms = sort @parms;
2440 @parms = grep(!$saw{$_}++, @parms);
2441 return @parms;
2442}
2443
Joe Perches03372db2010-03-05 13:43:00 -08002444sub clean_file_emails {
2445 my (@file_emails) = @_;
2446 my @fmt_emails = ();
2447
2448 foreach my $email (@file_emails) {
2449 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2450 my ($name, $address) = parse_email($email);
2451 if ($name eq '"[,\.]"') {
2452 $name = "";
2453 }
2454
2455 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2456 if (@nw > 2) {
2457 my $first = $nw[@nw - 3];
2458 my $middle = $nw[@nw - 2];
2459 my $last = $nw[@nw - 1];
2460
2461 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2462 (length($first) == 2 && substr($first, -1) eq ".")) ||
2463 (length($middle) == 1 ||
2464 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2465 $name = "$first $middle $last";
2466 } else {
2467 $name = "$middle $last";
2468 }
2469 }
2470
2471 if (substr($name, -1) =~ /[,\.]/) {
2472 $name = substr($name, 0, length($name) - 1);
2473 } elsif (substr($name, -2) =~ /[,\.]"/) {
2474 $name = substr($name, 0, length($name) - 2) . '"';
2475 }
2476
2477 if (substr($name, 0, 1) =~ /[,\.]/) {
2478 $name = substr($name, 1, length($name) - 1);
2479 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2480 $name = '"' . substr($name, 2, length($name) - 2);
2481 }
2482
2483 my $fmt_email = format_email($name, $address, $email_usename);
2484 push(@fmt_emails, $fmt_email);
2485 }
2486 return @fmt_emails;
2487}
2488
Joe Perches3c7385b2009-12-14 18:00:46 -08002489sub merge_email {
2490 my @lines;
2491 my %saw;
2492
2493 for (@_) {
2494 my ($address, $role) = @$_;
2495 if (!$saw{$address}) {
2496 if ($output_roles) {
Joe Perches60db31a2009-12-14 18:00:50 -08002497 push(@lines, "$address ($role)");
Joe Perches3c7385b2009-12-14 18:00:46 -08002498 } else {
Joe Perches60db31a2009-12-14 18:00:50 -08002499 push(@lines, $address);
Joe Perches3c7385b2009-12-14 18:00:46 -08002500 }
2501 $saw{$address} = 1;
2502 }
2503 }
2504
2505 return @lines;
2506}
2507
Joe Perchescb7301c2009-04-07 20:40:12 -07002508sub output {
Joe Perchesa8af2432009-12-14 18:00:49 -08002509 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002510
2511 if ($output_multiline) {
2512 foreach my $line (@parms) {
2513 print("${line}\n");
2514 }
2515 } else {
2516 print(join($output_separator, @parms));
2517 print("\n");
2518 }
2519}
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002520
2521my $rfc822re;
2522
2523sub make_rfc822re {
2524# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2525# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2526# This regexp will only work on addresses which have had comments stripped
2527# and replaced with rfc822_lwsp.
2528
2529 my $specials = '()<>@,;:\\\\".\\[\\]';
2530 my $controls = '\\000-\\037\\177';
2531
2532 my $dtext = "[^\\[\\]\\r\\\\]";
2533 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2534
2535 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2536
2537# Use zero-width assertion to spot the limit of an atom. A simple
2538# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2539 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2540 my $word = "(?:$atom|$quoted_string)";
2541 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2542
2543 my $sub_domain = "(?:$atom|$domain_literal)";
2544 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2545
2546 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2547
2548 my $phrase = "$word*";
2549 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2550 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2551 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2552
2553 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2554 my $address = "(?:$mailbox|$group)";
2555
2556 return "$rfc822_lwsp*$address";
2557}
2558
2559sub rfc822_strip_comments {
2560 my $s = shift;
2561# Recursively remove comments, and replace with a single space. The simpler
2562# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2563# chars in atoms, for example.
2564
2565 while ($s =~ s/^((?:[^"\\]|\\.)*
2566 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2567 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2568 return $s;
2569}
2570
2571# valid: returns true if the parameter is an RFC822 valid address
2572#
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002573sub rfc822_valid {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002574 my $s = rfc822_strip_comments(shift);
2575
2576 if (!$rfc822re) {
2577 $rfc822re = make_rfc822re();
2578 }
2579
2580 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2581}
2582
2583# validlist: In scalar context, returns true if the parameter is an RFC822
2584# valid list of addresses.
2585#
2586# In list context, returns an empty list on failure (an invalid
2587# address was found); otherwise a list whose first element is the
2588# number of addresses found and whose remaining elements are the
2589# addresses. This is needed to disambiguate failure (invalid)
2590# from success with no addresses found, because an empty string is
2591# a valid list.
2592
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002593sub rfc822_validlist {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002594 my $s = rfc822_strip_comments(shift);
2595
2596 if (!$rfc822re) {
2597 $rfc822re = make_rfc822re();
2598 }
2599 # * null list items are valid according to the RFC
2600 # * the '1' business is to aid in distinguishing failure from no results
2601
2602 my @r;
2603 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2604 $s =~ m/^$rfc822_char*$/) {
Joe Perches5f2441e2009-06-16 15:34:02 -07002605 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
Joe Perches60db31a2009-12-14 18:00:50 -08002606 push(@r, $1);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002607 }
2608 return wantarray ? (scalar(@r), @r) : 1;
2609 }
Joe Perches60db31a2009-12-14 18:00:50 -08002610 return wantarray ? () : 0;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002611}