get_maintainer.pl 29.6 KB
Newer Older
J
Joe Perches 已提交
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
#
R
Roel Kluin 已提交
8 9
# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
#        perl scripts/get_maintainer.pl [OPTIONS] -f <file>
J
Joe Perches 已提交
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.23';
J
Joe Perches 已提交
17 18 19 20 21 22 23 24 25 26

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

my $lk_path = "./";
my $email = 1;
my $email_usename = 1;
my $email_maintainer = 1;
my $email_list = 1;
my $email_subscriber_list = 0;
my $email_git_penguin_chiefs = 0;
27 28
my $email_git = 1;
my $email_git_blame = 0;
J
Joe Perches 已提交
29 30
my $email_git_min_signatures = 1;
my $email_git_max_maintainers = 5;
31
my $email_git_min_percent = 5;
J
Joe Perches 已提交
32
my $email_git_since = "1-year-ago";
33
my $email_hg_since = "-365";
34
my $email_remove_duplicates = 1;
J
Joe Perches 已提交
35 36
my $output_multiline = 1;
my $output_separator = ", ";
37 38
my $output_roles = 0;
my $output_rolestats = 0;
J
Joe Perches 已提交
39 40 41 42
my $scm = 0;
my $web = 0;
my $subsystem = 0;
my $status = 0;
43
my $keywords = 1;
44
my $from_filename = 0;
45
my $pattern_depth = 0;
J
Joe Perches 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
my $version = 0;
my $help = 0;

my $exit = 0;

my @penguin_chief = ();
push(@penguin_chief,"Linus Torvalds:torvalds\@linux-foundation.org");
#Andrew wants in on most everything - 2009/01/14
#push(@penguin_chief,"Andrew Morton:akpm\@linux-foundation.org");

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);
    }
}
my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)";

66
# rfc822 email address - preloaded methods go here.
67
my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
68
my $rfc822_char = '[\\000-\\377]';
69

70 71 72 73 74 75 76
# VCS command support: class-like functions and strings

my %VCS_cmds;

my %VCS_cmds_git = (
    "execute_cmd" => \&git_execute_cmd,
    "available" => '(which("git") ne "") && (-d ".git")',
77 78
    "find_signers_cmd" => "git log --no-color --since=\$email_git_since -- \$file",
    "find_commit_signers_cmd" => "git log --no-color -1 \$commit",
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
    "blame_file_cmd" => "git blame -l \$file",
    "commit_pattern" => "^commit [0-9a-f]{40,40}",
    "blame_commit_pattern" => "^([0-9a-f]+) "
);

my %VCS_cmds_hg = (
    "execute_cmd" => \&hg_execute_cmd,
    "available" => '(which("hg") ne "") && (-d ".hg")',
    "find_signers_cmd" =>
	"hg log --date=\$email_hg_since" .
		" --template='commit {node}\\n{desc}\\n' -- \$file",
    "find_commit_signers_cmd" => "hg log --template='{desc}\\n' -r \$commit",
    "blame_range_cmd" => "",		# not supported
    "blame_file_cmd" => "hg blame -c \$file",
    "commit_pattern" => "^commit [0-9a-f]{40,40}",
    "blame_commit_pattern" => "^([0-9a-f]+):"
);

J
Joe Perches 已提交
98 99 100
if (!GetOptions(
		'email!' => \$email,
		'git!' => \$email_git,
101
		'git-blame!' => \$email_git_blame,
J
Joe Perches 已提交
102 103 104
		'git-chief-penguins!' => \$email_git_penguin_chiefs,
		'git-min-signatures=i' => \$email_git_min_signatures,
		'git-max-maintainers=i' => \$email_git_max_maintainers,
105
		'git-min-percent=i' => \$email_git_min_percent,
J
Joe Perches 已提交
106
		'git-since=s' => \$email_git_since,
107
		'hg-since=s' => \$email_hg_since,
108
		'remove-duplicates!' => \$email_remove_duplicates,
J
Joe Perches 已提交
109 110 111 112 113
		'm!' => \$email_maintainer,
		'n!' => \$email_usename,
		'l!' => \$email_list,
		's!' => \$email_subscriber_list,
		'multiline!' => \$output_multiline,
114 115
		'roles!' => \$output_roles,
		'rolestats!' => \$output_rolestats,
J
Joe Perches 已提交
116 117 118 119 120
		'separator=s' => \$output_separator,
		'subsystem!' => \$subsystem,
		'status!' => \$status,
		'scm!' => \$scm,
		'web!' => \$web,
121
		'pattern-depth=i' => \$pattern_depth,
122
		'k|keywords!' => \$keywords,
123
		'f|file' => \$from_filename,
J
Joe Perches 已提交
124 125 126
		'v|version' => \$version,
		'h|help' => \$help,
		)) {
127
    die "$P: invalid argument - use --help if necessary\n";
J
Joe Perches 已提交
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
}

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

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

