get_maintainer.pl 58.2 KB
Newer Older
Joe Perches's avatar
Joe Perches committed
1
2
3
4
5
6
7
#!/usr/bin/perl -w
# (c) 2007, Joe Perches <joe@perches.com>
#           created from checkpatch.pl
#
# Print selected MAINTAINERS information for
# the files modified in a patch or for a file
#
Roel Kluin's avatar
Roel Kluin committed
8
9
# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
#        perl scripts/get_maintainer.pl [OPTIONS] -f <file>
Joe Perches's avatar
Joe Perches committed
10
11
12
13
14
15
#
# Licensed under the terms of the GNU GPL License version 2

use strict;

my $P = $0;
16
my $V = '0.26';
Joe Perches's avatar
Joe Perches committed
17
18
19
20
21
22
23

use Getopt::Long qw(:config no_auto_abbrev);

my $lk_path = "./";
my $email = 1;
my $email_usename = 1;
my $email_maintainer = 1;
24
my $email_reviewer = 1;
Joe Perches's avatar
Joe Perches committed
25
26
27
my $email_list = 1;
my $email_subscriber_list = 0;
my $email_git_penguin_chiefs = 0;
28
my $email_git = 0;
29
my $email_git_all_signature_types = 0;
30
my $email_git_blame = 0;
31
my $email_git_blame_signatures = 1;
32
my $email_git_fallback = 1;
Joe Perches's avatar
Joe Perches committed
33
34
my $email_git_min_signatures = 1;
my $email_git_max_maintainers = 5;
35
my $email_git_min_percent = 5;
Joe Perches's avatar
Joe Perches committed
36
my $email_git_since = "1-year-ago";
37
my $email_hg_since = "-365";
38
my $interactive = 0;
39
my $email_remove_duplicates = 1;
40
my $email_use_mailmap = 1;
Joe Perches's avatar
Joe Perches committed
41
42
my $output_multiline = 1;
my $output_separator = ", ";
43
my $output_roles = 0;
44
my $output_rolestats = 1;
45
my $output_section_maxlen = 50;
Joe Perches's avatar
Joe Perches committed
46
47
48
49
my $scm = 0;
my $web = 0;
my $subsystem = 0;
my $status = 0;
50
my $keywords = 1;
51
my $sections = 0;
52
my $file_emails = 0;
53
my $from_filename = 0;
54
my $pattern_depth = 0;
Joe Perches's avatar
Joe Perches committed
55
56
57
my $version = 0;
my $help = 0;

58
59
my $vcs_used = 0;

Joe Perches's avatar
Joe Perches committed
60
61
my $exit = 0;

62
63
my %commit_author_hash;
my %commit_signer_hash;
64

Joe Perches's avatar
Joe Perches committed
65
my @penguin_chief = ();
66
push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
Joe Perches's avatar
Joe Perches committed
67
#Andrew wants in on most everything - 2009/01/14
68
#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
Joe Perches's avatar
Joe Perches committed
69
70
71
72
73
74
75
76
77

my @penguin_chief_names = ();
foreach my $chief (@penguin_chief) {
    if ($chief =~ m/^(.*):(.*)/) {
	my $chief_name = $1;
	my $chief_addr = $2;
	push(@penguin_chief_names, $chief_name);
    }
}
78
79
80
81
82
83
84
85
86
my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";

# Signature types of people who are either
# 	a) responsible for the code in question, or
# 	b) familiar enough with it to give relevant feedback
my @signature_tags = ();
push(@signature_tags, "Signed-off-by:");
push(@signature_tags, "Reviewed-by:");
push(@signature_tags, "Acked-by:");
Joe Perches's avatar
Joe Perches committed
87

88
89
my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";

90
# rfc822 email address - preloaded methods go here.
91
my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
92
my $rfc822_char = '[\\000-\\377]';
93

94
95
96
97
98
99
# VCS command support: class-like functions and strings

my %VCS_cmds;

my %VCS_cmds_git = (
    "execute_cmd" => \&git_execute_cmd,
100
    "available" => '(which("git") ne "") && (-e ".git")',
101
    "find_signers_cmd" =>
102
	"git log --no-color --follow --since=\$email_git_since " .
103
	    '--numstat --no-merges ' .
104
105
106
107
108
109
110
111
	    '--format="GitCommit: %H%n' .
		      'GitAuthor: %an <%ae>%n' .
		      'GitDate: %aD%n' .
		      'GitSubject: %s%n' .
		      '%b%n"' .
	    " -- \$file",
    "find_commit_signers_cmd" =>
	"git log --no-color " .
112
	    '--numstat ' .
113
114
115
116
117
118
119
120
	    '--format="GitCommit: %H%n' .
		      'GitAuthor: %an <%ae>%n' .
		      'GitDate: %aD%n' .
		      'GitSubject: %s%n' .
		      '%b%n"' .
	    " -1 \$commit",
    "find_commit_author_cmd" =>
	"git log --no-color " .
121
	    '--numstat ' .
122
123
124
125
126
	    '--format="GitCommit: %H%n' .
		      'GitAuthor: %an <%ae>%n' .
		      'GitDate: %aD%n' .
		      'GitSubject: %s%n"' .
	    " -1 \$commit",
127
128
    "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
    "blame_file_cmd" => "git blame -l \$file",
129
    "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
130
    "blame_commit_pattern" => "^([0-9a-f]+) ",
131
132
    "author_pattern" => "^GitAuthor: (.*)",
    "subject_pattern" => "^GitSubject: (.*)",
133
    "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
134
135
136
137
138
139
);

