blob: 5a36354a38cc32601a6c07c417fe66cdb19a106e [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 &&
960 ($email_git || ($email_git_fallback &&
961 !$exact_pattern_match_hash{$file}))) {
962 vcs_file_signoffs($file);
963 }
964 if ($email && $email_git_blame) {
965 vcs_file_blame($file);
966 }
967 }
968
Joe Perches683c6f82010-10-26 14:22:55 -0700969 if ($email) {
970 foreach my $chief (@penguin_chief) {
971 if ($chief =~ m/^(.*):(.*)/) {
972 my $email_address;
973
974 $email_address = format_email($1, $2, $email_usename);
975 if ($email_git_penguin_chiefs) {
976 push(@email_to, [$email_address, 'chief penguin']);
977 } else {
978 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
979 }
980 }
981 }
982
983 foreach my $email (@file_emails) {
984 my ($name, $address) = parse_email($email);
985
986 my $tmp_email = format_email($name, $address, $email_usename);
987 push_email_address($tmp_email, '');
988 add_role($tmp_email, 'in file');
989 }
990 }
991
Douglas Anderson0ef82fc2020-02-20 20:04:12 -0800992 foreach my $fix (@fixes) {
993 vcs_add_commit_signers($fix, "blamed_fixes");
994 }
995
Joe Perches683c6f82010-10-26 14:22:55 -0700996 my @to = ();
997 if ($email || $email_list) {
998 if ($email) {
999 @to = (@to, @email_to);
1000 }
1001 if ($email_list) {
1002 @to = (@to, @list_to);
1003 }
1004 }
1005
Joe Perches6ef1c522010-10-26 14:22:56 -07001006 if ($interactive) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001007 @to = interactive_get_maintainers(\@to);
Joe Perches6ef1c522010-10-26 14:22:56 -07001008 }
Joe Perches683c6f82010-10-26 14:22:55 -07001009
1010 return @to;
1011}
1012
Joe Perchescb7301c2009-04-07 20:40:12 -07001013sub file_match_pattern {
1014 my ($file, $pattern) = @_;
1015 if (substr($pattern, -1) eq "/") {
1016 if ($file =~ m@^$pattern@) {
1017 return 1;
1018 }
1019 } else {
1020 if ($file =~ m@^$pattern@) {
1021 my $s1 = ($file =~ tr@/@@);
1022 my $s2 = ($pattern =~ tr@/@@);
1023 if ($s1 == $s2) {
1024 return 1;
1025 }
1026 }
1027 }
1028 return 0;
1029}
1030
1031sub usage {
1032 print <<EOT;
1033usage: $P [options] patchfile
Joe Perches870020f2009-07-29 15:04:28 -07001034 $P [options] -f file|directory
Joe Perchescb7301c2009-04-07 20:40:12 -07001035version: $V
1036
1037MAINTAINER field selection options:
1038 --email => print email address(es) if any
1039 --git => include recent git \*-by: signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001040 --git-all-signature-types => include signers regardless of signature type
Joe Perches683c6f82010-10-26 14:22:55 -07001041 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
Joe Perchese3e9d112010-10-26 14:22:53 -07001042 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
Joe Perchescb7301c2009-04-07 20:40:12 -07001043 --git-chief-penguins => include ${penguin_chiefs}
Joe Perchese4d26b02010-05-24 14:33:17 -07001044 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1045 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1046 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
Joe Perchesf5492662009-09-21 17:04:13 -07001047 --git-blame => use git blame to find modified commits for patch or file
Brian Norris3cbcca82015-11-06 16:30:41 -08001048 --git-blame-signatures => when used with --git-blame, also include all commit signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001049 --git-since => git history to use (default: $email_git_since)
1050 --hg-since => hg history to use (default: $email_hg_since)
Florian Micklerdace8e32010-10-26 14:22:54 -07001051 --interactive => display a menu (mostly useful if used with the --git option)
Joe Perchescb7301c2009-04-07 20:40:12 -07001052 --m => include maintainer(s) if any
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001053 --r => include reviewer(s) if any
Joe Perchescb7301c2009-04-07 20:40:12 -07001054 --n => include name 'Full Name <addr\@domain.tld>'
1055 --l => include list(s) if any
Joe Perches49662502019-07-16 16:27:09 -07001056 --moderated => include moderated lists(s) if any (default: true)
1057 --s => include subscriber only list(s) if any (default: false)
Joe Perches11ecf532009-09-21 17:04:22 -07001058 --remove-duplicates => minimize duplicate email names/addresses
Joe Perches3c7385b2009-12-14 18:00:46 -08001059 --roles => show roles (status:subsystem, git-signer, list, etc...)
1060 --rolestats => show roles and statistics (commits/total_commits, %)
Joe Perches03372db2010-03-05 13:43:00 -08001061 --file-emails => add email addresses found in -f file (default: 0 (off))
Joe Perches2f5bd3432019-12-04 16:50:29 -08001062 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
Joe Perchescb7301c2009-04-07 20:40:12 -07001063 --scm => print SCM tree(s) if any
1064 --status => print status if any
1065 --subsystem => print subsystem name if any
1066 --web => print website(s) if any
1067
1068Output type options:
1069 --separator [, ] => separator for multiple entries on 1 line
Joe Perches42498312009-09-21 17:04:21 -07001070 using --separator also sets --nomultiline if --separator is not [, ]
Joe Perchescb7301c2009-04-07 20:40:12 -07001071 --multiline => print 1 entry per line
1072
Joe Perchescb7301c2009-04-07 20:40:12 -07001073Other options:
Joe Perches3fb55652009-09-21 17:04:17 -07001074 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
Joe Perchesb9e23312010-10-26 14:22:58 -07001075 --keywords => scan patch for keywords (default: $keywords)
1076 --sections => print all of the subsystem sections with pattern matches
Joe Perches03aed212016-12-12 16:45:59 -08001077 --letters => print all matching 'letter' types from all matching sections
Joe Perchesb9e23312010-10-26 14:22:58 -07001078 --mailmap => use .mailmap file (default: $email_use_mailmap)
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001079 --no-tree => run without a kernel tree
Tom Saegere1f75902017-11-17 15:27:42 -08001080 --self-test => show potential issues with MAINTAINERS file content
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001081 --version => show version
Joe Perchescb7301c2009-04-07 20:40:12 -07001082 --help => show this help information
1083
Joe Perches3fb55652009-09-21 17:04:17 -07001084Default options:
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001085 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1086 --pattern-depth=0 --remove-duplicates --rolestats]
Joe Perches3fb55652009-09-21 17:04:17 -07001087
Joe Perches870020f2009-07-29 15:04:28 -07001088Notes:
1089 Using "-f directory" may give unexpected results:
Joe Perchesf5492662009-09-21 17:04:13 -07001090 Used with "--git", git signators for _all_ files in and below
1091 directory are examined as git recurses directories.
1092 Any specified X: (exclude) pattern matches are _not_ ignored.
1093 Used with "--nogit", directory is used as a pattern match,
Joe Perches60db31a2009-12-14 18:00:50 -08001094 no individual file within the directory or subdirectory
1095 is matched.
Joe Perchesf5492662009-09-21 17:04:13 -07001096 Used with "--git-blame", does not iterate all files in directory
1097 Using "--git-blame" is slow and may add old committers and authors
1098 that are no longer active maintainers to the output.
Joe Perches3c7385b2009-12-14 18:00:46 -08001099 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1100 other automated tools that expect only ["name"] <email address>
1101 may not work because of additional output after <email address>.
1102 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1103 not the percentage of the entire file authored. # of commits is
1104 not a good measure of amount of code authored. 1 major commit may
1105 contain a thousand lines, 5 trivial commits may modify a single line.
Joe Perches60db31a2009-12-14 18:00:50 -08001106 If git is not installed, but mercurial (hg) is installed and an .hg
1107 repository exists, the following options apply to mercurial:
1108 --git,
1109 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1110 --git-blame
1111 Use --hg-since not --git-since to control date selection
Joe Perches368669d2010-05-24 14:33:19 -07001112 File ".get_maintainer.conf", if it exists in the linux kernel source root
1113 directory, can change whatever get_maintainer defaults are desired.
1114 Entries in this file can be any command line argument.
1115 This file is prepended to any additional command line arguments.
1116 Multiple lines and # comments are allowed.
Brian Norrisb1312bf2015-11-06 16:30:46 -08001117 Most options have both positive and negative forms.
1118 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1119
Joe Perchescb7301c2009-04-07 20:40:12 -07001120EOT
1121}
1122
1123sub top_of_kernel_tree {
Joe Perches47abc722010-10-26 14:22:57 -07001124 my ($lk_path) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07001125
Joe Perches47abc722010-10-26 14:22:57 -07001126 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1127 $lk_path .= "/";
1128 }
1129 if ( (-f "${lk_path}COPYING")
1130 && (-f "${lk_path}CREDITS")
1131 && (-f "${lk_path}Kbuild")
Joe Perches6f7d98e2017-08-04 21:45:48 -07001132 && (-e "${lk_path}MAINTAINERS")
Joe Perches47abc722010-10-26 14:22:57 -07001133 && (-f "${lk_path}Makefile")
1134 && (-f "${lk_path}README")
1135 && (-d "${lk_path}Documentation")
1136 && (-d "${lk_path}arch")
1137 && (-d "${lk_path}include")
1138 && (-d "${lk_path}drivers")
1139 && (-d "${lk_path}fs")
1140 && (-d "${lk_path}init")
1141 && (-d "${lk_path}ipc")
1142 && (-d "${lk_path}kernel")
1143 && (-d "${lk_path}lib")
1144 && (-d "${lk_path}scripts")) {
1145 return 1;
1146 }
1147 return 0;
Joe Perchescb7301c2009-04-07 20:40:12 -07001148}
1149
Joe Perches0e70e832009-09-21 17:04:20 -07001150sub parse_email {
1151 my ($formatted_email) = @_;
1152
1153 my $name = "";
1154 my $address = "";
1155
Joe Perches11ecf532009-09-21 17:04:22 -07001156 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001157 $name = $1;
1158 $address = $2;
Joe Perches11ecf532009-09-21 17:04:22 -07001159 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001160 $address = $1;
Joe Perchesb7816552009-09-21 17:04:24 -07001161 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001162 $address = $1;
1163 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001164
1165 $name =~ s/^\s+|\s+$//g;
Joe Perchesd7895042009-06-16 15:34:02 -07001166 $name =~ s/^\"|\"$//g;
Joe Perches0e70e832009-09-21 17:04:20 -07001167 $address =~ s/^\s+|\s+$//g;
Joe Perchescb7301c2009-04-07 20:40:12 -07001168
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001169 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perchescb7301c2009-04-07 20:40:12 -07001170 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
Joe Perches0e70e832009-09-21 17:04:20 -07001171 $name = "\"$name\"";
Joe Perchescb7301c2009-04-07 20:40:12 -07001172 }
Joe Perches0e70e832009-09-21 17:04:20 -07001173
1174 return ($name, $address);
1175}
1176
1177sub format_email {
Joe Perchesa8af2432009-12-14 18:00:49 -08001178 my ($name, $address, $usename) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001179
1180 my $formatted_email;
1181
1182 $name =~ s/^\s+|\s+$//g;
1183 $name =~ s/^\"|\"$//g;
1184 $address =~ s/^\s+|\s+$//g;
1185
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001186 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perches0e70e832009-09-21 17:04:20 -07001187 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1188 $name = "\"$name\"";
1189 }
1190
Joe Perchesa8af2432009-12-14 18:00:49 -08001191 if ($usename) {
Joe Perches0e70e832009-09-21 17:04:20 -07001192 if ("$name" eq "") {
1193 $formatted_email = "$address";
1194 } else {
Joe Perchesa8af2432009-12-14 18:00:49 -08001195 $formatted_email = "$name <$address>";
Joe Perches0e70e832009-09-21 17:04:20 -07001196 }
1197 } else {
1198 $formatted_email = $address;
1199 }
1200
Joe Perchescb7301c2009-04-07 20:40:12 -07001201 return $formatted_email;
1202}
1203
Joe Perches272a8972010-01-08 14:42:48 -08001204sub find_first_section {
1205 my $index = 0;
1206
1207 while ($index < @typevalue) {
1208 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001209 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perches272a8972010-01-08 14:42:48 -08001210 last;
1211 }
1212 $index++;
1213 }
1214
1215 return $index;
1216}
1217
Joe Perchesb7816552009-09-21 17:04:24 -07001218sub find_starting_index {
Joe Perchesb7816552009-09-21 17:04:24 -07001219 my ($index) = @_;
1220
1221 while ($index > 0) {
1222 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001223 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001224 last;
1225 }
1226 $index--;
1227 }
1228
1229 return $index;
1230}
1231
1232sub find_ending_index {
1233 my ($index) = @_;
1234
1235 while ($index < @typevalue) {
1236 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001237 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001238 last;
1239 }
1240 $index++;
1241 }
1242
1243 return $index;
1244}
1245
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001246sub get_subsystem_name {
1247 my ($index) = @_;
1248
1249 my $start = find_starting_index($index);
1250
1251 my $subsystem = $typevalue[$start];
1252 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1253 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1254 $subsystem =~ s/\s*$//;
1255 $subsystem = $subsystem . "...";
1256 }
1257 return $subsystem;
1258}
1259
Joe Perches3c7385b2009-12-14 18:00:46 -08001260sub get_maintainer_role {
1261 my ($index) = @_;
1262
1263 my $i;
1264 my $start = find_starting_index($index);
1265 my $end = find_ending_index($index);
1266
Joe Perches0ede2742012-03-23 15:01:56 -07001267 my $role = "unknown";
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001268 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001269
1270 for ($i = $start + 1; $i < $end; $i++) {
1271 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001272 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001273 my $ptype = $1;
1274 my $pvalue = $2;
1275 if ($ptype eq "S") {
1276 $role = $pvalue;
1277 }
1278 }
1279 }
1280
1281 $role = lc($role);
1282 if ($role eq "supported") {
1283 $role = "supporter";
1284 } elsif ($role eq "maintained") {
1285 $role = "maintainer";
1286 } elsif ($role eq "odd fixes") {
1287 $role = "odd fixer";
1288 } elsif ($role eq "orphan") {
1289 $role = "orphan minder";
1290 } elsif ($role eq "obsolete") {
1291 $role = "obsolete minder";
1292 } elsif ($role eq "buried alive in reporters") {
1293 $role = "chief penguin";
1294 }
1295
1296 return $role . ":" . $subsystem;
1297}
1298
1299sub get_list_role {
1300 my ($index) = @_;
1301
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001302 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001303
1304 if ($subsystem eq "THE REST") {
1305 $subsystem = "";
1306 }
1307
1308 return $subsystem;
1309}
1310
Joe Perchescb7301c2009-04-07 20:40:12 -07001311sub add_categories {
1312 my ($index) = @_;
1313
Joe Perchesb7816552009-09-21 17:04:24 -07001314 my $i;
1315 my $start = find_starting_index($index);
1316 my $end = find_ending_index($index);
1317
1318 push(@subsystem, $typevalue[$start]);
1319
1320 for ($i = $start + 1; $i < $end; $i++) {
1321 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001322 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001323 my $ptype = $1;
1324 my $pvalue = $2;
1325 if ($ptype eq "L") {
Joe Perches290603c2009-06-16 15:33:58 -07001326 my $list_address = $pvalue;
1327 my $list_additional = "";
Joe Perches3c7385b2009-12-14 18:00:46 -08001328 my $list_role = get_list_role($i);
1329
1330 if ($list_role ne "") {
1331 $list_role = ":" . $list_role;
1332 }
Joe Perches290603c2009-06-16 15:33:58 -07001333 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1334 $list_address = $1;
1335 $list_additional = $2;
1336 }
Joe Perchesbdf7c682009-06-16 15:33:59 -07001337 if ($list_additional =~ m/subscribers-only/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001338 if ($email_subscriber_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001339 if (!$hash_list_to{lc($list_address)}) {
1340 $hash_list_to{lc($list_address)} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001341 push(@list_to, [$list_address,
1342 "subscriber list${list_role}"]);
1343 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001344 }
1345 } else {
1346 if ($email_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001347 if (!$hash_list_to{lc($list_address)}) {
Richard Weinberger728f5a92012-03-23 15:01:56 -07001348 if ($list_additional =~ m/moderated/) {
Joe Perches49662502019-07-16 16:27:09 -07001349 if ($email_moderated_list) {
1350 $hash_list_to{lc($list_address)} = 1;
1351 push(@list_to, [$list_address,
1352 "moderated list${list_role}"]);
1353 }
Richard Weinberger728f5a92012-03-23 15:01:56 -07001354 } else {
Joe Perches49662502019-07-16 16:27:09 -07001355 $hash_list_to{lc($list_address)} = 1;
Richard Weinberger728f5a92012-03-23 15:01:56 -07001356 push(@list_to, [$list_address,
1357 "open list${list_role}"]);
1358 }
Joe Perches683c6f82010-10-26 14:22:55 -07001359 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001360 }
1361 }
1362 } elsif ($ptype eq "M") {
Joe Perches0e70e832009-09-21 17:04:20 -07001363 if ($email_maintainer) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001364 my $role = get_maintainer_role($i);
1365 push_email_addresses($pvalue, $role);
Joe Perchescb7301c2009-04-07 20:40:12 -07001366 }
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001367 } elsif ($ptype eq "R") {
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001368 if ($email_reviewer) {
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001369 my $subsystem = get_subsystem_name($i);
1370 push_email_addresses($pvalue, "reviewer:$subsystem");
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001371 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001372 } elsif ($ptype eq "T") {
1373 push(@scm, $pvalue);
1374 } elsif ($ptype eq "W") {
1375 push(@web, $pvalue);
1376 } elsif ($ptype eq "S") {
1377 push(@status, $pvalue);
1378 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001379 }
1380 }
1381}
1382
Joe Perches11ecf532009-09-21 17:04:22 -07001383sub email_inuse {
1384 my ($name, $address) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001385
Joe Perches11ecf532009-09-21 17:04:22 -07001386 return 1 if (($name eq "") && ($address eq ""));
Joe Perches6ef1c522010-10-26 14:22:56 -07001387 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1388 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
Joe Perches11ecf532009-09-21 17:04:22 -07001389
Joe Perches0e70e832009-09-21 17:04:20 -07001390 return 0;
1391}
1392
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001393sub push_email_address {
Joe Perches3c7385b2009-12-14 18:00:46 -08001394 my ($line, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001395
Joe Perches0e70e832009-09-21 17:04:20 -07001396 my ($name, $address) = parse_email($line);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001397
Joe Perchesb7816552009-09-21 17:04:24 -07001398 if ($address eq "") {
1399 return 0;
1400 }
1401
Joe Perches11ecf532009-09-21 17:04:22 -07001402 if (!$email_remove_duplicates) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001403 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perches11ecf532009-09-21 17:04:22 -07001404 } elsif (!email_inuse($name, $address)) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001405 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perchesfae99202010-10-26 14:22:58 -07001406 $email_hash_name{lc($name)}++ if ($name ne "");
Joe Perches6ef1c522010-10-26 14:22:56 -07001407 $email_hash_address{lc($address)}++;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001408 }
Joe Perchesb7816552009-09-21 17:04:24 -07001409
1410 return 1;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001411}
1412
1413sub push_email_addresses {
Joe Perches3c7385b2009-12-14 18:00:46 -08001414 my ($address, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001415
1416 my @address_list = ();
1417
Joe Perches5f2441e2009-06-16 15:34:02 -07001418 if (rfc822_valid($address)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001419 push_email_address($address, $role);
Joe Perches5f2441e2009-06-16 15:34:02 -07001420 } elsif (@address_list = rfc822_validlist($address)) {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001421 my $array_count = shift(@address_list);
1422 while (my $entry = shift(@address_list)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001423 push_email_address($entry, $role);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001424 }
Joe Perches5f2441e2009-06-16 15:34:02 -07001425 } else {
Joe Perches3c7385b2009-12-14 18:00:46 -08001426 if (!push_email_address($address, $role)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001427 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1428 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001429 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001430}
1431
Joe Perches3c7385b2009-12-14 18:00:46 -08001432sub add_role {
1433 my ($line, $role) = @_;
1434
1435 my ($name, $address) = parse_email($line);
Joe Perchesa8af2432009-12-14 18:00:49 -08001436 my $email = format_email($name, $address, $email_usename);
Joe Perches3c7385b2009-12-14 18:00:46 -08001437
1438 foreach my $entry (@email_to) {
1439 if ($email_remove_duplicates) {
1440 my ($entry_name, $entry_address) = parse_email($entry->[0]);
Joe Perches03372db2010-03-05 13:43:00 -08001441 if (($name eq $entry_name || $address eq $entry_address)
1442 && ($role eq "" || !($entry->[1] =~ m/$role/))
1443 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001444 if ($entry->[1] eq "") {
1445 $entry->[1] = "$role";
1446 } else {
1447 $entry->[1] = "$entry->[1],$role";
1448 }
1449 }
1450 } else {
Joe Perches03372db2010-03-05 13:43:00 -08001451 if ($email eq $entry->[0]
1452 && ($role eq "" || !($entry->[1] =~ m/$role/))
1453 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001454 if ($entry->[1] eq "") {
1455 $entry->[1] = "$role";
1456 } else {
1457 $entry->[1] = "$entry->[1],$role";
1458 }
1459 }
1460 }
1461 }
1462}
1463
Joe Perchescb7301c2009-04-07 20:40:12 -07001464sub which {
1465 my ($bin) = @_;
1466
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001467 foreach my $path (split(/:/, $ENV{PATH})) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001468 if (-e "$path/$bin") {
1469 return "$path/$bin";
1470 }
1471 }
1472
1473 return "";
1474}
1475
Joe Perchesbcde44e2010-10-26 14:22:53 -07001476sub which_conf {
1477 my ($conf) = @_;
1478
1479 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1480 if (-e "$path/$conf") {
1481 return "$path/$conf";
1482 }
1483 }
1484
1485 return "";
1486}
1487
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001488sub mailmap_email {
Joe Perchesb9e23312010-10-26 14:22:58 -07001489 my ($line) = @_;
Joe Perches8cbb3a72009-09-21 17:04:21 -07001490
Joe Perches47abc722010-10-26 14:22:57 -07001491 my ($name, $address) = parse_email($line);
1492 my $email = format_email($name, $address, 1);
1493 my $real_name = $name;
1494 my $real_address = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001495
Joe Perches47abc722010-10-26 14:22:57 -07001496 if (exists $mailmap->{names}->{$email} ||
1497 exists $mailmap->{addresses}->{$email}) {
1498 if (exists $mailmap->{names}->{$email}) {
1499 $real_name = $mailmap->{names}->{$email};
Joe Perches8cbb3a72009-09-21 17:04:21 -07001500 }
Joe Perches47abc722010-10-26 14:22:57 -07001501 if (exists $mailmap->{addresses}->{$email}) {
1502 $real_address = $mailmap->{addresses}->{$email};
1503 }
1504 } else {
1505 if (exists $mailmap->{names}->{$address}) {
1506 $real_name = $mailmap->{names}->{$address};
1507 }
1508 if (exists $mailmap->{addresses}->{$address}) {
1509 $real_address = $mailmap->{addresses}->{$address};
1510 }
1511 }
1512 return format_email($real_name, $real_address, 1);
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001513}
1514
1515sub mailmap {
1516 my (@addresses) = @_;
1517
Joe Perchesb9e23312010-10-26 14:22:58 -07001518 my @mapped_emails = ();
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001519 foreach my $line (@addresses) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001520 push(@mapped_emails, mailmap_email($line));
Joe Perches8cbb3a72009-09-21 17:04:21 -07001521 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001522 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1523 return @mapped_emails;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001524}
1525
1526sub merge_by_realname {
Joe Perches47abc722010-10-26 14:22:57 -07001527 my %address_map;
1528 my (@emails) = @_;
Joe Perchesb9e23312010-10-26 14:22:58 -07001529
Joe Perches47abc722010-10-26 14:22:57 -07001530 foreach my $email (@emails) {
1531 my ($name, $address) = parse_email($email);
Joe Perchesb9e23312010-10-26 14:22:58 -07001532 if (exists $address_map{$name}) {
Joe Perches47abc722010-10-26 14:22:57 -07001533 $address = $address_map{$name};
Joe Perchesb9e23312010-10-26 14:22:58 -07001534 $email = format_email($name, $address, 1);
1535 } else {
1536 $address_map{$name} = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001537 }
Joe Perches47abc722010-10-26 14:22:57 -07001538 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07001539}
1540
Joe Perches60db31a2009-12-14 18:00:50 -08001541sub git_execute_cmd {
1542 my ($cmd) = @_;
1543 my @lines = ();
Joe Perchescb7301c2009-04-07 20:40:12 -07001544
Joe Perches60db31a2009-12-14 18:00:50 -08001545 my $output = `$cmd`;
1546 $output =~ s/^\s*//gm;
1547 @lines = split("\n", $output);
1548
1549 return @lines;
Joe Perchesa8af2432009-12-14 18:00:49 -08001550}
1551
Joe Perches60db31a2009-12-14 18:00:50 -08001552sub hg_execute_cmd {
Joe Perchesa8af2432009-12-14 18:00:49 -08001553 my ($cmd) = @_;
Joe Perches60db31a2009-12-14 18:00:50 -08001554 my @lines = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001555
Joe Perches60db31a2009-12-14 18:00:50 -08001556 my $output = `$cmd`;
1557 @lines = split("\n", $output);
1558
1559 return @lines;
1560}
1561
Joe Perches683c6f82010-10-26 14:22:55 -07001562sub extract_formatted_signatures {
1563 my (@signature_lines) = @_;
1564
1565 my @type = @signature_lines;
1566
1567 s/\s*(.*):.*/$1/ for (@type);
1568
1569 # cut -f2- -d":"
1570 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1571
1572## Reformat email addresses (with names) to avoid badly written signatures
1573
1574 foreach my $signer (@signature_lines) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001575 $signer = deduplicate_email($signer);
Joe Perches683c6f82010-10-26 14:22:55 -07001576 }
1577
1578 return (\@type, \@signature_lines);
1579}
1580
Joe Perches60db31a2009-12-14 18:00:50 -08001581sub vcs_find_signers {
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001582 my ($cmd, $file) = @_;
Joe Perchesa8af2432009-12-14 18:00:49 -08001583 my $commits;
Joe Perches683c6f82010-10-26 14:22:55 -07001584 my @lines = ();
1585 my @signatures = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001586 my @authors = ();
1587 my @stats = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001588
Joe Perches60db31a2009-12-14 18:00:50 -08001589 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
Joe Perchescb7301c2009-04-07 20:40:12 -07001590
Joe Perches60db31a2009-12-14 18:00:50 -08001591 my $pattern = $VCS_cmds{"commit_pattern"};
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001592 my $author_pattern = $VCS_cmds{"author_pattern"};
1593 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1594
1595 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
Joe Perchescb7301c2009-04-07 20:40:12 -07001596
Joe Perches60db31a2009-12-14 18:00:50 -08001597 $commits = grep(/$pattern/, @lines); # of commits
Joe Perchesafa81ee2009-07-29 15:04:28 -07001598
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001599 @authors = grep(/$author_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001600 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001601 @stats = grep(/$stat_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001602
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001603# print("stats: <@stats>\n");
1604
1605 return (0, \@signatures, \@authors, \@stats) if !@signatures;
Joe Perches683c6f82010-10-26 14:22:55 -07001606
1607 save_commits_by_author(@lines) if ($interactive);
1608 save_commits_by_signer(@lines) if ($interactive);
1609
Joe Perches0e70e832009-09-21 17:04:20 -07001610 if (!$email_git_penguin_chiefs) {
Joe Perches683c6f82010-10-26 14:22:55 -07001611 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
Joe Perches0e70e832009-09-21 17:04:20 -07001612 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001613
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001614 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
Joe Perches683c6f82010-10-26 14:22:55 -07001615 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
Joe Perches63ab52d2010-10-26 14:22:51 -07001616
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001617 return ($commits, $signers_ref, $authors_ref, \@stats);
Joe Perchesa8af2432009-12-14 18:00:49 -08001618}
1619
Joe Perches63ab52d2010-10-26 14:22:51 -07001620sub vcs_find_author {
1621 my ($cmd) = @_;
1622 my @lines = ();
1623
1624 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1625
1626 if (!$email_git_penguin_chiefs) {
1627 @lines = grep(!/${penguin_chiefs}/i, @lines);
1628 }
1629
1630 return @lines if !@lines;
1631
Joe Perches683c6f82010-10-26 14:22:55 -07001632 my @authors = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07001633 foreach my $line (@lines) {
Joe Perches683c6f82010-10-26 14:22:55 -07001634 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1635 my $author = $1;
1636 my ($name, $address) = parse_email($author);
1637 $author = format_email($name, $address, 1);
1638 push(@authors, $author);
1639 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001640 }
1641
Joe Perches683c6f82010-10-26 14:22:55 -07001642 save_commits_by_author(@lines) if ($interactive);
1643 save_commits_by_signer(@lines) if ($interactive);
1644
1645 return @authors;
Joe Perches63ab52d2010-10-26 14:22:51 -07001646}
1647
Joe Perches60db31a2009-12-14 18:00:50 -08001648sub vcs_save_commits {
1649 my ($cmd) = @_;
1650 my @lines = ();
1651 my @commits = ();
1652
1653 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1654
1655 foreach my $line (@lines) {
1656 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1657 push(@commits, $1);
1658 }
1659 }
1660
1661 return @commits;
1662}
1663
1664sub vcs_blame {
1665 my ($file) = @_;
1666 my $cmd;
1667 my @commits = ();
1668
1669 return @commits if (!(-f $file));
1670
1671 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1672 my @all_commits = ();
1673
1674 $cmd = $VCS_cmds{"blame_file_cmd"};
1675 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1676 @all_commits = vcs_save_commits($cmd);
1677
1678 foreach my $file_range_diff (@range) {
1679 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1680 my $diff_file = $1;
1681 my $diff_start = $2;
1682 my $diff_length = $3;
1683 next if ("$file" ne "$diff_file");
1684 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1685 push(@commits, $all_commits[$i]);
1686 }
1687 }
1688 } elsif (@range) {
1689 foreach my $file_range_diff (@range) {
1690 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1691 my $diff_file = $1;
1692 my $diff_start = $2;
1693 my $diff_length = $3;
1694 next if ("$file" ne "$diff_file");
1695 $cmd = $VCS_cmds{"blame_range_cmd"};
1696 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1697 push(@commits, vcs_save_commits($cmd));
1698 }
1699 } else {
1700 $cmd = $VCS_cmds{"blame_file_cmd"};
1701 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1702 @commits = vcs_save_commits($cmd);
1703 }
1704
Joe Perches63ab52d2010-10-26 14:22:51 -07001705 foreach my $commit (@commits) {
1706 $commit =~ s/^\^//g;
1707 }
1708
Joe Perches60db31a2009-12-14 18:00:50 -08001709 return @commits;
1710}
1711
1712my $printed_novcs = 0;
1713sub vcs_exists {
1714 %VCS_cmds = %VCS_cmds_git;
1715 return 1 if eval $VCS_cmds{"available"};
1716 %VCS_cmds = %VCS_cmds_hg;
Joe Perches683c6f82010-10-26 14:22:55 -07001717 return 2 if eval $VCS_cmds{"available"};
Joe Perches60db31a2009-12-14 18:00:50 -08001718 %VCS_cmds = ();
1719 if (!$printed_novcs) {
1720 warn("$P: No supported VCS found. Add --nogit to options?\n");
1721 warn("Using a git repository produces better results.\n");
1722 warn("Try Linus Torvalds' latest git repository using:\n");
Ralf Thielow3d1c2f72011-08-25 15:59:07 -07001723 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
Joe Perches60db31a2009-12-14 18:00:50 -08001724 $printed_novcs = 1;
1725 }
1726 return 0;
1727}
1728
Joe Perches683c6f82010-10-26 14:22:55 -07001729sub vcs_is_git {
Joe Perchesb9e23312010-10-26 14:22:58 -07001730 vcs_exists();
Joe Perches683c6f82010-10-26 14:22:55 -07001731 return $vcs_used == 1;
1732}
1733
1734sub vcs_is_hg {
1735 return $vcs_used == 2;
1736}
1737
Joe Perches2f5bd3432019-12-04 16:50:29 -08001738sub vcs_add_commit_signers {
1739 return if (!vcs_exists());
1740
1741 my ($commit, $desc) = @_;
1742 my $commit_count = 0;
1743 my $commit_authors_ref;
1744 my $commit_signers_ref;
1745 my $stats_ref;
1746 my @commit_authors = ();
1747 my @commit_signers = ();
1748 my $cmd;
1749
1750 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1751 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1752
1753 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1754 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1755 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1756
1757 foreach my $signer (@commit_signers) {
1758 $signer = deduplicate_email($signer);
1759 }
1760
1761 vcs_assign($desc, 1, @commit_signers);
1762}
1763
Joe Perches6ef1c522010-10-26 14:22:56 -07001764sub interactive_get_maintainers {
Joe Perches683c6f82010-10-26 14:22:55 -07001765 my ($list_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07001766 my @list = @$list_ref;
1767
Joe Perches683c6f82010-10-26 14:22:55 -07001768 vcs_exists();
Florian Micklerdace8e32010-10-26 14:22:54 -07001769
1770 my %selected;
Joe Perches683c6f82010-10-26 14:22:55 -07001771 my %authored;
1772 my %signed;
Florian Micklerdace8e32010-10-26 14:22:54 -07001773 my $count = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001774 my $maintained = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001775 foreach my $entry (@list) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001776 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1777 $selected{$count} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001778 $authored{$count} = 0;
1779 $signed{$count} = 0;
1780 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001781 }
1782
1783 #menu loop
Joe Perches683c6f82010-10-26 14:22:55 -07001784 my $done = 0;
1785 my $print_options = 0;
1786 my $redraw = 1;
1787 while (!$done) {
1788 $count = 0;
1789 if ($redraw) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001790 printf STDERR "\n%1s %2s %-65s",
1791 "*", "#", "email/list and role:stats";
1792 if ($email_git ||
1793 ($email_git_fallback && !$maintained) ||
1794 $email_git_blame) {
1795 print STDERR "auth sign";
1796 }
1797 print STDERR "\n";
Joe Perches683c6f82010-10-26 14:22:55 -07001798 foreach my $entry (@list) {
1799 my $email = $entry->[0];
1800 my $role = $entry->[1];
1801 my $sel = "";
1802 $sel = "*" if ($selected{$count});
1803 my $commit_author = $commit_author_hash{$email};
1804 my $commit_signer = $commit_signer_hash{$email};
1805 my $authored = 0;
1806 my $signed = 0;
1807 $authored++ for (@{$commit_author});
1808 $signed++ for (@{$commit_signer});
1809 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1810 printf STDERR "%4d %4d", $authored, $signed
1811 if ($authored > 0 || $signed > 0);
1812 printf STDERR "\n %s\n", $role;
1813 if ($authored{$count}) {
1814 my $commit_author = $commit_author_hash{$email};
1815 foreach my $ref (@{$commit_author}) {
1816 print STDERR " Author: @{$ref}[1]\n";
Florian Micklerdace8e32010-10-26 14:22:54 -07001817 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001818 }
Joe Perches683c6f82010-10-26 14:22:55 -07001819 if ($signed{$count}) {
1820 my $commit_signer = $commit_signer_hash{$email};
1821 foreach my $ref (@{$commit_signer}) {
1822 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1823 }
1824 }
1825
1826 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001827 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001828 }
Joe Perches683c6f82010-10-26 14:22:55 -07001829 my $date_ref = \$email_git_since;
1830 $date_ref = \$email_hg_since if (vcs_is_hg());
1831 if ($print_options) {
1832 $print_options = 0;
1833 if (vcs_exists()) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001834 print STDERR <<EOT
1835
1836Version Control options:
1837g use git history [$email_git]
1838gf use git-fallback [$email_git_fallback]
1839b use git blame [$email_git_blame]
1840bs use blame signatures [$email_git_blame_signatures]
1841c# minimum commits [$email_git_min_signatures]
1842%# min percent [$email_git_min_percent]
1843d# history to use [$$date_ref]
1844x# max maintainers [$email_git_max_maintainers]
1845t all signature types [$email_git_all_signature_types]
1846m use .mailmap [$email_use_mailmap]
1847EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001848 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001849 print STDERR <<EOT
1850
1851Additional options:
18520 toggle all
1853tm toggle maintainers
1854tg toggle git entries
1855tl toggle open list entries
1856ts toggle subscriber list entries
Joe Perches0c78c012020-06-04 16:50:01 -07001857f emails in file [$email_file_emails]
Joe Perchesb9e23312010-10-26 14:22:58 -07001858k keywords in file [$keywords]
1859r remove duplicates [$email_remove_duplicates]
1860p# pattern match depth [$pattern_depth]
1861EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001862 }
1863 print STDERR
1864"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1865
1866 my $input = <STDIN>;
Florian Micklerdace8e32010-10-26 14:22:54 -07001867 chomp($input);
1868
Joe Perches683c6f82010-10-26 14:22:55 -07001869 $redraw = 1;
1870 my $rerun = 0;
1871 my @wish = split(/[, ]+/, $input);
1872 foreach my $nr (@wish) {
1873 $nr = lc($nr);
1874 my $sel = substr($nr, 0, 1);
1875 my $str = substr($nr, 1);
1876 my $val = 0;
1877 $val = $1 if $str =~ /^(\d+)$/;
1878
1879 if ($sel eq "y") {
1880 $interactive = 0;
1881 $done = 1;
1882 $output_rolestats = 0;
1883 $output_roles = 0;
1884 last;
1885 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1886 $selected{$nr - 1} = !$selected{$nr - 1};
1887 } elsif ($sel eq "*" || $sel eq '^') {
1888 my $toggle = 0;
1889 $toggle = 1 if ($sel eq '*');
1890 for (my $i = 0; $i < $count; $i++) {
1891 $selected{$i} = $toggle;
Florian Micklerdace8e32010-10-26 14:22:54 -07001892 }
Joe Perches683c6f82010-10-26 14:22:55 -07001893 } elsif ($sel eq "0") {
1894 for (my $i = 0; $i < $count; $i++) {
1895 $selected{$i} = !$selected{$i};
1896 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001897 } elsif ($sel eq "t") {
1898 if (lc($str) eq "m") {
1899 for (my $i = 0; $i < $count; $i++) {
1900 $selected{$i} = !$selected{$i}
1901 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1902 }
1903 } elsif (lc($str) eq "g") {
1904 for (my $i = 0; $i < $count; $i++) {
1905 $selected{$i} = !$selected{$i}
1906 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1907 }
1908 } elsif (lc($str) eq "l") {
1909 for (my $i = 0; $i < $count; $i++) {
1910 $selected{$i} = !$selected{$i}
1911 if ($list[$i]->[1] =~ /^(open list)/i);
1912 }
1913 } elsif (lc($str) eq "s") {
1914 for (my $i = 0; $i < $count; $i++) {
1915 $selected{$i} = !$selected{$i}
1916 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1917 }
1918 }
Joe Perches683c6f82010-10-26 14:22:55 -07001919 } elsif ($sel eq "a") {
1920 if ($val > 0 && $val <= $count) {
1921 $authored{$val - 1} = !$authored{$val - 1};
1922 } elsif ($str eq '*' || $str eq '^') {
1923 my $toggle = 0;
1924 $toggle = 1 if ($str eq '*');
1925 for (my $i = 0; $i < $count; $i++) {
1926 $authored{$i} = $toggle;
1927 }
1928 }
1929 } elsif ($sel eq "s") {
1930 if ($val > 0 && $val <= $count) {
1931 $signed{$val - 1} = !$signed{$val - 1};
1932 } elsif ($str eq '*' || $str eq '^') {
1933 my $toggle = 0;
1934 $toggle = 1 if ($str eq '*');
1935 for (my $i = 0; $i < $count; $i++) {
1936 $signed{$i} = $toggle;
1937 }
1938 }
1939 } elsif ($sel eq "o") {
1940 $print_options = 1;
1941 $redraw = 1;
1942 } elsif ($sel eq "g") {
1943 if ($str eq "f") {
1944 bool_invert(\$email_git_fallback);
Florian Micklerdace8e32010-10-26 14:22:54 -07001945 } else {
Joe Perches683c6f82010-10-26 14:22:55 -07001946 bool_invert(\$email_git);
Florian Micklerdace8e32010-10-26 14:22:54 -07001947 }
Joe Perches683c6f82010-10-26 14:22:55 -07001948 $rerun = 1;
1949 } elsif ($sel eq "b") {
1950 if ($str eq "s") {
1951 bool_invert(\$email_git_blame_signatures);
1952 } else {
1953 bool_invert(\$email_git_blame);
1954 }
1955 $rerun = 1;
1956 } elsif ($sel eq "c") {
1957 if ($val > 0) {
1958 $email_git_min_signatures = $val;
1959 $rerun = 1;
1960 }
1961 } elsif ($sel eq "x") {
1962 if ($val > 0) {
1963 $email_git_max_maintainers = $val;
1964 $rerun = 1;
1965 }
1966 } elsif ($sel eq "%") {
1967 if ($str ne "" && $val >= 0) {
1968 $email_git_min_percent = $val;
1969 $rerun = 1;
1970 }
1971 } elsif ($sel eq "d") {
1972 if (vcs_is_git()) {
1973 $email_git_since = $str;
1974 } elsif (vcs_is_hg()) {
1975 $email_hg_since = $str;
1976 }
1977 $rerun = 1;
1978 } elsif ($sel eq "t") {
1979 bool_invert(\$email_git_all_signature_types);
1980 $rerun = 1;
1981 } elsif ($sel eq "f") {
Joe Perches0c78c012020-06-04 16:50:01 -07001982 bool_invert(\$email_file_emails);
Joe Perches683c6f82010-10-26 14:22:55 -07001983 $rerun = 1;
1984 } elsif ($sel eq "r") {
1985 bool_invert(\$email_remove_duplicates);
1986 $rerun = 1;
Joe Perchesb9e23312010-10-26 14:22:58 -07001987 } elsif ($sel eq "m") {
1988 bool_invert(\$email_use_mailmap);
1989 read_mailmap();
1990 $rerun = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001991 } elsif ($sel eq "k") {
1992 bool_invert(\$keywords);
1993 $rerun = 1;
1994 } elsif ($sel eq "p") {
1995 if ($str ne "" && $val >= 0) {
1996 $pattern_depth = $val;
1997 $rerun = 1;
1998 }
Joe Perches6ef1c522010-10-26 14:22:56 -07001999 } elsif ($sel eq "h" || $sel eq "?") {
2000 print STDERR <<EOT
2001
2002Interactive mode allows you to select the various maintainers, submitters,
2003commit signers and mailing lists that could be CC'd on a patch.
2004
2005Any *'d entry is selected.
2006
Joe Perches47abc722010-10-26 14:22:57 -07002007If you have git or hg installed, you can choose to summarize the commit
Joe Perches6ef1c522010-10-26 14:22:56 -07002008history of files in the patch. Also, each line of the current file can
2009be matched to its commit author and that commits signers with blame.
2010
2011Various knobs exist to control the length of time for active commit
2012tracking, the maximum number of commit authors and signers to add,
2013and such.
2014
2015Enter selections at the prompt until you are satisfied that the selected
2016maintainers are appropriate. You may enter multiple selections separated
2017by either commas or spaces.
2018
2019EOT
Joe Perches683c6f82010-10-26 14:22:55 -07002020 } else {
2021 print STDERR "invalid option: '$nr'\n";
2022 $redraw = 0;
2023 }
2024 }
2025 if ($rerun) {
2026 print STDERR "git-blame can be very slow, please have patience..."
2027 if ($email_git_blame);
Joe Perches6ef1c522010-10-26 14:22:56 -07002028 goto &get_maintainers;
Joe Perches683c6f82010-10-26 14:22:55 -07002029 }
2030 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002031
2032 #drop not selected entries
2033 $count = 0;
Joe Perches683c6f82010-10-26 14:22:55 -07002034 my @new_emailto = ();
2035 foreach my $entry (@list) {
2036 if ($selected{$count}) {
2037 push(@new_emailto, $list[$count]);
Florian Micklerdace8e32010-10-26 14:22:54 -07002038 }
2039 $count++;
2040 }
Joe Perches683c6f82010-10-26 14:22:55 -07002041 return @new_emailto;
Florian Micklerdace8e32010-10-26 14:22:54 -07002042}
2043
Joe Perches683c6f82010-10-26 14:22:55 -07002044sub bool_invert {
2045 my ($bool_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002046
Joe Perches683c6f82010-10-26 14:22:55 -07002047 if ($$bool_ref) {
2048 $$bool_ref = 0;
2049 } else {
2050 $$bool_ref = 1;
Florian Micklerdace8e32010-10-26 14:22:54 -07002051 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002052}
2053
Joe Perchesb9e23312010-10-26 14:22:58 -07002054sub deduplicate_email {
2055 my ($email) = @_;
2056
2057 my $matched = 0;
2058 my ($name, $address) = parse_email($email);
2059 $email = format_email($name, $address, 1);
2060 $email = mailmap_email($email);
2061
2062 return $email if (!$email_remove_duplicates);
2063
2064 ($name, $address) = parse_email($email);
2065
Joe Perchesfae99202010-10-26 14:22:58 -07002066 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002067 $name = $deduplicate_name_hash{lc($name)}->[0];
2068 $address = $deduplicate_name_hash{lc($name)}->[1];
2069 $matched = 1;
2070 } elsif ($deduplicate_address_hash{lc($address)}) {
2071 $name = $deduplicate_address_hash{lc($address)}->[0];
2072 $address = $deduplicate_address_hash{lc($address)}->[1];
2073 $matched = 1;
2074 }
2075 if (!$matched) {
2076 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2077 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2078 }
2079 $email = format_email($name, $address, 1);
2080 $email = mailmap_email($email);
2081 return $email;
2082}
2083
Joe Perches683c6f82010-10-26 14:22:55 -07002084sub save_commits_by_author {
2085 my (@lines) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002086
Joe Perches683c6f82010-10-26 14:22:55 -07002087 my @authors = ();
2088 my @commits = ();
2089 my @subjects = ();
2090
2091 foreach my $line (@lines) {
2092 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2093 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002094 $author = deduplicate_email($author);
Joe Perches683c6f82010-10-26 14:22:55 -07002095 push(@authors, $author);
2096 }
2097 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2098 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2099 }
2100
2101 for (my $i = 0; $i < @authors; $i++) {
2102 my $exists = 0;
2103 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2104 if (@{$ref}[0] eq $commits[$i] &&
2105 @{$ref}[1] eq $subjects[$i]) {
2106 $exists = 1;
2107 last;
2108 }
2109 }
2110 if (!$exists) {
2111 push(@{$commit_author_hash{$authors[$i]}},
2112 [ ($commits[$i], $subjects[$i]) ]);
2113 }
2114 }
2115}
2116
2117sub save_commits_by_signer {
2118 my (@lines) = @_;
2119
2120 my $commit = "";
2121 my $subject = "";
2122
2123 foreach my $line (@lines) {
2124 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2125 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2126 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2127 my @signatures = ($line);
2128 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2129 my @types = @$types_ref;
2130 my @signers = @$signers_ref;
2131
2132 my $type = $types[0];
2133 my $signer = $signers[0];
2134
Joe Perchesb9e23312010-10-26 14:22:58 -07002135 $signer = deduplicate_email($signer);
Joe Perches6ef1c522010-10-26 14:22:56 -07002136
Joe Perches683c6f82010-10-26 14:22:55 -07002137 my $exists = 0;
2138 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2139 if (@{$ref}[0] eq $commit &&
2140 @{$ref}[1] eq $subject &&
2141 @{$ref}[2] eq $type) {
2142 $exists = 1;
2143 last;
2144 }
2145 }
2146 if (!$exists) {
2147 push(@{$commit_signer_hash{$signer}},
2148 [ ($commit, $subject, $type) ]);
2149 }
2150 }
2151 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002152}
2153
Joe Perches60db31a2009-12-14 18:00:50 -08002154sub vcs_assign {
Joe Perchesa8af2432009-12-14 18:00:49 -08002155 my ($role, $divisor, @lines) = @_;
2156
2157 my %hash;
2158 my $count = 0;
2159
Joe Perchesa8af2432009-12-14 18:00:49 -08002160 return if (@lines <= 0);
2161
2162 if ($divisor <= 0) {
Joe Perches60db31a2009-12-14 18:00:50 -08002163 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
Joe Perchesa8af2432009-12-14 18:00:49 -08002164 $divisor = 1;
Joe Perches3c7385b2009-12-14 18:00:46 -08002165 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07002166
Florian Mickler7fa8ff22010-10-26 14:22:56 -07002167 @lines = mailmap(@lines);
Joe Perches0e70e832009-09-21 17:04:20 -07002168
Joe Perches63ab52d2010-10-26 14:22:51 -07002169 return if (@lines <= 0);
2170
Joe Perches0e70e832009-09-21 17:04:20 -07002171 @lines = sort(@lines);
Joe Perchesafa81ee2009-07-29 15:04:28 -07002172
Joe Perches11ecf532009-09-21 17:04:22 -07002173 # uniq -c
2174 $hash{$_}++ for @lines;
2175
2176 # sort -rn
2177 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2178 my $sign_offs = $hash{$line};
Joe Perchesa8af2432009-12-14 18:00:49 -08002179 my $percent = $sign_offs * 100 / $divisor;
Joe Perches3c7385b2009-12-14 18:00:46 -08002180
Joe Perchesa8af2432009-12-14 18:00:49 -08002181 $percent = 100 if ($percent > 100);
Joe Perches435de072015-06-25 15:01:50 -07002182 next if (ignore_email_address($line));
Joe Perches11ecf532009-09-21 17:04:22 -07002183 $count++;
2184 last if ($sign_offs < $email_git_min_signatures ||
2185 $count > $email_git_max_maintainers ||
Joe Perchesa8af2432009-12-14 18:00:49 -08002186 $percent < $email_git_min_percent);
Joe Perches3c7385b2009-12-14 18:00:46 -08002187 push_email_address($line, '');
Joe Perches3c7385b2009-12-14 18:00:46 -08002188 if ($output_rolestats) {
Joe Perchesa8af2432009-12-14 18:00:49 -08002189 my $fmt_percent = sprintf("%.0f", $percent);
2190 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2191 } else {
2192 add_role($line, $role);
Joe Perches3c7385b2009-12-14 18:00:46 -08002193 }
Joe Perchesf5492662009-09-21 17:04:13 -07002194 }
2195}
2196
Joe Perches60db31a2009-12-14 18:00:50 -08002197sub vcs_file_signoffs {
Joe Perchesa8af2432009-12-14 18:00:49 -08002198 my ($file) = @_;
2199
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002200 my $authors_ref;
2201 my $signers_ref;
2202 my $stats_ref;
2203 my @authors = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002204 my @signers = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002205 my @stats = ();
Joe Perches60db31a2009-12-14 18:00:50 -08002206 my $commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002207
Joe Perches683c6f82010-10-26 14:22:55 -07002208 $vcs_used = vcs_exists();
2209 return if (!$vcs_used);
Joe Perchesa8af2432009-12-14 18:00:49 -08002210
Joe Perches60db31a2009-12-14 18:00:50 -08002211 my $cmd = $VCS_cmds{"find_signers_cmd"};
2212 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2213
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002214 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2215
2216 @signers = @{$signers_ref} if defined $signers_ref;
2217 @authors = @{$authors_ref} if defined $authors_ref;
2218 @stats = @{$stats_ref} if defined $stats_ref;
2219
2220# print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
Joe Perchesb9e23312010-10-26 14:22:58 -07002221
2222 foreach my $signer (@signers) {
2223 $signer = deduplicate_email($signer);
2224 }
2225
Joe Perches60db31a2009-12-14 18:00:50 -08002226 vcs_assign("commit_signer", $commits, @signers);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002227 vcs_assign("authored", $commits, @authors);
2228 if ($#authors == $#stats) {
2229 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2230 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2231
2232 my $added = 0;
2233 my $deleted = 0;
2234 for (my $i = 0; $i <= $#stats; $i++) {
2235 if ($stats[$i] =~ /$stat_pattern/) {
2236 $added += $1;
2237 $deleted += $2;
2238 }
2239 }
2240 my @tmp_authors = uniq(@authors);
2241 foreach my $author (@tmp_authors) {
2242 $author = deduplicate_email($author);
2243 }
2244 @tmp_authors = uniq(@tmp_authors);
2245 my @list_added = ();
2246 my @list_deleted = ();
2247 foreach my $author (@tmp_authors) {
2248 my $auth_added = 0;
2249 my $auth_deleted = 0;
2250 for (my $i = 0; $i <= $#stats; $i++) {
2251 if ($author eq deduplicate_email($authors[$i]) &&
2252 $stats[$i] =~ /$stat_pattern/) {
2253 $auth_added += $1;
2254 $auth_deleted += $2;
2255 }
2256 }
2257 for (my $i = 0; $i < $auth_added; $i++) {
2258 push(@list_added, $author);
2259 }
2260 for (my $i = 0; $i < $auth_deleted; $i++) {
2261 push(@list_deleted, $author);
2262 }
2263 }
2264 vcs_assign("added_lines", $added, @list_added);
2265 vcs_assign("removed_lines", $deleted, @list_deleted);
2266 }
Joe Perchesa8af2432009-12-14 18:00:49 -08002267}
2268
Joe Perches60db31a2009-12-14 18:00:50 -08002269sub vcs_file_blame {
Joe Perchesf5492662009-09-21 17:04:13 -07002270 my ($file) = @_;
2271
Joe Perches60db31a2009-12-14 18:00:50 -08002272 my @signers = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07002273 my @all_commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002274 my @commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002275 my $total_commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002276 my $total_lines;
Joe Perchesf5492662009-09-21 17:04:13 -07002277
Joe Perches683c6f82010-10-26 14:22:55 -07002278 $vcs_used = vcs_exists();
2279 return if (!$vcs_used);
Joe Perchesf5492662009-09-21 17:04:13 -07002280
Joe Perches63ab52d2010-10-26 14:22:51 -07002281 @all_commits = vcs_blame($file);
2282 @commits = uniq(@all_commits);
Joe Perchesa8af2432009-12-14 18:00:49 -08002283 $total_commits = @commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002284 $total_lines = @all_commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002285
Joe Perches683c6f82010-10-26 14:22:55 -07002286 if ($email_git_blame_signatures) {
2287 if (vcs_is_hg()) {
2288 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002289 my $commit_authors_ref;
2290 my $commit_signers_ref;
2291 my $stats_ref;
2292 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002293 my @commit_signers = ();
2294 my $commit = join(" -r ", @commits);
2295 my $cmd;
Joe Perchesf5492662009-09-21 17:04:13 -07002296
Joe Perches683c6f82010-10-26 14:22:55 -07002297 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2298 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
Joe Perches60db31a2009-12-14 18:00:50 -08002299
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002300 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2301 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2302 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches63ab52d2010-10-26 14:22:51 -07002303
Joe Perches683c6f82010-10-26 14:22:55 -07002304 push(@signers, @commit_signers);
2305 } else {
2306 foreach my $commit (@commits) {
2307 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002308 my $commit_authors_ref;
2309 my $commit_signers_ref;
2310 my $stats_ref;
2311 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002312 my @commit_signers = ();
2313 my $cmd;
2314
2315 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2316 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2317
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002318 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2319 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2320 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches683c6f82010-10-26 14:22:55 -07002321
2322 push(@signers, @commit_signers);
2323 }
2324 }
Joe Perchesf5492662009-09-21 17:04:13 -07002325 }
2326
Joe Perchesa8af2432009-12-14 18:00:49 -08002327 if ($from_filename) {
Joe Perches63ab52d2010-10-26 14:22:51 -07002328 if ($output_rolestats) {
2329 my @blame_signers;
Joe Perches683c6f82010-10-26 14:22:55 -07002330 if (vcs_is_hg()) {{ # Double brace for last exit
2331 my $commit_count;
2332 my @commit_signers = ();
2333 @commits = uniq(@commits);
2334 @commits = sort(@commits);
2335 my $commit = join(" -r ", @commits);
2336 my $cmd;
2337
2338 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2339 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2340
2341 my @lines = ();
2342
2343 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2344
2345 if (!$email_git_penguin_chiefs) {
2346 @lines = grep(!/${penguin_chiefs}/i, @lines);
2347 }
2348
2349 last if !@lines;
2350
2351 my @authors = ();
2352 foreach my $line (@lines) {
2353 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2354 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002355 $author = deduplicate_email($author);
2356 push(@authors, $author);
Joe Perches683c6f82010-10-26 14:22:55 -07002357 }
2358 }
2359
2360 save_commits_by_author(@lines) if ($interactive);
2361 save_commits_by_signer(@lines) if ($interactive);
2362
2363 push(@signers, @authors);
2364 }}
2365 else {
2366 foreach my $commit (@commits) {
2367 my $i;
2368 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2369 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2370 my @author = vcs_find_author($cmd);
2371 next if !@author;
Joe Perchesb9e23312010-10-26 14:22:58 -07002372
2373 my $formatted_author = deduplicate_email($author[0]);
2374
Joe Perches683c6f82010-10-26 14:22:55 -07002375 my $count = grep(/$commit/, @all_commits);
2376 for ($i = 0; $i < $count ; $i++) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002377 push(@blame_signers, $formatted_author);
Joe Perches683c6f82010-10-26 14:22:55 -07002378 }
Joe Perches63ab52d2010-10-26 14:22:51 -07002379 }
2380 }
2381 if (@blame_signers) {
2382 vcs_assign("authored lines", $total_lines, @blame_signers);
2383 }
2384 }
Joe Perchesb9e23312010-10-26 14:22:58 -07002385 foreach my $signer (@signers) {
2386 $signer = deduplicate_email($signer);
2387 }
Joe Perches60db31a2009-12-14 18:00:50 -08002388 vcs_assign("commits", $total_commits, @signers);
Joe Perchesa8af2432009-12-14 18:00:49 -08002389 } else {
Joe Perchesb9e23312010-10-26 14:22:58 -07002390 foreach my $signer (@signers) {
2391 $signer = deduplicate_email($signer);
2392 }
Joe Perches60db31a2009-12-14 18:00:50 -08002393 vcs_assign("modified commits", $total_commits, @signers);
Joe Perchesf5492662009-09-21 17:04:13 -07002394 }
Joe Perchescb7301c2009-04-07 20:40:12 -07002395}
2396
Joe Perches4cad35a2016-08-02 14:04:10 -07002397sub vcs_file_exists {
2398 my ($file) = @_;
2399
2400 my $exists;
2401
2402 my $vcs_used = vcs_exists();
2403 return 0 if (!$vcs_used);
2404
2405 my $cmd = $VCS_cmds{"file_exists_cmd"};
2406 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
Joe Perches8582fb52016-08-25 15:16:48 -07002407 $cmd .= " 2>&1";
Joe Perches4cad35a2016-08-02 14:04:10 -07002408 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2409
Joe Perches8582fb52016-08-25 15:16:48 -07002410 return 0 if ($? != 0);
2411
Joe Perches4cad35a2016-08-02 14:04:10 -07002412 return $exists;
2413}
2414
Tom Saegere1f75902017-11-17 15:27:42 -08002415sub vcs_list_files {
2416 my ($file) = @_;
2417
2418 my @lsfiles = ();
2419
2420 my $vcs_used = vcs_exists();
2421 return 0 if (!$vcs_used);
2422
2423 my $cmd = $VCS_cmds{"list_files_cmd"};
2424 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2425 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2426
2427 return () if ($? != 0);
2428
2429 return @lsfiles;
2430}
2431
Joe Perchescb7301c2009-04-07 20:40:12 -07002432sub uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002433 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002434
2435 my %saw;
2436 @parms = grep(!$saw{$_}++, @parms);
2437 return @parms;
2438}
2439
2440sub sort_and_uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002441 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002442
2443 my %saw;
2444 @parms = sort @parms;
2445 @parms = grep(!$saw{$_}++, @parms);
2446 return @parms;
2447}
2448
Joe Perches03372db2010-03-05 13:43:00 -08002449sub clean_file_emails {
2450 my (@file_emails) = @_;
2451 my @fmt_emails = ();
2452
2453 foreach my $email (@file_emails) {
2454 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2455 my ($name, $address) = parse_email($email);
2456 if ($name eq '"[,\.]"') {
2457 $name = "";
2458 }
2459
2460 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2461 if (@nw > 2) {
2462 my $first = $nw[@nw - 3];
2463 my $middle = $nw[@nw - 2];
2464 my $last = $nw[@nw - 1];
2465
2466 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2467 (length($first) == 2 && substr($first, -1) eq ".")) ||
2468 (length($middle) == 1 ||
2469 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2470 $name = "$first $middle $last";
2471 } else {
2472 $name = "$middle $last";
2473 }
2474 }
2475
2476 if (substr($name, -1) =~ /[,\.]/) {
2477 $name = substr($name, 0, length($name) - 1);
2478 } elsif (substr($name, -2) =~ /[,\.]"/) {
2479 $name = substr($name, 0, length($name) - 2) . '"';
2480 }
2481
2482 if (substr($name, 0, 1) =~ /[,\.]/) {
2483 $name = substr($name, 1, length($name) - 1);
2484 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2485 $name = '"' . substr($name, 2, length($name) - 2);
2486 }
2487
2488 my $fmt_email = format_email($name, $address, $email_usename);
2489 push(@fmt_emails, $fmt_email);
2490 }
2491 return @fmt_emails;
2492}
2493
Joe Perches3c7385b2009-12-14 18:00:46 -08002494sub merge_email {
2495 my @lines;
2496 my %saw;
2497
2498 for (@_) {
2499 my ($address, $role) = @$_;
2500 if (!$saw{$address}) {
2501 if ($output_roles) {
Joe Perches60db31a2009-12-14 18:00:50 -08002502 push(@lines, "$address ($role)");
Joe Perches3c7385b2009-12-14 18:00:46 -08002503 } else {
Joe Perches60db31a2009-12-14 18:00:50 -08002504 push(@lines, $address);
Joe Perches3c7385b2009-12-14 18:00:46 -08002505 }
2506 $saw{$address} = 1;
2507 }
2508 }
2509
2510 return @lines;
2511}
2512
Joe Perchescb7301c2009-04-07 20:40:12 -07002513sub output {
Joe Perchesa8af2432009-12-14 18:00:49 -08002514 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002515
2516 if ($output_multiline) {
2517 foreach my $line (@parms) {
2518 print("${line}\n");
2519 }
2520 } else {
2521 print(join($output_separator, @parms));
2522 print("\n");
2523 }
2524}
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002525
2526my $rfc822re;
2527
2528sub make_rfc822re {
2529# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2530# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2531# This regexp will only work on addresses which have had comments stripped
2532# and replaced with rfc822_lwsp.
2533
2534 my $specials = '()<>@,;:\\\\".\\[\\]';
2535 my $controls = '\\000-\\037\\177';
2536
2537 my $dtext = "[^\\[\\]\\r\\\\]";
2538 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2539
2540 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2541
2542# Use zero-width assertion to spot the limit of an atom. A simple
2543# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2544 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2545 my $word = "(?:$atom|$quoted_string)";
2546 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2547
2548 my $sub_domain = "(?:$atom|$domain_literal)";
2549 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2550
2551 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2552
2553 my $phrase = "$word*";
2554 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2555 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2556 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2557
2558 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2559 my $address = "(?:$mailbox|$group)";
2560
2561 return "$rfc822_lwsp*$address";
2562}
2563
2564sub rfc822_strip_comments {
2565 my $s = shift;
2566# Recursively remove comments, and replace with a single space. The simpler
2567# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2568# chars in atoms, for example.
2569
2570 while ($s =~ s/^((?:[^"\\]|\\.)*
2571 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2572 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2573 return $s;
2574}
2575
2576# valid: returns true if the parameter is an RFC822 valid address
2577#
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002578sub rfc822_valid {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002579 my $s = rfc822_strip_comments(shift);
2580
2581 if (!$rfc822re) {
2582 $rfc822re = make_rfc822re();
2583 }
2584
2585 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2586}
2587
2588# validlist: In scalar context, returns true if the parameter is an RFC822
2589# valid list of addresses.
2590#
2591# In list context, returns an empty list on failure (an invalid
2592# address was found); otherwise a list whose first element is the
2593# number of addresses found and whose remaining elements are the
2594# addresses. This is needed to disambiguate failure (invalid)
2595# from success with no addresses found, because an empty string is
2596# a valid list.
2597
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002598sub rfc822_validlist {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002599 my $s = rfc822_strip_comments(shift);
2600
2601 if (!$rfc822re) {
2602 $rfc822re = make_rfc822re();
2603 }
2604 # * null list items are valid according to the RFC
2605 # * the '1' business is to aid in distinguishing failure from no results
2606
2607 my @r;
2608 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2609 $s =~ m/^$rfc822_char*$/) {
Joe Perches5f2441e2009-06-16 15:34:02 -07002610 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
Joe Perches60db31a2009-12-14 18:00:50 -08002611 push(@r, $1);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002612 }
2613 return wantarray ? (scalar(@r), @r) : 1;
2614 }
Joe Perches60db31a2009-12-14 18:00:50 -08002615 return wantarray ? () : 0;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002616}