if ($#ARGV < 0) {
    usage();
    die "$P: argument missing: patchfile or -f file please\n";
}

145 146 147 148
if ($output_separator ne ", ") {
    $output_multiline = 0;
}

149 150 151 152
if ($output_rolestats) {
    $output_roles = 1;
}

J
Joe Perches 已提交
153 154 155 156 157 158
my $selections = $email + $scm + $status + $subsystem + $web;
if ($selections == 0) {
    usage();
    die "$P:  Missing required option: email, scm, status, subsystem or web\n";
}

159 160 161
if ($email &&
    ($email_maintainer + $email_list + $email_subscriber_list +
     $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
J
Joe Perches 已提交
162 163 164 165 166 167 168 169 170 171 172 173
    usage();
    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 = ();
174 175
my %keyword_hash;

J
Joe Perches 已提交
176 177 178 179 180 181 182 183 184 185 186 187 188
open(MAINT, "<${lk_path}MAINTAINERS") || die "$P: Can't open MAINTAINERS\n";
while (<MAINT>) {
    my $line = $_;

    if ($line =~ m/^(\C):\s*(.*)/) {
	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 .
189 190 191 192
	    ##if pattern is a directory and it lacks a trailing slash, add one
	    if ((-d $value)) {
		$value =~ s@([^/])$@$1/@;
	    }
193 194
	} elsif ($type eq "K") {
	    $keyword_hash{@typevalue} = $value;
J
Joe Perches 已提交
195 196 197 198 199 200 201 202 203
	}
	push(@typevalue, "$type:$value");
    } elsif (!/^(\s)*$/) {
	$line =~ s/\n$//g;
	push(@typevalue, $line);
    }
}
close(MAINT);

204 205
my %mailmap;

206 207 208 209
if ($email_remove_duplicates) {
    open(MAILMAP, "<${lk_path}.mailmap") || warn "$P: Can't open .mailmap\n";
    while (<MAILMAP>) {
	my $line = $_;
210

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

214
	my ($name, $address) = parse_email($line);
215
	$line = format_email($name, $address, $email_usename);
216

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

219 220 221 222 223 224 225
	if (exists($mailmap{$name})) {
	    my $obj = $mailmap{$name};
	    push(@$obj, $address);
	} else {
	    my @arr = ($address);
	    $mailmap{$name} = \@arr;
	}
226
    }
227
    close(MAILMAP);
228 229
}

230
## use the filenames on the command line or find the filenames in the patchfiles
J
Joe Perches 已提交
231 232

my @files = ();
233
my @range = ();
234
my @keyword_tvi = ();
J
Joe Perches 已提交
235

236
foreach my $file (@ARGV) {
237 238 239 240
    ##if $file is a directory and it lacks a trailing slash, add one
    if ((-d $file)) {
	$file =~ s@([^/])$@$1/@;
    } elsif (!(-f $file)) {
241
	die "$P: file '${file}' not found\n";
J
Joe Perches 已提交
242
    }
243 244
    if ($from_filename) {
	push(@files, $file);
245 246
	if (-f $file && $keywords) {
	    open(FILE, "<$file") or die "$P: Can't open ${file}\n";
247 248 249 250
	    my $text = do { local($/) ; <FILE> };
	    foreach my $line (keys %keyword_hash) {
		if ($text =~ m/$keyword_hash{$line}/x) {
		    push(@keyword_tvi, $line);
251 252 253 254
		}
	    }
	    close(FILE);
	}
255 256
    } else {
	my $file_cnt = @files;
257
	my $lastfile;
258 259
	open(PATCH, "<$file") or die "$P: Can't open ${file}\n";
	while (<PATCH>) {
260
	    my $patch_line = $_;
261 262 263 264
	    if (m/^\+\+\+\s+(\S+)/) {
		my $filename = $1;
		$filename =~ s@^[^/]*/@@;
		$filename =~ s@\n@@;
265
		$lastfile = $filename;
266
		push(@files, $filename);
267 268 269 270
	    } elsif (m/^\@\@ -(\d+),(\d+)/) {
		if ($email_git_blame) {
		    push(@range, "$lastfile:$1:$2");
		}
271 272 273 274 275 276
	    } elsif ($keywords) {
		foreach my $line (keys %keyword_hash) {
		    if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) {
			push(@keyword_tvi, $line);
		    }
		}
277
	    }
J
Joe Perches 已提交
278
	}
279 280
	close(PATCH);
	if ($file_cnt == @files) {
281
	    warn "$P: file '${file}' doesn't appear to be a patch.  "
282 283 284
		. "Add -f to options?\n";
	}
	@files = sort_and_uniq(@files);
J
Joe Perches 已提交
285 286 287 288
    }
}

