blob: ab123b498fd9ba12f558198c04c6fa48ec4c23d0 [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) {
Rob Herring11fb4892022-04-29 14:38:00 -0700986 $email = mailmap_email($email);
Joe Perches683c6f82010-10-26 14:22:55 -0700987 my ($name, $address) = parse_email($email);
988
989 my $tmp_email = format_email($name, $address, $email_usename);
990 push_email_address($tmp_email, '');
991 add_role($tmp_email, 'in file');
992 }
993 }
994
Douglas Anderson0ef82fc2020-02-20 20:04:12 -0800995 foreach my $fix (@fixes) {
996 vcs_add_commit_signers($fix, "blamed_fixes");
997 }
998
Joe Perches683c6f82010-10-26 14:22:55 -0700999 my @to = ();
1000 if ($email || $email_list) {
1001 if ($email) {
1002 @to = (@to, @email_to);
1003 }
1004 if ($email_list) {
1005 @to = (@to, @list_to);
1006 }
1007 }
1008
Joe Perches6ef1c522010-10-26 14:22:56 -07001009 if ($interactive) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001010 @to = interactive_get_maintainers(\@to);
Joe Perches6ef1c522010-10-26 14:22:56 -07001011 }
Joe Perches683c6f82010-10-26 14:22:55 -07001012
1013 return @to;
1014}
1015
Joe Perchescb7301c2009-04-07 20:40:12 -07001016sub file_match_pattern {
1017 my ($file, $pattern) = @_;
1018 if (substr($pattern, -1) eq "/") {
1019 if ($file =~ m@^$pattern@) {
1020 return 1;
1021 }
1022 } else {
1023 if ($file =~ m@^$pattern@) {
1024 my $s1 = ($file =~ tr@/@@);
1025 my $s2 = ($pattern =~ tr@/@@);
1026 if ($s1 == $s2) {
1027 return 1;
1028 }
1029 }
1030 }
1031 return 0;
1032}
1033
1034sub usage {
1035 print <<EOT;
1036usage: $P [options] patchfile
Joe Perches870020f2009-07-29 15:04:28 -07001037 $P [options] -f file|directory
Joe Perchescb7301c2009-04-07 20:40:12 -07001038version: $V
1039
1040MAINTAINER field selection options:
1041 --email => print email address(es) if any
1042 --git => include recent git \*-by: signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001043 --git-all-signature-types => include signers regardless of signature type
Joe Perches683c6f82010-10-26 14:22:55 -07001044 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
Joe Perchese3e9d112010-10-26 14:22:53 -07001045 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
Joe Perchescb7301c2009-04-07 20:40:12 -07001046 --git-chief-penguins => include ${penguin_chiefs}
Joe Perchese4d26b02010-05-24 14:33:17 -07001047 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1048 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1049 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
Joe Perchesf5492662009-09-21 17:04:13 -07001050 --git-blame => use git blame to find modified commits for patch or file
Brian Norris3cbcca82015-11-06 16:30:41 -08001051 --git-blame-signatures => when used with --git-blame, also include all commit signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001052 --git-since => git history to use (default: $email_git_since)
1053 --hg-since => hg history to use (default: $email_hg_since)
Florian Micklerdace8e32010-10-26 14:22:54 -07001054 --interactive => display a menu (mostly useful if used with the --git option)
Joe Perchescb7301c2009-04-07 20:40:12 -07001055 --m => include maintainer(s) if any
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001056 --r => include reviewer(s) if any
Joe Perchescb7301c2009-04-07 20:40:12 -07001057 --n => include name 'Full Name <addr\@domain.tld>'
1058 --l => include list(s) if any
Joe Perches49662502019-07-16 16:27:09 -07001059 --moderated => include moderated lists(s) if any (default: true)
1060 --s => include subscriber only list(s) if any (default: false)
Joe Perches11ecf532009-09-21 17:04:22 -07001061 --remove-duplicates => minimize duplicate email names/addresses
Joe Perches3c7385b2009-12-14 18:00:46 -08001062 --roles => show roles (status:subsystem, git-signer, list, etc...)
1063 --rolestats => show roles and statistics (commits/total_commits, %)
Joe Perches03372db2010-03-05 13:43:00 -08001064 --file-emails => add email addresses found in -f file (default: 0 (off))
Joe Perches2f5bd3432019-12-04 16:50:29 -08001065 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
Joe Perchescb7301c2009-04-07 20:40:12 -07001066 --scm => print SCM tree(s) if any
1067 --status => print status if any
1068 --subsystem => print subsystem name if any
1069 --web => print website(s) if any
1070
1071Output type options:
1072 --separator [, ] => separator for multiple entries on 1 line
Joe Perches42498312009-09-21 17:04:21 -07001073 using --separator also sets --nomultiline if --separator is not [, ]
Joe Perchescb7301c2009-04-07 20:40:12 -07001074 --multiline => print 1 entry per line
1075
Joe Perchescb7301c2009-04-07 20:40:12 -07001076Other options:
Joe Perches3fb55652009-09-21 17:04:17 -07001077 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
Joe Perchesb9e23312010-10-26 14:22:58 -07001078 --keywords => scan patch for keywords (default: $keywords)
1079 --sections => print all of the subsystem sections with pattern matches
Joe Perches03aed212016-12-12 16:45:59 -08001080 --letters => print all matching 'letter' types from all matching sections
Joe Perchesb9e23312010-10-26 14:22:58 -07001081 --mailmap => use .mailmap file (default: $email_use_mailmap)
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001082 --no-tree => run without a kernel tree
Tom Saegere1f75902017-11-17 15:27:42 -08001083 --self-test => show potential issues with MAINTAINERS file content
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001084 --version => show version
Joe Perchescb7301c2009-04-07 20:40:12 -07001085 --help => show this help information
1086
Joe Perches3fb55652009-09-21 17:04:17 -07001087Default options:
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001088 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1089 --pattern-depth=0 --remove-duplicates --rolestats]
Joe Perches3fb55652009-09-21 17:04:17 -07001090
Joe Perches870020f2009-07-29 15:04:28 -07001091Notes:
1092 Using "-f directory" may give unexpected results:
Joe Perchesf5492662009-09-21 17:04:13 -07001093 Used with "--git", git signators for _all_ files in and below
1094 directory are examined as git recurses directories.
1095 Any specified X: (exclude) pattern matches are _not_ ignored.
1096 Used with "--nogit", directory is used as a pattern match,
Joe Perches60db31a2009-12-14 18:00:50 -08001097 no individual file within the directory or subdirectory
1098 is matched.
Joe Perchesf5492662009-09-21 17:04:13 -07001099 Used with "--git-blame", does not iterate all files in directory
1100 Using "--git-blame" is slow and may add old committers and authors
1101 that are no longer active maintainers to the output.
Joe Perches3c7385b2009-12-14 18:00:46 -08001102 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1103 other automated tools that expect only ["name"] <email address>
1104 may not work because of additional output after <email address>.
1105 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1106 not the percentage of the entire file authored. # of commits is
1107 not a good measure of amount of code authored. 1 major commit may
1108 contain a thousand lines, 5 trivial commits may modify a single line.
Joe Perches60db31a2009-12-14 18:00:50 -08001109 If git is not installed, but mercurial (hg) is installed and an .hg
1110 repository exists, the following options apply to mercurial:
1111 --git,
1112 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1113 --git-blame
1114 Use --hg-since not --git-since to control date selection
Joe Perches368669d2010-05-24 14:33:19 -07001115 File ".get_maintainer.conf", if it exists in the linux kernel source root
1116 directory, can change whatever get_maintainer defaults are desired.
1117 Entries in this file can be any command line argument.
1118 This file is prepended to any additional command line arguments.
1119 Multiple lines and # comments are allowed.
Brian Norrisb1312bf2015-11-06 16:30:46 -08001120 Most options have both positive and negative forms.
1121 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1122
Joe Perchescb7301c2009-04-07 20:40:12 -07001123EOT
1124}
1125
1126sub top_of_kernel_tree {
Joe Perches47abc722010-10-26 14:22:57 -07001127 my ($lk_path) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07001128
Joe Perches47abc722010-10-26 14:22:57 -07001129 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1130 $lk_path .= "/";
1131 }
1132 if ( (-f "${lk_path}COPYING")
1133 && (-f "${lk_path}CREDITS")
1134 && (-f "${lk_path}Kbuild")
Joe Perches6f7d98e2017-08-04 21:45:48 -07001135 && (-e "${lk_path}MAINTAINERS")
Joe Perches47abc722010-10-26 14:22:57 -07001136 && (-f "${lk_path}Makefile")
1137 && (-f "${lk_path}README")
1138 && (-d "${lk_path}Documentation")
1139 && (-d "${lk_path}arch")
1140 && (-d "${lk_path}include")
1141 && (-d "${lk_path}drivers")
1142 && (-d "${lk_path}fs")
1143 && (-d "${lk_path}init")
1144 && (-d "${lk_path}ipc")
1145 && (-d "${lk_path}kernel")
1146 && (-d "${lk_path}lib")
1147 && (-d "${lk_path}scripts")) {
1148 return 1;
1149 }
1150 return 0;
Joe Perchescb7301c2009-04-07 20:40:12 -07001151}
1152
Joe Perches0e70e832009-09-21 17:04:20 -07001153sub parse_email {
1154 my ($formatted_email) = @_;
1155
1156 my $name = "";
1157 my $address = "";
1158
Joe Perches11ecf532009-09-21 17:04:22 -07001159 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001160 $name = $1;
1161 $address = $2;
Joe Perches11ecf532009-09-21 17:04:22 -07001162 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001163 $address = $1;
Joe Perchesb7816552009-09-21 17:04:24 -07001164 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001165 $address = $1;
1166 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001167
1168 $name =~ s/^\s+|\s+$//g;
Joe Perchesd7895042009-06-16 15:34:02 -07001169 $name =~ s/^\"|\"$//g;
Joe Perches0e70e832009-09-21 17:04:20 -07001170 $address =~ s/^\s+|\s+$//g;
Joe Perchescb7301c2009-04-07 20:40:12 -07001171
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001172 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perchescb7301c2009-04-07 20:40:12 -07001173 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
Joe Perches0e70e832009-09-21 17:04:20 -07001174 $name = "\"$name\"";
Joe Perchescb7301c2009-04-07 20:40:12 -07001175 }
Joe Perches0e70e832009-09-21 17:04:20 -07001176
1177 return ($name, $address);
1178}
1179
1180sub format_email {
Joe Perchesa8af2432009-12-14 18:00:49 -08001181 my ($name, $address, $usename) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001182
1183 my $formatted_email;
1184
1185 $name =~ s/^\s+|\s+$//g;
1186 $name =~ s/^\"|\"$//g;
1187 $address =~ s/^\s+|\s+$//g;
1188
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001189 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perches0e70e832009-09-21 17:04:20 -07001190 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1191 $name = "\"$name\"";
1192 }
1193
Joe Perchesa8af2432009-12-14 18:00:49 -08001194 if ($usename) {
Joe Perches0e70e832009-09-21 17:04:20 -07001195 if ("$name" eq "") {
1196 $formatted_email = "$address";
1197 } else {
Joe Perchesa8af2432009-12-14 18:00:49 -08001198 $formatted_email = "$name <$address>";
Joe Perches0e70e832009-09-21 17:04:20 -07001199 }
1200 } else {
1201 $formatted_email = $address;
1202 }
1203
Joe Perchescb7301c2009-04-07 20:40:12 -07001204 return $formatted_email;
1205}
1206
Joe Perches272a8972010-01-08 14:42:48 -08001207sub find_first_section {
1208 my $index = 0;
1209
1210 while ($index < @typevalue) {
1211 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001212 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perches272a8972010-01-08 14:42:48 -08001213 last;
1214 }
1215 $index++;
1216 }
1217
1218 return $index;
1219}
1220
Joe Perchesb7816552009-09-21 17:04:24 -07001221sub find_starting_index {
Joe Perchesb7816552009-09-21 17:04:24 -07001222 my ($index) = @_;
1223
1224 while ($index > 0) {
1225 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001226 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001227 last;
1228 }
1229 $index--;
1230 }
1231
1232 return $index;
1233}
1234
1235sub find_ending_index {
1236 my ($index) = @_;
1237
1238 while ($index < @typevalue) {
1239 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001240 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001241 last;
1242 }
1243 $index++;
1244 }
1245
1246 return $index;
1247}
1248
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001249sub get_subsystem_name {
1250 my ($index) = @_;
1251
1252 my $start = find_starting_index($index);
1253
1254 my $subsystem = $typevalue[$start];
1255 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1256 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1257 $subsystem =~ s/\s*$//;
1258 $subsystem = $subsystem . "...";
1259 }
1260 return $subsystem;
1261}
1262
Joe Perches3c7385b2009-12-14 18:00:46 -08001263sub get_maintainer_role {
1264 my ($index) = @_;
1265
1266 my $i;
1267 my $start = find_starting_index($index);
1268 my $end = find_ending_index($index);
1269
Joe Perches0ede2742012-03-23 15:01:56 -07001270 my $role = "unknown";
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001271 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001272
1273 for ($i = $start + 1; $i < $end; $i++) {
1274 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001275 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001276 my $ptype = $1;
1277 my $pvalue = $2;
1278 if ($ptype eq "S") {
1279 $role = $pvalue;
1280 }
1281 }
1282 }
1283
1284 $role = lc($role);
1285 if ($role eq "supported") {
1286 $role = "supporter";
1287 } elsif ($role eq "maintained") {
1288 $role = "maintainer";
1289 } elsif ($role eq "odd fixes") {
1290 $role = "odd fixer";
1291 } elsif ($role eq "orphan") {
1292 $role = "orphan minder";
1293 } elsif ($role eq "obsolete") {
1294 $role = "obsolete minder";
1295 } elsif ($role eq "buried alive in reporters") {
1296 $role = "chief penguin";
1297 }
1298
1299 return $role . ":" . $subsystem;
1300}
1301
1302sub get_list_role {
1303 my ($index) = @_;
1304
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001305 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001306
1307 if ($subsystem eq "THE REST") {
1308 $subsystem = "";
1309 }
1310
1311 return $subsystem;
1312}
1313
Joe Perchescb7301c2009-04-07 20:40:12 -07001314sub add_categories {
1315 my ($index) = @_;
1316
Joe Perchesb7816552009-09-21 17:04:24 -07001317 my $i;
1318 my $start = find_starting_index($index);
1319 my $end = find_ending_index($index);
1320
1321 push(@subsystem, $typevalue[$start]);
1322
1323 for ($i = $start + 1; $i < $end; $i++) {
1324 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001325 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001326 my $ptype = $1;
1327 my $pvalue = $2;
1328 if ($ptype eq "L") {
Joe Perches290603c2009-06-16 15:33:58 -07001329 my $list_address = $pvalue;
1330 my $list_additional = "";
Joe Perches3c7385b2009-12-14 18:00:46 -08001331 my $list_role = get_list_role($i);
1332
1333 if ($list_role ne "") {
1334 $list_role = ":" . $list_role;
1335 }
Joe Perches290603c2009-06-16 15:33:58 -07001336 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1337 $list_address = $1;
1338 $list_additional = $2;
1339 }
Joe Perchesbdf7c682009-06-16 15:33:59 -07001340 if ($list_additional =~ m/subscribers-only/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001341 if ($email_subscriber_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001342 if (!$hash_list_to{lc($list_address)}) {
1343 $hash_list_to{lc($list_address)} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001344 push(@list_to, [$list_address,
1345 "subscriber list${list_role}"]);
1346 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001347 }
1348 } else {
1349 if ($email_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001350 if (!$hash_list_to{lc($list_address)}) {
Richard Weinberger728f5a92012-03-23 15:01:56 -07001351 if ($list_additional =~ m/moderated/) {
Joe Perches49662502019-07-16 16:27:09 -07001352 if ($email_moderated_list) {
1353 $hash_list_to{lc($list_address)} = 1;
1354 push(@list_to, [$list_address,
1355 "moderated list${list_role}"]);
1356 }
Richard Weinberger728f5a92012-03-23 15:01:56 -07001357 } else {
Joe Perches49662502019-07-16 16:27:09 -07001358 $hash_list_to{lc($list_address)} = 1;
Richard Weinberger728f5a92012-03-23 15:01:56 -07001359 push(@list_to, [$list_address,
1360 "open list${list_role}"]);
1361 }
Joe Perches683c6f82010-10-26 14:22:55 -07001362 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001363 }
1364 }
1365 } elsif ($ptype eq "M") {
Joe Perches0e70e832009-09-21 17:04:20 -07001366 if ($email_maintainer) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001367 my $role = get_maintainer_role($i);
1368 push_email_addresses($pvalue, $role);
Joe Perchescb7301c2009-04-07 20:40:12 -07001369 }
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001370 } elsif ($ptype eq "R") {
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001371 if ($email_reviewer) {
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001372 my $subsystem = get_subsystem_name($i);
1373 push_email_addresses($pvalue, "reviewer:$subsystem");
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001374 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001375 } elsif ($ptype eq "T") {
1376 push(@scm, $pvalue);
1377 } elsif ($ptype eq "W") {
1378 push(@web, $pvalue);
1379 } elsif ($ptype eq "S") {
1380 push(@status, $pvalue);
1381 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001382 }
1383 }
1384}
1385
Joe Perches11ecf532009-09-21 17:04:22 -07001386sub email_inuse {
1387 my ($name, $address) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001388
Joe Perches11ecf532009-09-21 17:04:22 -07001389 return 1 if (($name eq "") && ($address eq ""));
Joe Perches6ef1c522010-10-26 14:22:56 -07001390 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1391 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
Joe Perches11ecf532009-09-21 17:04:22 -07001392
Joe Perches0e70e832009-09-21 17:04:20 -07001393 return 0;
1394}
1395
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001396sub push_email_address {
Joe Perches3c7385b2009-12-14 18:00:46 -08001397 my ($line, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001398
Joe Perches0e70e832009-09-21 17:04:20 -07001399 my ($name, $address) = parse_email($line);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001400
Joe Perchesb7816552009-09-21 17:04:24 -07001401 if ($address eq "") {
1402 return 0;
1403 }
1404
Joe Perches11ecf532009-09-21 17:04:22 -07001405 if (!$email_remove_duplicates) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001406 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perches11ecf532009-09-21 17:04:22 -07001407 } elsif (!email_inuse($name, $address)) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001408 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perchesfae99202010-10-26 14:22:58 -07001409 $email_hash_name{lc($name)}++ if ($name ne "");
Joe Perches6ef1c522010-10-26 14:22:56 -07001410 $email_hash_address{lc($address)}++;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001411 }
Joe Perchesb7816552009-09-21 17:04:24 -07001412
1413 return 1;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001414}
1415
1416sub push_email_addresses {
Joe Perches3c7385b2009-12-14 18:00:46 -08001417 my ($address, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001418
1419 my @address_list = ();
1420
Joe Perches5f2441e2009-06-16 15:34:02 -07001421 if (rfc822_valid($address)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001422 push_email_address($address, $role);
Joe Perches5f2441e2009-06-16 15:34:02 -07001423 } elsif (@address_list = rfc822_validlist($address)) {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001424 my $array_count = shift(@address_list);
1425 while (my $entry = shift(@address_list)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001426 push_email_address($entry, $role);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001427 }
Joe Perches5f2441e2009-06-16 15:34:02 -07001428 } else {
Joe Perches3c7385b2009-12-14 18:00:46 -08001429 if (!push_email_address($address, $role)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001430 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1431 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001432 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001433}
1434
Joe Perches3c7385b2009-12-14 18:00:46 -08001435sub add_role {
1436 my ($line, $role) = @_;
1437
1438 my ($name, $address) = parse_email($line);
Joe Perchesa8af2432009-12-14 18:00:49 -08001439 my $email = format_email($name, $address, $email_usename);
Joe Perches3c7385b2009-12-14 18:00:46 -08001440
1441 foreach my $entry (@email_to) {
1442 if ($email_remove_duplicates) {
1443 my ($entry_name, $entry_address) = parse_email($entry->[0]);
Joe Perches03372db2010-03-05 13:43:00 -08001444 if (($name eq $entry_name || $address eq $entry_address)
1445 && ($role eq "" || !($entry->[1] =~ m/$role/))
1446 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001447 if ($entry->[1] eq "") {
1448 $entry->[1] = "$role";
1449 } else {
1450 $entry->[1] = "$entry->[1],$role";
1451 }
1452 }
1453 } else {
Joe Perches03372db2010-03-05 13:43:00 -08001454 if ($email eq $entry->[0]
1455 && ($role eq "" || !($entry->[1] =~ m/$role/))
1456 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001457 if ($entry->[1] eq "") {
1458 $entry->[1] = "$role";
1459 } else {
1460 $entry->[1] = "$entry->[1],$role";
1461 }
1462 }
1463 }
1464 }
1465}
1466
Joe Perchescb7301c2009-04-07 20:40:12 -07001467sub which {
1468 my ($bin) = @_;
1469
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001470 foreach my $path (split(/:/, $ENV{PATH})) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001471 if (-e "$path/$bin") {
1472 return "$path/$bin";
1473 }
1474 }
1475
1476 return "";
1477}
1478
Joe Perchesbcde44e2010-10-26 14:22:53 -07001479sub which_conf {
1480 my ($conf) = @_;
1481
1482 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1483 if (-e "$path/$conf") {
1484 return "$path/$conf";
1485 }
1486 }
1487
1488 return "";
1489}
1490
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001491sub mailmap_email {
Joe Perchesb9e23312010-10-26 14:22:58 -07001492 my ($line) = @_;
Joe Perches8cbb3a72009-09-21 17:04:21 -07001493
Joe Perches47abc722010-10-26 14:22:57 -07001494 my ($name, $address) = parse_email($line);
1495 my $email = format_email($name, $address, 1);
1496 my $real_name = $name;
1497 my $real_address = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001498
Joe Perches47abc722010-10-26 14:22:57 -07001499 if (exists $mailmap->{names}->{$email} ||
1500 exists $mailmap->{addresses}->{$email}) {
1501 if (exists $mailmap->{names}->{$email}) {
1502 $real_name = $mailmap->{names}->{$email};
Joe Perches8cbb3a72009-09-21 17:04:21 -07001503 }
Joe Perches47abc722010-10-26 14:22:57 -07001504 if (exists $mailmap->{addresses}->{$email}) {
1505 $real_address = $mailmap->{addresses}->{$email};
1506 }
1507 } else {
1508 if (exists $mailmap->{names}->{$address}) {
1509 $real_name = $mailmap->{names}->{$address};
1510 }
1511 if (exists $mailmap->{addresses}->{$address}) {
1512 $real_address = $mailmap->{addresses}->{$address};
1513 }
1514 }
1515 return format_email($real_name, $real_address, 1);
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001516}
1517
1518sub mailmap {
1519 my (@addresses) = @_;
1520
Joe Perchesb9e23312010-10-26 14:22:58 -07001521 my @mapped_emails = ();
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001522 foreach my $line (@addresses) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001523 push(@mapped_emails, mailmap_email($line));
Joe Perches8cbb3a72009-09-21 17:04:21 -07001524 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001525 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1526 return @mapped_emails;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001527}
1528
1529sub merge_by_realname {
Joe Perches47abc722010-10-26 14:22:57 -07001530 my %address_map;
1531 my (@emails) = @_;
Joe Perchesb9e23312010-10-26 14:22:58 -07001532
Joe Perches47abc722010-10-26 14:22:57 -07001533 foreach my $email (@emails) {
1534 my ($name, $address) = parse_email($email);
Joe Perchesb9e23312010-10-26 14:22:58 -07001535 if (exists $address_map{$name}) {
Joe Perches47abc722010-10-26 14:22:57 -07001536 $address = $address_map{$name};
Joe Perchesb9e23312010-10-26 14:22:58 -07001537 $email = format_email($name, $address, 1);
1538 } else {
1539 $address_map{$name} = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001540 }
Joe Perches47abc722010-10-26 14:22:57 -07001541 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07001542}
1543
Joe Perches60db31a2009-12-14 18:00:50 -08001544sub git_execute_cmd {
1545 my ($cmd) = @_;
1546 my @lines = ();
Joe Perchescb7301c2009-04-07 20:40:12 -07001547
Joe Perches60db31a2009-12-14 18:00:50 -08001548 my $output = `$cmd`;
1549 $output =~ s/^\s*//gm;
1550 @lines = split("\n", $output);
1551
1552 return @lines;
Joe Perchesa8af2432009-12-14 18:00:49 -08001553}
1554
Joe Perches60db31a2009-12-14 18:00:50 -08001555sub hg_execute_cmd {
Joe Perchesa8af2432009-12-14 18:00:49 -08001556 my ($cmd) = @_;
Joe Perches60db31a2009-12-14 18:00:50 -08001557 my @lines = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001558
Joe Perches60db31a2009-12-14 18:00:50 -08001559 my $output = `$cmd`;
1560 @lines = split("\n", $output);
1561
1562 return @lines;
1563}
1564
Joe Perches683c6f82010-10-26 14:22:55 -07001565sub extract_formatted_signatures {
1566 my (@signature_lines) = @_;
1567
1568 my @type = @signature_lines;
1569
1570 s/\s*(.*):.*/$1/ for (@type);
1571
1572 # cut -f2- -d":"
1573 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1574
1575## Reformat email addresses (with names) to avoid badly written signatures
1576
1577 foreach my $signer (@signature_lines) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001578 $signer = deduplicate_email($signer);
Joe Perches683c6f82010-10-26 14:22:55 -07001579 }
1580
1581 return (\@type, \@signature_lines);
1582}
1583
Joe Perches60db31a2009-12-14 18:00:50 -08001584sub vcs_find_signers {
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001585 my ($cmd, $file) = @_;
Joe Perchesa8af2432009-12-14 18:00:49 -08001586 my $commits;
Joe Perches683c6f82010-10-26 14:22:55 -07001587 my @lines = ();
1588 my @signatures = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001589 my @authors = ();
1590 my @stats = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001591
Joe Perches60db31a2009-12-14 18:00:50 -08001592 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
Joe Perchescb7301c2009-04-07 20:40:12 -07001593
Joe Perches60db31a2009-12-14 18:00:50 -08001594 my $pattern = $VCS_cmds{"commit_pattern"};
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001595 my $author_pattern = $VCS_cmds{"author_pattern"};
1596 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1597
1598 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
Joe Perchescb7301c2009-04-07 20:40:12 -07001599
Joe Perches60db31a2009-12-14 18:00:50 -08001600 $commits = grep(/$pattern/, @lines); # of commits
Joe Perchesafa81ee2009-07-29 15:04:28 -07001601
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001602 @authors = grep(/$author_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001603 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001604 @stats = grep(/$stat_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001605
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001606# print("stats: <@stats>\n");
1607
1608 return (0, \@signatures, \@authors, \@stats) if !@signatures;
Joe Perches683c6f82010-10-26 14:22:55 -07001609
1610 save_commits_by_author(@lines) if ($interactive);
1611 save_commits_by_signer(@lines) if ($interactive);
1612
Joe Perches0e70e832009-09-21 17:04:20 -07001613 if (!$email_git_penguin_chiefs) {
Joe Perches683c6f82010-10-26 14:22:55 -07001614 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
Joe Perches0e70e832009-09-21 17:04:20 -07001615 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001616
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001617 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
Joe Perches683c6f82010-10-26 14:22:55 -07001618 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
Joe Perches63ab52d2010-10-26 14:22:51 -07001619
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001620 return ($commits, $signers_ref, $authors_ref, \@stats);
Joe Perchesa8af2432009-12-14 18:00:49 -08001621}
1622
Joe Perches63ab52d2010-10-26 14:22:51 -07001623sub vcs_find_author {
1624 my ($cmd) = @_;
1625 my @lines = ();
1626
1627 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1628
1629 if (!$email_git_penguin_chiefs) {
1630 @lines = grep(!/${penguin_chiefs}/i, @lines);
1631 }
1632
1633 return @lines if !@lines;
1634
Joe Perches683c6f82010-10-26 14:22:55 -07001635 my @authors = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07001636 foreach my $line (@lines) {
Joe Perches683c6f82010-10-26 14:22:55 -07001637 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1638 my $author = $1;
1639 my ($name, $address) = parse_email($author);
1640 $author = format_email($name, $address, 1);
1641 push(@authors, $author);
1642 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001643 }
1644
Joe Perches683c6f82010-10-26 14:22:55 -07001645 save_commits_by_author(@lines) if ($interactive);
1646 save_commits_by_signer(@lines) if ($interactive);
1647
1648 return @authors;
Joe Perches63ab52d2010-10-26 14:22:51 -07001649}
1650
Joe Perches60db31a2009-12-14 18:00:50 -08001651sub vcs_save_commits {
1652 my ($cmd) = @_;
1653 my @lines = ();
1654 my @commits = ();
1655
1656 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1657
1658 foreach my $line (@lines) {
1659 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1660 push(@commits, $1);
1661 }
1662 }
1663
1664 return @commits;
1665}
1666
1667sub vcs_blame {
1668 my ($file) = @_;
1669 my $cmd;
1670 my @commits = ();
1671
1672 return @commits if (!(-f $file));
1673
1674 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1675 my @all_commits = ();
1676
1677 $cmd = $VCS_cmds{"blame_file_cmd"};
1678 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1679 @all_commits = vcs_save_commits($cmd);
1680
1681 foreach my $file_range_diff (@range) {
1682 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1683 my $diff_file = $1;
1684 my $diff_start = $2;
1685 my $diff_length = $3;
1686 next if ("$file" ne "$diff_file");
1687 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1688 push(@commits, $all_commits[$i]);
1689 }
1690 }
1691 } elsif (@range) {
1692 foreach my $file_range_diff (@range) {
1693 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1694 my $diff_file = $1;
1695 my $diff_start = $2;
1696 my $diff_length = $3;
1697 next if ("$file" ne "$diff_file");
1698 $cmd = $VCS_cmds{"blame_range_cmd"};
1699 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1700 push(@commits, vcs_save_commits($cmd));
1701 }
1702 } else {
1703 $cmd = $VCS_cmds{"blame_file_cmd"};
1704 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1705 @commits = vcs_save_commits($cmd);
1706 }
1707
Joe Perches63ab52d2010-10-26 14:22:51 -07001708 foreach my $commit (@commits) {
1709 $commit =~ s/^\^//g;
1710 }
1711
Joe Perches60db31a2009-12-14 18:00:50 -08001712 return @commits;
1713}
1714
1715my $printed_novcs = 0;
1716sub vcs_exists {
1717 %VCS_cmds = %VCS_cmds_git;
1718 return 1 if eval $VCS_cmds{"available"};
1719 %VCS_cmds = %VCS_cmds_hg;
Joe Perches683c6f82010-10-26 14:22:55 -07001720 return 2 if eval $VCS_cmds{"available"};
Joe Perches60db31a2009-12-14 18:00:50 -08001721 %VCS_cmds = ();
Randy Dunlap26d98e92022-01-19 18:08:50 -08001722 if (!$printed_novcs && $email_git) {
Joe Perches60db31a2009-12-14 18:00:50 -08001723 warn("$P: No supported VCS found. Add --nogit to options?\n");
1724 warn("Using a git repository produces better results.\n");
1725 warn("Try Linus Torvalds' latest git repository using:\n");
Ralf Thielow3d1c2f72011-08-25 15:59:07 -07001726 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
Joe Perches60db31a2009-12-14 18:00:50 -08001727 $printed_novcs = 1;
1728 }
1729 return 0;
1730}
1731
Joe Perches683c6f82010-10-26 14:22:55 -07001732sub vcs_is_git {
Joe Perchesb9e23312010-10-26 14:22:58 -07001733 vcs_exists();
Joe Perches683c6f82010-10-26 14:22:55 -07001734 return $vcs_used == 1;
1735}
1736
1737sub vcs_is_hg {
1738 return $vcs_used == 2;
1739}
1740
Joe Perches2f5bd3432019-12-04 16:50:29 -08001741sub vcs_add_commit_signers {
1742 return if (!vcs_exists());
1743
1744 my ($commit, $desc) = @_;
1745 my $commit_count = 0;
1746 my $commit_authors_ref;
1747 my $commit_signers_ref;
1748 my $stats_ref;
1749 my @commit_authors = ();
1750 my @commit_signers = ();
1751 my $cmd;
1752
1753 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1754 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1755
1756 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1757 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1758 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1759
1760 foreach my $signer (@commit_signers) {
1761 $signer = deduplicate_email($signer);
1762 }
1763
1764 vcs_assign($desc, 1, @commit_signers);
1765}
1766
Joe Perches6ef1c522010-10-26 14:22:56 -07001767sub interactive_get_maintainers {
Joe Perches683c6f82010-10-26 14:22:55 -07001768 my ($list_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07001769 my @list = @$list_ref;
1770
Joe Perches683c6f82010-10-26 14:22:55 -07001771 vcs_exists();
Florian Micklerdace8e32010-10-26 14:22:54 -07001772
1773 my %selected;
Joe Perches683c6f82010-10-26 14:22:55 -07001774 my %authored;
1775 my %signed;
Florian Micklerdace8e32010-10-26 14:22:54 -07001776 my $count = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001777 my $maintained = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001778 foreach my $entry (@list) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001779 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1780 $selected{$count} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001781 $authored{$count} = 0;
1782 $signed{$count} = 0;
1783 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001784 }
1785
1786 #menu loop
Joe Perches683c6f82010-10-26 14:22:55 -07001787 my $done = 0;
1788 my $print_options = 0;
1789 my $redraw = 1;
1790 while (!$done) {
1791 $count = 0;
1792 if ($redraw) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001793 printf STDERR "\n%1s %2s %-65s",
1794 "*", "#", "email/list and role:stats";
1795 if ($email_git ||
1796 ($email_git_fallback && !$maintained) ||
1797 $email_git_blame) {
1798 print STDERR "auth sign";
1799 }
1800 print STDERR "\n";
Joe Perches683c6f82010-10-26 14:22:55 -07001801 foreach my $entry (@list) {
1802 my $email = $entry->[0];
1803 my $role = $entry->[1];
1804 my $sel = "";
1805 $sel = "*" if ($selected{$count});
1806 my $commit_author = $commit_author_hash{$email};
1807 my $commit_signer = $commit_signer_hash{$email};
1808 my $authored = 0;
1809 my $signed = 0;
1810 $authored++ for (@{$commit_author});
1811 $signed++ for (@{$commit_signer});
1812 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1813 printf STDERR "%4d %4d", $authored, $signed
1814 if ($authored > 0 || $signed > 0);
1815 printf STDERR "\n %s\n", $role;
1816 if ($authored{$count}) {
1817 my $commit_author = $commit_author_hash{$email};
1818 foreach my $ref (@{$commit_author}) {
1819 print STDERR " Author: @{$ref}[1]\n";
Florian Micklerdace8e32010-10-26 14:22:54 -07001820 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001821 }
Joe Perches683c6f82010-10-26 14:22:55 -07001822 if ($signed{$count}) {
1823 my $commit_signer = $commit_signer_hash{$email};
1824 foreach my $ref (@{$commit_signer}) {
1825 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1826 }
1827 }
1828
1829 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001830 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001831 }
Joe Perches683c6f82010-10-26 14:22:55 -07001832 my $date_ref = \$email_git_since;
1833 $date_ref = \$email_hg_since if (vcs_is_hg());
1834 if ($print_options) {
1835 $print_options = 0;
1836 if (vcs_exists()) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001837 print STDERR <<EOT
1838
1839Version Control options:
1840g use git history [$email_git]
1841gf use git-fallback [$email_git_fallback]
1842b use git blame [$email_git_blame]
1843bs use blame signatures [$email_git_blame_signatures]
1844c# minimum commits [$email_git_min_signatures]
1845%# min percent [$email_git_min_percent]
1846d# history to use [$$date_ref]
1847x# max maintainers [$email_git_max_maintainers]
1848t all signature types [$email_git_all_signature_types]
1849m use .mailmap [$email_use_mailmap]
1850EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001851 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001852 print STDERR <<EOT
1853
1854Additional options:
18550 toggle all
1856tm toggle maintainers
1857tg toggle git entries
1858tl toggle open list entries
1859ts toggle subscriber list entries
Joe Perches0c78c012020-06-04 16:50:01 -07001860f emails in file [$email_file_emails]
Joe Perchesb9e23312010-10-26 14:22:58 -07001861k keywords in file [$keywords]
1862r remove duplicates [$email_remove_duplicates]
1863p# pattern match depth [$pattern_depth]
1864EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001865 }
1866 print STDERR
1867"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1868
1869 my $input = <STDIN>;
Florian Micklerdace8e32010-10-26 14:22:54 -07001870 chomp($input);
1871
Joe Perches683c6f82010-10-26 14:22:55 -07001872 $redraw = 1;
1873 my $rerun = 0;
1874 my @wish = split(/[, ]+/, $input);
1875 foreach my $nr (@wish) {
1876 $nr = lc($nr);
1877 my $sel = substr($nr, 0, 1);
1878 my $str = substr($nr, 1);
1879 my $val = 0;
1880 $val = $1 if $str =~ /^(\d+)$/;
1881
1882 if ($sel eq "y") {
1883 $interactive = 0;
1884 $done = 1;
1885 $output_rolestats = 0;
1886 $output_roles = 0;
1887 last;
1888 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1889 $selected{$nr - 1} = !$selected{$nr - 1};
1890 } elsif ($sel eq "*" || $sel eq '^') {
1891 my $toggle = 0;
1892 $toggle = 1 if ($sel eq '*');
1893 for (my $i = 0; $i < $count; $i++) {
1894 $selected{$i} = $toggle;
Florian Micklerdace8e32010-10-26 14:22:54 -07001895 }
Joe Perches683c6f82010-10-26 14:22:55 -07001896 } elsif ($sel eq "0") {
1897 for (my $i = 0; $i < $count; $i++) {
1898 $selected{$i} = !$selected{$i};
1899 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001900 } elsif ($sel eq "t") {
1901 if (lc($str) eq "m") {
1902 for (my $i = 0; $i < $count; $i++) {
1903 $selected{$i} = !$selected{$i}
1904 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1905 }
1906 } elsif (lc($str) eq "g") {
1907 for (my $i = 0; $i < $count; $i++) {
1908 $selected{$i} = !$selected{$i}
1909 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1910 }
1911 } elsif (lc($str) eq "l") {
1912 for (my $i = 0; $i < $count; $i++) {
1913 $selected{$i} = !$selected{$i}
1914 if ($list[$i]->[1] =~ /^(open list)/i);
1915 }
1916 } elsif (lc($str) eq "s") {
1917 for (my $i = 0; $i < $count; $i++) {
1918 $selected{$i} = !$selected{$i}
1919 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1920 }
1921 }
Joe Perches683c6f82010-10-26 14:22:55 -07001922 } elsif ($sel eq "a") {
1923 if ($val > 0 && $val <= $count) {
1924 $authored{$val - 1} = !$authored{$val - 1};
1925 } elsif ($str eq '*' || $str eq '^') {
1926 my $toggle = 0;
1927 $toggle = 1 if ($str eq '*');
1928 for (my $i = 0; $i < $count; $i++) {
1929 $authored{$i} = $toggle;
1930 }
1931 }
1932 } elsif ($sel eq "s") {
1933 if ($val > 0 && $val <= $count) {
1934 $signed{$val - 1} = !$signed{$val - 1};
1935 } elsif ($str eq '*' || $str eq '^') {
1936 my $toggle = 0;
1937 $toggle = 1 if ($str eq '*');
1938 for (my $i = 0; $i < $count; $i++) {
1939 $signed{$i} = $toggle;
1940 }
1941 }
1942 } elsif ($sel eq "o") {
1943 $print_options = 1;
1944 $redraw = 1;
1945 } elsif ($sel eq "g") {
1946 if ($str eq "f") {
1947 bool_invert(\$email_git_fallback);
Florian Micklerdace8e32010-10-26 14:22:54 -07001948 } else {
Joe Perches683c6f82010-10-26 14:22:55 -07001949 bool_invert(\$email_git);
Florian Micklerdace8e32010-10-26 14:22:54 -07001950 }
Joe Perches683c6f82010-10-26 14:22:55 -07001951 $rerun = 1;
1952 } elsif ($sel eq "b") {
1953 if ($str eq "s") {
1954 bool_invert(\$email_git_blame_signatures);
1955 } else {
1956 bool_invert(\$email_git_blame);
1957 }
1958 $rerun = 1;
1959 } elsif ($sel eq "c") {
1960 if ($val > 0) {
1961 $email_git_min_signatures = $val;
1962 $rerun = 1;
1963 }
1964 } elsif ($sel eq "x") {
1965 if ($val > 0) {
1966 $email_git_max_maintainers = $val;
1967 $rerun = 1;
1968 }
1969 } elsif ($sel eq "%") {
1970 if ($str ne "" && $val >= 0) {
1971 $email_git_min_percent = $val;
1972 $rerun = 1;
1973 }
1974 } elsif ($sel eq "d") {
1975 if (vcs_is_git()) {
1976 $email_git_since = $str;
1977 } elsif (vcs_is_hg()) {
1978 $email_hg_since = $str;
1979 }
1980 $rerun = 1;
1981 } elsif ($sel eq "t") {
1982 bool_invert(\$email_git_all_signature_types);
1983 $rerun = 1;
1984 } elsif ($sel eq "f") {
Joe Perches0c78c012020-06-04 16:50:01 -07001985 bool_invert(\$email_file_emails);
Joe Perches683c6f82010-10-26 14:22:55 -07001986 $rerun = 1;
1987 } elsif ($sel eq "r") {
1988 bool_invert(\$email_remove_duplicates);
1989 $rerun = 1;
Joe Perchesb9e23312010-10-26 14:22:58 -07001990 } elsif ($sel eq "m") {
1991 bool_invert(\$email_use_mailmap);
1992 read_mailmap();
1993 $rerun = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001994 } elsif ($sel eq "k") {
1995 bool_invert(\$keywords);
1996 $rerun = 1;
1997 } elsif ($sel eq "p") {
1998 if ($str ne "" && $val >= 0) {
1999 $pattern_depth = $val;
2000 $rerun = 1;
2001 }
Joe Perches6ef1c522010-10-26 14:22:56 -07002002 } elsif ($sel eq "h" || $sel eq "?") {
2003 print STDERR <<EOT
2004
2005Interactive mode allows you to select the various maintainers, submitters,
2006commit signers and mailing lists that could be CC'd on a patch.
2007
2008Any *'d entry is selected.
2009
Joe Perches47abc722010-10-26 14:22:57 -07002010If you have git or hg installed, you can choose to summarize the commit
Joe Perches6ef1c522010-10-26 14:22:56 -07002011history of files in the patch. Also, each line of the current file can
2012be matched to its commit author and that commits signers with blame.
2013
2014Various knobs exist to control the length of time for active commit
2015tracking, the maximum number of commit authors and signers to add,
2016and such.
2017
2018Enter selections at the prompt until you are satisfied that the selected
2019maintainers are appropriate. You may enter multiple selections separated
2020by either commas or spaces.
2021
2022EOT
Joe Perches683c6f82010-10-26 14:22:55 -07002023 } else {
2024 print STDERR "invalid option: '$nr'\n";
2025 $redraw = 0;
2026 }
2027 }
2028 if ($rerun) {
2029 print STDERR "git-blame can be very slow, please have patience..."
2030 if ($email_git_blame);
Joe Perches6ef1c522010-10-26 14:22:56 -07002031 goto &get_maintainers;
Joe Perches683c6f82010-10-26 14:22:55 -07002032 }
2033 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002034
2035 #drop not selected entries
2036 $count = 0;
Joe Perches683c6f82010-10-26 14:22:55 -07002037 my @new_emailto = ();
2038 foreach my $entry (@list) {
2039 if ($selected{$count}) {
2040 push(@new_emailto, $list[$count]);
Florian Micklerdace8e32010-10-26 14:22:54 -07002041 }
2042 $count++;
2043 }
Joe Perches683c6f82010-10-26 14:22:55 -07002044 return @new_emailto;
Florian Micklerdace8e32010-10-26 14:22:54 -07002045}
2046
Joe Perches683c6f82010-10-26 14:22:55 -07002047sub bool_invert {
2048 my ($bool_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002049
Joe Perches683c6f82010-10-26 14:22:55 -07002050 if ($$bool_ref) {
2051 $$bool_ref = 0;
2052 } else {
2053 $$bool_ref = 1;
Florian Micklerdace8e32010-10-26 14:22:54 -07002054 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002055}
2056
Joe Perchesb9e23312010-10-26 14:22:58 -07002057sub deduplicate_email {
2058 my ($email) = @_;
2059
2060 my $matched = 0;
2061 my ($name, $address) = parse_email($email);
2062 $email = format_email($name, $address, 1);
2063 $email = mailmap_email($email);
2064
2065 return $email if (!$email_remove_duplicates);
2066
2067 ($name, $address) = parse_email($email);
2068
Joe Perchesfae99202010-10-26 14:22:58 -07002069 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002070 $name = $deduplicate_name_hash{lc($name)}->[0];
2071 $address = $deduplicate_name_hash{lc($name)}->[1];
2072 $matched = 1;
2073 } elsif ($deduplicate_address_hash{lc($address)}) {
2074 $name = $deduplicate_address_hash{lc($address)}->[0];
2075 $address = $deduplicate_address_hash{lc($address)}->[1];
2076 $matched = 1;
2077 }
2078 if (!$matched) {
2079 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2080 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2081 }
2082 $email = format_email($name, $address, 1);
2083 $email = mailmap_email($email);
2084 return $email;
2085}
2086
Joe Perches683c6f82010-10-26 14:22:55 -07002087sub save_commits_by_author {
2088 my (@lines) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002089
Joe Perches683c6f82010-10-26 14:22:55 -07002090 my @authors = ();
2091 my @commits = ();
2092 my @subjects = ();
2093
2094 foreach my $line (@lines) {
2095 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2096 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002097 $author = deduplicate_email($author);
Joe Perches683c6f82010-10-26 14:22:55 -07002098 push(@authors, $author);
2099 }
2100 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2101 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2102 }
2103
2104 for (my $i = 0; $i < @authors; $i++) {
2105 my $exists = 0;
2106 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2107 if (@{$ref}[0] eq $commits[$i] &&
2108 @{$ref}[1] eq $subjects[$i]) {
2109 $exists = 1;
2110 last;
2111 }
2112 }
2113 if (!$exists) {
2114 push(@{$commit_author_hash{$authors[$i]}},
2115 [ ($commits[$i], $subjects[$i]) ]);
2116 }
2117 }
2118}
2119
2120sub save_commits_by_signer {
2121 my (@lines) = @_;
2122
2123 my $commit = "";
2124 my $subject = "";
2125
2126 foreach my $line (@lines) {
2127 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2128 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2129 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2130 my @signatures = ($line);
2131 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2132 my @types = @$types_ref;
2133 my @signers = @$signers_ref;
2134
2135 my $type = $types[0];
2136 my $signer = $signers[0];
2137
Joe Perchesb9e23312010-10-26 14:22:58 -07002138 $signer = deduplicate_email($signer);
Joe Perches6ef1c522010-10-26 14:22:56 -07002139
Joe Perches683c6f82010-10-26 14:22:55 -07002140 my $exists = 0;
2141 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2142 if (@{$ref}[0] eq $commit &&
2143 @{$ref}[1] eq $subject &&
2144 @{$ref}[2] eq $type) {
2145 $exists = 1;
2146 last;
2147 }
2148 }
2149 if (!$exists) {
2150 push(@{$commit_signer_hash{$signer}},
2151 [ ($commit, $subject, $type) ]);
2152 }
2153 }
2154 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002155}
2156
Joe Perches60db31a2009-12-14 18:00:50 -08002157sub vcs_assign {
Joe Perchesa8af2432009-12-14 18:00:49 -08002158 my ($role, $divisor, @lines) = @_;
2159
2160 my %hash;
2161 my $count = 0;
2162
Joe Perchesa8af2432009-12-14 18:00:49 -08002163 return if (@lines <= 0);
2164
2165 if ($divisor <= 0) {
Joe Perches60db31a2009-12-14 18:00:50 -08002166 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
Joe Perchesa8af2432009-12-14 18:00:49 -08002167 $divisor = 1;
Joe Perches3c7385b2009-12-14 18:00:46 -08002168 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07002169
Florian Mickler7fa8ff22010-10-26 14:22:56 -07002170 @lines = mailmap(@lines);
Joe Perches0e70e832009-09-21 17:04:20 -07002171
Joe Perches63ab52d2010-10-26 14:22:51 -07002172 return if (@lines <= 0);
2173
Joe Perches0e70e832009-09-21 17:04:20 -07002174 @lines = sort(@lines);
Joe Perchesafa81ee2009-07-29 15:04:28 -07002175
Joe Perches11ecf532009-09-21 17:04:22 -07002176 # uniq -c
2177 $hash{$_}++ for @lines;
2178
2179 # sort -rn
2180 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2181 my $sign_offs = $hash{$line};
Joe Perchesa8af2432009-12-14 18:00:49 -08002182 my $percent = $sign_offs * 100 / $divisor;
Joe Perches3c7385b2009-12-14 18:00:46 -08002183
Joe Perchesa8af2432009-12-14 18:00:49 -08002184 $percent = 100 if ($percent > 100);
Joe Perches435de072015-06-25 15:01:50 -07002185 next if (ignore_email_address($line));
Joe Perches11ecf532009-09-21 17:04:22 -07002186 $count++;
2187 last if ($sign_offs < $email_git_min_signatures ||
2188 $count > $email_git_max_maintainers ||
Joe Perchesa8af2432009-12-14 18:00:49 -08002189 $percent < $email_git_min_percent);
Joe Perches3c7385b2009-12-14 18:00:46 -08002190 push_email_address($line, '');
Joe Perches3c7385b2009-12-14 18:00:46 -08002191 if ($output_rolestats) {
Joe Perchesa8af2432009-12-14 18:00:49 -08002192 my $fmt_percent = sprintf("%.0f", $percent);
2193 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2194 } else {
2195 add_role($line, $role);
Joe Perches3c7385b2009-12-14 18:00:46 -08002196 }
Joe Perchesf5492662009-09-21 17:04:13 -07002197 }
2198}
2199
Joe Perches60db31a2009-12-14 18:00:50 -08002200sub vcs_file_signoffs {
Joe Perchesa8af2432009-12-14 18:00:49 -08002201 my ($file) = @_;
2202
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002203 my $authors_ref;
2204 my $signers_ref;
2205 my $stats_ref;
2206 my @authors = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002207 my @signers = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002208 my @stats = ();
Joe Perches60db31a2009-12-14 18:00:50 -08002209 my $commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002210
Joe Perches683c6f82010-10-26 14:22:55 -07002211 $vcs_used = vcs_exists();
2212 return if (!$vcs_used);
Joe Perchesa8af2432009-12-14 18:00:49 -08002213
Joe Perches60db31a2009-12-14 18:00:50 -08002214 my $cmd = $VCS_cmds{"find_signers_cmd"};
2215 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2216
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002217 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2218
2219 @signers = @{$signers_ref} if defined $signers_ref;
2220 @authors = @{$authors_ref} if defined $authors_ref;
2221 @stats = @{$stats_ref} if defined $stats_ref;
2222
2223# print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
Joe Perchesb9e23312010-10-26 14:22:58 -07002224
2225 foreach my $signer (@signers) {
2226 $signer = deduplicate_email($signer);
2227 }
2228
Joe Perches60db31a2009-12-14 18:00:50 -08002229 vcs_assign("commit_signer", $commits, @signers);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002230 vcs_assign("authored", $commits, @authors);
2231 if ($#authors == $#stats) {
2232 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2233 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2234
2235 my $added = 0;
2236 my $deleted = 0;
2237 for (my $i = 0; $i <= $#stats; $i++) {
2238 if ($stats[$i] =~ /$stat_pattern/) {
2239 $added += $1;
2240 $deleted += $2;
2241 }
2242 }
2243 my @tmp_authors = uniq(@authors);
2244 foreach my $author (@tmp_authors) {
2245 $author = deduplicate_email($author);
2246 }
2247 @tmp_authors = uniq(@tmp_authors);
2248 my @list_added = ();
2249 my @list_deleted = ();
2250 foreach my $author (@tmp_authors) {
2251 my $auth_added = 0;
2252 my $auth_deleted = 0;
2253 for (my $i = 0; $i <= $#stats; $i++) {
2254 if ($author eq deduplicate_email($authors[$i]) &&
2255 $stats[$i] =~ /$stat_pattern/) {
2256 $auth_added += $1;
2257 $auth_deleted += $2;
2258 }
2259 }
2260 for (my $i = 0; $i < $auth_added; $i++) {
2261 push(@list_added, $author);
2262 }
2263 for (my $i = 0; $i < $auth_deleted; $i++) {
2264 push(@list_deleted, $author);
2265 }
2266 }
2267 vcs_assign("added_lines", $added, @list_added);
2268 vcs_assign("removed_lines", $deleted, @list_deleted);
2269 }
Joe Perchesa8af2432009-12-14 18:00:49 -08002270}
2271
Joe Perches60db31a2009-12-14 18:00:50 -08002272sub vcs_file_blame {
Joe Perchesf5492662009-09-21 17:04:13 -07002273 my ($file) = @_;
2274
Joe Perches60db31a2009-12-14 18:00:50 -08002275 my @signers = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07002276 my @all_commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002277 my @commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002278 my $total_commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002279 my $total_lines;
Joe Perchesf5492662009-09-21 17:04:13 -07002280
Joe Perches683c6f82010-10-26 14:22:55 -07002281 $vcs_used = vcs_exists();
2282 return if (!$vcs_used);
Joe Perchesf5492662009-09-21 17:04:13 -07002283
Joe Perches63ab52d2010-10-26 14:22:51 -07002284 @all_commits = vcs_blame($file);
2285 @commits = uniq(@all_commits);
Joe Perchesa8af2432009-12-14 18:00:49 -08002286 $total_commits = @commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002287 $total_lines = @all_commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002288
Joe Perches683c6f82010-10-26 14:22:55 -07002289 if ($email_git_blame_signatures) {
2290 if (vcs_is_hg()) {
2291 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002292 my $commit_authors_ref;
2293 my $commit_signers_ref;
2294 my $stats_ref;
2295 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002296 my @commit_signers = ();
2297 my $commit = join(" -r ", @commits);
2298 my $cmd;
Joe Perchesf5492662009-09-21 17:04:13 -07002299
Joe Perches683c6f82010-10-26 14:22:55 -07002300 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2301 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
Joe Perches60db31a2009-12-14 18:00:50 -08002302
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002303 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2304 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2305 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches63ab52d2010-10-26 14:22:51 -07002306
Joe Perches683c6f82010-10-26 14:22:55 -07002307 push(@signers, @commit_signers);
2308 } else {
2309 foreach my $commit (@commits) {
2310 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002311 my $commit_authors_ref;
2312 my $commit_signers_ref;
2313 my $stats_ref;
2314 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002315 my @commit_signers = ();
2316 my $cmd;
2317
2318 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2319 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2320
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002321 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2322 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2323 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches683c6f82010-10-26 14:22:55 -07002324
2325 push(@signers, @commit_signers);
2326 }
2327 }
Joe Perchesf5492662009-09-21 17:04:13 -07002328 }
2329
Joe Perchesa8af2432009-12-14 18:00:49 -08002330 if ($from_filename) {
Joe Perches63ab52d2010-10-26 14:22:51 -07002331 if ($output_rolestats) {
2332 my @blame_signers;
Joe Perches683c6f82010-10-26 14:22:55 -07002333 if (vcs_is_hg()) {{ # Double brace for last exit
2334 my $commit_count;
2335 my @commit_signers = ();
2336 @commits = uniq(@commits);
2337 @commits = sort(@commits);
2338 my $commit = join(" -r ", @commits);
2339 my $cmd;
2340
2341 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2342 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2343
2344 my @lines = ();
2345
2346 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2347
2348 if (!$email_git_penguin_chiefs) {
2349 @lines = grep(!/${penguin_chiefs}/i, @lines);
2350 }
2351
2352 last if !@lines;
2353
2354 my @authors = ();
2355 foreach my $line (@lines) {
2356 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2357 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002358 $author = deduplicate_email($author);
2359 push(@authors, $author);
Joe Perches683c6f82010-10-26 14:22:55 -07002360 }
2361 }
2362
2363 save_commits_by_author(@lines) if ($interactive);
2364 save_commits_by_signer(@lines) if ($interactive);
2365
2366 push(@signers, @authors);
2367 }}
2368 else {
2369 foreach my $commit (@commits) {
2370 my $i;
2371 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2372 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2373 my @author = vcs_find_author($cmd);
2374 next if !@author;
Joe Perchesb9e23312010-10-26 14:22:58 -07002375
2376 my $formatted_author = deduplicate_email($author[0]);
2377
Joe Perches683c6f82010-10-26 14:22:55 -07002378 my $count = grep(/$commit/, @all_commits);
2379 for ($i = 0; $i < $count ; $i++) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002380 push(@blame_signers, $formatted_author);
Joe Perches683c6f82010-10-26 14:22:55 -07002381 }
Joe Perches63ab52d2010-10-26 14:22:51 -07002382 }
2383 }
2384 if (@blame_signers) {
2385 vcs_assign("authored lines", $total_lines, @blame_signers);
2386 }
2387 }
Joe Perchesb9e23312010-10-26 14:22:58 -07002388 foreach my $signer (@signers) {
2389 $signer = deduplicate_email($signer);
2390 }
Joe Perches60db31a2009-12-14 18:00:50 -08002391 vcs_assign("commits", $total_commits, @signers);
Joe Perchesa8af2432009-12-14 18:00:49 -08002392 } else {
Joe Perchesb9e23312010-10-26 14:22:58 -07002393 foreach my $signer (@signers) {
2394 $signer = deduplicate_email($signer);
2395 }
Joe Perches60db31a2009-12-14 18:00:50 -08002396 vcs_assign("modified commits", $total_commits, @signers);
Joe Perchesf5492662009-09-21 17:04:13 -07002397 }
Joe Perchescb7301c2009-04-07 20:40:12 -07002398}
2399
Joe Perches4cad35a2016-08-02 14:04:10 -07002400sub vcs_file_exists {
2401 my ($file) = @_;
2402
2403 my $exists;
2404
2405 my $vcs_used = vcs_exists();
2406 return 0 if (!$vcs_used);
2407
2408 my $cmd = $VCS_cmds{"file_exists_cmd"};
2409 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
Joe Perches8582fb52016-08-25 15:16:48 -07002410 $cmd .= " 2>&1";
Joe Perches4cad35a2016-08-02 14:04:10 -07002411 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2412
Joe Perches8582fb52016-08-25 15:16:48 -07002413 return 0 if ($? != 0);
2414
Joe Perches4cad35a2016-08-02 14:04:10 -07002415 return $exists;
2416}
2417
Tom Saegere1f75902017-11-17 15:27:42 -08002418sub vcs_list_files {
2419 my ($file) = @_;
2420
2421 my @lsfiles = ();
2422
2423 my $vcs_used = vcs_exists();
2424 return 0 if (!$vcs_used);
2425
2426 my $cmd = $VCS_cmds{"list_files_cmd"};
2427 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2428 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2429
2430 return () if ($? != 0);
2431
2432 return @lsfiles;
2433}
2434
Joe Perchescb7301c2009-04-07 20:40:12 -07002435sub uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002436 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002437
2438 my %saw;
2439 @parms = grep(!$saw{$_}++, @parms);
2440 return @parms;
2441}
2442
2443sub sort_and_uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002444 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002445
2446 my %saw;
2447 @parms = sort @parms;
2448 @parms = grep(!$saw{$_}++, @parms);
2449 return @parms;
2450}
2451
Joe Perches03372db2010-03-05 13:43:00 -08002452sub clean_file_emails {
2453 my (@file_emails) = @_;
2454 my @fmt_emails = ();
2455
2456 foreach my $email (@file_emails) {
2457 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2458 my ($name, $address) = parse_email($email);
2459 if ($name eq '"[,\.]"') {
2460 $name = "";
2461 }
2462
2463 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2464 if (@nw > 2) {
2465 my $first = $nw[@nw - 3];
2466 my $middle = $nw[@nw - 2];
2467 my $last = $nw[@nw - 1];
2468
2469 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2470 (length($first) == 2 && substr($first, -1) eq ".")) ||
2471 (length($middle) == 1 ||
2472 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2473 $name = "$first $middle $last";
2474 } else {
2475 $name = "$middle $last";
2476 }
2477 }
2478
2479 if (substr($name, -1) =~ /[,\.]/) {
2480 $name = substr($name, 0, length($name) - 1);
2481 } elsif (substr($name, -2) =~ /[,\.]"/) {
2482 $name = substr($name, 0, length($name) - 2) . '"';
2483 }
2484
2485 if (substr($name, 0, 1) =~ /[,\.]/) {
2486 $name = substr($name, 1, length($name) - 1);
2487 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2488 $name = '"' . substr($name, 2, length($name) - 2);
2489 }
2490
2491 my $fmt_email = format_email($name, $address, $email_usename);
2492 push(@fmt_emails, $fmt_email);
2493 }
2494 return @fmt_emails;
2495}
2496
Joe Perches3c7385b2009-12-14 18:00:46 -08002497sub merge_email {
2498 my @lines;
2499 my %saw;
2500
2501 for (@_) {
2502 my ($address, $role) = @$_;
2503 if (!$saw{$address}) {
2504 if ($output_roles) {
Joe Perches60db31a2009-12-14 18:00:50 -08002505 push(@lines, "$address ($role)");
Joe Perches3c7385b2009-12-14 18:00:46 -08002506 } else {
Joe Perches60db31a2009-12-14 18:00:50 -08002507 push(@lines, $address);
Joe Perches3c7385b2009-12-14 18:00:46 -08002508 }
2509 $saw{$address} = 1;
2510 }
2511 }
2512
2513 return @lines;
2514}
2515
Joe Perchescb7301c2009-04-07 20:40:12 -07002516sub output {
Joe Perchesa8af2432009-12-14 18:00:49 -08002517 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002518
2519 if ($output_multiline) {
2520 foreach my $line (@parms) {
2521 print("${line}\n");
2522 }
2523 } else {
2524 print(join($output_separator, @parms));
2525 print("\n");
2526 }
2527}
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002528
2529my $rfc822re;
2530
2531sub make_rfc822re {
2532# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2533# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2534# This regexp will only work on addresses which have had comments stripped
2535# and replaced with rfc822_lwsp.
2536
2537 my $specials = '()<>@,;:\\\\".\\[\\]';
2538 my $controls = '\\000-\\037\\177';
2539
2540 my $dtext = "[^\\[\\]\\r\\\\]";
2541 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2542
2543 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2544
2545# Use zero-width assertion to spot the limit of an atom. A simple
2546# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2547 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2548 my $word = "(?:$atom|$quoted_string)";
2549 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2550
2551 my $sub_domain = "(?:$atom|$domain_literal)";
2552 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2553
2554 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2555
2556 my $phrase = "$word*";
2557 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2558 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2559 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2560
2561 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2562 my $address = "(?:$mailbox|$group)";
2563
2564 return "$rfc822_lwsp*$address";
2565}
2566
2567sub rfc822_strip_comments {
2568 my $s = shift;
2569# Recursively remove comments, and replace with a single space. The simpler
2570# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2571# chars in atoms, for example.
2572
2573 while ($s =~ s/^((?:[^"\\]|\\.)*
2574 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2575 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2576 return $s;
2577}
2578
2579# valid: returns true if the parameter is an RFC822 valid address
2580#
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002581sub rfc822_valid {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002582 my $s = rfc822_strip_comments(shift);
2583
2584 if (!$rfc822re) {
2585 $rfc822re = make_rfc822re();
2586 }
2587
2588 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2589}
2590
2591# validlist: In scalar context, returns true if the parameter is an RFC822
2592# valid list of addresses.
2593#
2594# In list context, returns an empty list on failure (an invalid
2595# address was found); otherwise a list whose first element is the
2596# number of addresses found and whose remaining elements are the
2597# addresses. This is needed to disambiguate failure (invalid)
2598# from success with no addresses found, because an empty string is
2599# a valid list.
2600
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002601sub rfc822_validlist {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002602 my $s = rfc822_strip_comments(shift);
2603
2604 if (!$rfc822re) {
2605 $rfc822re = make_rfc822re();
2606 }
2607 # * null list items are valid according to the RFC
2608 # * the '1' business is to aid in distinguishing failure from no results
2609
2610 my @r;
2611 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2612 $s =~ m/^$rfc822_char*$/) {
Joe Perches5f2441e2009-06-16 15:34:02 -07002613 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
Joe Perches60db31a2009-12-14 18:00:50 -08002614 push(@r, $1);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002615 }
2616 return wantarray ? (scalar(@r), @r) : 1;
2617 }
Joe Perches60db31a2009-12-14 18:00:50 -08002618 return wantarray ? () : 0;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002619}