my %VCS_cmds_hg = (
    "execute_cmd" => \&hg_execute_cmd,
    "available" => '(which("hg") ne "") && (-d ".hg")',
    "find_signers_cmd" =>
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
	"hg log --date=\$email_hg_since " .
	    "--template='HgCommit: {node}\\n" .
	                "HgAuthor: {author}\\n" .
			"HgSubject: {desc}\\n'" .
	    " -- \$file",
    "find_commit_signers_cmd" =>
	"hg log " .
	    "--template='HgSubject: {desc}\\n'" .
	    " -r \$commit",
    "find_commit_author_cmd" =>
	"hg log " .
	    "--template='HgCommit: {node}\\n" .
		        "HgAuthor: {author}\\n" .
			"HgSubject: {desc|firstline}\\n'" .
	    " -r \$commit",
155
    "blame_range_cmd" => "",		# not supported
156
157
158
159
160
    "blame_file_cmd" => "hg blame -n \$file",
    "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
    "blame_commit_pattern" => "^([ 0-9a-f]+):",
    "author_pattern" => "^HgAuthor: (.*)",
    "subject_pattern" => "^HgSubject: (.*)",
161
    "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
162
163
);

164
165
my $conf = which_conf(".get_maintainer.conf");
if (-f $conf) {
166
    my @conf_args;
167
168
169
    open(my $conffile, '<', "$conf")
	or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
    while (<$conffile>) {
	my $line = $_;

	$line =~ s/\s*\n?$//g;
	$line =~ s/^\s*//g;
	$line =~ s/\s+/ /g;

	next if ($line =~ m/^\s*#/);
	next if ($line =~ m/^\s*$/);

	my @words = split(" ", $line);
	foreach my $word (@words) {
	    last if ($word =~ m/^#/);
	    push (@conf_args, $word);
	}
    }
    close($conffile);
    unshift(@ARGV, @conf_args) if @conf_args;
}

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
my @ignore_emails = ();
my $ignore_file = which_conf(".get_maintainer.ignore");
if (-f $ignore_file) {
    open(my $ignore, '<', "$ignore_file")
	or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
    while (<$ignore>) {
	my $line = $_;

	$line =~ s/\s*\n?$//;
	$line =~ s/^\s*//;
	$line =~ s/\s+$//;
	$line =~ s/#.*$//;

	next if ($line =~ m/^\s*$/);
	if (rfc822_valid($line)) {
	    push(@ignore_emails, $line);
	}
    }
    close($ignore);
}

Joe Perches's avatar
Joe Perches committed
211
212
213
if (!GetOptions(
		'email!' => \$email,
		'git!' => \$email_git,
214
		'git-all-signature-types!' => \$email_git_all_signature_types,
215
		'git-blame!' => \$email_git_blame,
216
		'git-blame-signatures!' => \$email_git_blame_signatures,
217
		'git-fallback!' => \$email_git_fallback,
Joe Perches's avatar
Joe Perches committed
218
219
220
		'git-chief-penguins!' => \$email_git_penguin_chiefs,
		'git-min-signatures=i' => \$email_git_min_signatures,
		'git-max-maintainers=i' => \$email_git_max_maintainers,
221
		'git-min-percent=i' => \$email_git_min_percent,
Joe Perches's avatar
Joe Perches committed
222
		'git-since=s' => \$email_git_since,
223
		'hg-since=s' => \$email_hg_since,
224
		'i|interactive!' => \$interactive,
225
		'remove-duplicates!' => \$email_remove_duplicates,
226
		'mailmap!' => \$email_use_mailmap,
Joe Perches's avatar
Joe Perches committed
227
		'm!' => \$email_maintainer,
228
		'r!' => \$email_reviewer,
Joe Perches's avatar
Joe Perches committed
229
230
231
232
		'n!' => \$email_usename,
		'l!' => \$email_list,
		's!' => \$email_subscriber_list,
		'multiline!' => \$output_multiline,
233
234
		'roles!' => \$output_roles,
		'rolestats!' => \$output_rolestats,
Joe Perches's avatar
Joe Perches committed
235
236
237
238
239
		'separator=s' => \$output_separator,
		'subsystem!' => \$subsystem,
		'status!' => \$status,
		'scm!' => \$scm,
		'web!' => \$web,
240
		'pattern-depth=i' => \$pattern_depth,
241
		'k|keywords!' => \$keywords,
242
		'sections!' => \$sections,
243
		'fe|file-emails!' => \$file_emails,
244
		'f|file' => \$from_filename,
Joe Perches's avatar
Joe Perches committed
245
		'v|version' => \$version,
246
		'h|help|usage' => \$help,
Joe Perches's avatar
Joe Perches committed
247
		)) {
248
    die "$P: invalid argument - use --help if necessary\n";
Joe Perches's avatar
Joe Perches committed
249
250
251
252
253
254
255
256
257
258
259
260
}

if ($help != 0) {
    usage();
    exit 0;
}

if ($version != 0) {
    print("${P} ${V}\n");
    exit 0;
}

261
262
263
if (-t STDIN && !@ARGV) {
    # We're talking to a terminal, but have no command line arguments.
    die "$P: missing patchfile or -f file - use --help if necessary\n";
Joe Perches's avatar
Joe Perches committed
264
265
}

266
267
268
$output_multiline = 0 if ($output_separator ne ", ");
$output_rolestats = 1 if ($interactive);
$output_roles = 1 if ($output_rolestats);
269

270
271
272
273
274
275
276
277
if ($sections) {
    $email = 0;
    $email_list = 0;
    $scm = 0;
    $status = 0;
    $subsystem = 0;
    $web = 0;
    $keywords = 0;
278
    $interactive = 0;
279
280
281
282
283
} else {
    my $selections = $email + $scm + $status + $subsystem + $web;
    if ($selections == 0) {
	die "$P:  Missing required option: email, scm, status, subsystem or web\n";
    }
Joe Perches's avatar
Joe Perches committed
284
285
}

286
if ($email &&
287
288
    ($email_maintainer + $email_reviewer +
     $email_list + $email_subscriber_list +
289
     $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
Joe Perches's avatar
Joe Perches committed
290
291
292
293
294
295
296
297
298
299
300
    die "$P: Please select at least 1 email option\n";
}

if (!top_of_kernel_tree($lk_path)) {
    die "$P: The current directory does not appear to be "
	. "a linux kernel source tree.\n";
}

## Read MAINTAINERS for type/value pairs

my @typevalue = ();
301
302
my %keyword_hash;

303
304
305
open (my $maint, '<', "${lk_path}MAINTAINERS")
    or die "$P: Can't open MAINTAINERS: $!\n";
while (<$maint>) {
Joe Perches's avatar
Joe Perches committed
306
307
    my $line = $_;

308
    if ($line =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches's avatar
Joe Perches committed
309
310
311
312
313
314
315
316
	my $type = $1;
	my $value = $2;

	##Filename pattern matching
	if ($type eq "F" || $type eq "X") {
	    $value =~ s@\.@\\\.@g;       ##Convert . to \.
	    $value =~ s/\*/\.\*/g;       ##Convert * to .*
	    $value =~ s/\?/\./g;         ##Convert ? to .
317
318
319
320
	    ##if pattern is a directory and it lacks a trailing slash, add one
	    if ((-d $value)) {
		$value =~ s@([^/])$@$1/@;
	    }
321
322
	} elsif ($type eq "K") {
	    $keyword_hash{@typevalue} = $value;
Joe Perches's avatar
Joe Perches committed
323
324
325
326
327
328
329
	}
	push(@typevalue, "$type:$value");
    } elsif (!/^(\s)*$/) {
	$line =~ s/\n$//g;
	push(@typevalue, $line);
    }
}
330
close($maint);
Joe Perches's avatar
Joe Perches committed
331

332

333
334
335
336
#
# Read mail address map
#

337
338
339
my $mailmap;

read_mailmap();
340
341

sub read_mailmap {
342
    $mailmap = {
343
344
	names => {},
	addresses => {}
345
    };
346

347
    return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
348
349

    open(my $mailmap_file, '<', "${lk_path}.mailmap")
350
	or warn "$P: Can't open .mailmap: $!\n";
351

352
353
354
    while (<$mailmap_file>) {
	s/#.*$//; #strip comments
	s/^\s+|\s+$//g; #trim
355

356
357
358
359
360
361
362
	next if (/^\s*$/); #skip empty lines
	#entries have one of the following formats:
	# name1 <mail1>
	# <mail1> <mail2>
	# name1 <mail1> <mail2>
	# name1 <mail1> name2 <mail2>
	# (see man git-shortlog)
363
364

	if (/^([^<]+)<([^>]+)>$/) {
365
366
	    my $real_name = $1;
	    my $address = $2;
367

368
	    $real_name =~ s/\s+$//;
369
	    ($real_name, $address) = parse_email("$real_name <$address>");
370
	    $mailmap->{names}->{$address} = $real_name;
371

372
	} elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
373
374
	    my $real_address = $1;
	    my $wrong_address = $2;
375

376
	    $mailmap->{addresses}->{$wrong_address} = $real_address;
377

378
	} elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
379
	    my $real_name = $1;
380
381
	    my $real_address = $2;
	    my $wrong_address = $3;
382

383
	    $real_name =~ s/\s+$//;
384
385
	    ($real_name, $real_address) =
		parse_email("$real_name <$real_address>");
386
387
	    $mailmap->{names}->{$wrong_address} = $real_name;
	    $mailmap->{addresses}->{$wrong_address} = $real_address;
388

389
	} elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
390
391
392
393
	    my $real_name = $1;
	    my $real_address = $2;
	    my $wrong_name = $3;
	    my $wrong_address = $4;
394

395
	    $real_name =~ s/\s+$//;
396
397
398
	    ($real_name, $real_address) =
		parse_email("$real_name <$real_address>");

399
	    $wrong_name =~ s/\s+$//;
400
401
	    ($wrong_name, $wrong_address) =
		parse_email("$wrong_name <$wrong_address>");
402

403
404
405
	    my $wrong_email = format_email($wrong_name, $wrong_address, 1);
	    $mailmap->{names}->{$wrong_email} = $real_name;
	    $mailmap->{addresses}->{$wrong_email} = $real_address;
406
	}
407
    }
408
    close($mailmap_file);
409
410
}