my @email_to = ();
289
my @list_to = ();
J
Joe Perches 已提交
290 291 292 293 294 295 296 297 298
my @scm = ();
my @web = ();
my @subsystem = ();
my @status = ();

# Find responsible parties

foreach my $file (@files) {

299 300 301 302 303 304 305 306 307 308 309 310
    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

	for ($i = $start; $i < $end; $i++) {
	    my $line = $typevalue[$i];
311
	    if ($line =~ m/^(\C):\s*(.*)/) {
J
Joe Perches 已提交
312 313
		my $type = $1;
		my $value = $2;
314
		if ($type eq 'X') {
J
Joe Perches 已提交
315
		    if (file_match_pattern($file, $value)) {
316
			$exclude = 1;
J
Joe Perches 已提交
317 318 319 320
		    }
		}
	    }
	}
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340

	if (!$exclude) {
	    for ($i = $start; $i < $end; $i++) {
		my $line = $typevalue[$i];
		if ($line =~ m/^(\C):\s*(.*)/) {
		    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 "/");
			    if ($pattern_depth == 0 ||
				(($file_pd - $value_pd) < $pattern_depth)) {
				$hash{$tvi} = $value_pd;
			    }
			}
		    }
		}
	    }
341
	}
342 343 344 345 346 347 348

	$tvi += ($end - $start);

    }

    foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
	add_categories($line);
J
Joe Perches 已提交
349 350
    }

351
    if ($email && $email_git) {
352
	vcs_file_signoffs($file);
J
Joe Perches 已提交
353 354
    }

355
    if ($email && $email_git_blame) {
356
	vcs_file_blame($file);
357
    }
J
Joe Perches 已提交
358 359
}

360 361 362 363 364 365 366
if ($keywords) {
    @keyword_tvi = sort_and_uniq(@keyword_tvi);
    foreach my $line (@keyword_tvi) {
	add_categories($line);
    }
}

367
if ($email) {
J
Joe Perches 已提交
368 369
    foreach my $chief (@penguin_chief) {
	if ($chief =~ m/^(.*):(.*)/) {
370
	    my $email_address;
371

372
	    $email_address = format_email($1, $2, $email_usename);
373
	    if ($email_git_penguin_chiefs) {
374
		push(@email_to, [$email_address, 'chief penguin']);
375
	    } else {
376
		@email_to = grep($_->[0] !~ /${email_address}/, @email_to);
J
Joe Perches 已提交
377 378 379 380 381
	    }
	}
    }
}

382 383 384 385
if ($email || $email_list) {
    my @to = ();
    if ($email) {
	@to = (@to, @email_to);
J
Joe Perches 已提交
386
    }
387 388 389
    if ($email_list) {
	@to = (@to, @list_to);
    }
390
    output(merge_email(@to));
J
Joe Perches 已提交
391 392 393
}

if ($scm) {
394
    @scm = uniq(@scm);
J
Joe Perches 已提交
395 396 397 398
    output(@scm);
}

if ($status) {
399
    @status = uniq(@status);
J
Joe Perches 已提交
400 401 402 403
    output(@status);
}

if ($subsystem) {
404
    @subsystem = uniq(@subsystem);
J
Joe Perches 已提交
405 406 407 408
    output(@subsystem);
}

