blob: 34085d146fa2cadb0a92353c0f6cc0d89303af29 [file] [log] [blame]
Kamil Rytarowskicb77f0d2017-05-07 23:25:26 +02001#!/usr/bin/env perl
Joe Perches882ea1d2018-06-07 17:04:33 -07002# SPDX-License-Identifier: GPL-2.0
3#
Joe Perchescb7301c2009-04-07 20:40:12 -07004# (c) 2007, Joe Perches <joe@perches.com>
5# created from checkpatch.pl
6#
7# Print selected MAINTAINERS information for
8# the files modified in a patch or for a file
9#
Roel Kluin3bd7bf52009-11-11 14:26:13 -080010# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
11# perl scripts/get_maintainer.pl [OPTIONS] -f <file>
Joe Perchescb7301c2009-04-07 20:40:12 -070012
Kamil Rytarowskicb77f0d2017-05-07 23:25:26 +020013use warnings;
Joe Perchescb7301c2009-04-07 20:40:12 -070014use strict;
15
16my $P = $0;
Joe Perches7e1863a2011-01-12 16:59:49 -080017my $V = '0.26';
Joe Perchescb7301c2009-04-07 20:40:12 -070018
19use Getopt::Long qw(:config no_auto_abbrev);
Joe Perchesbe17bdd2016-01-20 14:58:24 -080020use Cwd;
Joe Perches6f7d98e2017-08-04 21:45:48 -070021use File::Find;
Joe Perchescb7301c2009-04-07 20:40:12 -070022
Joe Perchesbe17bdd2016-01-20 14:58:24 -080023my $cur_path = fastgetcwd() . '/';
Joe Perchescb7301c2009-04-07 20:40:12 -070024my $lk_path = "./";
25my $email = 1;
26my $email_usename = 1;
27my $email_maintainer = 1;
Joe Perchesc1c3f2c2014-06-02 12:05:17 -070028my $email_reviewer = 1;
Joe Perches2f5bd3432019-12-04 16:50:29 -080029my $email_fixes = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070030my $email_list = 1;
Joe Perches49662502019-07-16 16:27:09 -070031my $email_moderated_list = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070032my $email_subscriber_list = 0;
Joe Perchescb7301c2009-04-07 20:40:12 -070033my $email_git_penguin_chiefs = 0;
Joe Perchese3e9d112010-10-26 14:22:53 -070034my $email_git = 0;
Florian Mickler0fa05592010-05-24 14:33:20 -070035my $email_git_all_signature_types = 0;
Joe Perches60db31a2009-12-14 18:00:50 -080036my $email_git_blame = 0;
Joe Perches683c6f82010-10-26 14:22:55 -070037my $email_git_blame_signatures = 1;
Joe Perchese3e9d112010-10-26 14:22:53 -070038my $email_git_fallback = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070039my $email_git_min_signatures = 1;
40my $email_git_max_maintainers = 5;
Joe Perchesafa81ee2009-07-29 15:04:28 -070041my $email_git_min_percent = 5;
Joe Perchescb7301c2009-04-07 20:40:12 -070042my $email_git_since = "1-year-ago";
Joe Perches60db31a2009-12-14 18:00:50 -080043my $email_hg_since = "-365";
Florian Micklerdace8e32010-10-26 14:22:54 -070044my $interactive = 0;
Joe Perches11ecf532009-09-21 17:04:22 -070045my $email_remove_duplicates = 1;
Joe Perchesb9e23312010-10-26 14:22:58 -070046my $email_use_mailmap = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070047my $output_multiline = 1;
48my $output_separator = ", ";
Joe Perches3c7385b2009-12-14 18:00:46 -080049my $output_roles = 0;
Joe Perches7e1863a2011-01-12 16:59:49 -080050my $output_rolestats = 1;
Joe Perches364f68d2015-06-25 15:01:52 -070051my $output_section_maxlen = 50;
Joe Perchescb7301c2009-04-07 20:40:12 -070052my $scm = 0;
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -070053my $tree = 1;
Joe Perchescb7301c2009-04-07 20:40:12 -070054my $web = 0;
55my $subsystem = 0;
56my $status = 0;
Joe Perches03aed212016-12-12 16:45:59 -080057my $letters = "";
Joe Perchesdcf36a92009-10-26 16:49:47 -070058my $keywords = 1;
Joe Perches4b76c9d2010-03-05 13:43:03 -080059my $sections = 0;
Joe Perches03372db2010-03-05 13:43:00 -080060my $file_emails = 0;
Joe Perches4a7fdb52009-04-10 12:28:57 -070061my $from_filename = 0;
Joe Perches3fb55652009-09-21 17:04:17 -070062my $pattern_depth = 0;
Joe Perches083bf9c2017-11-17 15:27:46 -080063my $self_test = undef;
Joe Perchescb7301c2009-04-07 20:40:12 -070064my $version = 0;
65my $help = 0;
Joe Perches6f7d98e2017-08-04 21:45:48 -070066my $find_maintainer_files = 0;
Joe Perches5f0baf92018-08-21 21:56:52 -070067my $maintainer_path;
Joe Perches683c6f82010-10-26 14:22:55 -070068my $vcs_used = 0;
69
Joe Perchescb7301c2009-04-07 20:40:12 -070070my $exit = 0;
71
Joe Perches683c6f82010-10-26 14:22:55 -070072my %commit_author_hash;
73my %commit_signer_hash;
Florian Micklerdace8e32010-10-26 14:22:54 -070074
Joe Perchescb7301c2009-04-07 20:40:12 -070075my @penguin_chief = ();
Joe Perchese4d26b02010-05-24 14:33:17 -070076push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
Joe Perchescb7301c2009-04-07 20:40:12 -070077#Andrew wants in on most everything - 2009/01/14
Joe Perchese4d26b02010-05-24 14:33:17 -070078#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
Joe Perchescb7301c2009-04-07 20:40:12 -070079
80my @penguin_chief_names = ();
81foreach my $chief (@penguin_chief) {
82 if ($chief =~ m/^(.*):(.*)/) {
83 my $chief_name = $1;
84 my $chief_addr = $2;
85 push(@penguin_chief_names, $chief_name);
86 }
87}
Joe Perchese4d26b02010-05-24 14:33:17 -070088my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
89
90# Signature types of people who are either
91# a) responsible for the code in question, or
92# b) familiar enough with it to give relevant feedback
93my @signature_tags = ();
94push(@signature_tags, "Signed-off-by:");
95push(@signature_tags, "Reviewed-by:");
96push(@signature_tags, "Acked-by:");
Joe Perchescb7301c2009-04-07 20:40:12 -070097
Joe Perches7dea2682012-06-20 12:53:02 -070098my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
99
Joe Perches5f2441e2009-06-16 15:34:02 -0700100# rfc822 email address - preloaded methods go here.
Joe Perches1b5e1cf2009-06-16 15:34:01 -0700101my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
Joe Perchesdf4cc032009-06-16 15:34:04 -0700102my $rfc822_char = '[\\000-\\377]';
Joe Perches1b5e1cf2009-06-16 15:34:01 -0700103
Joe Perches60db31a2009-12-14 18:00:50 -0800104# VCS command support: class-like functions and strings
105
106my %VCS_cmds;
107
108my %VCS_cmds_git = (
109 "execute_cmd" => \&git_execute_cmd,
Richard Genoudec83b612014-02-10 14:25:31 -0800110 "available" => '(which("git") ne "") && (-e ".git")',
Joe Perches683c6f82010-10-26 14:22:55 -0700111 "find_signers_cmd" =>
Ian Campbelled128fea2012-01-10 15:08:41 -0800112 "git log --no-color --follow --since=\$email_git_since " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800113 '--numstat --no-merges ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700114 '--format="GitCommit: %H%n' .
115 'GitAuthor: %an <%ae>%n' .
116 'GitDate: %aD%n' .
117 'GitSubject: %s%n' .
118 '%b%n"' .
119 " -- \$file",
120 "find_commit_signers_cmd" =>
121 "git log --no-color " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800122 '--numstat ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700123 '--format="GitCommit: %H%n' .
124 'GitAuthor: %an <%ae>%n' .
125 'GitDate: %aD%n' .
126 'GitSubject: %s%n' .
127 '%b%n"' .
128 " -1 \$commit",
129 "find_commit_author_cmd" =>
130 "git log --no-color " .
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800131 '--numstat ' .
Joe Perches683c6f82010-10-26 14:22:55 -0700132 '--format="GitCommit: %H%n' .
133 'GitAuthor: %an <%ae>%n' .
134 'GitDate: %aD%n' .
135 'GitSubject: %s%n"' .
136 " -1 \$commit",
Joe Perches60db31a2009-12-14 18:00:50 -0800137 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
138 "blame_file_cmd" => "git blame -l \$file",
Joe Perches683c6f82010-10-26 14:22:55 -0700139 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
Florian Micklerdace8e32010-10-26 14:22:54 -0700140 "blame_commit_pattern" => "^([0-9a-f]+) ",
Joe Perches683c6f82010-10-26 14:22:55 -0700141 "author_pattern" => "^GitAuthor: (.*)",
142 "subject_pattern" => "^GitSubject: (.*)",
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800143 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
Joe Perches4cad35a2016-08-02 14:04:10 -0700144 "file_exists_cmd" => "git ls-files \$file",
Tom Saegere1f75902017-11-17 15:27:42 -0800145 "list_files_cmd" => "git ls-files \$file",
Joe Perches60db31a2009-12-14 18:00:50 -0800146);
147
148my %VCS_cmds_hg = (
149 "execute_cmd" => \&hg_execute_cmd,
150 "available" => '(which("hg") ne "") && (-d ".hg")',
151 "find_signers_cmd" =>
Joe Perches683c6f82010-10-26 14:22:55 -0700152 "hg log --date=\$email_hg_since " .
153 "--template='HgCommit: {node}\\n" .
154 "HgAuthor: {author}\\n" .
155 "HgSubject: {desc}\\n'" .
156 " -- \$file",
157 "find_commit_signers_cmd" =>
158 "hg log " .
159 "--template='HgSubject: {desc}\\n'" .
160 " -r \$commit",
161 "find_commit_author_cmd" =>
162 "hg log " .
163 "--template='HgCommit: {node}\\n" .
164 "HgAuthor: {author}\\n" .
165 "HgSubject: {desc|firstline}\\n'" .
166 " -r \$commit",
Joe Perches60db31a2009-12-14 18:00:50 -0800167 "blame_range_cmd" => "", # not supported
Joe Perches683c6f82010-10-26 14:22:55 -0700168 "blame_file_cmd" => "hg blame -n \$file",
169 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
170 "blame_commit_pattern" => "^([ 0-9a-f]+):",
171 "author_pattern" => "^HgAuthor: (.*)",
172 "subject_pattern" => "^HgSubject: (.*)",
Joe Perchesc9ecefe2014-01-23 15:54:20 -0800173 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
Joe Perches4cad35a2016-08-02 14:04:10 -0700174 "file_exists_cmd" => "hg files \$file",
Tom Saegere1f75902017-11-17 15:27:42 -0800175 "list_files_cmd" => "hg manifest -R \$file",
Joe Perches60db31a2009-12-14 18:00:50 -0800176);
177
Joe Perchesbcde44e2010-10-26 14:22:53 -0700178my $conf = which_conf(".get_maintainer.conf");
179if (-f $conf) {
Joe Perches368669d2010-05-24 14:33:19 -0700180 my @conf_args;
Joe Perchesbcde44e2010-10-26 14:22:53 -0700181 open(my $conffile, '<', "$conf")
182 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
183
Joe Perches368669d2010-05-24 14:33:19 -0700184 while (<$conffile>) {
185 my $line = $_;
186
187 $line =~ s/\s*\n?$//g;
188 $line =~ s/^\s*//g;
189 $line =~ s/\s+/ /g;
190
191 next if ($line =~ m/^\s*#/);
192 next if ($line =~ m/^\s*$/);
193
194 my @words = split(" ", $line);
195 foreach my $word (@words) {
196 last if ($word =~ m/^#/);
197 push (@conf_args, $word);
198 }
199 }
200 close($conffile);
201 unshift(@ARGV, @conf_args) if @conf_args;
202}
203
Joe Perches435de072015-06-25 15:01:50 -0700204my @ignore_emails = ();
205my $ignore_file = which_conf(".get_maintainer.ignore");
206if (-f $ignore_file) {
207 open(my $ignore, '<', "$ignore_file")
208 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
209 while (<$ignore>) {
210 my $line = $_;
211
212 $line =~ s/\s*\n?$//;
213 $line =~ s/^\s*//;
214 $line =~ s/\s+$//;
215 $line =~ s/#.*$//;
216
217 next if ($line =~ m/^\s*$/);
218 if (rfc822_valid($line)) {
219 push(@ignore_emails, $line);
220 }
221 }
222 close($ignore);
223}
224
Tom Saegere1f75902017-11-17 15:27:42 -0800225if ($#ARGV > 0) {
226 foreach (@ARGV) {
Joe Perches083bf9c2017-11-17 15:27:46 -0800227 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
Tom Saegere1f75902017-11-17 15:27:42 -0800228 die "$P: using --self-test does not allow any other option or argument\n";
229 }
230 }
231}
232
Joe Perchescb7301c2009-04-07 20:40:12 -0700233if (!GetOptions(
234 'email!' => \$email,
235 'git!' => \$email_git,
Joe Perchese4d26b02010-05-24 14:33:17 -0700236 'git-all-signature-types!' => \$email_git_all_signature_types,
Joe Perches60db31a2009-12-14 18:00:50 -0800237 'git-blame!' => \$email_git_blame,
Joe Perches683c6f82010-10-26 14:22:55 -0700238 'git-blame-signatures!' => \$email_git_blame_signatures,
Joe Perchese3e9d112010-10-26 14:22:53 -0700239 'git-fallback!' => \$email_git_fallback,
Joe Perchescb7301c2009-04-07 20:40:12 -0700240 'git-chief-penguins!' => \$email_git_penguin_chiefs,
241 'git-min-signatures=i' => \$email_git_min_signatures,
242 'git-max-maintainers=i' => \$email_git_max_maintainers,
Joe Perchesafa81ee2009-07-29 15:04:28 -0700243 'git-min-percent=i' => \$email_git_min_percent,
Joe Perchescb7301c2009-04-07 20:40:12 -0700244 'git-since=s' => \$email_git_since,
Joe Perches60db31a2009-12-14 18:00:50 -0800245 'hg-since=s' => \$email_hg_since,
Florian Micklerdace8e32010-10-26 14:22:54 -0700246 'i|interactive!' => \$interactive,
Joe Perches11ecf532009-09-21 17:04:22 -0700247 'remove-duplicates!' => \$email_remove_duplicates,
Joe Perchesb9e23312010-10-26 14:22:58 -0700248 'mailmap!' => \$email_use_mailmap,
Joe Perchescb7301c2009-04-07 20:40:12 -0700249 'm!' => \$email_maintainer,
Joe Perchesc1c3f2c2014-06-02 12:05:17 -0700250 'r!' => \$email_reviewer,
Joe Perchescb7301c2009-04-07 20:40:12 -0700251 'n!' => \$email_usename,
252 'l!' => \$email_list,
Joe Perches2f5bd3432019-12-04 16:50:29 -0800253 'fixes!' => \$email_fixes,
Joe Perches49662502019-07-16 16:27:09 -0700254 'moderated!' => \$email_moderated_list,
Joe Perchescb7301c2009-04-07 20:40:12 -0700255 's!' => \$email_subscriber_list,
256 'multiline!' => \$output_multiline,
Joe Perches3c7385b2009-12-14 18:00:46 -0800257 'roles!' => \$output_roles,
258 'rolestats!' => \$output_rolestats,
Joe Perchescb7301c2009-04-07 20:40:12 -0700259 'separator=s' => \$output_separator,
260 'subsystem!' => \$subsystem,
261 'status!' => \$status,
262 'scm!' => \$scm,
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -0700263 'tree!' => \$tree,
Joe Perchescb7301c2009-04-07 20:40:12 -0700264 'web!' => \$web,
Joe Perches03aed212016-12-12 16:45:59 -0800265 'letters=s' => \$letters,
Joe Perches3fb55652009-09-21 17:04:17 -0700266 'pattern-depth=i' => \$pattern_depth,
Joe Perchesdcf36a92009-10-26 16:49:47 -0700267 'k|keywords!' => \$keywords,
Joe Perches4b76c9d2010-03-05 13:43:03 -0800268 'sections!' => \$sections,
Joe Perches03372db2010-03-05 13:43:00 -0800269 'fe|file-emails!' => \$file_emails,
Joe Perches4a7fdb52009-04-10 12:28:57 -0700270 'f|file' => \$from_filename,
Joe Perches6f7d98e2017-08-04 21:45:48 -0700271 'find-maintainer-files' => \$find_maintainer_files,
Joe Perches5f0baf92018-08-21 21:56:52 -0700272 'mpath|maintainer-path=s' => \$maintainer_path,
Joe Perches083bf9c2017-11-17 15:27:46 -0800273 'self-test:s' => \$self_test,
Joe Perchescb7301c2009-04-07 20:40:12 -0700274 'v|version' => \$version,
Joe Perches64f77f32010-03-05 13:43:04 -0800275 'h|help|usage' => \$help,
Joe Perchescb7301c2009-04-07 20:40:12 -0700276 )) {
Joe Perches3c7385b2009-12-14 18:00:46 -0800277 die "$P: invalid argument - use --help if necessary\n";
Joe Perchescb7301c2009-04-07 20:40:12 -0700278}
279
280if ($help != 0) {
281 usage();
282 exit 0;
283}
284
285if ($version != 0) {
286 print("${P} ${V}\n");
287 exit 0;
288}
289
Joe Perches083bf9c2017-11-17 15:27:46 -0800290if (defined $self_test) {
Tom Saegere1f75902017-11-17 15:27:42 -0800291 read_all_maintainer_files();
Joe Perches083bf9c2017-11-17 15:27:46 -0800292 self_test();
Tom Saegere1f75902017-11-17 15:27:42 -0800293 exit 0;
294}
295
Joe Perches64f77f32010-03-05 13:43:04 -0800296if (-t STDIN && !@ARGV) {
297 # We're talking to a terminal, but have no command line arguments.
298 die "$P: missing patchfile or -f file - use --help if necessary\n";
Joe Perchescb7301c2009-04-07 20:40:12 -0700299}
300
Joe Perches683c6f82010-10-26 14:22:55 -0700301$output_multiline = 0 if ($output_separator ne ", ");
302$output_rolestats = 1 if ($interactive);
303$output_roles = 1 if ($output_rolestats);
Joe Perches3c7385b2009-12-14 18:00:46 -0800304
Joe Perches03aed212016-12-12 16:45:59 -0800305if ($sections || $letters ne "") {
306 $sections = 1;
Joe Perches4b76c9d2010-03-05 13:43:03 -0800307 $email = 0;
308 $email_list = 0;
309 $scm = 0;
310 $status = 0;
311 $subsystem = 0;
312 $web = 0;
313 $keywords = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -0700314 $interactive = 0;
Joe Perches4b76c9d2010-03-05 13:43:03 -0800315} else {
316 my $selections = $email + $scm + $status + $subsystem + $web;
317 if ($selections == 0) {
Joe Perches4b76c9d2010-03-05 13:43:03 -0800318 die "$P: Missing required option: email, scm, status, subsystem or web\n";
319 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700320}
321
Joe Perchesf5492662009-09-21 17:04:13 -0700322if ($email &&
Joe Perchesc1c3f2c2014-06-02 12:05:17 -0700323 ($email_maintainer + $email_reviewer +
324 $email_list + $email_subscriber_list +
Joe Perchesf5492662009-09-21 17:04:13 -0700325 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
Joe Perchescb7301c2009-04-07 20:40:12 -0700326 die "$P: Please select at least 1 email option\n";
327}
328
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -0700329if ($tree && !top_of_kernel_tree($lk_path)) {
Joe Perchescb7301c2009-04-07 20:40:12 -0700330 die "$P: The current directory does not appear to be "
331 . "a linux kernel source tree.\n";
332}
333
334## Read MAINTAINERS for type/value pairs
335
336my @typevalue = ();
Joe Perchesdcf36a92009-10-26 16:49:47 -0700337my %keyword_hash;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700338my @mfiles = ();
Joe Perches083bf9c2017-11-17 15:27:46 -0800339my @self_test_info = ();
Joe Perchesdcf36a92009-10-26 16:49:47 -0700340
Joe Perches6f7d98e2017-08-04 21:45:48 -0700341sub read_maintainer_file {
342 my ($file) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -0700343
Joe Perches6f7d98e2017-08-04 21:45:48 -0700344 open (my $maint, '<', "$file")
345 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
Tom Saegere1f75902017-11-17 15:27:42 -0800346 my $i = 1;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700347 while (<$maint>) {
348 my $line = $_;
Joe Perches083bf9c2017-11-17 15:27:46 -0800349 chomp $line;
Joe Perchescb7301c2009-04-07 20:40:12 -0700350
Joe Perches6f7d98e2017-08-04 21:45:48 -0700351 if ($line =~ m/^([A-Z]):\s*(.*)/) {
352 my $type = $1;
353 my $value = $2;
354
355 ##Filename pattern matching
356 if ($type eq "F" || $type eq "X") {
357 $value =~ s@\.@\\\.@g; ##Convert . to \.
358 $value =~ s/\*/\.\*/g; ##Convert * to .*
359 $value =~ s/\?/\./g; ##Convert ? to .
360 ##if pattern is a directory and it lacks a trailing slash, add one
361 if ((-d $value)) {
362 $value =~ s@([^/])$@$1/@;
363 }
364 } elsif ($type eq "K") {
365 $keyword_hash{@typevalue} = $value;
Joe Perches870020f2009-07-29 15:04:28 -0700366 }
Joe Perches6f7d98e2017-08-04 21:45:48 -0700367 push(@typevalue, "$type:$value");
368 } elsif (!(/^\s*$/ || /^\s*\#/)) {
Joe Perches6f7d98e2017-08-04 21:45:48 -0700369 push(@typevalue, $line);
Joe Perchescb7301c2009-04-07 20:40:12 -0700370 }
Joe Perches083bf9c2017-11-17 15:27:46 -0800371 if (defined $self_test) {
372 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
373 }
Tom Saegere1f75902017-11-17 15:27:42 -0800374 $i++;
Joe Perches6f7d98e2017-08-04 21:45:48 -0700375 }
376 close($maint);
377}
378
379sub find_is_maintainer_file {
380 my ($file) = $_;
381 return if ($file !~ m@/MAINTAINERS$@);
382 $file = $File::Find::name;
383 return if (! -f $file);
384 push(@mfiles, $file);
385}
386
387sub find_ignore_git {
388 return grep { $_ !~ /^\.git$/; } @_;
389}
390
Tom Saegere1f75902017-11-17 15:27:42 -0800391read_all_maintainer_files();
392
393sub read_all_maintainer_files {
Joe Perches5f0baf92018-08-21 21:56:52 -0700394 my $path = "${lk_path}MAINTAINERS";
395 if (defined $maintainer_path) {
396 $path = $maintainer_path;
397 # Perl Cookbook tilde expansion if necessary
398 $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
Joe Perchescb7301c2009-04-07 20:40:12 -0700399 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700400
Joe Perches5f0baf92018-08-21 21:56:52 -0700401 if (-d $path) {
402 $path .= '/' if ($path !~ m@/$@);
Joe Perches0fbd75f2018-08-21 21:56:55 -0700403 if ($find_maintainer_files) {
404 find( { wanted => \&find_is_maintainer_file,
405 preprocess => \&find_ignore_git,
406 no_chdir => 1,
407 }, "$path");
408 } else {
Joe Perches5f0baf92018-08-21 21:56:52 -0700409 opendir(DIR, "$path") or die $!;
410 my @files = readdir(DIR);
411 closedir(DIR);
412 foreach my $file (@files) {
413 push(@mfiles, "$path$file") if ($file !~ /^\./);
414 }
415 }
Joe Perches5f0baf92018-08-21 21:56:52 -0700416 } elsif (-f "$path") {
417 push(@mfiles, "$path");
Tom Saegere1f75902017-11-17 15:27:42 -0800418 } else {
Joe Perches5f0baf92018-08-21 21:56:52 -0700419 die "$P: MAINTAINER file not found '$path'\n";
Tom Saegere1f75902017-11-17 15:27:42 -0800420 }
Joe Perches5f0baf92018-08-21 21:56:52 -0700421 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
Tom Saegere1f75902017-11-17 15:27:42 -0800422 foreach my $file (@mfiles) {
Joe Perches5f0baf92018-08-21 21:56:52 -0700423 read_maintainer_file("$file");
Tom Saegere1f75902017-11-17 15:27:42 -0800424 }
Joe Perches6f7d98e2017-08-04 21:45:48 -0700425}
Joe Perches8cbb3a72009-09-21 17:04:21 -0700426
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700427#
428# Read mail address map
429#
430
Joe Perchesb9e23312010-10-26 14:22:58 -0700431my $mailmap;
432
433read_mailmap();
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700434
435sub read_mailmap {
Joe Perchesb9e23312010-10-26 14:22:58 -0700436 $mailmap = {
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700437 names => {},
438 addresses => {}
Joe Perches47abc722010-10-26 14:22:57 -0700439 };
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700440
Joe Perchesb9e23312010-10-26 14:22:58 -0700441 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700442
443 open(my $mailmap_file, '<', "${lk_path}.mailmap")
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800444 or warn "$P: Can't open .mailmap: $!\n";
Joe Perches8cbb3a72009-09-21 17:04:21 -0700445
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700446 while (<$mailmap_file>) {
447 s/#.*$//; #strip comments
448 s/^\s+|\s+$//g; #trim
Joe Perches8cbb3a72009-09-21 17:04:21 -0700449
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700450 next if (/^\s*$/); #skip empty lines
451 #entries have one of the following formats:
452 # name1 <mail1>
453 # <mail1> <mail2>
454 # name1 <mail1> <mail2>
455 # name1 <mail1> name2 <mail2>
456 # (see man git-shortlog)
Joe Perches0334b382011-07-25 17:13:13 -0700457
458 if (/^([^<]+)<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700459 my $real_name = $1;
460 my $address = $2;
Joe Perches8cbb3a72009-09-21 17:04:21 -0700461
Joe Perches47abc722010-10-26 14:22:57 -0700462 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700463 ($real_name, $address) = parse_email("$real_name <$address>");
Joe Perches47abc722010-10-26 14:22:57 -0700464 $mailmap->{names}->{$address} = $real_name;
Joe Perches8cbb3a72009-09-21 17:04:21 -0700465
Joe Perches0334b382011-07-25 17:13:13 -0700466 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700467 my $real_address = $1;
468 my $wrong_address = $2;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700469
Joe Perches47abc722010-10-26 14:22:57 -0700470 $mailmap->{addresses}->{$wrong_address} = $real_address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700471
Joe Perches0334b382011-07-25 17:13:13 -0700472 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
Joe Perchesb9e23312010-10-26 14:22:58 -0700473 my $real_name = $1;
Joe Perches47abc722010-10-26 14:22:57 -0700474 my $real_address = $2;
475 my $wrong_address = $3;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700476
Joe Perches47abc722010-10-26 14:22:57 -0700477 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700478 ($real_name, $real_address) =
479 parse_email("$real_name <$real_address>");
Joe Perches47abc722010-10-26 14:22:57 -0700480 $mailmap->{names}->{$wrong_address} = $real_name;
481 $mailmap->{addresses}->{$wrong_address} = $real_address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700482
Joe Perches0334b382011-07-25 17:13:13 -0700483 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
Joe Perches47abc722010-10-26 14:22:57 -0700484 my $real_name = $1;
485 my $real_address = $2;
486 my $wrong_name = $3;
487 my $wrong_address = $4;
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700488
Joe Perches47abc722010-10-26 14:22:57 -0700489 $real_name =~ s/\s+$//;
Joe Perchesb9e23312010-10-26 14:22:58 -0700490 ($real_name, $real_address) =
491 parse_email("$real_name <$real_address>");
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700492
Joe Perchesb9e23312010-10-26 14:22:58 -0700493 $wrong_name =~ s/\s+$//;
494 ($wrong_name, $wrong_address) =
495 parse_email("$wrong_name <$wrong_address>");
496
497 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
498 $mailmap->{names}->{$wrong_email} = $real_name;
499 $mailmap->{addresses}->{$wrong_email} = $real_address;
Joe Perches11ecf532009-09-21 17:04:22 -0700500 }
Joe Perches8cbb3a72009-09-21 17:04:21 -0700501 }
Florian Mickler7fa8ff22010-10-26 14:22:56 -0700502 close($mailmap_file);
Joe Perches8cbb3a72009-09-21 17:04:21 -0700503}
504
Joe Perches4a7fdb52009-04-10 12:28:57 -0700505## use the filenames on the command line or find the filenames in the patchfiles
Joe Perchescb7301c2009-04-07 20:40:12 -0700506
507my @files = ();
Joe Perches2f5bd3432019-12-04 16:50:29 -0800508my @fixes = (); # If a patch description includes Fixes: lines
Joe Perchesf5492662009-09-21 17:04:13 -0700509my @range = ();
Joe Perchesdcf36a92009-10-26 16:49:47 -0700510my @keyword_tvi = ();
Joe Perches03372db2010-03-05 13:43:00 -0800511my @file_emails = ();
Joe Perchescb7301c2009-04-07 20:40:12 -0700512
Joe Perches64f77f32010-03-05 13:43:04 -0800513if (!@ARGV) {
514 push(@ARGV, "&STDIN");
515}
516
Joe Perches4a7fdb52009-04-10 12:28:57 -0700517foreach my $file (@ARGV) {
Joe Perches64f77f32010-03-05 13:43:04 -0800518 if ($file ne "&STDIN") {
519 ##if $file is a directory and it lacks a trailing slash, add one
520 if ((-d $file)) {
521 $file =~ s@([^/])$@$1/@;
522 } elsif (!(-f $file)) {
523 die "$P: file '${file}' not found\n";
524 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700525 }
Joe Perchesaec742e2016-08-10 08:45:11 -0700526 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
Joe Perchesbe17bdd2016-01-20 14:58:24 -0800527 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
528 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
Joe Perches4a7fdb52009-04-10 12:28:57 -0700529 push(@files, $file);
Joe Perchesfab9ed12010-10-26 14:22:52 -0700530 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800531 open(my $f, '<', $file)
532 or die "$P: Can't open $file: $!\n";
533 my $text = do { local($/) ; <$f> };
534 close($f);
Joe Perches03372db2010-03-05 13:43:00 -0800535 if ($keywords) {
536 foreach my $line (keys %keyword_hash) {
537 if ($text =~ m/$keyword_hash{$line}/x) {
538 push(@keyword_tvi, $line);
539 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700540 }
541 }
Joe Perches03372db2010-03-05 13:43:00 -0800542 if ($file_emails) {
543 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;
544 push(@file_emails, clean_file_emails(@poss_addr));
545 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700546 }
Joe Perches4a7fdb52009-04-10 12:28:57 -0700547 } else {
548 my $file_cnt = @files;
Joe Perchesf5492662009-09-21 17:04:13 -0700549 my $lastfile;
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800550
Wolfram Sang3a4df132010-03-23 13:35:18 -0700551 open(my $patch, "< $file")
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800552 or die "$P: Can't open $file: $!\n";
Joe Perches7764dcb2011-03-22 16:34:24 -0700553
554 # We can check arbitrary information before the patch
555 # like the commit message, mail headers, etc...
556 # This allows us to match arbitrary keywords against any part
557 # of a git format-patch generated file (subject tags, etc...)
558
559 my $patch_prefix = ""; #Parsing the intro
560
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800561 while (<$patch>) {
Joe Perchesdcf36a92009-10-26 16:49:47 -0700562 my $patch_line = $_;
Joe Perches0455c742018-06-07 17:10:38 -0700563 if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
564 my $filename = $1;
565 push(@files, $filename);
566 } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
567 my $filename = $1;
568 push(@files, $filename);
569 } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
570 my $filename1 = $1;
571 my $filename2 = $2;
572 push(@files, $filename1);
573 push(@files, $filename2);
Joe Perches2f5bd3432019-12-04 16:50:29 -0800574 } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
575 push(@fixes, $1) if ($email_fixes);
Joe Perches0455c742018-06-07 17:10:38 -0700576 } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
Joe Perches4a7fdb52009-04-10 12:28:57 -0700577 my $filename = $1;
578 $filename =~ s@^[^/]*/@@;
579 $filename =~ s@\n@@;
Joe Perchesf5492662009-09-21 17:04:13 -0700580 $lastfile = $filename;
Joe Perches4a7fdb52009-04-10 12:28:57 -0700581 push(@files, $filename);
Joe Perches7764dcb2011-03-22 16:34:24 -0700582 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
Joe Perchesf5492662009-09-21 17:04:13 -0700583 } elsif (m/^\@\@ -(\d+),(\d+)/) {
584 if ($email_git_blame) {
585 push(@range, "$lastfile:$1:$2");
586 }
Joe Perchesdcf36a92009-10-26 16:49:47 -0700587 } elsif ($keywords) {
588 foreach my $line (keys %keyword_hash) {
Joe Perches7764dcb2011-03-22 16:34:24 -0700589 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
Joe Perchesdcf36a92009-10-26 16:49:47 -0700590 push(@keyword_tvi, $line);
591 }
592 }
Joe Perches4a7fdb52009-04-10 12:28:57 -0700593 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700594 }
Stephen Hemminger22dd5b02010-03-05 13:43:06 -0800595 close($patch);
596
Joe Perches4a7fdb52009-04-10 12:28:57 -0700597 if ($file_cnt == @files) {
Joe Perches7f29fd272009-06-16 15:34:04 -0700598 warn "$P: file '${file}' doesn't appear to be a patch. "
Joe Perches4a7fdb52009-04-10 12:28:57 -0700599 . "Add -f to options?\n";
600 }
601 @files = sort_and_uniq(@files);
Joe Perchescb7301c2009-04-07 20:40:12 -0700602 }
Joe Perchescb7301c2009-04-07 20:40:12 -0700603}
604
Joe Perches03372db2010-03-05 13:43:00 -0800605@file_emails = uniq(@file_emails);
Joe Perches2f5bd3432019-12-04 16:50:29 -0800606@fixes = uniq(@fixes);
Joe Perches03372db2010-03-05 13:43:00 -0800607
Joe Perches683c6f82010-10-26 14:22:55 -0700608my %email_hash_name;
609my %email_hash_address;
Joe Perchescb7301c2009-04-07 20:40:12 -0700610my @email_to = ();
Joe Perches683c6f82010-10-26 14:22:55 -0700611my %hash_list_to;
Joe Perches290603c2009-06-16 15:33:58 -0700612my @list_to = ();
Joe Perchescb7301c2009-04-07 20:40:12 -0700613my @scm = ();
614my @web = ();
615my @subsystem = ();
616my @status = ();
Joe Perchesb9e23312010-10-26 14:22:58 -0700617my %deduplicate_name_hash = ();
618my %deduplicate_address_hash = ();
Joe Perchescb7301c2009-04-07 20:40:12 -0700619
Joe Perches6ef1c522010-10-26 14:22:56 -0700620my @maintainers = get_maintainers();
Joe Perches6ef1c522010-10-26 14:22:56 -0700621if (@maintainers) {
622 @maintainers = merge_email(@maintainers);
623 output(@maintainers);
624}
Joe Perchescb7301c2009-04-07 20:40:12 -0700625
626if ($scm) {
Joe Perchesb7816552009-09-21 17:04:24 -0700627 @scm = uniq(@scm);
Joe Perchescb7301c2009-04-07 20:40:12 -0700628 output(@scm);
629}
Joe Perches683c6f82010-10-26 14:22:55 -0700630
Joe Perchescb7301c2009-04-07 20:40:12 -0700631if ($status) {
Joe Perchesb7816552009-09-21 17:04:24 -0700632 @status = uniq(@status);
Joe Perchescb7301c2009-04-07 20:40:12 -0700633 output(@status);
634}
635
636if ($subsystem) {
Joe Perchesb7816552009-09-21 17:04:24 -0700637 @subsystem = uniq(@subsystem);
Joe Perchescb7301c2009-04-07 20:40:12 -0700638 output(@subsystem);
639}
640
641if ($web) {
Joe Perchesb7816552009-09-21 17:04:24 -0700642 @web = uniq(@web);
Joe Perchescb7301c2009-04-07 20:40:12 -0700643 output(@web);
644}
645
646exit($exit);
647
Joe Perches083bf9c2017-11-17 15:27:46 -0800648sub self_test {
Tom Saegere1f75902017-11-17 15:27:42 -0800649 my @lsfiles = ();
Joe Perches083bf9c2017-11-17 15:27:46 -0800650 my @good_links = ();
651 my @bad_links = ();
652 my @section_headers = ();
653 my $index = 0;
Tom Saegere1f75902017-11-17 15:27:42 -0800654
655 @lsfiles = vcs_list_files($lk_path);
656
Joe Perches083bf9c2017-11-17 15:27:46 -0800657 for my $x (@self_test_info) {
658 $index++;
659
660 ## Section header duplication and missing section content
661 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
662 $x->{line} =~ /^\S[^:]/ &&
663 defined $self_test_info[$index] &&
664 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
665 my $has_S = 0;
666 my $has_F = 0;
667 my $has_ML = 0;
668 my $status = "";
669 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
670 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
671 } else {
672 push(@section_headers, $x->{line});
673 }
674 my $nextline = $index;
675 while (defined $self_test_info[$nextline] &&
676 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
677 my $type = $1;
678 my $value = $2;
679 if ($type eq "S") {
680 $has_S = 1;
681 $status = $value;
682 } elsif ($type eq "F" || $type eq "N") {
683 $has_F = 1;
684 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
685 $has_ML = 1;
686 }
687 $nextline++;
688 }
689 if (!$has_ML && $status !~ /orphan|obsolete/i) {
690 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
691 }
692 if (!$has_S) {
693 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
694 }
695 if (!$has_F) {
696 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
697 }
698 }
699
700 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
701
702 my $type = $1;
703 my $value = $2;
704
705 ## Filename pattern matching
706 if (($type eq "F" || $type eq "X") &&
707 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
708 $value =~ s@\.@\\\.@g; ##Convert . to \.
709 $value =~ s/\*/\.\*/g; ##Convert * to .*
710 $value =~ s/\?/\./g; ##Convert ? to .
711 ##if pattern is a directory and it lacks a trailing slash, add one
712 if ((-d $value)) {
713 $value =~ s@([^/])$@$1/@;
714 }
715 if (!grep(m@^$value@, @lsfiles)) {
716 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
717 }
718
719 ## Link reachability
720 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
721 $value =~ /^https?:/ &&
722 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
723 next if (grep(m@^\Q$value\E$@, @good_links));
724 my $isbad = 0;
725 if (grep(m@^\Q$value\E$@, @bad_links)) {
726 $isbad = 1;
727 } else {
728 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
729 if ($? == 0) {
730 push(@good_links, $value);
731 } else {
732 push(@bad_links, $value);
733 $isbad = 1;
734 }
735 }
736 if ($isbad) {
737 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
738 }
739
740 ## SCM reachability
741 } elsif ($type eq "T" &&
742 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
743 next if (grep(m@^\Q$value\E$@, @good_links));
744 my $isbad = 0;
745 if (grep(m@^\Q$value\E$@, @bad_links)) {
746 $isbad = 1;
747 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
748 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
749 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
750 my $url = $1;
751 my $branch = "";
752 $branch = $3 if $3;
753 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
754 if ($? == 0) {
755 push(@good_links, $value);
756 } else {
757 push(@bad_links, $value);
758 $isbad = 1;
759 }
760 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
761 my $url = $1;
762 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
763 if ($? == 0) {
764 push(@good_links, $value);
765 } else {
766 push(@bad_links, $value);
767 $isbad = 1;
768 }
769 }
770 if ($isbad) {
771 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
772 }
773 }
Tom Saegere1f75902017-11-17 15:27:42 -0800774 }
775}
776
Joe Perches435de072015-06-25 15:01:50 -0700777sub ignore_email_address {
778 my ($address) = @_;
779
780 foreach my $ignore (@ignore_emails) {
781 return 1 if ($ignore eq $address);
782 }
783
784 return 0;
785}
786
Joe Perchesab6c9372011-01-12 16:59:50 -0800787sub range_is_maintained {
788 my ($start, $end) = @_;
789
790 for (my $i = $start; $i < $end; $i++) {
791 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700792 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchesab6c9372011-01-12 16:59:50 -0800793 my $type = $1;
794 my $value = $2;
795 if ($type eq 'S') {
796 if ($value =~ /(maintain|support)/i) {
797 return 1;
798 }
799 }
800 }
801 }
802 return 0;
803}
804
805sub range_has_maintainer {
806 my ($start, $end) = @_;
807
808 for (my $i = $start; $i < $end; $i++) {
809 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700810 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchesab6c9372011-01-12 16:59:50 -0800811 my $type = $1;
812 my $value = $2;
813 if ($type eq 'M') {
814 return 1;
815 }
816 }
817 }
818 return 0;
819}
820
Joe Perches6ef1c522010-10-26 14:22:56 -0700821sub get_maintainers {
Joe Perches683c6f82010-10-26 14:22:55 -0700822 %email_hash_name = ();
823 %email_hash_address = ();
824 %commit_author_hash = ();
825 %commit_signer_hash = ();
826 @email_to = ();
827 %hash_list_to = ();
828 @list_to = ();
829 @scm = ();
830 @web = ();
831 @subsystem = ();
832 @status = ();
Joe Perchesb9e23312010-10-26 14:22:58 -0700833 %deduplicate_name_hash = ();
834 %deduplicate_address_hash = ();
Joe Perches683c6f82010-10-26 14:22:55 -0700835 if ($email_git_all_signature_types) {
836 $signature_pattern = "(.+?)[Bb][Yy]:";
837 } else {
838 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
839 }
840
841 # Find responsible parties
842
Joe Perchesb9e23312010-10-26 14:22:58 -0700843 my %exact_pattern_match_hash = ();
Joe Perches6ef1c522010-10-26 14:22:56 -0700844
Joe Perches683c6f82010-10-26 14:22:55 -0700845 foreach my $file (@files) {
846
847 my %hash;
Joe Perches683c6f82010-10-26 14:22:55 -0700848 my $tvi = find_first_section();
849 while ($tvi < @typevalue) {
850 my $start = find_starting_index($tvi);
851 my $end = find_ending_index($tvi);
852 my $exclude = 0;
853 my $i;
854
855 #Do not match excluded file patterns
856
857 for ($i = $start; $i < $end; $i++) {
858 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700859 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches683c6f82010-10-26 14:22:55 -0700860 my $type = $1;
861 my $value = $2;
862 if ($type eq 'X') {
863 if (file_match_pattern($file, $value)) {
864 $exclude = 1;
865 last;
866 }
867 }
868 }
869 }
870
871 if (!$exclude) {
872 for ($i = $start; $i < $end; $i++) {
873 my $line = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -0700874 if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches683c6f82010-10-26 14:22:55 -0700875 my $type = $1;
876 my $value = $2;
877 if ($type eq 'F') {
878 if (file_match_pattern($file, $value)) {
879 my $value_pd = ($value =~ tr@/@@);
880 my $file_pd = ($file =~ tr@/@@);
881 $value_pd++ if (substr($value,-1,1) ne "/");
882 $value_pd = -1 if ($value =~ /^\.\*/);
Joe Perchesab6c9372011-01-12 16:59:50 -0800883 if ($value_pd >= $file_pd &&
884 range_is_maintained($start, $end) &&
885 range_has_maintainer($start, $end)) {
Joe Perches6ef1c522010-10-26 14:22:56 -0700886 $exact_pattern_match_hash{$file} = 1;
887 }
Joe Perches683c6f82010-10-26 14:22:55 -0700888 if ($pattern_depth == 0 ||
889 (($file_pd - $value_pd) < $pattern_depth)) {
890 $hash{$tvi} = $value_pd;
891 }
892 }
Stephen Warrenbbbe96e2013-04-29 16:17:23 -0700893 } elsif ($type eq 'N') {
Stephen Warreneb90d082013-02-27 17:02:53 -0800894 if ($file =~ m/$value/x) {
895 $hash{$tvi} = 0;
896 }
Joe Perches683c6f82010-10-26 14:22:55 -0700897 }
898 }
899 }
900 }
901 $tvi = $end + 1;
902 }
903
904 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
905 add_categories($line);
906 if ($sections) {
907 my $i;
908 my $start = find_starting_index($line);
909 my $end = find_ending_index($line);
910 for ($i = $start; $i < $end; $i++) {
911 my $line = $typevalue[$i];
912 if ($line =~ /^[FX]:/) { ##Restore file patterns
913 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
914 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
915 $line =~ s/\\\./\./g; ##Convert \. to .
916 $line =~ s/\.\*/\*/g; ##Convert .* to *
917 }
Joe Perches03aed212016-12-12 16:45:59 -0800918 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
919 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
920 print("$line\n");
921 }
Joe Perches683c6f82010-10-26 14:22:55 -0700922 }
923 print("\n");
924 }
925 }
Joe Perches683c6f82010-10-26 14:22:55 -0700926 }
927
928 if ($keywords) {
929 @keyword_tvi = sort_and_uniq(@keyword_tvi);
930 foreach my $line (@keyword_tvi) {
931 add_categories($line);
932 }
933 }
934
Joe Perches2f5bd3432019-12-04 16:50:29 -0800935 foreach my $fix (@fixes) {
936 vcs_add_commit_signers($fix, "blamed_fixes");
937 }
938
Joe Perchesb9e23312010-10-26 14:22:58 -0700939 foreach my $email (@email_to, @list_to) {
940 $email->[0] = deduplicate_email($email->[0]);
941 }
Joe Perches6ef1c522010-10-26 14:22:56 -0700942
943 foreach my $file (@files) {
944 if ($email &&
945 ($email_git || ($email_git_fallback &&
946 !$exact_pattern_match_hash{$file}))) {
947 vcs_file_signoffs($file);
948 }
949 if ($email && $email_git_blame) {
950 vcs_file_blame($file);
951 }
952 }
953
Joe Perches683c6f82010-10-26 14:22:55 -0700954 if ($email) {
955 foreach my $chief (@penguin_chief) {
956 if ($chief =~ m/^(.*):(.*)/) {
957 my $email_address;
958
959 $email_address = format_email($1, $2, $email_usename);
960 if ($email_git_penguin_chiefs) {
961 push(@email_to, [$email_address, 'chief penguin']);
962 } else {
963 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
964 }
965 }
966 }
967
968 foreach my $email (@file_emails) {
969 my ($name, $address) = parse_email($email);
970
971 my $tmp_email = format_email($name, $address, $email_usename);
972 push_email_address($tmp_email, '');
973 add_role($tmp_email, 'in file');
974 }
975 }
976
977 my @to = ();
978 if ($email || $email_list) {
979 if ($email) {
980 @to = (@to, @email_to);
981 }
982 if ($email_list) {
983 @to = (@to, @list_to);
984 }
985 }
986
Joe Perches6ef1c522010-10-26 14:22:56 -0700987 if ($interactive) {
Joe Perchesb9e23312010-10-26 14:22:58 -0700988 @to = interactive_get_maintainers(\@to);
Joe Perches6ef1c522010-10-26 14:22:56 -0700989 }
Joe Perches683c6f82010-10-26 14:22:55 -0700990
991 return @to;
992}
993
Joe Perchescb7301c2009-04-07 20:40:12 -0700994sub file_match_pattern {
995 my ($file, $pattern) = @_;
996 if (substr($pattern, -1) eq "/") {
997 if ($file =~ m@^$pattern@) {
998 return 1;
999 }
1000 } else {
1001 if ($file =~ m@^$pattern@) {
1002 my $s1 = ($file =~ tr@/@@);
1003 my $s2 = ($pattern =~ tr@/@@);
1004 if ($s1 == $s2) {
1005 return 1;
1006 }
1007 }
1008 }
1009 return 0;
1010}
1011
1012sub usage {
1013 print <<EOT;
1014usage: $P [options] patchfile
Joe Perches870020f2009-07-29 15:04:28 -07001015 $P [options] -f file|directory
Joe Perchescb7301c2009-04-07 20:40:12 -07001016version: $V
1017
1018MAINTAINER field selection options:
1019 --email => print email address(es) if any
1020 --git => include recent git \*-by: signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001021 --git-all-signature-types => include signers regardless of signature type
Joe Perches683c6f82010-10-26 14:22:55 -07001022 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
Joe Perchese3e9d112010-10-26 14:22:53 -07001023 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
Joe Perchescb7301c2009-04-07 20:40:12 -07001024 --git-chief-penguins => include ${penguin_chiefs}
Joe Perchese4d26b02010-05-24 14:33:17 -07001025 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1026 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1027 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
Joe Perchesf5492662009-09-21 17:04:13 -07001028 --git-blame => use git blame to find modified commits for patch or file
Brian Norris3cbcca82015-11-06 16:30:41 -08001029 --git-blame-signatures => when used with --git-blame, also include all commit signers
Joe Perchese4d26b02010-05-24 14:33:17 -07001030 --git-since => git history to use (default: $email_git_since)
1031 --hg-since => hg history to use (default: $email_hg_since)
Florian Micklerdace8e32010-10-26 14:22:54 -07001032 --interactive => display a menu (mostly useful if used with the --git option)
Joe Perchescb7301c2009-04-07 20:40:12 -07001033 --m => include maintainer(s) if any
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001034 --r => include reviewer(s) if any
Joe Perchescb7301c2009-04-07 20:40:12 -07001035 --n => include name 'Full Name <addr\@domain.tld>'
1036 --l => include list(s) if any
Joe Perches49662502019-07-16 16:27:09 -07001037 --moderated => include moderated lists(s) if any (default: true)
1038 --s => include subscriber only list(s) if any (default: false)
Joe Perches11ecf532009-09-21 17:04:22 -07001039 --remove-duplicates => minimize duplicate email names/addresses
Joe Perches3c7385b2009-12-14 18:00:46 -08001040 --roles => show roles (status:subsystem, git-signer, list, etc...)
1041 --rolestats => show roles and statistics (commits/total_commits, %)
Joe Perches03372db2010-03-05 13:43:00 -08001042 --file-emails => add email addresses found in -f file (default: 0 (off))
Joe Perches2f5bd3432019-12-04 16:50:29 -08001043 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
Joe Perchescb7301c2009-04-07 20:40:12 -07001044 --scm => print SCM tree(s) if any
1045 --status => print status if any
1046 --subsystem => print subsystem name if any
1047 --web => print website(s) if any
1048
1049Output type options:
1050 --separator [, ] => separator for multiple entries on 1 line
Joe Perches42498312009-09-21 17:04:21 -07001051 using --separator also sets --nomultiline if --separator is not [, ]
Joe Perchescb7301c2009-04-07 20:40:12 -07001052 --multiline => print 1 entry per line
1053
Joe Perchescb7301c2009-04-07 20:40:12 -07001054Other options:
Joe Perches3fb55652009-09-21 17:04:17 -07001055 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
Joe Perchesb9e23312010-10-26 14:22:58 -07001056 --keywords => scan patch for keywords (default: $keywords)
1057 --sections => print all of the subsystem sections with pattern matches
Joe Perches03aed212016-12-12 16:45:59 -08001058 --letters => print all matching 'letter' types from all matching sections
Joe Perchesb9e23312010-10-26 14:22:58 -07001059 --mailmap => use .mailmap file (default: $email_use_mailmap)
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001060 --no-tree => run without a kernel tree
Tom Saegere1f75902017-11-17 15:27:42 -08001061 --self-test => show potential issues with MAINTAINERS file content
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001062 --version => show version
Joe Perchescb7301c2009-04-07 20:40:12 -07001063 --help => show this help information
1064
Joe Perches3fb55652009-09-21 17:04:17 -07001065Default options:
Antonio Nino Diaz31bb82c2018-08-21 21:56:48 -07001066 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1067 --pattern-depth=0 --remove-duplicates --rolestats]
Joe Perches3fb55652009-09-21 17:04:17 -07001068
Joe Perches870020f2009-07-29 15:04:28 -07001069Notes:
1070 Using "-f directory" may give unexpected results:
Joe Perchesf5492662009-09-21 17:04:13 -07001071 Used with "--git", git signators for _all_ files in and below
1072 directory are examined as git recurses directories.
1073 Any specified X: (exclude) pattern matches are _not_ ignored.
1074 Used with "--nogit", directory is used as a pattern match,
Joe Perches60db31a2009-12-14 18:00:50 -08001075 no individual file within the directory or subdirectory
1076 is matched.
Joe Perchesf5492662009-09-21 17:04:13 -07001077 Used with "--git-blame", does not iterate all files in directory
1078 Using "--git-blame" is slow and may add old committers and authors
1079 that are no longer active maintainers to the output.
Joe Perches3c7385b2009-12-14 18:00:46 -08001080 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1081 other automated tools that expect only ["name"] <email address>
1082 may not work because of additional output after <email address>.
1083 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1084 not the percentage of the entire file authored. # of commits is
1085 not a good measure of amount of code authored. 1 major commit may
1086 contain a thousand lines, 5 trivial commits may modify a single line.
Joe Perches60db31a2009-12-14 18:00:50 -08001087 If git is not installed, but mercurial (hg) is installed and an .hg
1088 repository exists, the following options apply to mercurial:
1089 --git,
1090 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1091 --git-blame
1092 Use --hg-since not --git-since to control date selection
Joe Perches368669d2010-05-24 14:33:19 -07001093 File ".get_maintainer.conf", if it exists in the linux kernel source root
1094 directory, can change whatever get_maintainer defaults are desired.
1095 Entries in this file can be any command line argument.
1096 This file is prepended to any additional command line arguments.
1097 Multiple lines and # comments are allowed.
Brian Norrisb1312bf2015-11-06 16:30:46 -08001098 Most options have both positive and negative forms.
1099 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1100
Joe Perchescb7301c2009-04-07 20:40:12 -07001101EOT
1102}
1103
1104sub top_of_kernel_tree {
Joe Perches47abc722010-10-26 14:22:57 -07001105 my ($lk_path) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07001106
Joe Perches47abc722010-10-26 14:22:57 -07001107 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1108 $lk_path .= "/";
1109 }
1110 if ( (-f "${lk_path}COPYING")
1111 && (-f "${lk_path}CREDITS")
1112 && (-f "${lk_path}Kbuild")
Joe Perches6f7d98e2017-08-04 21:45:48 -07001113 && (-e "${lk_path}MAINTAINERS")
Joe Perches47abc722010-10-26 14:22:57 -07001114 && (-f "${lk_path}Makefile")
1115 && (-f "${lk_path}README")
1116 && (-d "${lk_path}Documentation")
1117 && (-d "${lk_path}arch")
1118 && (-d "${lk_path}include")
1119 && (-d "${lk_path}drivers")
1120 && (-d "${lk_path}fs")
1121 && (-d "${lk_path}init")
1122 && (-d "${lk_path}ipc")
1123 && (-d "${lk_path}kernel")
1124 && (-d "${lk_path}lib")
1125 && (-d "${lk_path}scripts")) {
1126 return 1;
1127 }
1128 return 0;
Joe Perchescb7301c2009-04-07 20:40:12 -07001129}
1130
Joe Perches0e70e832009-09-21 17:04:20 -07001131sub parse_email {
1132 my ($formatted_email) = @_;
1133
1134 my $name = "";
1135 my $address = "";
1136
Joe Perches11ecf532009-09-21 17:04:22 -07001137 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001138 $name = $1;
1139 $address = $2;
Joe Perches11ecf532009-09-21 17:04:22 -07001140 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001141 $address = $1;
Joe Perchesb7816552009-09-21 17:04:24 -07001142 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001143 $address = $1;
1144 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001145
1146 $name =~ s/^\s+|\s+$//g;
Joe Perchesd7895042009-06-16 15:34:02 -07001147 $name =~ s/^\"|\"$//g;
Joe Perches0e70e832009-09-21 17:04:20 -07001148 $address =~ s/^\s+|\s+$//g;
Joe Perchescb7301c2009-04-07 20:40:12 -07001149
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001150 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perchescb7301c2009-04-07 20:40:12 -07001151 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
Joe Perches0e70e832009-09-21 17:04:20 -07001152 $name = "\"$name\"";
Joe Perchescb7301c2009-04-07 20:40:12 -07001153 }
Joe Perches0e70e832009-09-21 17:04:20 -07001154
1155 return ($name, $address);
1156}
1157
1158sub format_email {
Joe Perchesa8af2432009-12-14 18:00:49 -08001159 my ($name, $address, $usename) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001160
1161 my $formatted_email;
1162
1163 $name =~ s/^\s+|\s+$//g;
1164 $name =~ s/^\"|\"$//g;
1165 $address =~ s/^\s+|\s+$//g;
1166
Stephen Hemmingera63ceb42010-03-05 13:43:06 -08001167 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
Joe Perches0e70e832009-09-21 17:04:20 -07001168 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1169 $name = "\"$name\"";
1170 }
1171
Joe Perchesa8af2432009-12-14 18:00:49 -08001172 if ($usename) {
Joe Perches0e70e832009-09-21 17:04:20 -07001173 if ("$name" eq "") {
1174 $formatted_email = "$address";
1175 } else {
Joe Perchesa8af2432009-12-14 18:00:49 -08001176 $formatted_email = "$name <$address>";
Joe Perches0e70e832009-09-21 17:04:20 -07001177 }
1178 } else {
1179 $formatted_email = $address;
1180 }
1181
Joe Perchescb7301c2009-04-07 20:40:12 -07001182 return $formatted_email;
1183}
1184
Joe Perches272a8972010-01-08 14:42:48 -08001185sub find_first_section {
1186 my $index = 0;
1187
1188 while ($index < @typevalue) {
1189 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001190 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perches272a8972010-01-08 14:42:48 -08001191 last;
1192 }
1193 $index++;
1194 }
1195
1196 return $index;
1197}
1198
Joe Perchesb7816552009-09-21 17:04:24 -07001199sub find_starting_index {
Joe Perchesb7816552009-09-21 17:04:24 -07001200 my ($index) = @_;
1201
1202 while ($index > 0) {
1203 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001204 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001205 last;
1206 }
1207 $index--;
1208 }
1209
1210 return $index;
1211}
1212
1213sub find_ending_index {
1214 my ($index) = @_;
1215
1216 while ($index < @typevalue) {
1217 my $tv = $typevalue[$index];
Joe Perchesce8155f2015-06-25 15:01:55 -07001218 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001219 last;
1220 }
1221 $index++;
1222 }
1223
1224 return $index;
1225}
1226
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001227sub get_subsystem_name {
1228 my ($index) = @_;
1229
1230 my $start = find_starting_index($index);
1231
1232 my $subsystem = $typevalue[$start];
1233 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1234 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1235 $subsystem =~ s/\s*$//;
1236 $subsystem = $subsystem . "...";
1237 }
1238 return $subsystem;
1239}
1240
Joe Perches3c7385b2009-12-14 18:00:46 -08001241sub get_maintainer_role {
1242 my ($index) = @_;
1243
1244 my $i;
1245 my $start = find_starting_index($index);
1246 my $end = find_ending_index($index);
1247
Joe Perches0ede2742012-03-23 15:01:56 -07001248 my $role = "unknown";
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001249 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001250
1251 for ($i = $start + 1; $i < $end; $i++) {
1252 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001253 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001254 my $ptype = $1;
1255 my $pvalue = $2;
1256 if ($ptype eq "S") {
1257 $role = $pvalue;
1258 }
1259 }
1260 }
1261
1262 $role = lc($role);
1263 if ($role eq "supported") {
1264 $role = "supporter";
1265 } elsif ($role eq "maintained") {
1266 $role = "maintainer";
1267 } elsif ($role eq "odd fixes") {
1268 $role = "odd fixer";
1269 } elsif ($role eq "orphan") {
1270 $role = "orphan minder";
1271 } elsif ($role eq "obsolete") {
1272 $role = "obsolete minder";
1273 } elsif ($role eq "buried alive in reporters") {
1274 $role = "chief penguin";
1275 }
1276
1277 return $role . ":" . $subsystem;
1278}
1279
1280sub get_list_role {
1281 my ($index) = @_;
1282
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001283 my $subsystem = get_subsystem_name($index);
Joe Perches3c7385b2009-12-14 18:00:46 -08001284
1285 if ($subsystem eq "THE REST") {
1286 $subsystem = "";
1287 }
1288
1289 return $subsystem;
1290}
1291
Joe Perchescb7301c2009-04-07 20:40:12 -07001292sub add_categories {
1293 my ($index) = @_;
1294
Joe Perchesb7816552009-09-21 17:04:24 -07001295 my $i;
1296 my $start = find_starting_index($index);
1297 my $end = find_ending_index($index);
1298
1299 push(@subsystem, $typevalue[$start]);
1300
1301 for ($i = $start + 1; $i < $end; $i++) {
1302 my $tv = $typevalue[$i];
Joe Perchesce8155f2015-06-25 15:01:55 -07001303 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001304 my $ptype = $1;
1305 my $pvalue = $2;
1306 if ($ptype eq "L") {
Joe Perches290603c2009-06-16 15:33:58 -07001307 my $list_address = $pvalue;
1308 my $list_additional = "";
Joe Perches3c7385b2009-12-14 18:00:46 -08001309 my $list_role = get_list_role($i);
1310
1311 if ($list_role ne "") {
1312 $list_role = ":" . $list_role;
1313 }
Joe Perches290603c2009-06-16 15:33:58 -07001314 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1315 $list_address = $1;
1316 $list_additional = $2;
1317 }
Joe Perchesbdf7c682009-06-16 15:33:59 -07001318 if ($list_additional =~ m/subscribers-only/) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001319 if ($email_subscriber_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001320 if (!$hash_list_to{lc($list_address)}) {
1321 $hash_list_to{lc($list_address)} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001322 push(@list_to, [$list_address,
1323 "subscriber list${list_role}"]);
1324 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001325 }
1326 } else {
1327 if ($email_list) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001328 if (!$hash_list_to{lc($list_address)}) {
Richard Weinberger728f5a92012-03-23 15:01:56 -07001329 if ($list_additional =~ m/moderated/) {
Joe Perches49662502019-07-16 16:27:09 -07001330 if ($email_moderated_list) {
1331 $hash_list_to{lc($list_address)} = 1;
1332 push(@list_to, [$list_address,
1333 "moderated list${list_role}"]);
1334 }
Richard Weinberger728f5a92012-03-23 15:01:56 -07001335 } else {
Joe Perches49662502019-07-16 16:27:09 -07001336 $hash_list_to{lc($list_address)} = 1;
Richard Weinberger728f5a92012-03-23 15:01:56 -07001337 push(@list_to, [$list_address,
1338 "open list${list_role}"]);
1339 }
Joe Perches683c6f82010-10-26 14:22:55 -07001340 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001341 }
1342 }
1343 } elsif ($ptype eq "M") {
Joe Perches0e70e832009-09-21 17:04:20 -07001344 my ($name, $address) = parse_email($pvalue);
1345 if ($name eq "") {
Joe Perchesb7816552009-09-21 17:04:24 -07001346 if ($i > 0) {
1347 my $tv = $typevalue[$i - 1];
Joe Perchesce8155f2015-06-25 15:01:55 -07001348 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches0e70e832009-09-21 17:04:20 -07001349 if ($1 eq "P") {
1350 $name = $2;
Joe Perchesa8af2432009-12-14 18:00:49 -08001351 $pvalue = format_email($name, $address, $email_usename);
Joe Perches5f2441e2009-06-16 15:34:02 -07001352 }
1353 }
1354 }
1355 }
Joe Perches0e70e832009-09-21 17:04:20 -07001356 if ($email_maintainer) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001357 my $role = get_maintainer_role($i);
1358 push_email_addresses($pvalue, $role);
Joe Perchescb7301c2009-04-07 20:40:12 -07001359 }
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001360 } elsif ($ptype eq "R") {
1361 my ($name, $address) = parse_email($pvalue);
1362 if ($name eq "") {
1363 if ($i > 0) {
1364 my $tv = $typevalue[$i - 1];
Joe Perchesce8155f2015-06-25 15:01:55 -07001365 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001366 if ($1 eq "P") {
1367 $name = $2;
1368 $pvalue = format_email($name, $address, $email_usename);
1369 }
1370 }
1371 }
1372 }
1373 if ($email_reviewer) {
Joe Perches2a7cb1d2015-11-06 16:30:52 -08001374 my $subsystem = get_subsystem_name($i);
1375 push_email_addresses($pvalue, "reviewer:$subsystem");
Joe Perchesc1c3f2c2014-06-02 12:05:17 -07001376 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001377 } elsif ($ptype eq "T") {
1378 push(@scm, $pvalue);
1379 } elsif ($ptype eq "W") {
1380 push(@web, $pvalue);
1381 } elsif ($ptype eq "S") {
1382 push(@status, $pvalue);
1383 }
Joe Perchescb7301c2009-04-07 20:40:12 -07001384 }
1385 }
1386}
1387
Joe Perches11ecf532009-09-21 17:04:22 -07001388sub email_inuse {
1389 my ($name, $address) = @_;
Joe Perches0e70e832009-09-21 17:04:20 -07001390
Joe Perches11ecf532009-09-21 17:04:22 -07001391 return 1 if (($name eq "") && ($address eq ""));
Joe Perches6ef1c522010-10-26 14:22:56 -07001392 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1393 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
Joe Perches11ecf532009-09-21 17:04:22 -07001394
Joe Perches0e70e832009-09-21 17:04:20 -07001395 return 0;
1396}
1397
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001398sub push_email_address {
Joe Perches3c7385b2009-12-14 18:00:46 -08001399 my ($line, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001400
Joe Perches0e70e832009-09-21 17:04:20 -07001401 my ($name, $address) = parse_email($line);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001402
Joe Perchesb7816552009-09-21 17:04:24 -07001403 if ($address eq "") {
1404 return 0;
1405 }
1406
Joe Perches11ecf532009-09-21 17:04:22 -07001407 if (!$email_remove_duplicates) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001408 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perches11ecf532009-09-21 17:04:22 -07001409 } elsif (!email_inuse($name, $address)) {
Joe Perchesa8af2432009-12-14 18:00:49 -08001410 push(@email_to, [format_email($name, $address, $email_usename), $role]);
Joe Perchesfae99202010-10-26 14:22:58 -07001411 $email_hash_name{lc($name)}++ if ($name ne "");
Joe Perches6ef1c522010-10-26 14:22:56 -07001412 $email_hash_address{lc($address)}++;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001413 }
Joe Perchesb7816552009-09-21 17:04:24 -07001414
1415 return 1;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001416}
1417
1418sub push_email_addresses {
Joe Perches3c7385b2009-12-14 18:00:46 -08001419 my ($address, $role) = @_;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001420
1421 my @address_list = ();
1422
Joe Perches5f2441e2009-06-16 15:34:02 -07001423 if (rfc822_valid($address)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001424 push_email_address($address, $role);
Joe Perches5f2441e2009-06-16 15:34:02 -07001425 } elsif (@address_list = rfc822_validlist($address)) {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001426 my $array_count = shift(@address_list);
1427 while (my $entry = shift(@address_list)) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001428 push_email_address($entry, $role);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001429 }
Joe Perches5f2441e2009-06-16 15:34:02 -07001430 } else {
Joe Perches3c7385b2009-12-14 18:00:46 -08001431 if (!push_email_address($address, $role)) {
Joe Perchesb7816552009-09-21 17:04:24 -07001432 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1433 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001434 }
Joe Perches1b5e1cf2009-06-16 15:34:01 -07001435}
1436
Joe Perches3c7385b2009-12-14 18:00:46 -08001437sub add_role {
1438 my ($line, $role) = @_;
1439
1440 my ($name, $address) = parse_email($line);
Joe Perchesa8af2432009-12-14 18:00:49 -08001441 my $email = format_email($name, $address, $email_usename);
Joe Perches3c7385b2009-12-14 18:00:46 -08001442
1443 foreach my $entry (@email_to) {
1444 if ($email_remove_duplicates) {
1445 my ($entry_name, $entry_address) = parse_email($entry->[0]);
Joe Perches03372db2010-03-05 13:43:00 -08001446 if (($name eq $entry_name || $address eq $entry_address)
1447 && ($role eq "" || !($entry->[1] =~ m/$role/))
1448 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001449 if ($entry->[1] eq "") {
1450 $entry->[1] = "$role";
1451 } else {
1452 $entry->[1] = "$entry->[1],$role";
1453 }
1454 }
1455 } else {
Joe Perches03372db2010-03-05 13:43:00 -08001456 if ($email eq $entry->[0]
1457 && ($role eq "" || !($entry->[1] =~ m/$role/))
1458 ) {
Joe Perches3c7385b2009-12-14 18:00:46 -08001459 if ($entry->[1] eq "") {
1460 $entry->[1] = "$role";
1461 } else {
1462 $entry->[1] = "$entry->[1],$role";
1463 }
1464 }
1465 }
1466 }
1467}
1468
Joe Perchescb7301c2009-04-07 20:40:12 -07001469sub which {
1470 my ($bin) = @_;
1471
Joe Perchesf5f5078d2009-06-16 15:34:00 -07001472 foreach my $path (split(/:/, $ENV{PATH})) {
Joe Perchescb7301c2009-04-07 20:40:12 -07001473 if (-e "$path/$bin") {
1474 return "$path/$bin";
1475 }
1476 }
1477
1478 return "";
1479}
1480
Joe Perchesbcde44e2010-10-26 14:22:53 -07001481sub which_conf {
1482 my ($conf) = @_;
1483
1484 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1485 if (-e "$path/$conf") {
1486 return "$path/$conf";
1487 }
1488 }
1489
1490 return "";
1491}
1492
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001493sub mailmap_email {
Joe Perchesb9e23312010-10-26 14:22:58 -07001494 my ($line) = @_;
Joe Perches8cbb3a72009-09-21 17:04:21 -07001495
Joe Perches47abc722010-10-26 14:22:57 -07001496 my ($name, $address) = parse_email($line);
1497 my $email = format_email($name, $address, 1);
1498 my $real_name = $name;
1499 my $real_address = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001500
Joe Perches47abc722010-10-26 14:22:57 -07001501 if (exists $mailmap->{names}->{$email} ||
1502 exists $mailmap->{addresses}->{$email}) {
1503 if (exists $mailmap->{names}->{$email}) {
1504 $real_name = $mailmap->{names}->{$email};
Joe Perches8cbb3a72009-09-21 17:04:21 -07001505 }
Joe Perches47abc722010-10-26 14:22:57 -07001506 if (exists $mailmap->{addresses}->{$email}) {
1507 $real_address = $mailmap->{addresses}->{$email};
1508 }
1509 } else {
1510 if (exists $mailmap->{names}->{$address}) {
1511 $real_name = $mailmap->{names}->{$address};
1512 }
1513 if (exists $mailmap->{addresses}->{$address}) {
1514 $real_address = $mailmap->{addresses}->{$address};
1515 }
1516 }
1517 return format_email($real_name, $real_address, 1);
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001518}
1519
1520sub mailmap {
1521 my (@addresses) = @_;
1522
Joe Perchesb9e23312010-10-26 14:22:58 -07001523 my @mapped_emails = ();
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001524 foreach my $line (@addresses) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001525 push(@mapped_emails, mailmap_email($line));
Joe Perches8cbb3a72009-09-21 17:04:21 -07001526 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001527 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1528 return @mapped_emails;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001529}
1530
1531sub merge_by_realname {
Joe Perches47abc722010-10-26 14:22:57 -07001532 my %address_map;
1533 my (@emails) = @_;
Joe Perchesb9e23312010-10-26 14:22:58 -07001534
Joe Perches47abc722010-10-26 14:22:57 -07001535 foreach my $email (@emails) {
1536 my ($name, $address) = parse_email($email);
Joe Perchesb9e23312010-10-26 14:22:58 -07001537 if (exists $address_map{$name}) {
Joe Perches47abc722010-10-26 14:22:57 -07001538 $address = $address_map{$name};
Joe Perchesb9e23312010-10-26 14:22:58 -07001539 $email = format_email($name, $address, 1);
1540 } else {
1541 $address_map{$name} = $address;
Florian Mickler7fa8ff22010-10-26 14:22:56 -07001542 }
Joe Perches47abc722010-10-26 14:22:57 -07001543 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07001544}
1545
Joe Perches60db31a2009-12-14 18:00:50 -08001546sub git_execute_cmd {
1547 my ($cmd) = @_;
1548 my @lines = ();
Joe Perchescb7301c2009-04-07 20:40:12 -07001549
Joe Perches60db31a2009-12-14 18:00:50 -08001550 my $output = `$cmd`;
1551 $output =~ s/^\s*//gm;
1552 @lines = split("\n", $output);
1553
1554 return @lines;
Joe Perchesa8af2432009-12-14 18:00:49 -08001555}
1556
Joe Perches60db31a2009-12-14 18:00:50 -08001557sub hg_execute_cmd {
Joe Perchesa8af2432009-12-14 18:00:49 -08001558 my ($cmd) = @_;
Joe Perches60db31a2009-12-14 18:00:50 -08001559 my @lines = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001560
Joe Perches60db31a2009-12-14 18:00:50 -08001561 my $output = `$cmd`;
1562 @lines = split("\n", $output);
1563
1564 return @lines;
1565}
1566
Joe Perches683c6f82010-10-26 14:22:55 -07001567sub extract_formatted_signatures {
1568 my (@signature_lines) = @_;
1569
1570 my @type = @signature_lines;
1571
1572 s/\s*(.*):.*/$1/ for (@type);
1573
1574 # cut -f2- -d":"
1575 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1576
1577## Reformat email addresses (with names) to avoid badly written signatures
1578
1579 foreach my $signer (@signature_lines) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001580 $signer = deduplicate_email($signer);
Joe Perches683c6f82010-10-26 14:22:55 -07001581 }
1582
1583 return (\@type, \@signature_lines);
1584}
1585
Joe Perches60db31a2009-12-14 18:00:50 -08001586sub vcs_find_signers {
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001587 my ($cmd, $file) = @_;
Joe Perchesa8af2432009-12-14 18:00:49 -08001588 my $commits;
Joe Perches683c6f82010-10-26 14:22:55 -07001589 my @lines = ();
1590 my @signatures = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001591 my @authors = ();
1592 my @stats = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08001593
Joe Perches60db31a2009-12-14 18:00:50 -08001594 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
Joe Perchescb7301c2009-04-07 20:40:12 -07001595
Joe Perches60db31a2009-12-14 18:00:50 -08001596 my $pattern = $VCS_cmds{"commit_pattern"};
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001597 my $author_pattern = $VCS_cmds{"author_pattern"};
1598 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1599
1600 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
Joe Perchescb7301c2009-04-07 20:40:12 -07001601
Joe Perches60db31a2009-12-14 18:00:50 -08001602 $commits = grep(/$pattern/, @lines); # of commits
Joe Perchesafa81ee2009-07-29 15:04:28 -07001603
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001604 @authors = grep(/$author_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001605 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001606 @stats = grep(/$stat_pattern/, @lines);
Joe Perches683c6f82010-10-26 14:22:55 -07001607
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001608# print("stats: <@stats>\n");
1609
1610 return (0, \@signatures, \@authors, \@stats) if !@signatures;
Joe Perches683c6f82010-10-26 14:22:55 -07001611
1612 save_commits_by_author(@lines) if ($interactive);
1613 save_commits_by_signer(@lines) if ($interactive);
1614
Joe Perches0e70e832009-09-21 17:04:20 -07001615 if (!$email_git_penguin_chiefs) {
Joe Perches683c6f82010-10-26 14:22:55 -07001616 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
Joe Perches0e70e832009-09-21 17:04:20 -07001617 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001618
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001619 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
Joe Perches683c6f82010-10-26 14:22:55 -07001620 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
Joe Perches63ab52d2010-10-26 14:22:51 -07001621
Joe Perchesc9ecefe2014-01-23 15:54:20 -08001622 return ($commits, $signers_ref, $authors_ref, \@stats);
Joe Perchesa8af2432009-12-14 18:00:49 -08001623}
1624
Joe Perches63ab52d2010-10-26 14:22:51 -07001625sub vcs_find_author {
1626 my ($cmd) = @_;
1627 my @lines = ();
1628
1629 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1630
1631 if (!$email_git_penguin_chiefs) {
1632 @lines = grep(!/${penguin_chiefs}/i, @lines);
1633 }
1634
1635 return @lines if !@lines;
1636
Joe Perches683c6f82010-10-26 14:22:55 -07001637 my @authors = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07001638 foreach my $line (@lines) {
Joe Perches683c6f82010-10-26 14:22:55 -07001639 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1640 my $author = $1;
1641 my ($name, $address) = parse_email($author);
1642 $author = format_email($name, $address, 1);
1643 push(@authors, $author);
1644 }
Joe Perches63ab52d2010-10-26 14:22:51 -07001645 }
1646
Joe Perches683c6f82010-10-26 14:22:55 -07001647 save_commits_by_author(@lines) if ($interactive);
1648 save_commits_by_signer(@lines) if ($interactive);
1649
1650 return @authors;
Joe Perches63ab52d2010-10-26 14:22:51 -07001651}
1652
Joe Perches60db31a2009-12-14 18:00:50 -08001653sub vcs_save_commits {
1654 my ($cmd) = @_;
1655 my @lines = ();
1656 my @commits = ();
1657
1658 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1659
1660 foreach my $line (@lines) {
1661 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1662 push(@commits, $1);
1663 }
1664 }
1665
1666 return @commits;
1667}
1668
1669sub vcs_blame {
1670 my ($file) = @_;
1671 my $cmd;
1672 my @commits = ();
1673
1674 return @commits if (!(-f $file));
1675
1676 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1677 my @all_commits = ();
1678
1679 $cmd = $VCS_cmds{"blame_file_cmd"};
1680 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1681 @all_commits = vcs_save_commits($cmd);
1682
1683 foreach my $file_range_diff (@range) {
1684 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1685 my $diff_file = $1;
1686 my $diff_start = $2;
1687 my $diff_length = $3;
1688 next if ("$file" ne "$diff_file");
1689 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1690 push(@commits, $all_commits[$i]);
1691 }
1692 }
1693 } elsif (@range) {
1694 foreach my $file_range_diff (@range) {
1695 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1696 my $diff_file = $1;
1697 my $diff_start = $2;
1698 my $diff_length = $3;
1699 next if ("$file" ne "$diff_file");
1700 $cmd = $VCS_cmds{"blame_range_cmd"};
1701 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1702 push(@commits, vcs_save_commits($cmd));
1703 }
1704 } else {
1705 $cmd = $VCS_cmds{"blame_file_cmd"};
1706 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1707 @commits = vcs_save_commits($cmd);
1708 }
1709
Joe Perches63ab52d2010-10-26 14:22:51 -07001710 foreach my $commit (@commits) {
1711 $commit =~ s/^\^//g;
1712 }
1713
Joe Perches60db31a2009-12-14 18:00:50 -08001714 return @commits;
1715}
1716
1717my $printed_novcs = 0;
1718sub vcs_exists {
1719 %VCS_cmds = %VCS_cmds_git;
1720 return 1 if eval $VCS_cmds{"available"};
1721 %VCS_cmds = %VCS_cmds_hg;
Joe Perches683c6f82010-10-26 14:22:55 -07001722 return 2 if eval $VCS_cmds{"available"};
Joe Perches60db31a2009-12-14 18:00:50 -08001723 %VCS_cmds = ();
1724 if (!$printed_novcs) {
1725 warn("$P: No supported VCS found. Add --nogit to options?\n");
1726 warn("Using a git repository produces better results.\n");
1727 warn("Try Linus Torvalds' latest git repository using:\n");
Ralf Thielow3d1c2f72011-08-25 15:59:07 -07001728 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
Joe Perches60db31a2009-12-14 18:00:50 -08001729 $printed_novcs = 1;
1730 }
1731 return 0;
1732}
1733
Joe Perches683c6f82010-10-26 14:22:55 -07001734sub vcs_is_git {
Joe Perchesb9e23312010-10-26 14:22:58 -07001735 vcs_exists();
Joe Perches683c6f82010-10-26 14:22:55 -07001736 return $vcs_used == 1;
1737}
1738
1739sub vcs_is_hg {
1740 return $vcs_used == 2;
1741}
1742
Joe Perches2f5bd3432019-12-04 16:50:29 -08001743sub vcs_add_commit_signers {
1744 return if (!vcs_exists());
1745
1746 my ($commit, $desc) = @_;
1747 my $commit_count = 0;
1748 my $commit_authors_ref;
1749 my $commit_signers_ref;
1750 my $stats_ref;
1751 my @commit_authors = ();
1752 my @commit_signers = ();
1753 my $cmd;
1754
1755 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1756 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1757
1758 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1759 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1760 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1761
1762 foreach my $signer (@commit_signers) {
1763 $signer = deduplicate_email($signer);
1764 }
1765
1766 vcs_assign($desc, 1, @commit_signers);
1767}
1768
Joe Perches6ef1c522010-10-26 14:22:56 -07001769sub interactive_get_maintainers {
Joe Perches683c6f82010-10-26 14:22:55 -07001770 my ($list_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07001771 my @list = @$list_ref;
1772
Joe Perches683c6f82010-10-26 14:22:55 -07001773 vcs_exists();
Florian Micklerdace8e32010-10-26 14:22:54 -07001774
1775 my %selected;
Joe Perches683c6f82010-10-26 14:22:55 -07001776 my %authored;
1777 my %signed;
Florian Micklerdace8e32010-10-26 14:22:54 -07001778 my $count = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001779 my $maintained = 0;
Joe Perches6ef1c522010-10-26 14:22:56 -07001780 foreach my $entry (@list) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001781 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1782 $selected{$count} = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001783 $authored{$count} = 0;
1784 $signed{$count} = 0;
1785 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001786 }
1787
1788 #menu loop
Joe Perches683c6f82010-10-26 14:22:55 -07001789 my $done = 0;
1790 my $print_options = 0;
1791 my $redraw = 1;
1792 while (!$done) {
1793 $count = 0;
1794 if ($redraw) {
Joe Perches6ef1c522010-10-26 14:22:56 -07001795 printf STDERR "\n%1s %2s %-65s",
1796 "*", "#", "email/list and role:stats";
1797 if ($email_git ||
1798 ($email_git_fallback && !$maintained) ||
1799 $email_git_blame) {
1800 print STDERR "auth sign";
1801 }
1802 print STDERR "\n";
Joe Perches683c6f82010-10-26 14:22:55 -07001803 foreach my $entry (@list) {
1804 my $email = $entry->[0];
1805 my $role = $entry->[1];
1806 my $sel = "";
1807 $sel = "*" if ($selected{$count});
1808 my $commit_author = $commit_author_hash{$email};
1809 my $commit_signer = $commit_signer_hash{$email};
1810 my $authored = 0;
1811 my $signed = 0;
1812 $authored++ for (@{$commit_author});
1813 $signed++ for (@{$commit_signer});
1814 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1815 printf STDERR "%4d %4d", $authored, $signed
1816 if ($authored > 0 || $signed > 0);
1817 printf STDERR "\n %s\n", $role;
1818 if ($authored{$count}) {
1819 my $commit_author = $commit_author_hash{$email};
1820 foreach my $ref (@{$commit_author}) {
1821 print STDERR " Author: @{$ref}[1]\n";
Florian Micklerdace8e32010-10-26 14:22:54 -07001822 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001823 }
Joe Perches683c6f82010-10-26 14:22:55 -07001824 if ($signed{$count}) {
1825 my $commit_signer = $commit_signer_hash{$email};
1826 foreach my $ref (@{$commit_signer}) {
1827 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1828 }
1829 }
1830
1831 $count++;
Florian Micklerdace8e32010-10-26 14:22:54 -07001832 }
Florian Micklerdace8e32010-10-26 14:22:54 -07001833 }
Joe Perches683c6f82010-10-26 14:22:55 -07001834 my $date_ref = \$email_git_since;
1835 $date_ref = \$email_hg_since if (vcs_is_hg());
1836 if ($print_options) {
1837 $print_options = 0;
1838 if (vcs_exists()) {
Joe Perchesb9e23312010-10-26 14:22:58 -07001839 print STDERR <<EOT
1840
1841Version Control options:
1842g use git history [$email_git]
1843gf use git-fallback [$email_git_fallback]
1844b use git blame [$email_git_blame]
1845bs use blame signatures [$email_git_blame_signatures]
1846c# minimum commits [$email_git_min_signatures]
1847%# min percent [$email_git_min_percent]
1848d# history to use [$$date_ref]
1849x# max maintainers [$email_git_max_maintainers]
1850t all signature types [$email_git_all_signature_types]
1851m use .mailmap [$email_use_mailmap]
1852EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001853 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001854 print STDERR <<EOT
1855
1856Additional options:
18570 toggle all
1858tm toggle maintainers
1859tg toggle git entries
1860tl toggle open list entries
1861ts toggle subscriber list entries
1862f emails in file [$file_emails]
1863k keywords in file [$keywords]
1864r remove duplicates [$email_remove_duplicates]
1865p# pattern match depth [$pattern_depth]
1866EOT
Joe Perches683c6f82010-10-26 14:22:55 -07001867 }
1868 print STDERR
1869"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1870
1871 my $input = <STDIN>;
Florian Micklerdace8e32010-10-26 14:22:54 -07001872 chomp($input);
1873
Joe Perches683c6f82010-10-26 14:22:55 -07001874 $redraw = 1;
1875 my $rerun = 0;
1876 my @wish = split(/[, ]+/, $input);
1877 foreach my $nr (@wish) {
1878 $nr = lc($nr);
1879 my $sel = substr($nr, 0, 1);
1880 my $str = substr($nr, 1);
1881 my $val = 0;
1882 $val = $1 if $str =~ /^(\d+)$/;
1883
1884 if ($sel eq "y") {
1885 $interactive = 0;
1886 $done = 1;
1887 $output_rolestats = 0;
1888 $output_roles = 0;
1889 last;
1890 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1891 $selected{$nr - 1} = !$selected{$nr - 1};
1892 } elsif ($sel eq "*" || $sel eq '^') {
1893 my $toggle = 0;
1894 $toggle = 1 if ($sel eq '*');
1895 for (my $i = 0; $i < $count; $i++) {
1896 $selected{$i} = $toggle;
Florian Micklerdace8e32010-10-26 14:22:54 -07001897 }
Joe Perches683c6f82010-10-26 14:22:55 -07001898 } elsif ($sel eq "0") {
1899 for (my $i = 0; $i < $count; $i++) {
1900 $selected{$i} = !$selected{$i};
1901 }
Joe Perchesb9e23312010-10-26 14:22:58 -07001902 } elsif ($sel eq "t") {
1903 if (lc($str) eq "m") {
1904 for (my $i = 0; $i < $count; $i++) {
1905 $selected{$i} = !$selected{$i}
1906 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1907 }
1908 } elsif (lc($str) eq "g") {
1909 for (my $i = 0; $i < $count; $i++) {
1910 $selected{$i} = !$selected{$i}
1911 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1912 }
1913 } elsif (lc($str) eq "l") {
1914 for (my $i = 0; $i < $count; $i++) {
1915 $selected{$i} = !$selected{$i}
1916 if ($list[$i]->[1] =~ /^(open list)/i);
1917 }
1918 } elsif (lc($str) eq "s") {
1919 for (my $i = 0; $i < $count; $i++) {
1920 $selected{$i} = !$selected{$i}
1921 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1922 }
1923 }
Joe Perches683c6f82010-10-26 14:22:55 -07001924 } elsif ($sel eq "a") {
1925 if ($val > 0 && $val <= $count) {
1926 $authored{$val - 1} = !$authored{$val - 1};
1927 } elsif ($str eq '*' || $str eq '^') {
1928 my $toggle = 0;
1929 $toggle = 1 if ($str eq '*');
1930 for (my $i = 0; $i < $count; $i++) {
1931 $authored{$i} = $toggle;
1932 }
1933 }
1934 } elsif ($sel eq "s") {
1935 if ($val > 0 && $val <= $count) {
1936 $signed{$val - 1} = !$signed{$val - 1};
1937 } elsif ($str eq '*' || $str eq '^') {
1938 my $toggle = 0;
1939 $toggle = 1 if ($str eq '*');
1940 for (my $i = 0; $i < $count; $i++) {
1941 $signed{$i} = $toggle;
1942 }
1943 }
1944 } elsif ($sel eq "o") {
1945 $print_options = 1;
1946 $redraw = 1;
1947 } elsif ($sel eq "g") {
1948 if ($str eq "f") {
1949 bool_invert(\$email_git_fallback);
Florian Micklerdace8e32010-10-26 14:22:54 -07001950 } else {
Joe Perches683c6f82010-10-26 14:22:55 -07001951 bool_invert(\$email_git);
Florian Micklerdace8e32010-10-26 14:22:54 -07001952 }
Joe Perches683c6f82010-10-26 14:22:55 -07001953 $rerun = 1;
1954 } elsif ($sel eq "b") {
1955 if ($str eq "s") {
1956 bool_invert(\$email_git_blame_signatures);
1957 } else {
1958 bool_invert(\$email_git_blame);
1959 }
1960 $rerun = 1;
1961 } elsif ($sel eq "c") {
1962 if ($val > 0) {
1963 $email_git_min_signatures = $val;
1964 $rerun = 1;
1965 }
1966 } elsif ($sel eq "x") {
1967 if ($val > 0) {
1968 $email_git_max_maintainers = $val;
1969 $rerun = 1;
1970 }
1971 } elsif ($sel eq "%") {
1972 if ($str ne "" && $val >= 0) {
1973 $email_git_min_percent = $val;
1974 $rerun = 1;
1975 }
1976 } elsif ($sel eq "d") {
1977 if (vcs_is_git()) {
1978 $email_git_since = $str;
1979 } elsif (vcs_is_hg()) {
1980 $email_hg_since = $str;
1981 }
1982 $rerun = 1;
1983 } elsif ($sel eq "t") {
1984 bool_invert(\$email_git_all_signature_types);
1985 $rerun = 1;
1986 } elsif ($sel eq "f") {
1987 bool_invert(\$file_emails);
1988 $rerun = 1;
1989 } elsif ($sel eq "r") {
1990 bool_invert(\$email_remove_duplicates);
1991 $rerun = 1;
Joe Perchesb9e23312010-10-26 14:22:58 -07001992 } elsif ($sel eq "m") {
1993 bool_invert(\$email_use_mailmap);
1994 read_mailmap();
1995 $rerun = 1;
Joe Perches683c6f82010-10-26 14:22:55 -07001996 } elsif ($sel eq "k") {
1997 bool_invert(\$keywords);
1998 $rerun = 1;
1999 } elsif ($sel eq "p") {
2000 if ($str ne "" && $val >= 0) {
2001 $pattern_depth = $val;
2002 $rerun = 1;
2003 }
Joe Perches6ef1c522010-10-26 14:22:56 -07002004 } elsif ($sel eq "h" || $sel eq "?") {
2005 print STDERR <<EOT
2006
2007Interactive mode allows you to select the various maintainers, submitters,
2008commit signers and mailing lists that could be CC'd on a patch.
2009
2010Any *'d entry is selected.
2011
Joe Perches47abc722010-10-26 14:22:57 -07002012If you have git or hg installed, you can choose to summarize the commit
Joe Perches6ef1c522010-10-26 14:22:56 -07002013history of files in the patch. Also, each line of the current file can
2014be matched to its commit author and that commits signers with blame.
2015
2016Various knobs exist to control the length of time for active commit
2017tracking, the maximum number of commit authors and signers to add,
2018and such.
2019
2020Enter selections at the prompt until you are satisfied that the selected
2021maintainers are appropriate. You may enter multiple selections separated
2022by either commas or spaces.
2023
2024EOT
Joe Perches683c6f82010-10-26 14:22:55 -07002025 } else {
2026 print STDERR "invalid option: '$nr'\n";
2027 $redraw = 0;
2028 }
2029 }
2030 if ($rerun) {
2031 print STDERR "git-blame can be very slow, please have patience..."
2032 if ($email_git_blame);
Joe Perches6ef1c522010-10-26 14:22:56 -07002033 goto &get_maintainers;
Joe Perches683c6f82010-10-26 14:22:55 -07002034 }
2035 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002036
2037 #drop not selected entries
2038 $count = 0;
Joe Perches683c6f82010-10-26 14:22:55 -07002039 my @new_emailto = ();
2040 foreach my $entry (@list) {
2041 if ($selected{$count}) {
2042 push(@new_emailto, $list[$count]);
Florian Micklerdace8e32010-10-26 14:22:54 -07002043 }
2044 $count++;
2045 }
Joe Perches683c6f82010-10-26 14:22:55 -07002046 return @new_emailto;
Florian Micklerdace8e32010-10-26 14:22:54 -07002047}
2048
Joe Perches683c6f82010-10-26 14:22:55 -07002049sub bool_invert {
2050 my ($bool_ref) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002051
Joe Perches683c6f82010-10-26 14:22:55 -07002052 if ($$bool_ref) {
2053 $$bool_ref = 0;
2054 } else {
2055 $$bool_ref = 1;
Florian Micklerdace8e32010-10-26 14:22:54 -07002056 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002057}
2058
Joe Perchesb9e23312010-10-26 14:22:58 -07002059sub deduplicate_email {
2060 my ($email) = @_;
2061
2062 my $matched = 0;
2063 my ($name, $address) = parse_email($email);
2064 $email = format_email($name, $address, 1);
2065 $email = mailmap_email($email);
2066
2067 return $email if (!$email_remove_duplicates);
2068
2069 ($name, $address) = parse_email($email);
2070
Joe Perchesfae99202010-10-26 14:22:58 -07002071 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002072 $name = $deduplicate_name_hash{lc($name)}->[0];
2073 $address = $deduplicate_name_hash{lc($name)}->[1];
2074 $matched = 1;
2075 } elsif ($deduplicate_address_hash{lc($address)}) {
2076 $name = $deduplicate_address_hash{lc($address)}->[0];
2077 $address = $deduplicate_address_hash{lc($address)}->[1];
2078 $matched = 1;
2079 }
2080 if (!$matched) {
2081 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2082 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2083 }
2084 $email = format_email($name, $address, 1);
2085 $email = mailmap_email($email);
2086 return $email;
2087}
2088
Joe Perches683c6f82010-10-26 14:22:55 -07002089sub save_commits_by_author {
2090 my (@lines) = @_;
Florian Micklerdace8e32010-10-26 14:22:54 -07002091
Joe Perches683c6f82010-10-26 14:22:55 -07002092 my @authors = ();
2093 my @commits = ();
2094 my @subjects = ();
2095
2096 foreach my $line (@lines) {
2097 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2098 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002099 $author = deduplicate_email($author);
Joe Perches683c6f82010-10-26 14:22:55 -07002100 push(@authors, $author);
2101 }
2102 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2103 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2104 }
2105
2106 for (my $i = 0; $i < @authors; $i++) {
2107 my $exists = 0;
2108 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2109 if (@{$ref}[0] eq $commits[$i] &&
2110 @{$ref}[1] eq $subjects[$i]) {
2111 $exists = 1;
2112 last;
2113 }
2114 }
2115 if (!$exists) {
2116 push(@{$commit_author_hash{$authors[$i]}},
2117 [ ($commits[$i], $subjects[$i]) ]);
2118 }
2119 }
2120}
2121
2122sub save_commits_by_signer {
2123 my (@lines) = @_;
2124
2125 my $commit = "";
2126 my $subject = "";
2127
2128 foreach my $line (@lines) {
2129 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2130 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2131 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2132 my @signatures = ($line);
2133 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2134 my @types = @$types_ref;
2135 my @signers = @$signers_ref;
2136
2137 my $type = $types[0];
2138 my $signer = $signers[0];
2139
Joe Perchesb9e23312010-10-26 14:22:58 -07002140 $signer = deduplicate_email($signer);
Joe Perches6ef1c522010-10-26 14:22:56 -07002141
Joe Perches683c6f82010-10-26 14:22:55 -07002142 my $exists = 0;
2143 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2144 if (@{$ref}[0] eq $commit &&
2145 @{$ref}[1] eq $subject &&
2146 @{$ref}[2] eq $type) {
2147 $exists = 1;
2148 last;
2149 }
2150 }
2151 if (!$exists) {
2152 push(@{$commit_signer_hash{$signer}},
2153 [ ($commit, $subject, $type) ]);
2154 }
2155 }
2156 }
Florian Micklerdace8e32010-10-26 14:22:54 -07002157}
2158
Joe Perches60db31a2009-12-14 18:00:50 -08002159sub vcs_assign {
Joe Perchesa8af2432009-12-14 18:00:49 -08002160 my ($role, $divisor, @lines) = @_;
2161
2162 my %hash;
2163 my $count = 0;
2164
Joe Perchesa8af2432009-12-14 18:00:49 -08002165 return if (@lines <= 0);
2166
2167 if ($divisor <= 0) {
Joe Perches60db31a2009-12-14 18:00:50 -08002168 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
Joe Perchesa8af2432009-12-14 18:00:49 -08002169 $divisor = 1;
Joe Perches3c7385b2009-12-14 18:00:46 -08002170 }
Joe Perches8cbb3a72009-09-21 17:04:21 -07002171
Florian Mickler7fa8ff22010-10-26 14:22:56 -07002172 @lines = mailmap(@lines);
Joe Perches0e70e832009-09-21 17:04:20 -07002173
Joe Perches63ab52d2010-10-26 14:22:51 -07002174 return if (@lines <= 0);
2175
Joe Perches0e70e832009-09-21 17:04:20 -07002176 @lines = sort(@lines);
Joe Perchesafa81ee2009-07-29 15:04:28 -07002177
Joe Perches11ecf532009-09-21 17:04:22 -07002178 # uniq -c
2179 $hash{$_}++ for @lines;
2180
2181 # sort -rn
2182 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2183 my $sign_offs = $hash{$line};
Joe Perchesa8af2432009-12-14 18:00:49 -08002184 my $percent = $sign_offs * 100 / $divisor;
Joe Perches3c7385b2009-12-14 18:00:46 -08002185
Joe Perchesa8af2432009-12-14 18:00:49 -08002186 $percent = 100 if ($percent > 100);
Joe Perches435de072015-06-25 15:01:50 -07002187 next if (ignore_email_address($line));
Joe Perches11ecf532009-09-21 17:04:22 -07002188 $count++;
2189 last if ($sign_offs < $email_git_min_signatures ||
2190 $count > $email_git_max_maintainers ||
Joe Perchesa8af2432009-12-14 18:00:49 -08002191 $percent < $email_git_min_percent);
Joe Perches3c7385b2009-12-14 18:00:46 -08002192 push_email_address($line, '');
Joe Perches3c7385b2009-12-14 18:00:46 -08002193 if ($output_rolestats) {
Joe Perchesa8af2432009-12-14 18:00:49 -08002194 my $fmt_percent = sprintf("%.0f", $percent);
2195 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2196 } else {
2197 add_role($line, $role);
Joe Perches3c7385b2009-12-14 18:00:46 -08002198 }
Joe Perchesf5492662009-09-21 17:04:13 -07002199 }
2200}
2201
Joe Perches60db31a2009-12-14 18:00:50 -08002202sub vcs_file_signoffs {
Joe Perchesa8af2432009-12-14 18:00:49 -08002203 my ($file) = @_;
2204
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002205 my $authors_ref;
2206 my $signers_ref;
2207 my $stats_ref;
2208 my @authors = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002209 my @signers = ();
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002210 my @stats = ();
Joe Perches60db31a2009-12-14 18:00:50 -08002211 my $commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002212
Joe Perches683c6f82010-10-26 14:22:55 -07002213 $vcs_used = vcs_exists();
2214 return if (!$vcs_used);
Joe Perchesa8af2432009-12-14 18:00:49 -08002215
Joe Perches60db31a2009-12-14 18:00:50 -08002216 my $cmd = $VCS_cmds{"find_signers_cmd"};
2217 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2218
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002219 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2220
2221 @signers = @{$signers_ref} if defined $signers_ref;
2222 @authors = @{$authors_ref} if defined $authors_ref;
2223 @stats = @{$stats_ref} if defined $stats_ref;
2224
2225# print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
Joe Perchesb9e23312010-10-26 14:22:58 -07002226
2227 foreach my $signer (@signers) {
2228 $signer = deduplicate_email($signer);
2229 }
2230
Joe Perches60db31a2009-12-14 18:00:50 -08002231 vcs_assign("commit_signer", $commits, @signers);
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002232 vcs_assign("authored", $commits, @authors);
2233 if ($#authors == $#stats) {
2234 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2235 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2236
2237 my $added = 0;
2238 my $deleted = 0;
2239 for (my $i = 0; $i <= $#stats; $i++) {
2240 if ($stats[$i] =~ /$stat_pattern/) {
2241 $added += $1;
2242 $deleted += $2;
2243 }
2244 }
2245 my @tmp_authors = uniq(@authors);
2246 foreach my $author (@tmp_authors) {
2247 $author = deduplicate_email($author);
2248 }
2249 @tmp_authors = uniq(@tmp_authors);
2250 my @list_added = ();
2251 my @list_deleted = ();
2252 foreach my $author (@tmp_authors) {
2253 my $auth_added = 0;
2254 my $auth_deleted = 0;
2255 for (my $i = 0; $i <= $#stats; $i++) {
2256 if ($author eq deduplicate_email($authors[$i]) &&
2257 $stats[$i] =~ /$stat_pattern/) {
2258 $auth_added += $1;
2259 $auth_deleted += $2;
2260 }
2261 }
2262 for (my $i = 0; $i < $auth_added; $i++) {
2263 push(@list_added, $author);
2264 }
2265 for (my $i = 0; $i < $auth_deleted; $i++) {
2266 push(@list_deleted, $author);
2267 }
2268 }
2269 vcs_assign("added_lines", $added, @list_added);
2270 vcs_assign("removed_lines", $deleted, @list_deleted);
2271 }
Joe Perchesa8af2432009-12-14 18:00:49 -08002272}
2273
Joe Perches60db31a2009-12-14 18:00:50 -08002274sub vcs_file_blame {
Joe Perchesf5492662009-09-21 17:04:13 -07002275 my ($file) = @_;
2276
Joe Perches60db31a2009-12-14 18:00:50 -08002277 my @signers = ();
Joe Perches63ab52d2010-10-26 14:22:51 -07002278 my @all_commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002279 my @commits = ();
Joe Perchesa8af2432009-12-14 18:00:49 -08002280 my $total_commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002281 my $total_lines;
Joe Perchesf5492662009-09-21 17:04:13 -07002282
Joe Perches683c6f82010-10-26 14:22:55 -07002283 $vcs_used = vcs_exists();
2284 return if (!$vcs_used);
Joe Perchesf5492662009-09-21 17:04:13 -07002285
Joe Perches63ab52d2010-10-26 14:22:51 -07002286 @all_commits = vcs_blame($file);
2287 @commits = uniq(@all_commits);
Joe Perchesa8af2432009-12-14 18:00:49 -08002288 $total_commits = @commits;
Joe Perches63ab52d2010-10-26 14:22:51 -07002289 $total_lines = @all_commits;
Joe Perchesa8af2432009-12-14 18:00:49 -08002290
Joe Perches683c6f82010-10-26 14:22:55 -07002291 if ($email_git_blame_signatures) {
2292 if (vcs_is_hg()) {
2293 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002294 my $commit_authors_ref;
2295 my $commit_signers_ref;
2296 my $stats_ref;
2297 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002298 my @commit_signers = ();
2299 my $commit = join(" -r ", @commits);
2300 my $cmd;
Joe Perchesf5492662009-09-21 17:04:13 -07002301
Joe Perches683c6f82010-10-26 14:22:55 -07002302 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2303 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
Joe Perches60db31a2009-12-14 18:00:50 -08002304
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002305 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2306 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2307 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches63ab52d2010-10-26 14:22:51 -07002308
Joe Perches683c6f82010-10-26 14:22:55 -07002309 push(@signers, @commit_signers);
2310 } else {
2311 foreach my $commit (@commits) {
2312 my $commit_count;
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002313 my $commit_authors_ref;
2314 my $commit_signers_ref;
2315 my $stats_ref;
2316 my @commit_authors = ();
Joe Perches683c6f82010-10-26 14:22:55 -07002317 my @commit_signers = ();
2318 my $cmd;
2319
2320 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2321 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2322
Joe Perchesc9ecefe2014-01-23 15:54:20 -08002323 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2324 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2325 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
Joe Perches683c6f82010-10-26 14:22:55 -07002326
2327 push(@signers, @commit_signers);
2328 }
2329 }
Joe Perchesf5492662009-09-21 17:04:13 -07002330 }
2331
Joe Perchesa8af2432009-12-14 18:00:49 -08002332 if ($from_filename) {
Joe Perches63ab52d2010-10-26 14:22:51 -07002333 if ($output_rolestats) {
2334 my @blame_signers;
Joe Perches683c6f82010-10-26 14:22:55 -07002335 if (vcs_is_hg()) {{ # Double brace for last exit
2336 my $commit_count;
2337 my @commit_signers = ();
2338 @commits = uniq(@commits);
2339 @commits = sort(@commits);
2340 my $commit = join(" -r ", @commits);
2341 my $cmd;
2342
2343 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2344 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2345
2346 my @lines = ();
2347
2348 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2349
2350 if (!$email_git_penguin_chiefs) {
2351 @lines = grep(!/${penguin_chiefs}/i, @lines);
2352 }
2353
2354 last if !@lines;
2355
2356 my @authors = ();
2357 foreach my $line (@lines) {
2358 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2359 my $author = $1;
Joe Perchesb9e23312010-10-26 14:22:58 -07002360 $author = deduplicate_email($author);
2361 push(@authors, $author);
Joe Perches683c6f82010-10-26 14:22:55 -07002362 }
2363 }
2364
2365 save_commits_by_author(@lines) if ($interactive);
2366 save_commits_by_signer(@lines) if ($interactive);
2367
2368 push(@signers, @authors);
2369 }}
2370 else {
2371 foreach my $commit (@commits) {
2372 my $i;
2373 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2374 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2375 my @author = vcs_find_author($cmd);
2376 next if !@author;
Joe Perchesb9e23312010-10-26 14:22:58 -07002377
2378 my $formatted_author = deduplicate_email($author[0]);
2379
Joe Perches683c6f82010-10-26 14:22:55 -07002380 my $count = grep(/$commit/, @all_commits);
2381 for ($i = 0; $i < $count ; $i++) {
Joe Perchesb9e23312010-10-26 14:22:58 -07002382 push(@blame_signers, $formatted_author);
Joe Perches683c6f82010-10-26 14:22:55 -07002383 }
Joe Perches63ab52d2010-10-26 14:22:51 -07002384 }
2385 }
2386 if (@blame_signers) {
2387 vcs_assign("authored lines", $total_lines, @blame_signers);
2388 }
2389 }
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("commits", $total_commits, @signers);
Joe Perchesa8af2432009-12-14 18:00:49 -08002394 } else {
Joe Perchesb9e23312010-10-26 14:22:58 -07002395 foreach my $signer (@signers) {
2396 $signer = deduplicate_email($signer);
2397 }
Joe Perches60db31a2009-12-14 18:00:50 -08002398 vcs_assign("modified commits", $total_commits, @signers);
Joe Perchesf5492662009-09-21 17:04:13 -07002399 }
Joe Perchescb7301c2009-04-07 20:40:12 -07002400}
2401
Joe Perches4cad35a2016-08-02 14:04:10 -07002402sub vcs_file_exists {
2403 my ($file) = @_;
2404
2405 my $exists;
2406
2407 my $vcs_used = vcs_exists();
2408 return 0 if (!$vcs_used);
2409
2410 my $cmd = $VCS_cmds{"file_exists_cmd"};
2411 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
Joe Perches8582fb52016-08-25 15:16:48 -07002412 $cmd .= " 2>&1";
Joe Perches4cad35a2016-08-02 14:04:10 -07002413 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2414
Joe Perches8582fb52016-08-25 15:16:48 -07002415 return 0 if ($? != 0);
2416
Joe Perches4cad35a2016-08-02 14:04:10 -07002417 return $exists;
2418}
2419
Tom Saegere1f75902017-11-17 15:27:42 -08002420sub vcs_list_files {
2421 my ($file) = @_;
2422
2423 my @lsfiles = ();
2424
2425 my $vcs_used = vcs_exists();
2426 return 0 if (!$vcs_used);
2427
2428 my $cmd = $VCS_cmds{"list_files_cmd"};
2429 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2430 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2431
2432 return () if ($? != 0);
2433
2434 return @lsfiles;
2435}
2436
Joe Perchescb7301c2009-04-07 20:40:12 -07002437sub uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002438 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002439
2440 my %saw;
2441 @parms = grep(!$saw{$_}++, @parms);
2442 return @parms;
2443}
2444
2445sub sort_and_uniq {
Joe Perchesa8af2432009-12-14 18:00:49 -08002446 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002447
2448 my %saw;
2449 @parms = sort @parms;
2450 @parms = grep(!$saw{$_}++, @parms);
2451 return @parms;
2452}
2453
Joe Perches03372db2010-03-05 13:43:00 -08002454sub clean_file_emails {
2455 my (@file_emails) = @_;
2456 my @fmt_emails = ();
2457
2458 foreach my $email (@file_emails) {
2459 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2460 my ($name, $address) = parse_email($email);
2461 if ($name eq '"[,\.]"') {
2462 $name = "";
2463 }
2464
2465 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2466 if (@nw > 2) {
2467 my $first = $nw[@nw - 3];
2468 my $middle = $nw[@nw - 2];
2469 my $last = $nw[@nw - 1];
2470
2471 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2472 (length($first) == 2 && substr($first, -1) eq ".")) ||
2473 (length($middle) == 1 ||
2474 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2475 $name = "$first $middle $last";
2476 } else {
2477 $name = "$middle $last";
2478 }
2479 }
2480
2481 if (substr($name, -1) =~ /[,\.]/) {
2482 $name = substr($name, 0, length($name) - 1);
2483 } elsif (substr($name, -2) =~ /[,\.]"/) {
2484 $name = substr($name, 0, length($name) - 2) . '"';
2485 }
2486
2487 if (substr($name, 0, 1) =~ /[,\.]/) {
2488 $name = substr($name, 1, length($name) - 1);
2489 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2490 $name = '"' . substr($name, 2, length($name) - 2);
2491 }
2492
2493 my $fmt_email = format_email($name, $address, $email_usename);
2494 push(@fmt_emails, $fmt_email);
2495 }
2496 return @fmt_emails;
2497}
2498
Joe Perches3c7385b2009-12-14 18:00:46 -08002499sub merge_email {
2500 my @lines;
2501 my %saw;
2502
2503 for (@_) {
2504 my ($address, $role) = @$_;
2505 if (!$saw{$address}) {
2506 if ($output_roles) {
Joe Perches60db31a2009-12-14 18:00:50 -08002507 push(@lines, "$address ($role)");
Joe Perches3c7385b2009-12-14 18:00:46 -08002508 } else {
Joe Perches60db31a2009-12-14 18:00:50 -08002509 push(@lines, $address);
Joe Perches3c7385b2009-12-14 18:00:46 -08002510 }
2511 $saw{$address} = 1;
2512 }
2513 }
2514
2515 return @lines;
2516}
2517
Joe Perchescb7301c2009-04-07 20:40:12 -07002518sub output {
Joe Perchesa8af2432009-12-14 18:00:49 -08002519 my (@parms) = @_;
Joe Perchescb7301c2009-04-07 20:40:12 -07002520
2521 if ($output_multiline) {
2522 foreach my $line (@parms) {
2523 print("${line}\n");
2524 }
2525 } else {
2526 print(join($output_separator, @parms));
2527 print("\n");
2528 }
2529}
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002530
2531my $rfc822re;
2532
2533sub make_rfc822re {
2534# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2535# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2536# This regexp will only work on addresses which have had comments stripped
2537# and replaced with rfc822_lwsp.
2538
2539 my $specials = '()<>@,;:\\\\".\\[\\]';
2540 my $controls = '\\000-\\037\\177';
2541
2542 my $dtext = "[^\\[\\]\\r\\\\]";
2543 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2544
2545 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2546
2547# Use zero-width assertion to spot the limit of an atom. A simple
2548# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2549 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2550 my $word = "(?:$atom|$quoted_string)";
2551 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2552
2553 my $sub_domain = "(?:$atom|$domain_literal)";
2554 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2555
2556 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2557
2558 my $phrase = "$word*";
2559 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2560 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2561 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2562
2563 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2564 my $address = "(?:$mailbox|$group)";
2565
2566 return "$rfc822_lwsp*$address";
2567}
2568
2569sub rfc822_strip_comments {
2570 my $s = shift;
2571# Recursively remove comments, and replace with a single space. The simpler
2572# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2573# chars in atoms, for example.
2574
2575 while ($s =~ s/^((?:[^"\\]|\\.)*
2576 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2577 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2578 return $s;
2579}
2580
2581# valid: returns true if the parameter is an RFC822 valid address
2582#
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002583sub rfc822_valid {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002584 my $s = rfc822_strip_comments(shift);
2585
2586 if (!$rfc822re) {
2587 $rfc822re = make_rfc822re();
2588 }
2589
2590 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2591}
2592
2593# validlist: In scalar context, returns true if the parameter is an RFC822
2594# valid list of addresses.
2595#
2596# In list context, returns an empty list on failure (an invalid
2597# address was found); otherwise a list whose first element is the
2598# number of addresses found and whose remaining elements are the
2599# addresses. This is needed to disambiguate failure (invalid)
2600# from success with no addresses found, because an empty string is
2601# a valid list.
2602
Stephen Hemminger22dd5b02010-03-05 13:43:06 -08002603sub rfc822_validlist {
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002604 my $s = rfc822_strip_comments(shift);
2605
2606 if (!$rfc822re) {
2607 $rfc822re = make_rfc822re();
2608 }
2609 # * null list items are valid according to the RFC
2610 # * the '1' business is to aid in distinguishing failure from no results
2611
2612 my @r;
2613 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2614 $s =~ m/^$rfc822_char*$/) {
Joe Perches5f2441e2009-06-16 15:34:02 -07002615 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
Joe Perches60db31a2009-12-14 18:00:50 -08002616 push(@r, $1);
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002617 }
2618 return wantarray ? (scalar(@r), @r) : 1;
2619 }
Joe Perches60db31a2009-12-14 18:00:50 -08002620 return wantarray ? () : 0;
Joe Perches1b5e1cf2009-06-16 15:34:01 -07002621}