411
## use the filenames on the command line or find the filenames in the patchfiles
Joe Perches's avatar
Joe Perches committed
412
413

my @files = ();
414
my @range = ();
415
my @keyword_tvi = ();
416
my @file_emails = ();
Joe Perches's avatar
Joe Perches committed
417

418
419
420
421
if (!@ARGV) {
    push(@ARGV, "&STDIN");
}

422
foreach my $file (@ARGV) {
423
424
425
426
427
428
429
    if ($file ne "&STDIN") {
	##if $file is a directory and it lacks a trailing slash, add one
	if ((-d $file)) {
	    $file =~ s@([^/])$@$1/@;
	} elsif (!(-f $file)) {
	    die "$P: file '${file}' not found\n";
	}
Joe Perches's avatar
Joe Perches committed
430
    }
431
432
    if ($from_filename) {
	push(@files, $file);
433
	if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
434
435
436
437
	    open(my $f, '<', $file)
		or die "$P: Can't open $file: $!\n";
	    my $text = do { local($/) ; <$f> };
	    close($f);
438
439
440
441
442
	    if ($keywords) {
		foreach my $line (keys %keyword_hash) {
		    if ($text =~ m/$keyword_hash{$line}/x) {
			push(@keyword_tvi, $line);
		    }
443
444
		}
	    }
445
446
447
448
	    if ($file_emails) {
		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;
		push(@file_emails, clean_file_emails(@poss_addr));
	    }
449
	}
450
451
    } else {
	my $file_cnt = @files;
452
	my $lastfile;
453

454
	open(my $patch, "< $file")
455
	    or die "$P: Can't open $file: $!\n";
456
457
458
459
460
461
462
463

	# We can check arbitrary information before the patch
	# like the commit message, mail headers, etc...
	# This allows us to match arbitrary keywords against any part
	# of a git format-patch generated file (subject tags, etc...)

	my $patch_prefix = "";			#Parsing the intro

464
	while (<$patch>) {
465
	    my $patch_line = $_;
466
	    if (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
467
468
469
		my $filename = $1;
		$filename =~ s@^[^/]*/@@;
		$filename =~ s@\n@@;
470
		$lastfile = $filename;
471
		push(@files, $filename);
472
		$patch_prefix = "^[+-].*";	#Now parsing the actual patch
473
474
475
476
	    } elsif (m/^\@\@ -(\d+),(\d+)/) {
		if ($email_git_blame) {
		    push(@range, "$lastfile:$1:$2");
		}
477
478
	    } elsif ($keywords) {
		foreach my $line (keys %keyword_hash) {
479
		    if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
480
481
482
			push(@keyword_tvi, $line);
		    }
		}
483
	    }
Joe Perches's avatar
Joe Perches committed
484
	}