if ($web) {
409
    @web = uniq(@web);
J
Joe Perches 已提交
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
    output(@web);
}

exit($exit);

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
436
       $P [options] -f file|directory
J
Joe Perches 已提交
437 438 439 440 441 442 443 444
version: $V

MAINTAINER field selection options:
  --email => print email address(es) if any
    --git => include recent git \*-by: signers
    --git-chief-penguins => include ${penguin_chiefs}
    --git-min-signatures => number of signatures required (default: 1)
    --git-max-maintainers => maximum maintainers to add (default: 5)
445
    --git-min-percent => minimum percentage of commits required (default: 5)
446
    --git-blame => use git blame to find modified commits for patch or file
447 448
    --git-since => git history to use (default: 1-year-ago)
    --hg-since => hg history to use (default: -365)
J
Joe Perches 已提交
449 450 451 452
    --m => include maintainer(s) if any
    --n => include name 'Full Name <addr\@domain.tld>'
    --l => include list(s) if any
    --s => include subscriber only list(s) if any
453
    --remove-duplicates => minimize duplicate email names/addresses
454 455
    --roles => show roles (status:subsystem, git-signer, list, etc...)
    --rolestats => show roles and statistics (commits/total_commits, %)
J
Joe Perches 已提交
456 457 458 459 460 461 462
  --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
463
    using --separator also sets --nomultiline if --separator is not [, ]
J
Joe Perches 已提交
464 465 466
  --multiline => print 1 entry per line

Other options:
467
  --pattern-depth => Number of pattern directory traversals (default: 0 (all))
468
  --keywords => scan patch for keywords (default: 1 (on))
469
  --version => show version
J
Joe Perches 已提交
470 471
  --help => show this help information

472
Default options:
473
  [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates]
474

475 476
Notes:
  Using "-f directory" may give unexpected results:
477 478 479 480
      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,
481 482
          no individual file within the directory or subdirectory
          is matched.
483 484 485
      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.
486 487 488 489 490 491 492
  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.
493 494 495 496 497 498
  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
J
Joe Perches 已提交
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
EOT
}

sub top_of_kernel_tree {
	my ($lk_path) = @_;

	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;
}

529 530 531 532 533 534
sub parse_email {
    my ($formatted_email) = @_;

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

535
    if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
536 537
	$name = $1;
	$address = $2;
538
    } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
539
	$address = $1;
540
    } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
541 542
	$address = $1;
    }
J
Joe Perches 已提交
543 544

    $name =~ s/^\s+|\s+$//g;
545
    $name =~ s/^\"|\"$//g;
546
    $address =~ s/^\s+|\s+$//g;
J
Joe Perches 已提交
547

548 549 550 551 552 553 554 555 556
    if ($name =~ /[^a-z0-9 \.\-]/i) {    ##has "must quote" chars
	$name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
	$name = "\"$name\"";
    }

    return ($name, $address);
}

sub format_email {
557
    my ($name, $address, $usename) = @_;
558 559 560 561 562 563

    my $formatted_email;

    $name =~ s/^\s+|\s+$//g;
    $name =~ s/^\"|\"$//g;
    $address =~ s/^\s+|\s+$//g;
J
Joe Perches 已提交
564 565 566

    if ($name =~ /[^a-z0-9 \.\-]/i) {    ##has "must quote" chars
	$name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
567 568 569
	$name = "\"$name\"";
    }

570
    if ($usename) {
571 572 573
	if ("$name" eq "") {
	    $formatted_email = "$address";
	} else {
574
	    $formatted_email = "$name <$address>";
575
	}
J
Joe Perches 已提交
576
    } else {
577
	$formatted_email = $address;
J
Joe Perches 已提交
578
    }
579

J
Joe Perches 已提交
580 581 582
    return $formatted_email;
}

583 584 585 586 587 588 589 590 591 592 593 594 595 596
sub find_first_section {
    my $index = 0;

    while ($index < @typevalue) {
	my $tv = $typevalue[$index];
	if (($tv =~ m/^(\C):\s*(.*)/)) {
	    last;
	}
	$index++;
    }

    return $index;
}

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
sub find_starting_index {
    my ($index) = @_;

    while ($index > 0) {
	my $tv = $typevalue[$index];
	if (!($tv =~ m/^(\C):\s*(.*)/)) {
	    last;
	}
	$index--;
    }

    return $index;
}

