blob: 2075db0c08b8e02a912fbb9e5cc3a3ffc1df57d8 [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 Perchese33c9fe2020-06-04 16:50:04 -070022use File::Spec::Functions;
Joe Perchescb7301c2009-04-07 20:40:12 -070023
Joe Perchesbe17bdd2016-01-20 14:58:24 -080024my $cur_path = fastgetcwd() . '/';
Joe Perchescb7301c2009-04-07 20:40:12 -070025my $lk_path = "./";
26my $email = 1;
27my $email_usename = 1;
28my $email_maintainer = 1;
Joe Perchesc1c3f2c2014-06-02 12:05:17 -070029my $email_reviewer = 1;
Joe Perches2f5bd3432019-12-04 16:50:29 -080030my $email_fixes = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070031my $email_list = 1;
Joe Perches49662502019-07-16 16:27:09 -070032my $email_moderated_list = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070033my $email_subscriber_list = 0;
Joe Perchescb7301c2009-04-07 20:40:12 -070034my $email_git_penguin_chiefs = 0;
Joe Perchese3e9d112010-10-26 14:22:53 -070035my $email_git = 0;
Florian Mickler0fa05592010-05-24 14:33:20 -070036my $email_git_all_signature_types = 0;
Joe Perches60db31a2009-12-14 18:00:50 -080037my $email_git_blame = 0;
Joe Perches683c6f82010-10-26 14:22:55 -070038my $email_git_blame_signatures = 1;
Joe Perchese3e9d112010-10-26 14:22:53 -070039my $email_git_fallback = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070040my $email_git_min_signatures = 1;
41my $email_git_max_maintainers = 5;
Joe Perchesafa81ee2009-07-29 15:04:28 -070042my $email_git_min_percent = 5;
Joe Perchescb7301c2009-04-07 20:40:12 -070043my $email_git_since = "1-year-ago";
Joe Perches60db31a2009-12-14 18:00:50 -080044my $email_hg_since = "-365";
Florian Micklerdace8e32010-10-26 14:22:54 -070045my $interactive = 0;
Joe Perches11ecf532009-09-21 17:04:22 -070046my $email_remove_duplicates = 1;
Joe Perchesb9e23312010-10-26 14:22:58 -070047my $email_use_mailmap = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070048my $output_multiline = 1;
49my $output_separator = ", ";
Joe Perches3c7385b2009-12-14 18:00:46 -080050my $output_roles = 0;
Joe Perches7e1863a2011-01-12 16:59:49 -080051my $output_rolestats = 1;
Joe Perches364f68d2015-06-25 15:01:52 -070052my $output_section_maxlen = 50;
Joe Perchescb7301c2009-04-07 20:40:12 -070053my $scm = 0;
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -070054my $tree = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070055my $web = 0;
56my $subsystem = 0;
57my $status = 0;
Joe Perches03aed212016-12-12 16:45:59 -080058my $letters = "";
Joe Perchesdcf36a92009-10-26 16:49:47 -070059my $keywords = 1;
Joe Perches4b76c9d2010-03-05 13:43:03 -080060my $sections = 0;
Joe Perches0c78c012020-06-04 16:50:01 -070061my $email_file_emails = 0;
Joe Perches4a7fdb52009-04-10 12:28:57 -070062my $from_filename = 0;
Joe Perches3fb55652009-09-21 17:04:17 -070063my $pattern_depth = 0;
Joe Perches083bf9c2017-11-17 15:27:46 -080064my $self_test = undef;
Joe Perchescb7301c2009-04-07 20:40:12 -070065my $version = 0;
66my $help = 0;
Joe Perches6f7d98e2017-08-04 21:45:48 -070067my $find_maintainer_files = 0;
Joe Perches5f0baf92018-08-21 21:56:52 -070068my $maintainer_path;
Joe Perches683c6f82010-10-26 14:22:55 -070069my $vcs_used = 0;
70
Joe Perchescb7301c2009-04-07 20:40:12 -070071my $exit = 0;
72
Joe Perches0c78c012020-06-04 16:50:01 -070073my @files = ();
74my @fixes = (); # If a patch description includes Fixes: lines
75my @range = ();
76my @keyword_tvi = ();
77my @file_emails = ();
78
Joe Perches683c6f82010-10-26 14:22:55 -070079my %commit_author_hash;
80my %commit_signer_hash;
Florian Micklerdace8e32010-10-26 14:22:54 -070081
Joe Perchescb7301c2009-04-07 20:40:12 -070082my @penguin_chief = ();
Joe Perchese4d26b02010-05-24 14:33:17 -070083push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
Joe Perchescb7301c2009-04-07 20:40:12 -070084#Andrew wants in on most everything - 2009/01/14
Joe Perchese4d26b02010-05-24 14:33:17 -070085#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
Joe Perchescb7301c2009-04-07 20:40:12 -070086
87my @penguin_chief_names = ();
88foreach my $chief (@penguin_chief) {
89 if ($chief =~ m/^(.*):(.*)/) {
90 my $chief_name = $1;
91 my $chief_addr = $2;
92 push(@penguin_chief_names, $chief_name);
93 }
94}
Joe Perchese4d26b02010-05-24 14:33:17 -070095my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
96
97# Signature types of people who are either
98# a) responsible for the code in question, or
99# b) familiar enough with it to give relevant feedback
100my @signature_tags = ();
101push(@signature_tags, "Signed-off-by:");
102push(@signature_tags, "Reviewed-by:");
103push(@signature_tags, "Acked-by:");
Joe Perchescb7301c2009-04-07 20:40:12 -0700104
Joe Perches7dea2682012-06-20 12:53:02 -0700105my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
106
Joe Perches5f2441e2009-06-16 15:34:02 -0700107# rfc822 email address - preloaded methods go here.
Joe Perches1b5e1cf2009-06-16 15:34:01 -0700108my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
Joe Perchesdf4cc032009-06-16 15:34:04 -0700109my $rfc822_char = '[\\000-\\377]';
Joe Perches1b5e1cf2009-06-16 15:34:01 -0700110
Joe Perches60db31a2009-12-14 18:00:50 -0800111# VCS command support: class-like functions and strings
112
113my %VCS_cmds;
114
115my %VCS_cmds_git = (
116 "execute_cmd" => \&git_execute_cmd,
Richard Genoudec83b612014-02-10 14:25:31 -0800117 "available" => '(which("git") ne "") && (-e ".git")',
Joe Perches683c6f82010-10-26 14:22:55 -0700118 "find_signers_cmd" =>
Ian Campbelled128fea2012-01-10 15:08:41 -0800119 "git log --no-color --follow --since=\$email_git_since " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800120 '--numstat --no-merges ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700121 '--format="GitCommit: %H%n' .
122 'GitAuthor: %an <%ae>%n' .
123 'GitDate: %aD%n' .
124 'GitSubject: %s%n' .
125 '%b%n"' .
126 " -- \$file",
127 "find_commit_signers_cmd" =>
128 "git log --no-color " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800129 '--numstat ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700130 '--format="GitCommit: %H%n' .
131 'GitAuthor: %an <%ae>%n' .
132 'GitDate: %aD%n' .
133 'GitSubject: %s%n' .
134 '%b%n"' .
135 " -1 \$commit",
136 "find_commit_author_cmd" =>
137 "git log --no-color " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800138 '--numstat ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700139 '--format="GitCommit: %H%n' .
140 'GitAuthor: %an <%ae>%n' .
141 'GitDate: %aD%n' .
142 'GitSubject: %s%n"' .
143 " -1 \$commit",
Joe Perches60db31a2009-12-14 18:00:50 -0800144 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
145 "blame_file_cmd" => "git blame -l \$file",
Joe Perches683c6f82010-10-26 14:22:55 -0700146 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
Florian Micklerdace8e32010-10-26 14:22:54 -0700147 "blame_commit_pattern" => "^([0-9a-f]+) ",
Joe Perches683c6f82010-10-26 14:22:55 -0700148 "author_pattern" => "^GitAuthor: (.*)",
149 "subject_pattern" => "^GitSubject: (.*)",
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800150 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
Joe Perches4cad35a2016-08-02 14:04:10 -0700151 "file_exists_cmd" => "git ls-files \$file",
Tom Saegere1f75902017-11-17 15:27:42 -0800152 "list_files_cmd" => "git ls-files \$file",
Joe Perches60db31a2009-12-14 18:00:50 -0800153);
154
155my %VCS_cmds_hg = (
156 "execute_cmd" => \&hg_execute_cmd,
157 "available" => '(which("hg") ne "") && (-d ".hg")',
158 "find_signers_cmd" =>
Joe Perches683c6f82010-10-26 14:22:55 -0700159 "hg log --date=\$email_hg_since " .
160 "--template='HgCommit: {node}\\n" .
161 "HgAuthor: {author}\\n" .
162 "HgSubject: {desc}\\n'" .
163 " -- \$file",
164 "find_commit_signers_cmd" =>
165 "hg log " .
166 "--template='HgSubject: {desc}\\n'" .
167 " -r \$commit",
168 "find_commit_author_cmd" =>
169 "hg log " .
170 "--template='HgCommit: {node}\\n" .
171 "HgAuthor: {author}\\n" .
172 "HgSubject: {desc|firstline}\\n'" .
173 " -r \$commit",
Joe Perches60db31a2009-12-14 18:00:50 -0800174 "blame_range_cmd" => "", # not supported
Joe Perches683c6f82010-10-26 14:22:55 -0700175 "blame_file_cmd" => "hg blame -n \$file",
176 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
177 "blame_commit_pattern" => "^([ 0-9a-f]+):",
178 "author_pattern" => "^HgAuthor: (.*)",
179 "subject_pattern" => "^HgSubject: (.*)",
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800180 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
Joe Perches4cad35a2016-08-02 14:04:10 -0700181 "file_exists_cmd" => "hg files \$file",
Tom Saegere1f75902017-11-17 15:27:42 -0800182 "list_files_cmd" => "hg manifest -R \$file",
Joe Perches60db31a2009-12-14 18:00:50 -0800183);
184
Joe Perchesbcde44e2010-10-26 14:22:53 -0700185my $conf = which_conf(".get_maintainer.conf");
186if (-f $conf) {
Joe Perches368669d2010-05-24 14:33:19 -0700187 my @conf_args;
Joe Perchesbcde44e2010-10-26 14:22:53 -0700188 open(my $conffile, '<', "$conf")
189 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
190
Joe Perches368669d2010-05-24 14:33:19 -0700191 while (<$conffile>) {
192 my $line = $_;
193
194 $line =~ s/\s*\n?$//g;
195 $line =~ s/^\s*//g;
196 $line =~ s/\s+/ /g;
197
198 next if ($line =~ m/^\s*#/);
199 next if ($line =~ m/^\s*$/);
200
201 my @words = split(" ", $line);
202 foreach my $word (@words) {
203 last if ($word =~ m/^#/);
204 push (@conf_args, $word);
205 }
206 }
207 close($conffile);
208 unshift(@ARGV, @conf_args) if @conf_args;
209}
210
Joe Perches435de072015-06-25 15:01:50 -0700211my @ignore_emails = ();
212my $ignore_file = which_conf(".get_maintainer.ignore");
213if (-f $ignore_file) {
214 open(my $ignore, '<', "$ignore_file")
215 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
216 while (<$ignore>) {
217 my $line = $_;
218
219 $line =~ s/\s*\n?$//;
220 $line =~ s/^\s*//;
221 $line =~ s/\s+$//;
222 $line =~ s/#.*$//;
223
224 next if ($line =~ m/^\s*$/);
225 if (rfc822_valid($line)) {
226 push(@ignore_emails, $line);
227 }
228 }
229 close($ignore);
230}
231
Tom Saegere1f75902017-11-17 15:27:42 -0800232if ($#ARGV > 0) {
233 foreach (@ARGV) {
Joe Perches083bf9c2017-11-17 15:27:46 -0800234 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
Tom Saegere1f75902017-11-17 15:27:42 -0800235 die "$P: using --self-test does not allow any other option or argument\n";
236 }
237 }
238}
239
Joe Perchescb7301c2009-04-07 20:40:12 -0700240if (!GetOptions(
241 'email!' => \$email,
242 'git!' => \$email_git,
Joe Perchese4d26b02010-05-24 14:33:17 -0700243 'git-all-signature-types!' => \$email_git_all_signature_types,
Joe Perches60db31a2009-12-14 18:00:50 -0800244 'git-blame!' => \$email_git_blame,
Joe Perches683c6f82010-10-26 14:22:55 -0700245 'git-blame-signatures!' => \$email_git_blame_signatures,
Joe Perchese3e9d112010-10-26 14:22:53 -0700246 'git-fallback!' => \$email_git_fallback,
Joe Perchescb7301c2009-04-07 20:40:12 -0700247 'git-chief-penguins!' => \$email_git_penguin_chiefs,
248 'git-min-signatures=i' => \$email_git_min_signatures,
249 'git-max-maintainers=i' => \$email_git_max_maintainers,
Joe Perchesafa81ee2009-07-29 15:04:28 -0700250 'git-min-percent=i' => \$email_git_min_percent,
Joe Perchescb7301c2009-04-07 20:40:12 -0700251 'git-since=s' => \$email_git_since,
Joe Perches60db31a2009-12-14 18:00:50 -0800252 'hg-since=s' => \$email_hg_since,
Florian Micklerdace8e32010-10-26 14:22:54 -0700253 'i|interactive!' => \$interactive,
Joe Perches11ecf532009-09-21 17:04:22 -0700254 'remove-duplicates!' => \$email_remove_duplicates,
Joe Perchesb9e23312010-10-26 14:22:58 -0700255 'mailmap!' => \$email_use_mailmap,
Joe Perchescb7301c2009-04-07 20:40:12 -0700256 'm!' => \$email_maintainer,
Joe Perchesc1c3f2c2014-06-02 12:05:17 -0700257 'r!' => \$email_reviewer,
Joe Perchescb7301c2009-04-07 20:40:12 -0700258 'n!' => \$email_usename,
259 'l!' => \$email_list,
Joe Perches2f5bd3432019-12-04 16:50:29 -0800260 'fixes!' => \$email_fixes,
Joe Perches49662502019-07-16 16:27:09 -0700261 'moderated!' => \$email_moderated_list,
Joe Perchescb7301c2009-04-07 20:40:12 -0700262 's!' => \$email_subscriber_list,
263 'multiline!' => \$output_multiline,
Joe Perches3c7385b2009-12-14 18:00:46 -0800264 'roles!' => \$output_roles,
265 'rolestats!' => \$output_rolestats,
Joe Perchescb7301c2009-04-07 20:40:12 -0700266 'separator=s' => \$output_separator,
267 'subsystem!' => \$subsystem,
268 'status!' => \$status,
269 'scm!' => \$scm,
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -0700270 'tree!' => \$tree,
Joe Perchescb7301c2009-04-07 20:40:12 -0700271 'web!' => \$web,
Joe Perches03aed212016-12-12 16:45:59 -0800272 'letters=s' => \$letters,
Joe Perches3fb55652009-09-21 17:04:17 -0700273 'pattern-depth=i' => \$pattern_depth,
Joe Perchesdcf36a92009-10-26 16:49:47 -0700274 'k|keywords!' => \$keywords,
Joe Perches4b76c9d2010-03-05 13:43:03 -0800275 'sections!' => \$sections,
Joe Perches0c78c012020-06-04 16:50:01 -0700276 'fe|file-emails!' => \$email_file_emails,
Joe Perches4a7fdb52009-04-10 12:28:57 -0700277 'f|file' => \$from_filename,
Joe Perches6f7d98e2017-08-04 21:45:48 -0700278 'find-maintainer-files' => \$find_maintainer_files,
Joe Perches5f0baf92018-08-21 21:56:52 -0700279 'mpath|maintainer-path=s' => \$maintainer_path,
Joe Perches083bf9c2017-11-17 15:27:46 -0800280 'self-test:s' => \$self_test,
Joe Perchescb7301c2009-04-07 20:40:12 -0700281 'v|version' => \$version,
Joe Perches64f77f32010-03-05 13:43:04 -0800282 'h|help|usage' => \$help,
Joe Perchescb7301c2009-04-07 20:40:12 -0700283 )) {
Joe Perches3c7385b2009-12-14 18:00:46 -0800284 die "$P: invalid argument - use --help if necessary\n";
Joe Perchescb7301c2009-04-07 20:40:12 -0700285}
286
287if ($help != 0) {
288 usage();
289 exit 0;
290}
291
292if ($version != 0) {
293 print("${P} ${V}\n");
294 exit 0;
295}
296
Joe Perches083bf9c2017-11-17 15:27:46 -0800297if (defined $self_test) {
Tom Saegere1f75902017-11-17 15:27:42 -0800298 read_all_maintainer_files();
Joe Perches083bf9c2017-11-17 15:27:46 -0800299 self_test();
Tom Saegere1f75902017-11-17 15:27:42 -0800300 exit 0;
301}
302
Joe Perches64f77f32010-03-05 13:43:04 -0800303if (-t STDIN && !@ARGV) {
304 # We're talking to a terminal, but have no command line arguments.
305 die "$P: missing patchfile or -f file - use --help if necessary\n";
Joe Perchescb7301c2009-04-07 20:40:12 -0700306}
307
Joe Perches683c6f82010-10-26 14:22:55 -0700308$output_multiline = 0 if ($output_separator ne ", ");
309$output_rolestats = 1 if ($interactive);
310$output_roles = 1 if ($output_rolestats);
Joe Perches3c7385b2009-12-14 18:00:46 -0800311
Joe Perches03aed212016-12-12 16:45:59 -0800312if ($sections || $letters ne "") {
313 $sections = 1;
Joe Perches4b76c9d2010-03-05 13:43:03 -0800314 $email = 0;
315 $email_list = 0;
316 $scm = 0;
317 $status = 0;
318 $subsystem = 0;
319 $web = 0;
320 $keywords = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -0700321 $interactive = 0;
Joe Perches4b76c9d2010-03-05 13:43:03 -0800322} else {
323 my $selections = $email + $scm + $status + $subsystem + $web;
324 if ($selections == 0) {
Joe Perches4b76c9d2010-03-05 13:43:03 -0800325 die "$P: Missing required option: email, scm, status, subsystem or web\n";
326 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700327}
328
Joe Perchesf5492662009-09-21 17:04:13 -0700329if ($email &&
Joe Perchesc1c3f2c2014-06-02 12:05:17 -0700330 ($email_maintainer + $email_reviewer +
331 $email_list + $email_subscriber_list +
Joe Perchesf5492662009-09-21 17:04:13 -0700332 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
Joe Perchescb7301c2009-04-07 20:40:12 -0700333 die "$P: Please select at least 1 email option\n";
334}
335
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -0700336if ($tree && !top_of_kernel_tree($lk_path)) {
Joe Perchescb7301c2009-04-07 20:40:12 -0700337 die "$P: The current directory does not appear to be "
338 . "a linux kernel source tree.\n";
339}
340
341## Read MAINTAINERS for type/value pairs
342
343my @typevalue = ();
Joe Perchesdcf36a92009-10-26 16:49:47 -0700344my %keyword_hash;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700345my @mfiles = ();
Joe Perches083bf9c2017-11-17 15:27:46 -0800346my @self_test_info = ();
Joe Perchesdcf36a92009-10-26 16:49:47 -0700347
Joe Perches6f7d98e2017-08-04 21:45:48 -0700348sub read_maintainer_file {
349 my ($file) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -0700350
Joe Perches6f7d98e2017-08-04 21:45:48 -0700351 open (my $maint, '<', "$file")
352 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
Tom Saegere1f75902017-11-17 15:27:42 -0800353 my $i = 1;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700354 while (<$maint>) {
355 my $line = $_;
Joe Perches083bf9c2017-11-17 15:27:46 -0800356 chomp $line;
Joe Perchescb7301c2009-04-07 20:40:12 -0700357
Joe Perches6f7d98e2017-08-04 21:45:48 -0700358 if ($line =~ m/^([A-Z]):\s*(.*)/) {
359 my $type = $1;
360 my $value = $2;
361
362 ##Filename pattern matching
363 if ($type eq "F" || $type eq "X") {
364 $value =~ s@\.@\\\.@g; ##Convert . to \.
365 $value =~ s/\*/\.\*/g; ##Convert * to .*
366 $value =~ s/\?/\./g; ##Convert ? to .
367 ##if pattern is a directory and it lacks a trailing slash, add one
368 if ((-d $value)) {
369 $value =~ s@([^/])$@$1/@;
370 }
371 } elsif ($type eq "K") {
372 $keyword_hash{@typevalue} = $value;
Joe Perches870020f2009-07-29 15:04:28 -0700373 }
Joe Perches6f7d98e2017-08-04 21:45:48 -0700374 push(@typevalue, "$type:$value");
375 } elsif (!(/^\s*$/ || /^\s*\#/)) {
Joe Perches6f7d98e2017-08-04 21:45:48 -0700376 push(@typevalue, $line);
Joe Perchescb7301c2009-04-07 20:40:12 -0700377 }
Joe Perches083bf9c2017-11-17 15:27:46 -0800378 if (defined $self_test) {
379 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
380 }
Tom Saegere1f75902017-11-17 15:27:42 -0800381 $i++;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700382 }
383 close($maint);
384}
385
386sub find_is_maintainer_file {
387 my ($file) = $_;
388 return if ($file !~ m@/MAINTAINERS$@);
389 $file = $File::Find::name;
390 return if (! -f $file);
391 push(@mfiles, $file);
392}
393
394sub find_ignore_git {
395 return grep { $_ !~ /^\.git$/; } @_;
396}
397
Tom Saegere1f75902017-11-17 15:27:42 -0800398read_all_maintainer_files();
399
400sub read_all_maintainer_files {
Joe Perches5f0baf92018-08-21 21:56:52 -0700401 my $path = "${lk_path}MAINTAINERS";
402 if (defined $maintainer_path) {
403 $path = $maintainer_path;
404 # Perl Cookbook tilde expansion if necessary
405 $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
Joe Perchescb7301c2009-04-07 20:40:12 -0700406 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700407
Joe Perches5f0baf92018-08-21 21:56:52 -0700408 if (-d $path) {
409 $path .= '/' if ($path !~ m@/$@);
Joe Perches0fbd75f2018-08-21 21:56:55 -0700410 if ($find_maintainer_files) {
411 find( { wanted => \&find_is_maintainer_file,
412 preprocess => \&find_ignore_git,
413 no_chdir => 1,
414 }, "$path");
415 } else {
Joe Perches5f0baf92018-08-21 21:56:52 -0700416 opendir(DIR, "$path") or die $!;
417 my @files = readdir(DIR);
418 closedir(DIR);
419 foreach my $file (@files) {
420 push(@mfiles, "$path$file") if ($file !~ /^\./);
421 }
422 }
Joe Perches5f0baf92018-08-21 21:56:52 -0700423 } elsif (-f "$path") {
424 push(@mfiles, "$path");
Tom Saegere1f75902017-11-17 15:27:42 -0800425 } else {
Joe Perches5f0baf92018-08-21 21:56:52 -0700426 die "$P: MAINTAINER file not found '$path'\n";
Tom Saegere1f75902017-11-17 15:27:42 -0800427 }
Joe Perches5f0baf92018-08-21 21:56:52 -0700428 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
Tom Saegere1f75902017-11-17 15:27:42 -0800429 foreach my $file (@mfiles) {
Joe Perches5f0baf92018-08-21 21:56:52 -0700430 read_maintainer_file("$file");
Tom Saegere1f75902017-11-17 15:27:42 -0800431 }
Joe Perches6f7d98e2017-08-04 21:45:48 -0700432}
Joe Perches8cbb3a72009-09-21 17:04:21 -0700433
Joe Perches0c78c012020-06-04 16:50:01 -0700434sub maintainers_in_file {
435 my ($file) = @_;
436
437 return if ($file =~ m@\bMAINTAINERS$@);
438
439 if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) {
440 open(my $f, '<', $file)
441 or die "$P: Can't open $file: $!\n";
442 my $text = do { local($/) ; <$f> };
443 close($f);
444
445 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;
446 push(@file_emails, clean_file_emails(@poss_addr));
447 }
448}
449
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700450#
451# Read mail address map
452#
453
Joe Perchesb9e23312010-10-26 14:22:58 -0700454my $mailmap;
455
456read_mailmap();
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700457
458sub read_mailmap {
Joe Perchesb9e23312010-10-26 14:22:58 -0700459 $mailmap = {
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700460 names => {},
461 addresses => {}
Joe Perches47abc722010-10-26 14:22:57 -0700462 };
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700463
Joe Perchesb9e23312010-10-26 14:22:58 -0700464 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700465
466 open(my $mailmap_file, '<', "${lk_path}.mailmap")
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800467 or warn "$P: Can't open .mailmap: $!\n";
Joe Perches8cbb3a72009-09-21 17:04:21 -0700468
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700469 while (<$mailmap_file>) {
470 s/#.*$//; #strip comments
471 s/^\s+|\s+$//g; #trim
Joe Perches8cbb3a72009-09-21 17:04:21 -0700472
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700473 next if (/^\s*$/); #skip empty lines
474 #entries have one of the following formats:
475 # name1 <mail1>
476 # <mail1> <mail2>
477 # name1 <mail1> <mail2>
478 # name1 <mail1> name2 <mail2>
479 # (see man git-shortlog)
Joe Perches0334b382011-07-25 17:13:13 -0700480
481 if (/^([^<]+)<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700482 my $real_name = $1;
483 my $address = $2;
Joe Perches8cbb3a72009-09-21 17:04:21 -0700484
Joe Perches47abc722010-10-26 14:22:57 -0700485 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700486 ($real_name, $address) = parse_email("$real_name <$address>");
Joe Perches47abc722010-10-26 14:22:57 -0700487 $mailmap->{names}->{$address} = $real_name;
Joe Perches8cbb3a72009-09-21 17:04:21 -0700488
Joe Perches0334b382011-07-25 17:13:13 -0700489 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700490 my $real_address = $1;
491 my $wrong_address = $2;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700492
Joe Perches47abc722010-10-26 14:22:57 -0700493 $mailmap->{addresses}->{$wrong_address} = $real_address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700494
Joe Perches0334b382011-07-25 17:13:13 -0700495 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
Joe Perchesb9e23312010-10-26 14:22:58 -0700496 my $real_name = $1;
Joe Perches47abc722010-10-26 14:22:57 -0700497 my $real_address = $2;
498 my $wrong_address = $3;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700499
Joe Perches47abc722010-10-26 14:22:57 -0700500 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700501 ($real_name, $real_address) =
502 parse_email("$real_name <$real_address>");
Joe Perches47abc722010-10-26 14:22:57 -0700503 $mailmap->{names}->{$wrong_address} = $real_name;
504 $mailmap->{addresses}->{$wrong_address} = $real_address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700505
Joe Perches0334b382011-07-25 17:13:13 -0700506 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700507 my $real_name = $1;
508 my $real_address = $2;
509 my $wrong_name = $3;
510 my $wrong_address = $4;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700511
Joe Perches47abc722010-10-26 14:22:57 -0700512 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700513 ($real_name, $real_address) =
514 parse_email("$real_name <$real_address>");
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700515
Joe Perchesb9e23312010-10-26 14:22:58 -0700516 $wrong_name =~ s/\s+$//;
517 ($wrong_name, $wrong_address) =
518 parse_email("$wrong_name <$wrong_address>");
519
520 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
521 $mailmap->{names}->{$wrong_email} = $real_name;
522 $mailmap->{addresses}->{$wrong_email} = $real_address;
Joe Perches11ecf532009-09-21 17:04:22 -0700523 }
Joe Perches8cbb3a72009-09-21 17:04:21 -0700524 }
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700525 close($mailmap_file);
Joe Perches8cbb3a72009-09-21 17:04:21 -0700526}
527
Joe Perches4a7fdb52009-04-10 12:28:57 -0700528## use the filenames on the command line or find the filenames in the patchfiles
Joe Perchescb7301c2009-04-07 20:40:12 -0700529
Joe Perches64f77f32010-03-05 13:43:04 -0800530if (!@ARGV) {
531 push(@ARGV, "&STDIN");
532}
533
Joe Perches4a7fdb52009-04-10 12:28:57 -0700534foreach my $file (@ARGV) {
Joe Perches64f77f32010-03-05 13:43:04 -0800535 if ($file ne "&STDIN") {
Joe Perchese33c9fe2020-06-04 16:50:04 -0700536 $file = canonpath($file);
Joe Perches64f77f32010-03-05 13:43:04 -0800537 ##if $file is a directory and it lacks a trailing slash, add one
538 if ((-d $file)) {
539 $file =~ s@([^/])$@$1/@;
540 } elsif (!(-f $file)) {
541 die "$P: file '${file}' not found\n";
542 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700543 }
Joe Perchescdfe2d22020-10-15 20:10:34 -0700544 if ($from_filename && (vcs_exists() && !vcs_file_exists($file))) {
545 warn "$P: file '$file' not found in version control $!\n";
546 }
Joe Perchesaec742e2016-08-10 08:45:11 -0700547 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
Joe Perchesbe17bdd2016-01-20 14:58:24 -0800548 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
549 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
Joe Perches4a7fdb52009-04-10 12:28:57 -0700550 push(@files, $file);
Joe Perches0c78c012020-06-04 16:50:01 -0700551 if ($file ne "MAINTAINERS" && -f $file && $keywords) {
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800552 open(my $f, '<', $file)
553 or die "$P: Can't open $file: $!\n";
554 my $text = do { local($/) ; <$f> };
555 close($f);
Joe Perches03372db2010-03-05 13:43:00 -0800556 if ($keywords) {
557 foreach my $line (keys %keyword_hash) {
558 if ($text =~ m/$keyword_hash{$line}/x) {
559 push(@keyword_tvi, $line);
560 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700561 }
562 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700563 }
Joe Perches4a7fdb52009-04-10 12:28:57 -0700564 } else {
565 my $file_cnt = @files;
Joe Perchesf5492662009-09-21 17:04:13 -0700566 my $lastfile;
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800567
Wolfram Sang3a4df132010-03-23 13:35:18 -0700568 open(my $patch, "< $file")
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800569 or die "$P: Can't open $file: $!\n";
Joe Perches7764dcb2011-03-22 16:34:24 -0700570
571 # We can check arbitrary information before the patch
572 # like the commit message, mail headers, etc...
573 # This allows us to match arbitrary keywords against any part
574 # of a git format-patch generated file (subject tags, etc...)
575
576 my $patch_prefix = ""; #Parsing the intro
577
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800578 while (<$patch>) {
Joe Perchesdcf36a92009-10-26 16:49:47 -0700579 my $patch_line = $_;
Joe Perches0455c742018-06-07 17:10:38 -0700580 if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
581 my $filename = $1;
582 push(@files, $filename);
583 } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
584 my $filename = $1;
585 push(@files, $filename);
586 } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
587 my $filename1 = $1;
588 my $filename2 = $2;
589 push(@files, $filename1);
590 push(@files, $filename2);
Joe Perches2f5bd3432019-12-04 16:50:29 -0800591 } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
592 push(@fixes, $1) if ($email_fixes);
Joe Perches0455c742018-06-07 17:10:38 -0700593 } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
Joe Perches4a7fdb52009-04-10 12:28:57 -0700594 my $filename = $1;
595 $filename =~ s@^[^/]*/@@;
596 $filename =~ s@\n@@;
Joe Perchesf5492662009-09-21 17:04:13 -0700597 $lastfile = $filename;
Joe Perches4a7fdb52009-04-10 12:28:57 -0700598 push(@files, $filename);
Joe Perches7764dcb2011-03-22 16:34:24 -0700599 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
Joe Perchesf5492662009-09-21 17:04:13 -0700600 } elsif (m/^\@\@ -(\d+),(\d+)/) {
601 if ($email_git_blame) {
602 push(@range, "$lastfile:$1:$2");
603 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700604 } elsif ($keywords) {
605 foreach my $line (keys %keyword_hash) {
Joe Perches7764dcb2011-03-22 16:34:24 -0700606 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
Joe Perchesdcf36a92009-10-26 16:49:47 -0700607 push(@keyword_tvi, $line);
608 }
609 }
Joe Perches4a7fdb52009-04-10 12:28:57 -0700610 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700611 }
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800612 close($patch);
613
Joe Perches4a7fdb52009-04-10 12:28:57 -0700614 if ($file_cnt == @files) {
Joe Perches7f29fd272009-06-16 15:34:04 -0700615 warn "$P: file '${file}' doesn't appear to be a patch. "
Joe Perches4a7fdb52009-04-10 12:28:57 -0700616 . "Add -f to options?\n";
617 }
618 @files = sort_and_uniq(@files);
Joe Perchescb7301c2009-04-07 20:40:12 -0700619 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700620}
621
Joe Perches03372db2010-03-05 13:43:00 -0800622@file_emails = uniq(@file_emails);
Joe Perches2f5bd3432019-12-04 16:50:29 -0800623@fixes = uniq(@fixes);
Joe Perches03372db2010-03-05 13:43:00 -0800624
Joe Perches683c6f82010-10-26 14:22:55 -0700625my %email_hash_name;
626my %email_hash_address;
Joe Perchescb7301c2009-04-07 20:40:12 -0700627my @email_to = ();
Joe Perches683c6f82010-10-26 14:22:55 -0700628my %hash_list_to;
Joe Perches290603c2009-06-16 15:33:58 -0700629my @list_to = ();
Joe Perchescb7301c2009-04-07 20:40:12 -0700630my @scm = ();
631my @web = ();
632my @subsystem = ();
633my @status = ();
Joe Perchesb9e23312010-10-26 14:22:58 -0700634my %deduplicate_name_hash = ();
635my %deduplicate_address_hash = ();
Joe Perchescb7301c2009-04-07 20:40:12 -0700636
Joe Perches6ef1c522010-10-26 14:22:56 -0700637my @maintainers = get_maintainers();
Joe Perches6ef1c522010-10-26 14:22:56 -0700638if (@maintainers) {
639 @maintainers = merge_email(@maintainers);
640 output(@maintainers);
641}
Joe Perchescb7301c2009-04-07 20:40:12 -0700642
643if ($scm) {
Joe Perchesb7816552009-09-21 17:04:24 -0700644 @scm = uniq(@scm);
Joe Perchescb7301c2009-04-07 20:40:12 -0700645 output(@scm);
646}
Joe Perches683c6f82010-10-26 14:22:55 -0700647
Joe Perchescb7301c2009-04-07 20:40:12 -0700648if ($status) {
Joe Perchesb7816552009-09-21 17:04:24 -0700649 @status = uniq(@status);
Joe Perchescb7301c2009-04-07 20:40:12 -0700650 output(@status);
651}
652
653if ($subsystem) {
Joe Perchesb7816552009-09-21 17:04:24 -0700654 @subsystem = uniq(@subsystem);
Joe Perchescb7301c2009-04-07 20:40:12 -0700655 output(@subsystem);
656}
657
658if ($web) {
Joe Perchesb7816552009-09-21 17:04:24 -0700659 @web = uniq(@web);
Joe Perchescb7301c2009-04-07 20:40:12 -0700660 output(@web);
661}
662
663exit($exit);
664
Joe Perches083bf9c2017-11-17 15:27:46 -0800665sub self_test {
Tom Saegere1f75902017-11-17 15:27:42 -0800666 my @lsfiles = ();
Joe Perches083bf9c2017-11-17 15:27:46 -0800667 my @good_links = ();
668 my @bad_links = ();
669 my @section_headers = ();
670 my $index = 0;
Tom Saegere1f75902017-11-17 15:27:42 -0800671
672 @lsfiles = vcs_list_files($lk_path);
673
Joe Perches083bf9c2017-11-17 15:27:46 -0800674 for my $x (@self_test_info) {
675 $index++;
676
677 ## Section header duplication and missing section content
678 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
679 $x->{line} =~ /^\S[^:]/ &&
680 defined $self_test_info[$index] &&
681 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
682 my $has_S = 0;
683 my $has_F = 0;
684 my $has_ML = 0;
685 my $status = "";
686 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
687 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
688 } else {
689 push(@section_headers, $x->{line});
690 }
691 my $nextline = $index;
692 while (defined $self_test_info[$nextline] &&
693 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
694 my $type = $1;
695 my $value = $2;
696 if ($type eq "S") {
697 $has_S = 1;
698 $status = $value;
699 } elsif ($type eq "F" || $type eq "N") {
700 $has_F = 1;
701 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
702 $has_ML = 1;
703 }
704 $nextline++;
705 }
706 if (!$has_ML && $status !~ /orphan|obsolete/i) {
707 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
708 }
709 if (!$has_S) {
710 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
711 }
712 if (!$has_F) {
713 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
714 }
715 }
716
717 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
718
719 my $type = $1;
720 my $value = $2;
721
722 ## Filename pattern matching
723 if (($type eq "F" || $type eq "X") &&
724 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
725 $value =~ s@\.@\\\.@g; ##Convert . to \.
726 $value =~ s/\*/\.\*/g; ##Convert * to .*
727 $value =~ s/\?/\./g; ##Convert ? to .
728 ##if pattern is a directory and it lacks a trailing slash, add one
729 if ((-d $value)) {
730 $value =~ s@([^/])$@$1/@;
731 }
732 if (!grep(m@^$value@, @lsfiles)) {
733 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
734 }
735
736 ## Link reachability
737 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
738 $value =~ /^https?:/ &&
739 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
740 next if (grep(m@^\Q$value\E$@, @good_links));
741 my $isbad = 0;
742 if (grep(m@^\Q$value\E$@, @bad_links)) {
743 $isbad = 1;
744 } else {
745 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
746 if ($? == 0) {
747 push(@good_links, $value);
748 } else {
749 push(@bad_links, $value);
750 $isbad = 1;
751 }
752 }
753 if ($isbad) {
754 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
755 }
756
757 ## SCM reachability
758 } elsif ($type eq "T" &&
759 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
760 next if (grep(m@^\Q$value\E$@, @good_links));
761 my $isbad = 0;
762 if (grep(m@^\Q$value\E$@, @bad_links)) {
763 $isbad = 1;
764 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
765 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
766 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
767 my $url = $1;
768 my $branch = "";
769 $branch = $3 if $3;
770 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
771 if ($? == 0) {
772 push(@good_links, $value);
773 } else {
774 push(@bad_links, $value);
775 $isbad = 1;
776 }
777 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
778 my $url = $1;
779 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
780 if ($? == 0) {
781 push(@good_links, $value);
782 } else {
783 push(@bad_links, $value);
784 $isbad = 1;
785 }
786 }
787 if ($isbad) {
788 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
789 }
790 }
Tom Saegere1f75902017-11-17 15:27:42 -0800791 }
792}
793
Joe Perches435de072015-06-25 15:01:50 -0700794sub ignore_email_address {
795 my ($address) = @_;
796
797 foreach my $ignore (@ignore_emails) {
798 return 1 if ($ignore eq $address);
799 }
800
801 return 0;
802}
803
Joe Perchesab6c9372011-01-12 16:59:50 -0800804sub range_is_maintained {
805 my ($start, $end) = @_;
806
807 for (my $i = $start; $i < $end; $i++) {
808 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700809 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchesab6c9372011-01-12 16:59:50 -0800810 my $type = $1;
811 my $value = $2;
812 if ($type eq 'S') {
813 if ($value =~ /(maintain|support)/i) {
814 return 1;
815 }
816 }
817 }
818 }
819 return 0;
820}
821
822sub range_has_maintainer {
823 my ($start, $end) = @_;
824
825 for (my $i = $start; $i < $end; $i++) {
826 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700827 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchesab6c9372011-01-12 16:59:50 -0800828 my $type = $1;
829 my $value = $2;
830 if ($type eq 'M') {
831 return 1;
832 }
833 }
834 }
835 return 0;
836}
837
Joe Perches6ef1c522010-10-26 14:22:56 -0700838sub get_maintainers {
Joe Perches683c6f82010-10-26 14:22:55 -0700839 %email_hash_name = ();
840 %email_hash_address = ();
841 %commit_author_hash = ();
842 %commit_signer_hash = ();
843 @email_to = ();
844 %hash_list_to = ();
845 @list_to = ();
846 @scm = ();
847 @web = ();
848 @subsystem = ();
849 @status = ();
Joe Perchesb9e23312010-10-26 14:22:58 -0700850 %deduplicate_name_hash = ();
851 %deduplicate_address_hash = ();
Joe Perches683c6f82010-10-26 14:22:55 -0700852 if ($email_git_all_signature_types) {
853 $signature_pattern = "(.+?)[Bb][Yy]:";
854 } else {
855 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
856 }
857
858 # Find responsible parties
859
Joe Perchesb9e23312010-10-26 14:22:58 -0700860 my %exact_pattern_match_hash = ();
Joe Perches6ef1c522010-10-26 14:22:56 -0700861
Joe Perches683c6f82010-10-26 14:22:55 -0700862 foreach my $file (@files) {
863
864 my %hash;
Joe Perches683c6f82010-10-26 14:22:55 -0700865 my $tvi = find_first_section();
866 while ($tvi < @typevalue) {
867 my $start = find_starting_index($tvi);
868 my $end = find_ending_index($tvi);
869 my $exclude = 0;
870 my $i;
871
872 #Do not match excluded file patterns
873
874 for ($i = $start; $i < $end; $i++) {
875 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700876 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches683c6f82010-10-26 14:22:55 -0700877 my $type = $1;
878 my $value = $2;
879 if ($type eq 'X') {
880 if (file_match_pattern($file, $value)) {
881 $exclude = 1;
882 last;
883 }
884 }
885 }
886 }
887
888 if (!$exclude) {
889 for ($i = $start; $i < $end; $i++) {
890 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700891 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches683c6f82010-10-26 14:22:55 -0700892 my $type = $1;
893 my $value = $2;
894 if ($type eq 'F') {
895 if (file_match_pattern($file, $value)) {
896 my $value_pd = ($value =~ tr@/@@);
897 my $file_pd = ($file =~ tr@/@@);
898 $value_pd++ if (substr($value,-1,1) ne "/");
899 $value_pd = -1 if ($value =~ /^\.\*/);
Joe Perchesab6c9372011-01-12 16:59:50 -0800900 if ($value_pd >= $file_pd &&
901 range_is_maintained($start, $end) &&
902 range_has_maintainer($start, $end)) {
Joe Perches6ef1c522010-10-26 14:22:56 -0700903 $exact_pattern_match_hash{$file} = 1;
904 }
Joe Perches683c6f82010-10-26 14:22:55 -0700905 if ($pattern_depth == 0 ||
906 (($file_pd - $value_pd) < $pattern_depth)) {
907 $hash{$tvi} = $value_pd;
908 }
909 }
Stephen Warrenbbbe96e2013-04-29 16:17:23 -0700910 } elsif ($type eq 'N') {
Stephen Warreneb90d082013-02-27 17:02:53 -0800911 if ($file =~ m/$value/x) {
912 $hash{$tvi} = 0;
913 }
Joe Perches683c6f82010-10-26 14:22:55 -0700914 }
915 }
916 }
917 }
918 $tvi = $end + 1;
919 }
920
921 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
922 add_categories($line);
923 if ($sections) {
924 my $i;
925 my $start = find_starting_index($line);
926 my $end = find_ending_index($line);
927 for ($i = $start; $i < $end; $i++) {
928 my $line = $typevalue[$i];
929 if ($line =~ /^[FX]:/) { ##Restore file patterns
930 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
931 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
932 $line =~ s/\\\./\./g; ##Convert \. to .
933 $line =~ s/\.\*/\*/g; ##Convert .* to *
934 }
Joe Perches03aed212016-12-12 16:45:59 -0800935 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
936 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
937 print("$line\n");
938 }
Joe Perches683c6f82010-10-26 14:22:55 -0700939 }
940 print("\n");
941 }
942 }
Joe Perches0c78c012020-06-04 16:50:01 -0700943
944 maintainers_in_file($file);
Joe Perches683c6f82010-10-26 14:22:55 -0700945 }
946
947 if ($keywords) {
948 @keyword_tvi = sort_and_uniq(@keyword_tvi);
949 foreach my $line (@keyword_tvi) {
950 add_categories($line);
951 }
952 }
953
Joe Perchesb9e23312010-10-26 14:22:58 -0700954 foreach my $email (@email_to, @list_to) {
955 $email->[0] = deduplicate_email($email->[0]);
956 }
Joe Perches6ef1c522010-10-26 14:22:56 -0700957
958 foreach my $file (@files) {
959 if ($email &&
Joe Perches6343f6b2020-10-15 20:10:37 -0700960 ($email_git ||
961 ($email_git_fallback &&
962 $file !~ /MAINTAINERS$/ &&
963 !$exact_pattern_match_hash{$file}))) {
Joe Perches6ef1c522010-10-26 14:22:56 -0700964 vcs_file_signoffs($file);
965 }
966 if ($email && $email_git_blame) {
967 vcs_file_blame($file);
968 }
969 }
970
Joe Perches683c6f82010-10-26 14:22:55 -0700971 if ($email) {
972 foreach my $chief (@penguin_chief) {
973 if ($chief =~ m/^(.*):(.*)/) {
974 my $email_address;
975
976 $email_address = format_email($1, $2, $email_usename);
977 if ($email_git_penguin_chiefs) {
978 push(@email_to, [$email_address, 'chief penguin']);
979 } else {
980 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
981 }
982 }
983 }
984
985 foreach my $email (@file_emails) {
986 my ($name, $address) = parse_email($email);
987
988 my $tmp_email = format_email($name, $address, $email_usename);
989 push_email_address($tmp_email, '');
990 add_role($tmp_email, 'in file');
991 }
992 }
993
Douglas Anderson0ef82fc2020-02-20 20:04:12 -0800994 foreach my $fix (@fixes) {
995 vcs_add_commit_signers($fix, "blamed_fixes");
996 }
997
Joe Perches683c6f82010-10-26 14:22:55 -0700998 my @to = ();
999 if ($email || $email_list) {
1000 if ($email) {
1001 @to = (@to, @email_to);
1002 }
1003 if ($email_list) {
1004 @to = (@to, @list_to);
1005 }
1006 }
1007
Joe Perches6ef1c522010-10-26 14:22:56 -07001008 if ($interactive) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001009 @to = interactive_get_maintainers(\@to);
Joe Perches6ef1c522010-10-26 14:22:56 -07001010 }
Joe Perches683c6f82010-10-26 14:22:55 -07001011
1012 return @to;
1013}
1014
Joe Perchescb7301c2009-04-07 20:40:12 -07001015sub file_match_pattern {
1016 my ($file, $pattern) = @_;
1017 if (substr($pattern, -1) eq "/") {
1018 if ($file =~ m@^$pattern@) {
1019 return 1;
1020 }
1021 } else {
1022 if ($file =~ m@^$pattern@) {
1023 my $s1 = ($file =~ tr@/@@);
1024 my $s2 = ($pattern =~ tr@/@@);
1025 if ($s1 == $s2) {
1026 return 1;
1027 }
1028 }
1029 }
1030 return 0;
1031}
1032
1033sub usage {
1034 print <<EOT;
1035usage: $P [options] patchfile
Joe Perches870020f2009-07-29 15:04:28 -07001036 $P [options] -f file|directory
Joe Perchescb7301c2009-04-07 20:40:12 -07001037version: $V
1038
1039MAINTAINER field selection options:
1040 --email => print email address(es) if any
1041 --git => include recent git \*-by: signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001042 --git-all-signature-types => include signers regardless of signature type
Joe Perches683c6f82010-10-26 14:22:55 -07001043 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
Joe Perchese3e9d112010-10-26 14:22:53 -07001044 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
Joe Perchescb7301c2009-04-07 20:40:12 -07001045 --git-chief-penguins => include ${penguin_chiefs}
Joe Perchese4d26b02010-05-24 14:33:17 -07001046 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1047 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1048 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
Joe Perchesf5492662009-09-21 17:04:13 -07001049 --git-blame => use git blame to find modified commits for patch or file
Brian Norris3cbcca82015-11-06 16:30:41 -08001050 --git-blame-signatures => when used with --git-blame, also include all commit signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001051 --git-since => git history to use (default: $email_git_since)
1052 --hg-since => hg history to use (default: $email_hg_since)
Florian Micklerdace8e32010-10-26 14:22:54 -07001053 --interactive => display a menu (mostly useful if used with the --git option)
Joe Perchescb7301c2009-04-07 20:40:12 -07001054 --m => include maintainer(s) if any
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001055 --r => include reviewer(s) if any
Joe Perchescb7301c2009-04-07 20:40:12 -07001056 --n => include name 'Full Name <addr\@domain.tld>'
1057 --l => include list(s) if any
Joe Perches49662502019-07-16 16:27:09 -07001058 --moderated => include moderated lists(s) if any (default: true)
1059 --s => include subscriber only list(s) if any (default: false)
Joe Perches11ecf532009-09-21 17:04:22 -07001060 --remove-duplicates => minimize duplicate email names/addresses
Joe Perches3c7385b2009-12-14 18:00:46 -08001061 --roles => show roles (status:subsystem, git-signer, list, etc...)
1062 --rolestats => show roles and statistics (commits/total_commits, %)
Joe Perches03372db2010-03-05 13:43:00 -08001063 --file-emails => add email addresses found in -f file (default: 0 (off))
Joe Perches2f5bd3432019-12-04 16:50:29 -08001064 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
Joe Perchescb7301c2009-04-07 20:40:12 -07001065 --scm => print SCM tree(s) if any
1066 --status => print status if any
1067 --subsystem => print subsystem name if any
1068 --web => print website(s) if any
1069
1070Output type options:
1071 --separator [, ] => separator for multiple entries on 1 line
Joe Perches42498312009-09-21 17:04:21 -07001072 using --separator also sets --nomultiline if --separator is not [, ]
Joe Perchescb7301c2009-04-07 20:40:12 -07001073 --multiline => print 1 entry per line
1074
Joe Perchescb7301c2009-04-07 20:40:12 -07001075Other options:
Joe Perches3fb55652009-09-21 17:04:17 -07001076 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
Joe Perchesb9e23312010-10-26 14:22:58 -07001077 --keywords => scan patch for keywords (default: $keywords)
1078 --sections => print all of the subsystem sections with pattern matches
Joe Perches03aed212016-12-12 16:45:59 -08001079 --letters => print all matching 'letter' types from all matching sections
Joe Perchesb9e23312010-10-26 14:22:58 -07001080 --mailmap => use .mailmap file (default: $email_use_mailmap)
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001081 --no-tree => run without a kernel tree
Tom Saegere1f75902017-11-17 15:27:42 -08001082 --self-test => show potential issues with MAINTAINERS file content
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001083 --version => show version
Joe Perchescb7301c2009-04-07 20:40:12 -07001084 --help => show this help information
1085
Joe Perches3fb55652009-09-21 17:04:17 -07001086Default options:
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001087 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1088 --pattern-depth=0 --remove-duplicates --rolestats]
Joe Perches3fb55652009-09-21 17:04:17 -07001089
Joe Perches870020f2009-07-29 15:04:28 -07001090Notes:
1091 Using "-f directory" may give unexpected results:
Joe Perchesf5492662009-09-21 17:04:13 -07001092 Used with "--git", git signators for _all_ files in and below
1093 directory are examined as git recurses directories.
1094 Any specified X: (exclude) pattern matches are _not_ ignored.
1095 Used with "--nogit", directory is used as a pattern match,
Joe Perches60db31a2009-12-14 18:00:50 -08001096 no individual file within the directory or subdirectory
1097 is matched.
Joe Perchesf5492662009-09-21 17:04:13 -07001098 Used with "--git-blame", does not iterate all files in directory
1099 Using "--git-blame" is slow and may add old committers and authors
1100 that are no longer active maintainers to the output.
Joe Perches3c7385b2009-12-14 18:00:46 -08001101 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1102 other automated tools that expect only ["name"] <email address>
1103 may not work because of additional output after <email address>.
1104 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1105 not the percentage of the entire file authored. # of commits is
1106 not a good measure of amount of code authored. 1 major commit may
1107 contain a thousand lines, 5 trivial commits may modify a single line.
Joe Perches60db31a2009-12-14 18:00:50 -08001108 If git is not installed, but mercurial (hg) is installed and an .hg
1109 repository exists, the following options apply to mercurial:
1110 --git,
1111 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1112 --git-blame
1113 Use --hg-since not --git-since to control date selection
Joe Perches368669d2010-05-24 14:33:19 -07001114 File ".get_maintainer.conf", if it exists in the linux kernel source root
1115 directory, can change whatever get_maintainer defaults are desired.
1116 Entries in this file can be any command line argument.
1117 This file is prepended to any additional command line arguments.
1118 Multiple lines and # comments are allowed.
Brian Norrisb1312bf2015-11-06 16:30:46 -08001119 Most options have both positive and negative forms.
1120 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1121
Joe Perchescb7301c2009-04-07 20:40:12 -07001122EOT
1123}
1124
1125sub top_of_kernel_tree {
Joe Perches47abc722010-10-26 14:22:57 -07001126 my ($lk_path) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07001127
Joe Perches47abc722010-10-26 14:22:57 -07001128 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1129 $lk_path .= "/";
1130 }
1131 if ( (-f "${lk_path}COPYING")
1132 && (-f "${lk_path}CREDITS")
1133 && (-f "${lk_path}Kbuild")
Joe Perches6f7d98e2017-08-04 21:45:48 -07001134 && (-e "${lk_path}MAINTAINERS")
Joe Perches47abc722010-10-26 14:22:57 -07001135 && (-f "${lk_path}Makefile")
1136 && (-f "${lk_path}README")
1137 && (-d "${lk_path}Documentation")
1138 && (-d "${lk_path}arch")
1139 && (-d "${lk_path}include")
1140 && (-d "${lk_path}drivers")
1141 && (-d "${lk_path}fs")
1142 && (-d "${lk_path}init")
1143 && (-d "${lk_path}ipc")
1144 && (-d "${lk_path}kernel")
1145 && (-d "${lk_path}lib")
1146 && (-d "${lk_path}scripts")) {
1147 return 1;
1148 }
1149 return 0;
Joe Perchescb7301c2009-04-07 20:40:12 -07001150}
1151
Joe Perches0e70e832009-09-21 17:04:20 -07001152sub parse_email {
1153 my ($formatted_email) = @_;
1154
1155 my $name = "";
1156 my $address = "";
1157
Joe Perches11ecf532009-09-21 17:04:22 -07001158 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001159 $name = $1;
1160 $address = $2;
Joe Perches11ecf532009-09-21 17:04:22 -07001161 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001162 $address = $1;
Joe Perchesb7816552009-09-21 17:04:24 -07001163 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001164 $address = $1;
1165 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001166
1167 $name =~ s/^\s+|\s+$//g;
Joe Perchesd7895042009-06-16 15:34:02 -07001168 $name =~ s/^\"|\"$//g;
Joe Perches0e70e832009-09-21 17:04:20 -07001169 $address =~ s/^\s+|\s+$//g;
Joe Perchescb7301c2009-04-07 20:40:12 -07001170
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001171 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perchescb7301c2009-04-07 20:40:12 -07001172 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
Joe Perches0e70e832009-09-21 17:04:20 -07001173 $name = "\"$name\"";
Joe Perchescb7301c2009-04-07 20:40:12 -07001174 }
Joe Perches0e70e832009-09-21 17:04:20 -07001175
1176 return ($name, $address);
1177}
1178
1179sub format_email {
Joe Perchesa8af2432009-12-14 18:00:49 -08001180 my ($name, $address, $usename) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001181
1182 my $formatted_email;
1183
1184 $name =~ s/^\s+|\s+$//g;
1185 $name =~ s/^\"|\"$//g;
1186 $address =~ s/^\s+|\s+$//g;
1187
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001188 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perches0e70e832009-09-21 17:04:20 -07001189 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1190 $name = "\"$name\"";
1191 }
1192
Joe Perchesa8af2432009-12-14 18:00:49 -08001193 if ($usename) {
Joe Perches0e70e832009-09-21 17:04:20 -07001194 if ("$name" eq "") {
1195 $formatted_email = "$address";
1196 } else {
Joe Perchesa8af2432009-12-14 18:00:49 -08001197 $formatted_email = "$name <$address>";
Joe Perches0e70e832009-09-21 17:04:20 -07001198 }
1199 } else {
1200 $formatted_email = $address;
1201 }
1202
Joe Perchescb7301c2009-04-07 20:40:12 -07001203 return $formatted_email;
1204}
1205
Joe Perches272a8972010-01-08 14:42:48 -08001206sub find_first_section {
1207 my $index = 0;
1208
1209 while ($index < @typevalue) {
1210 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001211 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perches272a8972010-01-08 14:42:48 -08001212 last;
1213 }
1214 $index++;
1215 }
1216
1217 return $index;
1218}
1219
Joe Perchesb7816552009-09-21 17:04:24 -07001220sub find_starting_index {
Joe Perchesb7816552009-09-21 17:04:24 -07001221 my ($index) = @_;
1222
1223 while ($index > 0) {
1224 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001225 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001226 last;
1227 }
1228 $index--;
1229 }
1230
1231 return $index;
1232}
1233
1234sub find_ending_index {
1235 my ($index) = @_;
1236
1237 while ($index < @typevalue) {
1238 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001239 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001240 last;
1241 }
1242 $index++;
1243 }
1244
1245 return $index;
1246}
1247
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001248sub get_subsystem_name {
1249 my ($index) = @_;
1250
1251 my $start = find_starting_index($index);
1252
1253 my $subsystem = $typevalue[$start];
1254 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1255 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1256 $subsystem =~ s/\s*$//;
1257 $subsystem = $subsystem . "...";
1258 }
1259 return $subsystem;
1260}
1261
Joe Perches3c7385b2009-12-14 18:00:46 -08001262sub get_maintainer_role {
1263 my ($index) = @_;
1264
1265 my $i;
1266 my $start = find_starting_index($index);
1267 my $end = find_ending_index($index);
1268
Joe Perches0ede2742012-03-23 15:01:56 -07001269 my $role = "unknown";
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001270 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001271
1272 for ($i = $start + 1; $i < $end; $i++) {
1273 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001274 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001275 my $ptype = $1;
1276 my $pvalue = $2;
1277 if ($ptype eq "S") {
1278 $role = $pvalue;
1279 }
1280 }
1281 }
1282
1283 $role = lc($role);
1284 if ($role eq "supported") {
1285 $role = "supporter";
1286 } elsif ($role eq "maintained") {
1287 $role = "maintainer";
1288 } elsif ($role eq "odd fixes") {
1289 $role = "odd fixer";
1290 } elsif ($role eq "orphan") {
1291 $role = "orphan minder";
1292 } elsif ($role eq "obsolete") {
1293 $role = "obsolete minder";
1294 } elsif ($role eq "buried alive in reporters") {
1295 $role = "chief penguin";
1296 }
1297
1298 return $role . ":" . $subsystem;
1299}
1300
1301sub get_list_role {
1302 my ($index) = @_;
1303
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001304 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001305
1306 if ($subsystem eq "THE REST") {
1307 $subsystem = "";
1308 }
1309
1310 return $subsystem;
1311}
1312
Joe Perchescb7301c2009-04-07 20:40:12 -07001313sub add_categories {
1314 my ($index) = @_;
1315
Joe Perchesb7816552009-09-21 17:04:24 -07001316 my $i;
1317 my $start = find_starting_index($index);
1318 my $end = find_ending_index($index);
1319
1320 push(@subsystem, $typevalue[$start]);
1321
1322 for ($i = $start + 1; $i < $end; $i++) {
1323 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001324 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001325 my $ptype = $1;
1326 my $pvalue = $2;
1327 if ($ptype eq "L") {
Joe Perches290603c2009-06-16 15:33:58 -07001328 my $list_address = $pvalue;
1329 my $list_additional = "";
Joe Perches3c7385b2009-12-14 18:00:46 -08001330 my $list_role = get_list_role($i);
1331
1332 if ($list_role ne "") {
1333 $list_role = ":" . $list_role;
1334 }
Joe Perches290603c2009-06-16 15:33:58 -07001335 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1336 $list_address = $1;
1337 $list_additional = $2;
1338 }
Joe Perchesbdf7c682009-06-16 15:33:59 -07001339 if ($list_additional =~ m/subscribers-only/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001340 if ($email_subscriber_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001341 if (!$hash_list_to{lc($list_address)}) {
1342 $hash_list_to{lc($list_address)} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001343 push(@list_to, [$list_address,
1344 "subscriber list${list_role}"]);
1345 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001346 }
1347 } else {
1348 if ($email_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001349 if (!$hash_list_to{lc($list_address)}) {
Richard Weinberger728f5a92012-03-23 15:01:56 -07001350 if ($list_additional =~ m/moderated/) {
Joe Perches49662502019-07-16 16:27:09 -07001351 if ($email_moderated_list) {
1352 $hash_list_to{lc($list_address)} = 1;
1353 push(@list_to, [$list_address,
1354 "moderated list${list_role}"]);
1355 }
Richard Weinberger728f5a92012-03-23 15:01:56 -07001356 } else {
Joe Perches49662502019-07-16 16:27:09 -07001357 $hash_list_to{lc($list_address)} = 1;
Richard Weinberger728f5a92012-03-23 15:01:56 -07001358 push(@list_to, [$list_address,
1359 "open list${list_role}"]);
1360 }
Joe Perches683c6f82010-10-26 14:22:55 -07001361 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001362 }
1363 }
1364 } elsif ($ptype eq "M") {
Joe Perches0e70e832009-09-21 17:04:20 -07001365 if ($email_maintainer) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001366 my $role = get_maintainer_role($i);
1367 push_email_addresses($pvalue, $role);
Joe Perchescb7301c2009-04-07 20:40:12 -07001368 }
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001369 } elsif ($ptype eq "R") {
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001370 if ($email_reviewer) {
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001371 my $subsystem = get_subsystem_name($i);
1372 push_email_addresses($pvalue, "reviewer:$subsystem");
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001373 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001374 } elsif ($ptype eq "T") {
1375 push(@scm, $pvalue);
1376 } elsif ($ptype eq "W") {
1377 push(@web, $pvalue);
1378 } elsif ($ptype eq "S") {
1379 push(@status, $pvalue);
1380 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001381 }
1382 }
1383}
1384
Joe Perches11ecf532009-09-21 17:04:22 -07001385sub email_inuse {
1386 my ($name, $address) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001387
Joe Perches11ecf532009-09-21 17:04:22 -07001388 return 1 if (($name eq "") && ($address eq ""));
Joe Perches6ef1c522010-10-26 14:22:56 -07001389 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1390 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
Joe Perches11ecf532009-09-21 17:04:22 -07001391
Joe Perches0e70e832009-09-21 17:04:20 -07001392 return 0;
1393}
1394
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001395sub push_email_address {
Joe Perches3c7385b2009-12-14 18:00:46 -08001396 my ($line, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001397
Joe Perches0e70e832009-09-21 17:04:20 -07001398 my ($name, $address) = parse_email($line);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001399
Joe Perchesb7816552009-09-21 17:04:24 -07001400 if ($address eq "") {
1401 return 0;
1402 }
1403
Joe Perches11ecf532009-09-21 17:04:22 -07001404 if (!$email_remove_duplicates) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001405 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perches11ecf532009-09-21 17:04:22 -07001406 } elsif (!email_inuse($name, $address)) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001407 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perchesfae99202010-10-26 14:22:58 -07001408 $email_hash_name{lc($name)}++ if ($name ne "");
Joe Perches6ef1c522010-10-26 14:22:56 -07001409 $email_hash_address{lc($address)}++;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001410 }
Joe Perchesb7816552009-09-21 17:04:24 -07001411
1412 return 1;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001413}
1414
1415sub push_email_addresses {
Joe Perches3c7385b2009-12-14 18:00:46 -08001416 my ($address, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001417
1418 my @address_list = ();
1419
Joe Perches5f2441e2009-06-16 15:34:02 -07001420 if (rfc822_valid($address)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001421 push_email_address($address, $role);
Joe Perches5f2441e2009-06-16 15:34:02 -07001422 } elsif (@address_list = rfc822_validlist($address)) {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001423 my $array_count = shift(@address_list);
1424 while (my $entry = shift(@address_list)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001425 push_email_address($entry, $role);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001426 }
Joe Perches5f2441e2009-06-16 15:34:02 -07001427 } else {
Joe Perches3c7385b2009-12-14 18:00:46 -08001428 if (!push_email_address($address, $role)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001429 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1430 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001431 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001432}
1433
Joe Perches3c7385b2009-12-14 18:00:46 -08001434sub add_role {
1435 my ($line, $role) = @_;
1436
1437 my ($name, $address) = parse_email($line);
Joe Perchesa8af2432009-12-14 18:00:49 -08001438 my $email = format_email($name, $address, $email_usename);
Joe Perches3c7385b2009-12-14 18:00:46 -08001439
1440 foreach my $entry (@email_to) {
1441 if ($email_remove_duplicates) {
1442 my ($entry_name, $entry_address) = parse_email($entry->[0]);
Joe Perches03372db2010-03-05 13:43:00 -08001443 if (($name eq $entry_name || $address eq $entry_address)
1444 && ($role eq "" || !($entry->[1] =~ m/$role/))
1445 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001446 if ($entry->[1] eq "") {
1447 $entry->[1] = "$role";
1448 } else {
1449 $entry->[1] = "$entry->[1],$role";
1450 }
1451 }
1452 } else {
Joe Perches03372db2010-03-05 13:43:00 -08001453 if ($email eq $entry->[0]
1454 && ($role eq "" || !($entry->[1] =~ m/$role/))
1455 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001456 if ($entry->[1] eq "") {
1457 $entry->[1] = "$role";
1458 } else {
1459 $entry->[1] = "$entry->[1],$role";
1460 }
1461 }
1462 }
1463 }
1464}
1465
Joe Perchescb7301c2009-04-07 20:40:12 -07001466sub which {
1467 my ($bin) = @_;
1468
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001469 foreach my $path (split(/:/, $ENV{PATH})) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001470 if (-e "$path/$bin") {
1471 return "$path/$bin";
1472 }
1473 }
1474
1475 return "";
1476}
1477
Joe Perchesbcde44e2010-10-26 14:22:53 -07001478sub which_conf {
1479 my ($conf) = @_;
1480
1481 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1482 if (-e "$path/$conf") {
1483 return "$path/$conf";
1484 }
1485 }
1486
1487 return "";
1488}
1489
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001490sub mailmap_email {
Joe Perchesb9e23312010-10-26 14:22:58 -07001491 my ($line) = @_;
Joe Perches8cbb3a72009-09-21 17:04:21 -07001492
Joe Perches47abc722010-10-26 14:22:57 -07001493 my ($name, $address) = parse_email($line);
1494 my $email = format_email($name, $address, 1);
1495 my $real_name = $name;
1496 my $real_address = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001497
Joe Perches47abc722010-10-26 14:22:57 -07001498 if (exists $mailmap->{names}->{$email} ||
1499 exists $mailmap->{addresses}->{$email}) {
1500 if (exists $mailmap->{names}->{$email}) {
1501 $real_name = $mailmap->{names}->{$email};
Joe Perches8cbb3a72009-09-21 17:04:21 -07001502 }
Joe Perches47abc722010-10-26 14:22:57 -07001503 if (exists $mailmap->{addresses}->{$email}) {
1504 $real_address = $mailmap->{addresses}->{$email};
1505 }
1506 } else {
1507 if (exists $mailmap->{names}->{$address}) {
1508 $real_name = $mailmap->{names}->{$address};
1509 }
1510 if (exists $mailmap->{addresses}->{$address}) {
1511 $real_address = $mailmap->{addresses}->{$address};
1512 }
1513 }
1514 return format_email($real_name, $real_address, 1);
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001515}
1516
1517sub mailmap {
1518 my (@addresses) = @_;
1519
Joe Perchesb9e23312010-10-26 14:22:58 -07001520 my @mapped_emails = ();
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001521 foreach my $line (@addresses) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001522 push(@mapped_emails, mailmap_email($line));
Joe Perches8cbb3a72009-09-21 17:04:21 -07001523 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001524 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1525 return @mapped_emails;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001526}
1527
1528sub merge_by_realname {
Joe Perches47abc722010-10-26 14:22:57 -07001529 my %address_map;
1530 my (@emails) = @_;
Joe Perchesb9e23312010-10-26 14:22:58 -07001531
Joe Perches47abc722010-10-26 14:22:57 -07001532 foreach my $email (@emails) {
1533 my ($name, $address) = parse_email($email);
Joe Perchesb9e23312010-10-26 14:22:58 -07001534 if (exists $address_map{$name}) {
Joe Perches47abc722010-10-26 14:22:57 -07001535 $address = $address_map{$name};
Joe Perchesb9e23312010-10-26 14:22:58 -07001536 $email = format_email($name, $address, 1);
1537 } else {
1538 $address_map{$name} = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001539 }
Joe Perches47abc722010-10-26 14:22:57 -07001540 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07001541}
1542
Joe Perches60db31a2009-12-14 18:00:50 -08001543sub git_execute_cmd {
1544 my ($cmd) = @_;
1545 my @lines = ();
Joe Perchescb7301c2009-04-07 20:40:12 -07001546
Joe Perches60db31a2009-12-14 18:00:50 -08001547 my $output = `$cmd`;
1548 $output =~ s/^\s*//gm;
1549 @lines = split("\n", $output);
1550
1551 return @lines;
Joe Perchesa8af2432009-12-14 18:00:49 -08001552}
1553
Joe Perches60db31a2009-12-14 18:00:50 -08001554sub hg_execute_cmd {
Joe Perchesa8af2432009-12-14 18:00:49 -08001555 my ($cmd) = @_;
Joe Perches60db31a2009-12-14 18:00:50 -08001556 my @lines = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001557
Joe Perches60db31a2009-12-14 18:00:50 -08001558 my $output = `$cmd`;
1559 @lines = split("\n", $output);
1560
1561 return @lines;
1562}
1563
Joe Perches683c6f82010-10-26 14:22:55 -07001564sub extract_formatted_signatures {
1565 my (@signature_lines) = @_;
1566
1567 my @type = @signature_lines;
1568
1569 s/\s*(.*):.*/$1/ for (@type);
1570
1571 # cut -f2- -d":"
1572 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1573
1574## Reformat email addresses (with names) to avoid badly written signatures
1575
1576 foreach my $signer (@signature_lines) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001577 $signer = deduplicate_email($signer);
Joe Perches683c6f82010-10-26 14:22:55 -07001578 }
1579
1580 return (\@type, \@signature_lines);
1581}
1582
Joe Perches60db31a2009-12-14 18:00:50 -08001583sub vcs_find_signers {
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001584 my ($cmd, $file) = @_;
Joe Perchesa8af2432009-12-14 18:00:49 -08001585 my $commits;
Joe Perches683c6f82010-10-26 14:22:55 -07001586 my @lines = ();
1587 my @signatures = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001588 my @authors = ();
1589 my @stats = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001590
Joe Perches60db31a2009-12-14 18:00:50 -08001591 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
Joe Perchescb7301c2009-04-07 20:40:12 -07001592
Joe Perches60db31a2009-12-14 18:00:50 -08001593 my $pattern = $VCS_cmds{"commit_pattern"};
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001594 my $author_pattern = $VCS_cmds{"author_pattern"};
1595 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1596
1597 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
Joe Perchescb7301c2009-04-07 20:40:12 -07001598
Joe Perches60db31a2009-12-14 18:00:50 -08001599 $commits = grep(/$pattern/, @lines); # of commits
Joe Perchesafa81ee2009-07-29 15:04:28 -07001600
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001601 @authors = grep(/$author_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001602 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001603 @stats = grep(/$stat_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001604
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001605# print("stats: <@stats>\n");
1606
1607 return (0, \@signatures, \@authors, \@stats) if !@signatures;
Joe Perches683c6f82010-10-26 14:22:55 -07001608
1609 save_commits_by_author(@lines) if ($interactive);
1610 save_commits_by_signer(@lines) if ($interactive);
1611
Joe Perches0e70e832009-09-21 17:04:20 -07001612 if (!$email_git_penguin_chiefs) {
Joe Perches683c6f82010-10-26 14:22:55 -07001613 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
Joe Perches0e70e832009-09-21 17:04:20 -07001614 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001615
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001616 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
Joe Perches683c6f82010-10-26 14:22:55 -07001617 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
Joe Perches63ab52d2010-10-26 14:22:51 -07001618
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001619 return ($commits, $signers_ref, $authors_ref, \@stats);
Joe Perchesa8af2432009-12-14 18:00:49 -08001620}
1621
Joe Perches63ab52d2010-10-26 14:22:51 -07001622sub vcs_find_author {
1623 my ($cmd) = @_;
1624 my @lines = ();
1625
1626 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1627
1628 if (!$email_git_penguin_chiefs) {
1629 @lines = grep(!/${penguin_chiefs}/i, @lines);
1630 }
1631
1632 return @lines if !@lines;
1633
Joe Perches683c6f82010-10-26 14:22:55 -07001634 my @authors = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07001635 foreach my $line (@lines) {
Joe Perches683c6f82010-10-26 14:22:55 -07001636 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1637 my $author = $1;
1638 my ($name, $address) = parse_email($author);
1639 $author = format_email($name, $address, 1);
1640 push(@authors, $author);
1641 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001642 }
1643
Joe Perches683c6f82010-10-26 14:22:55 -07001644 save_commits_by_author(@lines) if ($interactive);
1645 save_commits_by_signer(@lines) if ($interactive);
1646
1647 return @authors;
Joe Perches63ab52d2010-10-26 14:22:51 -07001648}
1649
Joe Perches60db31a2009-12-14 18:00:50 -08001650sub vcs_save_commits {
1651 my ($cmd) = @_;
1652 my @lines = ();
1653 my @commits = ();
1654
1655 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1656
1657 foreach my $line (@lines) {
1658 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1659 push(@commits, $1);
1660 }
1661 }
1662
1663 return @commits;
1664}
1665
1666sub vcs_blame {
1667 my ($file) = @_;
1668 my $cmd;
1669 my @commits = ();
1670
1671 return @commits if (!(-f $file));
1672
1673 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1674 my @all_commits = ();
1675
1676 $cmd = $VCS_cmds{"blame_file_cmd"};
1677 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1678 @all_commits = vcs_save_commits($cmd);
1679
1680 foreach my $file_range_diff (@range) {
1681 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1682 my $diff_file = $1;
1683 my $diff_start = $2;
1684 my $diff_length = $3;
1685 next if ("$file" ne "$diff_file");
1686 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1687 push(@commits, $all_commits[$i]);
1688 }
1689 }
1690 } elsif (@range) {
1691 foreach my $file_range_diff (@range) {
1692 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1693 my $diff_file = $1;
1694 my $diff_start = $2;
1695 my $diff_length = $3;
1696 next if ("$file" ne "$diff_file");
1697 $cmd = $VCS_cmds{"blame_range_cmd"};
1698 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1699 push(@commits, vcs_save_commits($cmd));
1700 }
1701 } else {
1702 $cmd = $VCS_cmds{"blame_file_cmd"};
1703 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1704 @commits = vcs_save_commits($cmd);
1705 }
1706
Joe Perches63ab52d2010-10-26 14:22:51 -07001707 foreach my $commit (@commits) {
1708 $commit =~ s/^\^//g;
1709 }
1710
Joe Perches60db31a2009-12-14 18:00:50 -08001711 return @commits;
1712}
1713
1714my $printed_novcs = 0;
1715sub vcs_exists {
1716 %VCS_cmds = %VCS_cmds_git;
1717 return 1 if eval $VCS_cmds{"available"};
1718 %VCS_cmds = %VCS_cmds_hg;
Joe Perches683c6f82010-10-26 14:22:55 -07001719 return 2 if eval $VCS_cmds{"available"};
Joe Perches60db31a2009-12-14 18:00:50 -08001720 %VCS_cmds = ();
1721 if (!$printed_novcs) {
1722 warn("$P: No supported VCS found. Add --nogit to options?\n");
1723 warn("Using a git repository produces better results.\n");
1724 warn("Try Linus Torvalds' latest git repository using:\n");
Ralf Thielow3d1c2f72011-08-25 15:59:07 -07001725 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
Joe Perches60db31a2009-12-14 18:00:50 -08001726 $printed_novcs = 1;
1727 }
1728 return 0;
1729}
1730
Joe Perches683c6f82010-10-26 14:22:55 -07001731sub vcs_is_git {
Joe Perchesb9e23312010-10-26 14:22:58 -07001732 vcs_exists();
Joe Perches683c6f82010-10-26 14:22:55 -07001733 return $vcs_used == 1;
1734}
1735
1736sub vcs_is_hg {
1737 return $vcs_used == 2;
1738}
1739
Joe Perches2f5bd3432019-12-04 16:50:29 -08001740sub vcs_add_commit_signers {
1741 return if (!vcs_exists());
1742
1743 my ($commit, $desc) = @_;
1744 my $commit_count = 0;
1745 my $commit_authors_ref;
1746 my $commit_signers_ref;
1747 my $stats_ref;
1748 my @commit_authors = ();
1749 my @commit_signers = ();
1750 my $cmd;
1751
1752 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1753 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1754
1755 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1756 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1757 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1758
1759 foreach my $signer (@commit_signers) {
1760 $signer = deduplicate_email($signer);
1761 }
1762
1763 vcs_assign($desc, 1, @commit_signers);
1764}
1765
Joe Perches6ef1c522010-10-26 14:22:56 -07001766sub interactive_get_maintainers {
Joe Perches683c6f82010-10-26 14:22:55 -07001767 my ($list_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07001768 my @list = @$list_ref;
1769
Joe Perches683c6f82010-10-26 14:22:55 -07001770 vcs_exists();
Florian Micklerdace8e32010-10-26 14:22:54 -07001771
1772 my %selected;
Joe Perches683c6f82010-10-26 14:22:55 -07001773 my %authored;
1774 my %signed;
Florian Micklerdace8e32010-10-26 14:22:54 -07001775 my $count = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001776 my $maintained = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001777 foreach my $entry (@list) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001778 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1779 $selected{$count} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001780 $authored{$count} = 0;
1781 $signed{$count} = 0;
1782 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001783 }
1784
1785 #menu loop
Joe Perches683c6f82010-10-26 14:22:55 -07001786 my $done = 0;
1787 my $print_options = 0;
1788 my $redraw = 1;
1789 while (!$done) {
1790 $count = 0;
1791 if ($redraw) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001792 printf STDERR "\n%1s %2s %-65s",
1793 "*", "#", "email/list and role:stats";
1794 if ($email_git ||
1795 ($email_git_fallback && !$maintained) ||
1796 $email_git_blame) {
1797 print STDERR "auth sign";
1798 }
1799 print STDERR "\n";
Joe Perches683c6f82010-10-26 14:22:55 -07001800 foreach my $entry (@list) {
1801 my $email = $entry->[0];
1802 my $role = $entry->[1];
1803 my $sel = "";
1804 $sel = "*" if ($selected{$count});
1805 my $commit_author = $commit_author_hash{$email};
1806 my $commit_signer = $commit_signer_hash{$email};
1807 my $authored = 0;
1808 my $signed = 0;
1809 $authored++ for (@{$commit_author});
1810 $signed++ for (@{$commit_signer});
1811 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1812 printf STDERR "%4d %4d", $authored, $signed
1813 if ($authored > 0 || $signed > 0);
1814 printf STDERR "\n %s\n", $role;
1815 if ($authored{$count}) {
1816 my $commit_author = $commit_author_hash{$email};
1817 foreach my $ref (@{$commit_author}) {
1818 print STDERR " Author: @{$ref}[1]\n";
Florian Micklerdace8e32010-10-26 14:22:54 -07001819 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001820 }
Joe Perches683c6f82010-10-26 14:22:55 -07001821 if ($signed{$count}) {
1822 my $commit_signer = $commit_signer_hash{$email};
1823 foreach my $ref (@{$commit_signer}) {
1824 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1825 }
1826 }
1827
1828 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001829 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001830 }
Joe Perches683c6f82010-10-26 14:22:55 -07001831 my $date_ref = \$email_git_since;
1832 $date_ref = \$email_hg_since if (vcs_is_hg());
1833 if ($print_options) {
1834 $print_options = 0;
1835 if (vcs_exists()) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001836 print STDERR <<EOT
1837
1838Version Control options:
1839g use git history [$email_git]
1840gf use git-fallback [$email_git_fallback]
1841b use git blame [$email_git_blame]
1842bs use blame signatures [$email_git_blame_signatures]
1843c# minimum commits [$email_git_min_signatures]
1844%# min percent [$email_git_min_percent]
1845d# history to use [$$date_ref]
1846x# max maintainers [$email_git_max_maintainers]
1847t all signature types [$email_git_all_signature_types]
1848m use .mailmap [$email_use_mailmap]
1849EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001850 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001851 print STDERR <<EOT
1852
1853Additional options:
18540 toggle all
1855tm toggle maintainers
1856tg toggle git entries
1857tl toggle open list entries
1858ts toggle subscriber list entries
Joe Perches0c78c012020-06-04 16:50:01 -07001859f emails in file [$email_file_emails]
Joe Perchesb9e23312010-10-26 14:22:58 -07001860k keywords in file [$keywords]
1861r remove duplicates [$email_remove_duplicates]
1862p# pattern match depth [$pattern_depth]
1863EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001864 }
1865 print STDERR
1866"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1867
1868 my $input = <STDIN>;
Florian Micklerdace8e32010-10-26 14:22:54 -07001869 chomp($input);
1870
Joe Perches683c6f82010-10-26 14:22:55 -07001871 $redraw = 1;
1872 my $rerun = 0;
1873 my @wish = split(/[, ]+/, $input);
1874 foreach my $nr (@wish) {
1875 $nr = lc($nr);
1876 my $sel = substr($nr, 0, 1);
1877 my $str = substr($nr, 1);
1878 my $val = 0;
1879 $val = $1 if $str =~ /^(\d+)$/;
1880
1881 if ($sel eq "y") {
1882 $interactive = 0;
1883 $done = 1;
1884 $output_rolestats = 0;
1885 $output_roles = 0;
1886 last;
1887 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1888 $selected{$nr - 1} = !$selected{$nr - 1};
1889 } elsif ($sel eq "*" || $sel eq '^') {
1890 my $toggle = 0;
1891 $toggle = 1 if ($sel eq '*');
1892 for (my $i = 0; $i < $count; $i++) {
1893 $selected{$i} = $toggle;
Florian Micklerdace8e32010-10-26 14:22:54 -07001894 }
Joe Perches683c6f82010-10-26 14:22:55 -07001895 } elsif ($sel eq "0") {
1896 for (my $i = 0; $i < $count; $i++) {
1897 $selected{$i} = !$selected{$i};
1898 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001899 } elsif ($sel eq "t") {
1900 if (lc($str) eq "m") {
1901 for (my $i = 0; $i < $count; $i++) {
1902 $selected{$i} = !$selected{$i}
1903 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1904 }
1905 } elsif (lc($str) eq "g") {
1906 for (my $i = 0; $i < $count; $i++) {
1907 $selected{$i} = !$selected{$i}
1908 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1909 }
1910 } elsif (lc($str) eq "l") {
1911 for (my $i = 0; $i < $count; $i++) {
1912 $selected{$i} = !$selected{$i}
1913 if ($list[$i]->[1] =~ /^(open list)/i);
1914 }
1915 } elsif (lc($str) eq "s") {
1916 for (my $i = 0; $i < $count; $i++) {
1917 $selected{$i} = !$selected{$i}
1918 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1919 }
1920 }
Joe Perches683c6f82010-10-26 14:22:55 -07001921 } elsif ($sel eq "a") {
1922 if ($val > 0 && $val <= $count) {
1923 $authored{$val - 1} = !$authored{$val - 1};
1924 } elsif ($str eq '*' || $str eq '^') {
1925 my $toggle = 0;
1926 $toggle = 1 if ($str eq '*');
1927 for (my $i = 0; $i < $count; $i++) {
1928 $authored{$i} = $toggle;
1929 }
1930 }
1931 } elsif ($sel eq "s") {
1932 if ($val > 0 && $val <= $count) {
1933 $signed{$val - 1} = !$signed{$val - 1};
1934 } elsif ($str eq '*' || $str eq '^') {
1935 my $toggle = 0;
1936 $toggle = 1 if ($str eq '*');
1937 for (my $i = 0; $i < $count; $i++) {
1938 $signed{$i} = $toggle;
1939 }
1940 }
1941 } elsif ($sel eq "o") {
1942 $print_options = 1;
1943 $redraw = 1;
1944 } elsif ($sel eq "g") {
1945 if ($str eq "f") {
1946 bool_invert(\$email_git_fallback);
Florian Micklerdace8e32010-10-26 14:22:54 -07001947 } else {
Joe Perches683c6f82010-10-26 14:22:55 -07001948 bool_invert(\$email_git);
Florian Micklerdace8e32010-10-26 14:22:54 -07001949 }
Joe Perches683c6f82010-10-26 14:22:55 -07001950 $rerun = 1;
1951 } elsif ($sel eq "b") {
1952 if ($str eq "s") {
1953 bool_invert(\$email_git_blame_signatures);
1954 } else {
1955 bool_invert(\$email_git_blame);
1956 }
1957 $rerun = 1;
1958 } elsif ($sel eq "c") {
1959 if ($val > 0) {
1960 $email_git_min_signatures = $val;
1961 $rerun = 1;
1962 }
1963 } elsif ($sel eq "x") {
1964 if ($val > 0) {
1965 $email_git_max_maintainers = $val;
1966 $rerun = 1;
1967 }
1968 } elsif ($sel eq "%") {
1969 if ($str ne "" && $val >= 0) {
1970 $email_git_min_percent = $val;
1971 $rerun = 1;
1972 }
1973 } elsif ($sel eq "d") {
1974 if (vcs_is_git()) {
1975 $email_git_since = $str;
1976 } elsif (vcs_is_hg()) {
1977 $email_hg_since = $str;
1978 }
1979 $rerun = 1;
1980 } elsif ($sel eq "t") {
1981 bool_invert(\$email_git_all_signature_types);
1982 $rerun = 1;
1983 } elsif ($sel eq "f") {
Joe Perches0c78c012020-06-04 16:50:01 -07001984 bool_invert(\$email_file_emails);
Joe Perches683c6f82010-10-26 14:22:55 -07001985 $rerun = 1;
1986 } elsif ($sel eq "r") {
1987 bool_invert(\$email_remove_duplicates);
1988 $rerun = 1;
Joe Perchesb9e23312010-10-26 14:22:58 -07001989 } elsif ($sel eq "m") {
1990 bool_invert(\$email_use_mailmap);
1991 read_mailmap();
1992 $rerun = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001993 } elsif ($sel eq "k") {
1994 bool_invert(\$keywords);
1995 $rerun = 1;
1996 } elsif ($sel eq "p") {
1997 if ($str ne "" && $val >= 0) {
1998 $pattern_depth = $val;
1999 $rerun = 1;
2000 }
Joe Perches6ef1c522010-10-26 14:22:56 -07002001 } elsif ($sel eq "h" || $sel eq "?") {
2002 print STDERR <<EOT
2003
2004Interactive mode allows you to select the various maintainers, submitters,
2005commit signers and mailing lists that could be CC'd on a patch.
2006
2007Any *'d entry is selected.
2008
Joe Perches47abc722010-10-26 14:22:57 -07002009If you have git or hg installed, you can choose to summarize the commit
Joe Perches6ef1c522010-10-26 14:22:56 -07002010history of files in the patch. Also, each line of the current file can
2011be matched to its commit author and that commits signers with blame.
2012
2013Various knobs exist to control the length of time for active commit
2014tracking, the maximum number of commit authors and signers to add,
2015and such.
2016
2017Enter selections at the prompt until you are satisfied that the selected
2018maintainers are appropriate. You may enter multiple selections separated
2019by either commas or spaces.
2020
2021EOT
Joe Perches683c6f82010-10-26 14:22:55 -07002022 } else {
2023 print STDERR "invalid option: '$nr'\n";
2024 $redraw = 0;
2025 }
2026 }
2027 if ($rerun) {
2028 print STDERR "git-blame can be very slow, please have patience..."
2029 if ($email_git_blame);
Joe Perches6ef1c522010-10-26 14:22:56 -07002030 goto &get_maintainers;
Joe Perches683c6f82010-10-26 14:22:55 -07002031 }
2032 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002033
2034 #drop not selected entries
2035 $count = 0;
Joe Perches683c6f82010-10-26 14:22:55 -07002036 my @new_emailto = ();
2037 foreach my $entry (@list) {
2038 if ($selected{$count}) {
2039 push(@new_emailto, $list[$count]);
Florian Micklerdace8e32010-10-26 14:22:54 -07002040 }
2041 $count++;
2042 }
Joe Perches683c6f82010-10-26 14:22:55 -07002043 return @new_emailto;
Florian Micklerdace8e32010-10-26 14:22:54 -07002044}
2045
Joe Perches683c6f82010-10-26 14:22:55 -07002046sub bool_invert {
2047 my ($bool_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002048
Joe Perches683c6f82010-10-26 14:22:55 -07002049 if ($$bool_ref) {
2050 $$bool_ref = 0;
2051 } else {
2052 $$bool_ref = 1;
Florian Micklerdace8e32010-10-26 14:22:54 -07002053 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002054}
2055
Joe Perchesb9e23312010-10-26 14:22:58 -07002056sub deduplicate_email {
2057 my ($email) = @_;
2058
2059 my $matched = 0;
2060 my ($name, $address) = parse_email($email);
2061 $email = format_email($name, $address, 1);
2062 $email = mailmap_email($email);
2063
2064 return $email if (!$email_remove_duplicates);
2065
2066 ($name, $address) = parse_email($email);
2067
Joe Perchesfae99202010-10-26 14:22:58 -07002068 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002069 $name = $deduplicate_name_hash{lc($name)}->[0];
2070 $address = $deduplicate_name_hash{lc($name)}->[1];
2071 $matched = 1;
2072 } elsif ($deduplicate_address_hash{lc($address)}) {
2073 $name = $deduplicate_address_hash{lc($address)}->[0];
2074 $address = $deduplicate_address_hash{lc($address)}->[1];
2075 $matched = 1;
2076 }
2077 if (!$matched) {
2078 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2079 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2080 }
2081 $email = format_email($name, $address, 1);
2082 $email = mailmap_email($email);
2083 return $email;
2084}
2085
Joe Perches683c6f82010-10-26 14:22:55 -07002086sub save_commits_by_author {
2087 my (@lines) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002088
Joe Perches683c6f82010-10-26 14:22:55 -07002089 my @authors = ();
2090 my @commits = ();
2091 my @subjects = ();
2092
2093 foreach my $line (@lines) {
2094 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2095 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002096 $author = deduplicate_email($author);
Joe Perches683c6f82010-10-26 14:22:55 -07002097 push(@authors, $author);
2098 }
2099 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2100 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2101 }
2102
2103 for (my $i = 0; $i < @authors; $i++) {
2104 my $exists = 0;
2105 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2106 if (@{$ref}[0] eq $commits[$i] &&
2107 @{$ref}[1] eq $subjects[$i]) {
2108 $exists = 1;
2109 last;
2110 }
2111 }
2112 if (!$exists) {
2113 push(@{$commit_author_hash{$authors[$i]}},
2114 [ ($commits[$i], $subjects[$i]) ]);
2115 }
2116 }
2117}
2118
2119sub save_commits_by_signer {
2120 my (@lines) = @_;
2121
2122 my $commit = "";
2123 my $subject = "";
2124
2125 foreach my $line (@lines) {
2126 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2127 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2128 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2129 my @signatures = ($line);
2130 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2131 my @types = @$types_ref;
2132 my @signers = @$signers_ref;
2133
2134 my $type = $types[0];
2135 my $signer = $signers[0];
2136
Joe Perchesb9e23312010-10-26 14:22:58 -07002137 $signer = deduplicate_email($signer);
Joe Perches6ef1c522010-10-26 14:22:56 -07002138
Joe Perches683c6f82010-10-26 14:22:55 -07002139 my $exists = 0;
2140 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2141 if (@{$ref}[0] eq $commit &&
2142 @{$ref}[1] eq $subject &&
2143 @{$ref}[2] eq $type) {
2144 $exists = 1;
2145 last;
2146 }
2147 }
2148 if (!$exists) {
2149 push(@{$commit_signer_hash{$signer}},
2150 [ ($commit, $subject, $type) ]);
2151 }
2152 }
2153 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002154}
2155
Joe Perches60db31a2009-12-14 18:00:50 -08002156sub vcs_assign {
Joe Perchesa8af2432009-12-14 18:00:49 -08002157 my ($role, $divisor, @lines) = @_;
2158
2159 my %hash;
2160 my $count = 0;
2161
Joe Perchesa8af2432009-12-14 18:00:49 -08002162 return if (@lines <= 0);
2163
2164 if ($divisor <= 0) {
Joe Perches60db31a2009-12-14 18:00:50 -08002165 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
Joe Perchesa8af2432009-12-14 18:00:49 -08002166 $divisor = 1;
Joe Perches3c7385b2009-12-14 18:00:46 -08002167 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07002168
Florian Mickler7fa8ff22010-10-26 14:22:56 -07002169 @lines = mailmap(@lines);
Joe Perches0e70e832009-09-21 17:04:20 -07002170
Joe Perches63ab52d2010-10-26 14:22:51 -07002171 return if (@lines <= 0);
2172
Joe Perches0e70e832009-09-21 17:04:20 -07002173 @lines = sort(@lines);
Joe Perchesafa81ee2009-07-29 15:04:28 -07002174
Joe Perches11ecf532009-09-21 17:04:22 -07002175 # uniq -c
2176 $hash{$_}++ for @lines;
2177
2178 # sort -rn
2179 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2180 my $sign_offs = $hash{$line};
Joe Perchesa8af2432009-12-14 18:00:49 -08002181 my $percent = $sign_offs * 100 / $divisor;
Joe Perches3c7385b2009-12-14 18:00:46 -08002182
Joe Perchesa8af2432009-12-14 18:00:49 -08002183 $percent = 100 if ($percent > 100);
Joe Perches435de072015-06-25 15:01:50 -07002184 next if (ignore_email_address($line));
Joe Perches11ecf532009-09-21 17:04:22 -07002185 $count++;
2186 last if ($sign_offs < $email_git_min_signatures ||
2187 $count > $email_git_max_maintainers ||
Joe Perchesa8af2432009-12-14 18:00:49 -08002188 $percent < $email_git_min_percent);
Joe Perches3c7385b2009-12-14 18:00:46 -08002189 push_email_address($line, '');
Joe Perches3c7385b2009-12-14 18:00:46 -08002190 if ($output_rolestats) {
Joe Perchesa8af2432009-12-14 18:00:49 -08002191 my $fmt_percent = sprintf("%.0f", $percent);
2192 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2193 } else {
2194 add_role($line, $role);
Joe Perches3c7385b2009-12-14 18:00:46 -08002195 }
Joe Perchesf5492662009-09-21 17:04:13 -07002196 }
2197}
2198
Joe Perches60db31a2009-12-14 18:00:50 -08002199sub vcs_file_signoffs {
Joe Perchesa8af2432009-12-14 18:00:49 -08002200 my ($file) = @_;
2201
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002202 my $authors_ref;
2203 my $signers_ref;
2204 my $stats_ref;
2205 my @authors = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002206 my @signers = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002207 my @stats = ();
Joe Perches60db31a2009-12-14 18:00:50 -08002208 my $commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002209
Joe Perches683c6f82010-10-26 14:22:55 -07002210 $vcs_used = vcs_exists();
2211 return if (!$vcs_used);
Joe Perchesa8af2432009-12-14 18:00:49 -08002212
Joe Perches60db31a2009-12-14 18:00:50 -08002213 my $cmd = $VCS_cmds{"find_signers_cmd"};
2214 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2215
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002216 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2217
2218 @signers = @{$signers_ref} if defined $signers_ref;
2219 @authors = @{$authors_ref} if defined $authors_ref;
2220 @stats = @{$stats_ref} if defined $stats_ref;
2221
2222# print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
Joe Perchesb9e23312010-10-26 14:22:58 -07002223
2224 foreach my $signer (@signers) {
2225 $signer = deduplicate_email($signer);
2226 }
2227
Joe Perches60db31a2009-12-14 18:00:50 -08002228 vcs_assign("commit_signer", $commits, @signers);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002229 vcs_assign("authored", $commits, @authors);
2230 if ($#authors == $#stats) {
2231 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2232 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2233
2234 my $added = 0;
2235 my $deleted = 0;
2236 for (my $i = 0; $i <= $#stats; $i++) {
2237 if ($stats[$i] =~ /$stat_pattern/) {
2238 $added += $1;
2239 $deleted += $2;
2240 }
2241 }
2242 my @tmp_authors = uniq(@authors);
2243 foreach my $author (@tmp_authors) {
2244 $author = deduplicate_email($author);
2245 }
2246 @tmp_authors = uniq(@tmp_authors);
2247 my @list_added = ();
2248 my @list_deleted = ();
2249 foreach my $author (@tmp_authors) {
2250 my $auth_added = 0;
2251 my $auth_deleted = 0;
2252 for (my $i = 0; $i <= $#stats; $i++) {
2253 if ($author eq deduplicate_email($authors[$i]) &&
2254 $stats[$i] =~ /$stat_pattern/) {
2255 $auth_added += $1;
2256 $auth_deleted += $2;
2257 }
2258 }
2259 for (my $i = 0; $i < $auth_added; $i++) {
2260 push(@list_added, $author);
2261 }
2262 for (my $i = 0; $i < $auth_deleted; $i++) {
2263 push(@list_deleted, $author);
2264 }
2265 }
2266 vcs_assign("added_lines", $added, @list_added);
2267 vcs_assign("removed_lines", $deleted, @list_deleted);
2268 }
Joe Perchesa8af2432009-12-14 18:00:49 -08002269}
2270
Joe Perches60db31a2009-12-14 18:00:50 -08002271sub vcs_file_blame {
Joe Perchesf5492662009-09-21 17:04:13 -07002272 my ($file) = @_;
2273
Joe Perches60db31a2009-12-14 18:00:50 -08002274 my @signers = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07002275 my @all_commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002276 my @commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002277 my $total_commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002278 my $total_lines;
Joe Perchesf5492662009-09-21 17:04:13 -07002279
Joe Perches683c6f82010-10-26 14:22:55 -07002280 $vcs_used = vcs_exists();
2281 return if (!$vcs_used);
Joe Perchesf5492662009-09-21 17:04:13 -07002282
Joe Perches63ab52d2010-10-26 14:22:51 -07002283 @all_commits = vcs_blame($file);
2284 @commits = uniq(@all_commits);
Joe Perchesa8af2432009-12-14 18:00:49 -08002285 $total_commits = @commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002286 $total_lines = @all_commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002287
Joe Perches683c6f82010-10-26 14:22:55 -07002288 if ($email_git_blame_signatures) {
2289 if (vcs_is_hg()) {
2290 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002291 my $commit_authors_ref;
2292 my $commit_signers_ref;
2293 my $stats_ref;
2294 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002295 my @commit_signers = ();
2296 my $commit = join(" -r ", @commits);
2297 my $cmd;
Joe Perchesf5492662009-09-21 17:04:13 -07002298
Joe Perches683c6f82010-10-26 14:22:55 -07002299 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2300 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
Joe Perches60db31a2009-12-14 18:00:50 -08002301
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002302 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2303 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2304 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches63ab52d2010-10-26 14:22:51 -07002305
Joe Perches683c6f82010-10-26 14:22:55 -07002306 push(@signers, @commit_signers);
2307 } else {
2308 foreach my $commit (@commits) {
2309 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002310 my $commit_authors_ref;
2311 my $commit_signers_ref;
2312 my $stats_ref;
2313 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002314 my @commit_signers = ();
2315 my $cmd;
2316
2317 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2318 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2319
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002320 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2321 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2322 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches683c6f82010-10-26 14:22:55 -07002323
2324 push(@signers, @commit_signers);
2325 }
2326 }
Joe Perchesf5492662009-09-21 17:04:13 -07002327 }
2328
Joe Perchesa8af2432009-12-14 18:00:49 -08002329 if ($from_filename) {
Joe Perches63ab52d2010-10-26 14:22:51 -07002330 if ($output_rolestats) {
2331 my @blame_signers;
Joe Perches683c6f82010-10-26 14:22:55 -07002332 if (vcs_is_hg()) {{ # Double brace for last exit
2333 my $commit_count;
2334 my @commit_signers = ();
2335 @commits = uniq(@commits);
2336 @commits = sort(@commits);
2337 my $commit = join(" -r ", @commits);
2338 my $cmd;
2339
2340 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2341 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2342
2343 my @lines = ();
2344
2345 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2346
2347 if (!$email_git_penguin_chiefs) {
2348 @lines = grep(!/${penguin_chiefs}/i, @lines);
2349 }
2350
2351 last if !@lines;
2352
2353 my @authors = ();
2354 foreach my $line (@lines) {
2355 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2356 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002357 $author = deduplicate_email($author);
2358 push(@authors, $author);
Joe Perches683c6f82010-10-26 14:22:55 -07002359 }
2360 }
2361
2362 save_commits_by_author(@lines) if ($interactive);
2363 save_commits_by_signer(@lines) if ($interactive);
2364
2365 push(@signers, @authors);
2366 }}
2367 else {
2368 foreach my $commit (@commits) {
2369 my $i;
2370 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2371 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2372 my @author = vcs_find_author($cmd);
2373 next if !@author;
Joe Perchesb9e23312010-10-26 14:22:58 -07002374
2375 my $formatted_author = deduplicate_email($author[0]);
2376
Joe Perches683c6f82010-10-26 14:22:55 -07002377 my $count = grep(/$commit/, @all_commits);
2378 for ($i = 0; $i < $count ; $i++) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002379 push(@blame_signers, $formatted_author);
Joe Perches683c6f82010-10-26 14:22:55 -07002380 }
Joe Perches63ab52d2010-10-26 14:22:51 -07002381 }
2382 }
2383 if (@blame_signers) {
2384 vcs_assign("authored lines", $total_lines, @blame_signers);
2385 }
2386 }
Joe Perchesb9e23312010-10-26 14:22:58 -07002387 foreach my $signer (@signers) {
2388 $signer = deduplicate_email($signer);
2389 }
Joe Perches60db31a2009-12-14 18:00:50 -08002390 vcs_assign("commits", $total_commits, @signers);
Joe Perchesa8af2432009-12-14 18:00:49 -08002391 } else {
Joe Perchesb9e23312010-10-26 14:22:58 -07002392 foreach my $signer (@signers) {
2393 $signer = deduplicate_email($signer);
2394 }
Joe Perches60db31a2009-12-14 18:00:50 -08002395 vcs_assign("modified commits", $total_commits, @signers);
Joe Perchesf5492662009-09-21 17:04:13 -07002396 }
Joe Perchescb7301c2009-04-07 20:40:12 -07002397}
2398
Joe Perches4cad35a2016-08-02 14:04:10 -07002399sub vcs_file_exists {
2400 my ($file) = @_;
2401
2402 my $exists;
2403
2404 my $vcs_used = vcs_exists();
2405 return 0 if (!$vcs_used);
2406
2407 my $cmd = $VCS_cmds{"file_exists_cmd"};
2408 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
Joe Perches8582fb52016-08-25 15:16:48 -07002409 $cmd .= " 2>&1";
Joe Perches4cad35a2016-08-02 14:04:10 -07002410 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2411
Joe Perches8582fb52016-08-25 15:16:48 -07002412 return 0 if ($? != 0);
2413
Joe Perches4cad35a2016-08-02 14:04:10 -07002414 return $exists;
2415}
2416
Tom Saegere1f75902017-11-17 15:27:42 -08002417sub vcs_list_files {
2418 my ($file) = @_;
2419
2420 my @lsfiles = ();
2421
2422 my $vcs_used = vcs_exists();
2423 return 0 if (!$vcs_used);
2424
2425 my $cmd = $VCS_cmds{"list_files_cmd"};
2426 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2427 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2428
2429 return () if ($? != 0);
2430
2431 return @lsfiles;
2432}
2433
Joe Perchescb7301c2009-04-07 20:40:12 -07002434sub uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002435 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002436
2437 my %saw;
2438 @parms = grep(!$saw{$_}++, @parms);
2439 return @parms;
2440}
2441
2442sub sort_and_uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002443 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002444
2445 my %saw;
2446 @parms = sort @parms;
2447 @parms = grep(!$saw{$_}++, @parms);
2448 return @parms;
2449}
2450
Joe Perches03372db2010-03-05 13:43:00 -08002451sub clean_file_emails {
2452 my (@file_emails) = @_;
2453 my @fmt_emails = ();
2454
2455 foreach my $email (@file_emails) {
2456 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2457 my ($name, $address) = parse_email($email);
2458 if ($name eq '"[,\.]"') {
2459 $name = "";
2460 }
2461
2462 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2463 if (@nw > 2) {
2464 my $first = $nw[@nw - 3];
2465 my $middle = $nw[@nw - 2];
2466 my $last = $nw[@nw - 1];
2467
2468 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2469 (length($first) == 2 && substr($first, -1) eq ".")) ||
2470 (length($middle) == 1 ||
2471 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2472 $name = "$first $middle $last";
2473 } else {
2474 $name = "$middle $last";
2475 }
2476 }
2477
2478 if (substr($name, -1) =~ /[,\.]/) {
2479 $name = substr($name, 0, length($name) - 1);
2480 } elsif (substr($name, -2) =~ /[,\.]"/) {
2481 $name = substr($name, 0, length($name) - 2) . '"';
2482 }
2483
2484 if (substr($name, 0, 1) =~ /[,\.]/) {
2485 $name = substr($name, 1, length($name) - 1);
2486 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2487 $name = '"' . substr($name, 2, length($name) - 2);
2488 }
2489
2490 my $fmt_email = format_email($name, $address, $email_usename);
2491 push(@fmt_emails, $fmt_email);
2492 }
2493 return @fmt_emails;
2494}
2495
Joe Perches3c7385b2009-12-14 18:00:46 -08002496sub merge_email {
2497 my @lines;
2498 my %saw;
2499
2500 for (@_) {
2501 my ($address, $role) = @$_;
2502 if (!$saw{$address}) {
2503 if ($output_roles) {
Joe Perches60db31a2009-12-14 18:00:50 -08002504 push(@lines, "$address ($role)");
Joe Perches3c7385b2009-12-14 18:00:46 -08002505 } else {
Joe Perches60db31a2009-12-14 18:00:50 -08002506 push(@lines, $address);
Joe Perches3c7385b2009-12-14 18:00:46 -08002507 }
2508 $saw{$address} = 1;
2509 }
2510 }
2511
2512 return @lines;
2513}
2514
Joe Perchescb7301c2009-04-07 20:40:12 -07002515sub output {
Joe Perchesa8af2432009-12-14 18:00:49 -08002516 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002517
2518 if ($output_multiline) {
2519 foreach my $line (@parms) {
2520 print("${line}\n");
2521 }
2522 } else {
2523 print(join($output_separator, @parms));
2524 print("\n");
2525 }
2526}
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002527
2528my $rfc822re;
2529
2530sub make_rfc822re {
2531# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2532# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2533# This regexp will only work on addresses which have had comments stripped
2534# and replaced with rfc822_lwsp.
2535
2536 my $specials = '()<>@,;:\\\\".\\[\\]';
2537 my $controls = '\\000-\\037\\177';
2538
2539 my $dtext = "[^\\[\\]\\r\\\\]";
2540 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2541
2542 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2543
2544# Use zero-width assertion to spot the limit of an atom. A simple
2545# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2546 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2547 my $word = "(?:$atom|$quoted_string)";
2548 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2549
2550 my $sub_domain = "(?:$atom|$domain_literal)";
2551 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2552
2553 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2554
2555 my $phrase = "$word*";
2556 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2557 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2558 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2559
2560 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2561 my $address = "(?:$mailbox|$group)";
2562
2563 return "$rfc822_lwsp*$address";
2564}
2565
2566sub rfc822_strip_comments {
2567 my $s = shift;
2568# Recursively remove comments, and replace with a single space. The simpler
2569# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2570# chars in atoms, for example.
2571
2572 while ($s =~ s/^((?:[^"\\]|\\.)*
2573 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2574 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2575 return $s;
2576}
2577
2578# valid: returns true if the parameter is an RFC822 valid address
2579#
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002580sub rfc822_valid {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002581 my $s = rfc822_strip_comments(shift);
2582
2583 if (!$rfc822re) {
2584 $rfc822re = make_rfc822re();
2585 }
2586
2587 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2588}
2589
2590# validlist: In scalar context, returns true if the parameter is an RFC822
2591# valid list of addresses.
2592#
2593# In list context, returns an empty list on failure (an invalid
2594# address was found); otherwise a list whose first element is the
2595# number of addresses found and whose remaining elements are the
2596# addresses. This is needed to disambiguate failure (invalid)
2597# from success with no addresses found, because an empty string is
2598# a valid list.
2599
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002600sub rfc822_validlist {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002601 my $s = rfc822_strip_comments(shift);
2602
2603 if (!$rfc822re) {
2604 $rfc822re = make_rfc822re();
2605 }
2606 # * null list items are valid according to the RFC
2607 # * the '1' business is to aid in distinguishing failure from no results
2608
2609 my @r;
2610 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2611 $s =~ m/^$rfc822_char*$/) {
Joe Perches5f2441e2009-06-16 15:34:02 -07002612 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
Joe Perches60db31a2009-12-14 18:00:50 -08002613 push(@r, $1);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002614 }
2615 return wantarray ? (scalar(@r), @r) : 1;
2616 }
Joe Perches60db31a2009-12-14 18:00:50 -08002617 return wantarray ? () : 0;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002618}