485
486
	close($patch);

487
	if ($file_cnt == @files) {
488
	    warn "$P: file '${file}' doesn't appear to be a patch.  "
489
490
491
		. "Add -f to options?\n";
	}
	@files = sort_and_uniq(@files);
Joe Perches's avatar
Joe Perches committed
492
493
494
    }
}

495
496
@file_emails = uniq(@file_emails);

497
498
my %email_hash_name;
my %email_hash_address;
Joe Perches's avatar
Joe Perches committed
499
my @email_to = ();
500
my %hash_list_to;
501
my @list_to = ();
Joe Perches's avatar
Joe Perches committed
502
503
504
505
my @scm = ();
my @web = ();
my @subsystem = ();
my @status = ();
506
507
my %deduplicate_name_hash = ();
my %deduplicate_address_hash = ();
Joe Perches's avatar
Joe Perches committed
508

509
my @maintainers = get_maintainers();
Joe Perches's avatar
Joe Perches committed
510

511
512
513
514
if (@maintainers) {
    @maintainers = merge_email(@maintainers);
    output(@maintainers);
}
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537

if ($scm) {
    @scm = uniq(@scm);
    output(@scm);
}

if ($status) {
    @status = uniq(@status);
    output(@status);
}

if ($subsystem) {
    @subsystem = uniq(@subsystem);
    output(@subsystem);
}