sub find_ending_index {
J
Joe Perches 已提交
612 613
    my ($index) = @_;

614
    while ($index < @typevalue) {
J
Joe Perches 已提交
615
	my $tv = $typevalue[$index];
616 617 618 619 620 621 622 623 624
	if (!($tv =~ m/^(\C):\s*(.*)/)) {
	    last;
	}
	$index++;
    }

    return $index;
}

625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
sub get_maintainer_role {
    my ($index) = @_;

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

    my $role;
    my $subsystem = $typevalue[$start];
    if (length($subsystem) > 20) {
	$subsystem = substr($subsystem, 0, 17);
	$subsystem =~ s/\s*$//;
	$subsystem = $subsystem . "...";
    }

    for ($i = $start + 1; $i < $end; $i++) {
	my $tv = $typevalue[$i];
	if ($tv =~ m/^(\C):\s*(.*)/) {
	    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];
    if (length($subsystem) > 20) {
	$subsystem = substr($subsystem, 0, 17);
	$subsystem =~ s/\s*$//;
	$subsystem = $subsystem . "...";
    }

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

    return $subsystem;
}

690 691 692 693 694 695 696 697 698 699 700
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];
701
	if ($tv =~ m/^(\C):\s*(.*)/) {
J
Joe Perches 已提交
702 703 704
	    my $ptype = $1;
	    my $pvalue = $2;
	    if ($ptype eq "L") {
705 706
		my $list_address = $pvalue;
		my $list_additional = "";
707 708 709 710 711
		my $list_role = get_list_role($i);

		if ($list_role ne "") {
		    $list_role = ":" . $list_role;
		}
712 713 714 715
		if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
		    $list_address = $1;
		    $list_additional = $2;
		}
716
		if ($list_additional =~ m/subscribers-only/) {
J
Joe Perches 已提交
717
		    if ($email_subscriber_list) {
718
			push(@list_to, [$list_address, "subscriber list${list_role}"]);
J
Joe Perches 已提交
719 720 721
		    }
		} else {
		    if ($email_list) {
722
			push(@list_to, [$list_address, "open list${list_role}"]);
J
Joe Perches 已提交
723 724 725
		    }
		}
	    } elsif ($ptype eq "M") {
726 727
		my ($name, $address) = parse_email($pvalue);
		if ($name eq "") {
728 729
		    if ($i > 0) {
			my $tv = $typevalue[$i - 1];
730 731 732
			if ($tv =~ m/^(\C):\s*(.*)/) {
			    if ($1 eq "P") {
				$name = $2;
733
				$pvalue = format_email($name, $address, $email_usename);
734 735 736 737
			    }
			}
		    }
		}
738
		if ($email_maintainer) {
739 740
		    my $role = get_maintainer_role($i);
		    push_email_addresses($pvalue, $role);
J
Joe Perches 已提交
741 742 743 744 745 746 747 748 749 750 751 752
		}
	    } elsif ($ptype eq "T") {
		push(@scm, $pvalue);
	    } elsif ($ptype eq "W") {
		push(@web, $pvalue);
	    } elsif ($ptype eq "S") {
		push(@status, $pvalue);
	    }
	}
    }
}

753 754
my %email_hash_name;
my %email_hash_address;
755

756 757 758 759 760 761
sub email_inuse {
    my ($name, $address) = @_;

    return 1 if (($name eq "") && ($address eq ""));
    return 1 if (($name ne "") && exists($email_hash_name{$name}));
    return 1 if (($address ne "") && exists($email_hash_address{$address}));
762 763 764 765

    return 0;
}

766
sub push_email_address {
767
    my ($line, $role) = @_;
768

769
    my ($name, $address) = parse_email($line);
770

771 772 773 774
    if ($address eq "") {
	return 0;
    }

775
    if (!$email_remove_duplicates) {
776
	push(@email_to, [format_email($name, $address, $email_usename), $role]);
777
    } elsif (!email_inuse($name, $address)) {
778
	push(@email_to, [format_email($name, $address, $email_usename), $role]);
779 780
	$email_hash_name{$name}++;
	$email_hash_address{$address}++;
781
    }
782 783

    return 1;
784 785 786
}

sub push_email_addresses {
787
    my ($address, $role) = @_;
788 789 790

    my @address_list = ();

791
    if (rfc822_valid($address)) {
792
	push_email_address($address, $role);
793
    } elsif (@address_list = rfc822_validlist($address)) {
794 795
	my $array_count = shift(@address_list);
	while (my $entry = shift(@address_list)) {
796
	    push_email_address($entry, $role);
797
	}
798
    } else {
799
	if (!push_email_address($address, $role)) {
800 801
	    warn("Invalid MAINTAINERS address: '" . $address . "'\n");
	}
802 803 804
    }
}

805 806 807 808
sub add_role {
    my ($line, $role) = @_;

    my ($name, $address) = parse_email($line);
809
    my $email = format_email($name, $address, $email_usename);
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832

    foreach my $entry (@email_to) {
	if ($email_remove_duplicates) {
	    my ($entry_name, $entry_address) = parse_email($entry->[0]);
	    if ($name eq $entry_name || $address eq $entry_address) {
		if ($entry->[1] eq "") {
		    $entry->[1] = "$role";
		} else {
		    $entry->[1] = "$entry->[1],$role";
		}
	    }
	} else {
	    if ($email eq $entry->[0]) {
		if ($entry->[1] eq "") {
		    $entry->[1] = "$role";
		} else {
		    $entry->[1] = "$entry->[1],$role";
		}
	    }
	}
    }
}

J
Joe Perches 已提交
833 834 835
sub which {
    my ($bin) = @_;

836
    foreach my $path (split(/:/, $ENV{PATH})) {
J
Joe Perches 已提交
837 838 839 840 841 842 843 844
	if (-e "$path/$bin") {
	    return "$path/$bin";
	}
    }

    return "";
}

845
sub mailmap {
846
    my (@lines) = @_;
847 848 849 850 851 852
    my %hash;

    foreach my $line (@lines) {
	my ($name, $address) = parse_email($line);
	if (!exists($hash{$name})) {
	    $hash{$name} = $address;
853 854
	} elsif ($address ne $hash{$name}) {
	    $address = $hash{$name};
855
	    $line = format_email($name, $address, $email_usename);
856 857 858 859 860 861
	}
	if (exists($mailmap{$name})) {
	    my $obj = $mailmap{$name};
	    foreach my $map_address (@$obj) {
		if (($map_address eq $address) &&
		    ($map_address ne $hash{$name})) {
862
		    $line = format_email($name, $hash{$name}, $email_usename);
863 864 865 866 867 868 869 870
		}
	    }
	}
    }

    return @lines;
}

871 872 873
sub git_execute_cmd {
    my ($cmd) = @_;
    my @lines = ();
J
Joe Perches 已提交
874

875 876 877 878 879
    my $output = `$cmd`;
    $output =~ s/^\s*//gm;
    @lines = split("\n", $output);

    return @lines;
880 881
}

882
sub hg_execute_cmd {
883
    my ($cmd) = @_;
884 885 886 887
    my @lines = ();

    my $output = `$cmd`;
    @lines = split("\n", $output);
888

889 890 891 892 893
    return @lines;
}

sub vcs_find_signers {
    my ($cmd) = @_;
894 895 896
    my @lines = ();
    my $commits;

897
    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
J
Joe Perches 已提交
898

899
    my $pattern = $VCS_cmds{"commit_pattern"};
J
Joe Perches 已提交
900

901
    $commits = grep(/$pattern/, @lines);	# of commits
902

903 904 905 906 907 908 909
    @lines = grep(/^[-_ 	a-z]+by:.*\@.*$/i, @lines);
    if (!$email_git_penguin_chiefs) {
	@lines = grep(!/${penguin_chiefs}/i, @lines);
    }
    # cut -f2- -d":"
    s/.*:\s*(.+)\s*/$1/ for (@lines);

910 911
## Reformat email addresses (with names) to avoid badly written signatures

912 913
    foreach my $line (@lines) {
	my ($name, $address) = parse_email($line);
914 915 916 917 918 919
	$line = format_email($name, $address, 1);
    }

    return ($commits, @lines);
}

920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
sub vcs_save_commits {
    my ($cmd) = @_;
    my @lines = ();
    my @commits = ();

    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);

    foreach my $line (@lines) {
	if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
	    push(@commits, $1);
	}
    }

    return @commits;
}