if ($web) {
    @web = uniq(@web);
    output(@web);
}

exit($exit);

538
539
540
541
542
543
544
545
546
547
sub ignore_email_address {
    my ($address) = @_;

    foreach my $ignore (@ignore_emails) {
	return 1 if ($ignore eq $address);
    }

    return 0;
}

548
549
550
551
552
sub range_is_maintained {
    my ($start, $end) = @_;

    for (my $i = $start; $i < $end; $i++) {
	my $line = $typevalue[$i];
553
	if ($line =~ m/^([A-Z]):\s*(.*)/) {
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
	    my $type = $1;
	    my $value = $2;
	    if ($type eq 'S') {
		if ($value =~ /(maintain|support)/i) {
		    return 1;
		}
	    }
	}
    }
    return 0;
}

sub range_has_maintainer {
    my ($start, $end) = @_;

    for (my $i = $start; $i < $end; $i++) {
	my $line = $typevalue[$i];
571
	if ($line =~ m/^([A-Z]):\s*(.*)/) {
572
573
574
575
576
577
578
579
580
581
	    my $type = $1;
	    my $value = $2;
	    if ($type eq 'M') {
		return 1;
	    }
	}
    }
    return 0;
}

582
sub get_maintainers {
583
584
585
586
587
588
589
590
591
592
593
    %email_hash_name = ();
    %email_hash_address = ();
    %commit_author_hash = ();
    %commit_signer_hash = ();
    @email_to = ();
    %hash_list_to = ();
    @list_to = ();
    @scm = ();
    @web = ();
    @subsystem = ();
    @status = ();
594
595
    %deduplicate_name_hash = ();
    %deduplicate_address_hash = ();
596
597
598
599
600
601
602
603
    if ($email_git_all_signature_types) {
	$signature_pattern = "(.+?)[Bb][Yy]:";
    } else {
	$signature_pattern = "\(" . join("|", @signature_tags) . "\)";
    }

    # Find responsible parties

604
    my %exact_pattern_match_hash = ();
605

606
607
608
609
610
611
612
613
614
615
616
    foreach my $file (@files) {

	my %hash;
	my $tvi = find_first_section();
	while ($tvi < @typevalue) {
	    my $start = find_starting_index($tvi);
	    my $end = find_ending_index($tvi);
	    my $exclude = 0;
	    my $i;

	    #Do not match excluded file patterns
617
618
619

	    for ($i = $start; $i < $end; $i++) {
		my $line = $typevalue[$i];
620
		if ($line =~ m/^([A-Z]):\s*(.*)/) {
621
622
		    my $type = $1;
		    my $value = $2;
623
		    if ($type eq 'X') {
624
			if (file_match_pattern($file, $value)) {
625
626
627
628
629
630
631
632
633
634
			    $exclude = 1;
			    last;
			}
		    }
		}
	    }

	    if (!$exclude) {
		for ($i = $start; $i < $end; $i++) {
		    my $line = $typevalue[$i];
635
		    if ($line =~ m/^([A-Z]):\s*(.*)/) {
636
637
638
639
640
641
642
643
			my $type = $1;
			my $value = $2;
			if ($type eq 'F') {
			    if (file_match_pattern($file, $value)) {
				my $value_pd = ($value =~ tr@/@@);
				my $file_pd = ($file  =~ tr@/@@);
				$value_pd++ if (substr($value,-1,1) ne "/");
				$value_pd = -1 if ($value =~ /^\.\*/);
644
645
646
				if ($value_pd >= $file_pd &&
				    range_is_maintained($start, $end) &&
				    range_has_maintainer($start, $end)) {
647
648
				    $exact_pattern_match_hash{$file} = 1;
				}
649
650
651
652
				if ($pattern_depth == 0 ||
				    (($file_pd - $value_pd) < $pattern_depth)) {
				    $hash{$tvi} = $value_pd;
				}
653
			    }
654
			} elsif ($type eq 'N') {
655
656
657
			    if ($file =~ m/$value/x) {
				$hash{$tvi} = 0;
			    }
658
659
660
661
			}
		    }
		}
	    }
662
	    $tvi = $end + 1;
663
	}
664

665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
	foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
	    add_categories($line);
	    if ($sections) {
		my $i;
		my $start = find_starting_index($line);
		my $end = find_ending_index($line);
		for ($i = $start; $i < $end; $i++) {
		    my $line = $typevalue[$i];
		    if ($line =~ /^[FX]:/) {		##Restore file patterns
			$line =~ s/([^\\])\.([^\*])/$1\?$2/g;
			$line =~ s/([^\\])\.$/$1\?/g;	##Convert . back to ?
			$line =~ s/\\\./\./g;       	##Convert \. to .
			$line =~ s/\.\*/\*/g;       	##Convert .* to *
		    }
		    $line =~ s/^([A-Z]):/$1:\t/g;
		    print("$line\n");
681
		}
682
		print("\n");
683
	    }
684
	}
685
    }