sub vcs_blame {
    my ($file) = @_;
    my $cmd;
    my @commits = ();

    return @commits if (!(-f $file));

    if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
	my @all_commits = ();

	$cmd = $VCS_cmds{"blame_file_cmd"};
	$cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
	@all_commits = vcs_save_commits($cmd);

	foreach my $file_range_diff (@range) {
	    next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
	    my $diff_file = $1;
	    my $diff_start = $2;
	    my $diff_length = $3;
	    next if ("$file" ne "$diff_file");
	    for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
		push(@commits, $all_commits[$i]);
	    }
	}
    } elsif (@range) {
	foreach my $file_range_diff (@range) {
	    next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
	    my $diff_file = $1;
	    my $diff_start = $2;
	    my $diff_length = $3;
	    next if ("$file" ne "$diff_file");
	    $cmd = $VCS_cmds{"blame_range_cmd"};
	    $cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
	    push(@commits, vcs_save_commits($cmd));
	}
    } else {
	$cmd = $VCS_cmds{"blame_file_cmd"};
	$cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
	@commits = vcs_save_commits($cmd);
    }

    return @commits;
}

my $printed_novcs = 0;
sub vcs_exists {
    %VCS_cmds = %VCS_cmds_git;
    return 1 if eval $VCS_cmds{"available"};
    %VCS_cmds = %VCS_cmds_hg;
    return 1 if eval $VCS_cmds{"available"};
    %VCS_cmds = ();
    if (!$printed_novcs) {
	warn("$P: No supported VCS found.  Add --nogit to options?\n");
	warn("Using a git repository produces better results.\n");
	warn("Try Linus Torvalds' latest git repository using:\n");
	warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
	$printed_novcs = 1;
    }
    return 0;
}

sub vcs_assign {
998 999 1000 1001 1002 1003 1004 1005
    my ($role, $divisor, @lines) = @_;

    my %hash;
    my $count = 0;

    return if (@lines <= 0);

    if ($divisor <= 0) {
1006
	warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1007
	$divisor = 1;
1008
    }
1009

1010 1011 1012
    if ($email_remove_duplicates) {
	@lines = mailmap(@lines);
    }
1013 1014

    @lines = sort(@lines);
1015

1016
    # uniq -c
1017 1018
    $hash{$_}++ for @lines;

1019 1020
    # sort -rn
    foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1021
	my $sign_offs = $hash{$line};
1022
	my $percent = $sign_offs * 100 / $divisor;
1023

1024
	$percent = 100 if ($percent > 100);
1025 1026 1027
	$count++;
	last if ($sign_offs < $email_git_min_signatures ||
		 $count > $email_git_max_maintainers ||
1028
		 $percent < $email_git_min_percent);
1029 1030
	push_email_address($line, '');
	if ($output_rolestats) {
1031 1032 1033 1034
	    my $fmt_percent = sprintf("%.0f", $percent);
	    add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
	} else {
	    add_role($line, $role);
1035
	}
1036 1037 1038
    }
}

1039
sub vcs_file_signoffs {
1040 1041 1042
    my ($file) = @_;

    my @signers = ();
1043
    my $commits;
1044

1045
    return if (!vcs_exists());
1046

1047 1048
    my $cmd = $VCS_cmds{"find_signers_cmd"};
    $cmd =~ s/(\$\w+)/$1/eeg;		# interpolate $cmd
1049

1050 1051
    ($commits, @signers) = vcs_find_signers($cmd);
    vcs_assign("commit_signer", $commits, @signers);
1052 1053
}

1054
sub vcs_file_blame {
1055 1056
    my ($file) = @_;

1057
    my @signers = ();
1058
    my @commits = ();
1059
    my $total_commits;
1060

1061
    return if (!vcs_exists());
1062

1063
    @commits = vcs_blame($file);
1064
    @commits = uniq(@commits);
1065
    $total_commits = @commits;
1066

1067 1068 1069
    foreach my $commit (@commits) {
	my $commit_count;
	my @commit_signers = ();
1070

1071 1072 1073 1074 1075
	my $cmd = $VCS_cmds{"find_commit_signers_cmd"};
	$cmd =~ s/(\$\w+)/$1/eeg;	#interpolate $cmd

	($commit_count, @commit_signers) = vcs_find_signers($cmd);
	push(@signers, @commit_signers);
1076 1077
    }

1078
    if ($from_filename) {
1079
	vcs_assign("commits", $total_commits, @signers);
1080
    } else {
1081
	vcs_assign("modified commits", $total_commits, @signers);
J
Joe Perches 已提交
1082 1083 1084 1085
    }
}