Joe Perches's avatar
Joe Perches committed
686

687
688
689
690
691
    if ($keywords) {
	@keyword_tvi = sort_and_uniq(@keyword_tvi);
	foreach my $line (@keyword_tvi) {
	    add_categories($line);
	}
692
693
    }

694
695
696
    foreach my $email (@email_to, @list_to) {
	$email->[0] = deduplicate_email($email->[0]);
    }
697
698
699
700
701
702
703
704
705
706
707
708

    foreach my $file (@files) {
	if ($email &&
	    ($email_git || ($email_git_fallback &&
			    !$exact_pattern_match_hash{$file}))) {
	    vcs_file_signoffs($file);
	}
	if ($email && $email_git_blame) {
	    vcs_file_blame($file);
	}
    }

709
710
711
712
    if ($email) {
	foreach my $chief (@penguin_chief) {
	    if ($chief =~ m/^(.*):(.*)/) {
		my $email_address;
713

714
715
716
717
718
719
		$email_address = format_email($1, $2, $email_usename);
		if ($email_git_penguin_chiefs) {
		    push(@email_to, [$email_address, 'chief penguin']);
		} else {
		    @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
		}
Joe Perches's avatar
Joe Perches committed
720
721
	    }
	}
722

723
724
	foreach my $email (@file_emails) {
	    my ($name, $address) = parse_email($email);
725

726
727
728
729
	    my $tmp_email = format_email($name, $address, $email_usename);
	    push_email_address($tmp_email, '');
	    add_role($tmp_email, 'in file');
	}
730
    }
Joe Perches's avatar
Joe Perches committed
731

732
    my @to = ();
733
734
735
736
737
738
    if ($email || $email_list) {
	if ($email) {
	    @to = (@to, @email_to);
	}
	if ($email_list) {
	    @to = (@to, @list_to);
739
	}
740
    }
Joe Perches's avatar
Joe Perches committed
741

742
    if ($interactive) {
743
	@to = interactive_get_maintainers(\@to);
744
    }
Joe Perches's avatar
Joe Perches committed
745

746
    return @to;
Joe Perches's avatar
Joe Perches committed
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
}

sub file_match_pattern {
    my ($file, $pattern) = @_;
    if (substr($pattern, -1) eq "/") {
	if ($file =~ m@^$pattern@) {
	    return 1;
	}
    } else {
	if ($file =~ m@^$pattern@) {
	    my $s1 = ($file =~ tr@/@@);
	    my $s2 = ($pattern =~ tr@/@@);
	    if ($s1 == $s2) {
		return 1;
	    }
	}
    }
    return 0;
}