sub uniq {
1086
    my (@parms) = @_;
J
Joe Perches 已提交
1087 1088 1089 1090 1091 1092 1093

    my %saw;
    @parms = grep(!$saw{$_}++, @parms);
    return @parms;
}

sub sort_and_uniq {
1094
    my (@parms) = @_;
J
Joe Perches 已提交
1095 1096 1097 1098 1099 1100 1101

    my %saw;
    @parms = sort @parms;
    @parms = grep(!$saw{$_}++, @parms);
    return @parms;
}

1102 1103 1104 1105 1106 1107 1108 1109
sub merge_email {
    my @lines;
    my %saw;

    for (@_) {
	my ($address, $role) = @$_;
	if (!$saw{$address}) {
	    if ($output_roles) {
1110
		push(@lines, "$address ($role)");
1111
	    } else {
1112
		push(@lines, $address);
1113 1114 1115 1116 1117 1118 1119 1120
	    }
	    $saw{$address} = 1;
	}
    }

    return @lines;
}

J
Joe Perches 已提交
1121
sub output {
1122
    my (@parms) = @_;
J
Joe Perches 已提交
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132

    if ($output_multiline) {
	foreach my $line (@parms) {
	    print("${line}\n");
	}
    } else {
	print(join($output_separator, @parms));
	print("\n");
    }
}
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217

my $rfc822re;

sub make_rfc822re {
#   Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
#   comment.  We must allow for rfc822_lwsp (or comments) after each of these.
#   This regexp will only work on addresses which have had comments stripped
#   and replaced with rfc822_lwsp.

    my $specials = '()<>@,;:\\\\".\\[\\]';
    my $controls = '\\000-\\037\\177';

    my $dtext = "[^\\[\\]\\r\\\\]";
    my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";

    my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";

#   Use zero-width assertion to spot the limit of an atom.  A simple
#   $rfc822_lwsp* causes the regexp engine to hang occasionally.
    my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
    my $word = "(?:$atom|$quoted_string)";
    my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";

    my $sub_domain = "(?:$atom|$domain_literal)";
    my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";

    my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";

    my $phrase = "$word*";
    my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
    my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
    my $mailbox = "(?:$addr_spec|$phrase$route_addr)";

    my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
    my $address = "(?:$mailbox|$group)";

    return "$rfc822_lwsp*$address";
}

sub rfc822_strip_comments {
    my $s = shift;
#   Recursively remove comments, and replace with a single space.  The simpler
#   regexps in the Email Addressing FAQ are imperfect - they will miss escaped
#   chars in atoms, for example.

    while ($s =~ s/^((?:[^"\\]|\\.)*
                    (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
                    \((?:[^()\\]|\\.)*\)/$1 /osx) {}
    return $s;
}

#   valid: returns true if the parameter is an RFC822 valid address
#
sub rfc822_valid ($) {
    my $s = rfc822_strip_comments(shift);

    if (!$rfc822re) {
        $rfc822re = make_rfc822re();
    }

    return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
}

#   validlist: In scalar context, returns true if the parameter is an RFC822
#              valid list of addresses.
#
#              In list context, returns an empty list on failure (an invalid
#              address was found); otherwise a list whose first element is the
#              number of addresses found and whose remaining elements are the
#              addresses.  This is needed to disambiguate failure (invalid)
#              from success with no addresses found, because an empty string is
#              a valid list.

sub rfc822_validlist ($) {
    my $s = rfc822_strip_comments(shift);

    if (!$rfc822re) {
        $rfc822re = make_rfc822re();
    }
    # * null list items are valid according to the RFC
    # * the '1' business is to aid in distinguishing failure from no results

    my @r;
    if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
	$s =~ m/^$rfc822_char*$/) {
1218
        while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
1219
            push(@r, $1);
1220 1221 1222
        }
        return wantarray ? (scalar(@r), @r) : 1;
    }
1223
    return wantarray ? () : 0;
1224
}