sub usage {
    print <<EOT;
usage: $P [options] patchfile
770
       $P [options] -f file|directory
Joe Perches's avatar
Joe Perches committed
771
772
773
774
775
version: $V

MAINTAINER field selection options:
  --email => print email address(es) if any
    --git => include recent git \*-by: signers
776
    --git-all-signature-types => include signers regardless of signature type
777
        or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
778
    --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
Joe Perches's avatar
Joe Perches committed
779
    --git-chief-penguins => include ${penguin_chiefs}
780
781
782
    --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
    --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
    --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
783
    --git-blame => use git blame to find modified commits for patch or file
784
    --git-blame-signatures => when used with --git-blame, also include all commit signers
785
786
    --git-since => git history to use (default: $email_git_since)
    --hg-since => hg history to use (default: $email_hg_since)
787
    --interactive => display a menu (mostly useful if used with the --git option)
Joe Perches's avatar
Joe Perches committed
788
    --m => include maintainer(s) if any
789
    --r => include reviewer(s) if any
Joe Perches's avatar
Joe Perches committed
790
791
792
    --n => include name 'Full Name <addr\@domain.tld>'
    --l => include list(s) if any
    --s => include subscriber only list(s) if any
793
    --remove-duplicates => minimize duplicate email names/addresses
794
795
    --roles => show roles (status:subsystem, git-signer, list, etc...)
    --rolestats => show roles and statistics (commits/total_commits, %)
796
    --file-emails => add email addresses found in -f file (default: 0 (off))
Joe Perches's avatar
Joe Perches committed
797
798
799
800
801
802
803
  --scm => print SCM tree(s) if any
  --status => print status if any
  --subsystem => print subsystem name if any
  --web => print website(s) if any

Output type options:
  --separator [, ] => separator for multiple entries on 1 line
804
    using --separator also sets --nomultiline if --separator is not [, ]
Joe Perches's avatar
Joe Perches committed
805
806
807
  --multiline => print 1 entry per line

Other options:
808
  --pattern-depth => Number of pattern directory traversals (default: 0 (all))
809
810
811
  --keywords => scan patch for keywords (default: $keywords)
  --sections => print all of the subsystem sections with pattern matches
  --mailmap => use .mailmap file (default: $email_use_mailmap)
812
  --version => show version
Joe Perches's avatar
Joe Perches committed
813
814
  --help => show this help information

815
Default options:
816
  [--email --nogit --git-fallback --m --n --l --multiline --pattern-depth=0
817
   --remove-duplicates --rolestats]
818

819
820
Notes:
  Using "-f directory" may give unexpected results:
821
822
823
824
      Used with "--git", git signators for _all_ files in and below
          directory are examined as git recurses directories.
          Any specified X: (exclude) pattern matches are _not_ ignored.
      Used with "--nogit", directory is used as a pattern match,
825
826
          no individual file within the directory or subdirectory
          is matched.
827
828
829
      Used with "--git-blame", does not iterate all files in directory
  Using "--git-blame" is slow and may add old committers and authors
      that are no longer active maintainers to the output.
830
831
832
833
834
835
836
  Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
      other automated tools that expect only ["name"] <email address>
      may not work because of additional output after <email address>.
  Using "--rolestats" and "--git-blame" shows the #/total=% commits,
      not the percentage of the entire file authored.  # of commits is
      not a good measure of amount of code authored.  1 major commit may
      contain a thousand lines, 5 trivial commits may modify a single line.
837
838
839
840
841
842
  If git is not installed, but mercurial (hg) is installed and an .hg
      repository exists, the following options apply to mercurial:
          --git,
          --git-min-signatures, --git-max-maintainers, --git-min-percent, and
          --git-blame
      Use --hg-since not --git-since to control date selection
843
844
845
846
847
  File ".get_maintainer.conf", if it exists in the linux kernel source root
      directory, can change whatever get_maintainer defaults are desired.
      Entries in this file can be any command line argument.
      This file is prepended to any additional command line arguments.
      Multiple lines and # comments are allowed.
Joe Perches's avatar
Joe Perches committed
848
849
850
851
EOT
}

sub top_of_kernel_tree {
852
    my ($lk_path) = @_;
Joe Perches's avatar
Joe Perches committed
853

854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
    if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
	$lk_path .= "/";
    }
    if (   (-f "${lk_path}COPYING")
	&& (-f "${lk_path}CREDITS")
	&& (-f "${lk_path}Kbuild")
	&& (-f "${lk_path}MAINTAINERS")
	&& (-f "${lk_path}Makefile")
	&& (-f "${lk_path}README")
	&& (-d "${lk_path}Documentation")
	&& (-d "${lk_path}arch")
	&& (-d "${lk_path}include")
	&& (-d "${lk_path}drivers")
	&& (-d "${lk_path}fs")
	&& (-d "${lk_path}init")
	&& (-d "${lk_path}ipc")
	&& (-d "${lk_path}kernel")
	&& (-d "${lk_path}lib")
	&& (-d "${lk_path}scripts")) {
	return 1;
    }
    return 0;
Joe Perches's avatar
Joe Perches committed
876
877
}

878
879
880
881
882
883
sub parse_email {
    my ($formatted_email) = @_;

    my $name = "";
    my $address = "";

884
    if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
885
886
	$name = $1;
	$address = $2;
887
    } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
888
	$address = $1;
889
    } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
890
891
	$address = $1;
    }
Joe Perches's avatar
Joe Perches committed
892
893

    $name =~ s/^\s+|\s+$//g;
894
    $name =~ s/^\"|\"$//g;
895
    $address =~ s/^\s+|\s+$//g;
Joe Perches's avatar
Joe Perches committed
896

897
    if ($name =~ /[^\w \-]/i) {  	 ##has "must quote" chars
898
899
900
901
902
903
904
905
	$name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
	$name = "\"$name\"";
    }

    return ($name, $address);
}

sub format_email {
906
    my ($name, $address, $usename) = @_;
907
908
909
910
911
912

    my $formatted_email;

    $name =~ s/^\s+|\s+$//g;
    $name =~ s/^\"|\"$//g;
    $address =~ s/^\s+|\s+$//g;
Joe Perches's avatar
Joe Perches committed
913

914
    if ($name =~ /[^\w \-]/i) {          ##has "must quote" chars
Joe Perches's avatar
Joe Perches committed
915
	$name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
916
917
918
	$name = "\"$name\"";
    }

919
    if ($usename) {
920
921
922
	if ("$name" eq "") {
	    $formatted_email = "$address";
	} else {
923
	    $formatted_email = "$name <$address>";
924
	}
Joe Perches's avatar
Joe Perches committed
925
    } else {
926
	$formatted_email = $address;
Joe Perches's avatar
Joe Perches committed
927
    }
928

Joe Perches's avatar
Joe Perches committed
929
930
931
    return $formatted_email;
}

932
933
934
935
936
sub find_first_section {
    my $index = 0;

    while ($index < @typevalue) {
	my $tv = $typevalue[$index];
937
	if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
938
939
940
941
942
943
944
945
	    last;
	}
	$index++;
    }

    return $index;
}

946
947
948
949
950
sub find_starting_index {
    my ($index) = @_;

    while ($index > 0) {
	my $tv = $typevalue[$index];
951
	if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
952
953
954
955
956
957
958
959
960
	    last;
	}
	$index--;
    }

    return $index;
}

sub find_ending_index {
Joe Perches's avatar
Joe Perches committed
961
962
    my ($index) = @_;

963
    while ($index < @typevalue) {
Joe Perches's avatar
Joe Perches committed
964
	my $tv = $typevalue[$index];
965
	if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
966
967
968
969
970
971
972
973
	    last;
	}
	$index++;
    }

    return $index;
}

974
975
976
977
978
979
980
sub get_maintainer_role {
    my ($index) = @_;

    my $i;
    my $start = find_starting_index($index);
    my $end = find_ending_index($index);

981
    my $role = "unknown";
982
    my $subsystem = $typevalue[$start];
983
984
    if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
	$subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
985
986
987
988
989
990
	$subsystem =~ s/\s*$//;
	$subsystem = $subsystem . "...";
    }

    for ($i = $start + 1; $i < $end; $i++) {
	my $tv = $typevalue[$i];
991
	if ($tv =~ m/^([A-Z]):\s*(.*)/) {
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
	    my $ptype = $1;
	    my $pvalue = $2;
	    if ($ptype eq "S") {
		$role = $pvalue;
	    }
	}
    }

    $role = lc($role);
    if      ($role eq "supported") {
	$role = "supporter";
    } elsif ($role eq "maintained") {
	$role = "maintainer";
    } elsif ($role eq "odd fixes") {
	$role = "odd fixer";
    } elsif ($role eq "orphan") {
	$role = "orphan minder";
    } elsif ($role eq "obsolete") {
	$role = "obsolete minder";
    } elsif ($role eq "buried alive in reporters") {
	$role = "chief penguin";
    }

    return $role . ":" . $subsystem;
}

sub get_list_role {
    my ($index) = @_;

    my $i;
    my $start = find_starting_index($index);
    my $end = find_ending_index($index);

    my $subsystem = $typevalue[$start];
1026
1027
    if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
	$subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
	$subsystem =~ s/\s*$//;
	$subsystem = $subsystem . "...";
    }

    if ($subsystem eq "THE REST") {
	$subsystem = "";
    }

    return $subsystem;
}

1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
sub add_categories {
    my ($index) = @_;

    my $i;
    my $start = find_starting_index($index);
    my $end = find_ending_index($index);

    push(@subsystem, $typevalue[$start]);

    for ($i = $start + 1; $i < $end; $i++) {
	my $tv = $typevalue[$i];
1050
	if ($tv =~ m/^([A-Z]):\s*(.*)/) {
Joe Perches's avatar
Joe Perches committed
1051
1052
1053
	    my $ptype = $1;
	    my $pvalue = $2;
	    if ($ptype eq "L") {
1054
1055
		my $list_address = $pvalue;
		my $list_additional = "";
1056
1057
1058
1059
1060
		my $list_role = get_list_role($i);

		if ($list_role ne "") {
		    $list_role = ":" . $list_role;
		}
1061
1062
1063
1064
		if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
		    $list_address = $1;
		    $list_additional = $2;
		}
1065
		if ($list_additional =~ m/subscribers-only/) {
Joe Perches's avatar
Joe Perches committed
1066
		    if ($email_subscriber_list) {
1067
1068
			if (!$hash_list_to{lc($list_address)}) {
			    $hash_list_to{lc($list_address)} = 1;
Joe Perches's avatar