proc: remove docbook and example
[safe/jmp/linux-2.6] / scripts / get_maintainer.pl
index 4e11c27..445e884 100755 (executable)
@@ -13,7 +13,7 @@
 use strict;
 
 my $P = $0;
-my $V = '0.22';
+my $V = '0.23';
 
 use Getopt::Long qw(:config no_auto_abbrev);
 
@@ -23,13 +23,14 @@ my $email_usename = 1;
 my $email_maintainer = 1;
 my $email_list = 1;
 my $email_subscriber_list = 0;
-my $email_git = 1;
 my $email_git_penguin_chiefs = 0;
+my $email_git = 1;
+my $email_git_blame = 0;
 my $email_git_min_signatures = 1;
 my $email_git_max_maintainers = 5;
 my $email_git_min_percent = 5;
 my $email_git_since = "1-year-ago";
-my $email_git_blame = 0;
+my $email_hg_since = "-365";
 my $email_remove_duplicates = 1;
 my $output_multiline = 1;
 my $output_separator = ", ";
@@ -66,15 +67,44 @@ my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)";
 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
 my $rfc822_char = '[\\000-\\377]';
 
+# 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")',
+    "find_signers_cmd" => "git log --since=\$email_git_since -- \$file",
+    "find_commit_signers_cmd" => "git log -1 \$commit",
+    "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]+):"
+);
+
 if (!GetOptions(
                'email!' => \$email,
                'git!' => \$email_git,
+               'git-blame!' => \$email_git_blame,
                'git-chief-penguins!' => \$email_git_penguin_chiefs,
                'git-min-signatures=i' => \$email_git_min_signatures,
                'git-max-maintainers=i' => \$email_git_max_maintainers,
                'git-min-percent=i' => \$email_git_min_percent,
                'git-since=s' => \$email_git_since,
-               'git-blame!' => \$email_git_blame,
+               'hg-since=s' => \$email_hg_since,
                'remove-duplicates!' => \$email_remove_duplicates,
                'm!' => \$email_maintainer,
                'n!' => \$email_usename,
@@ -182,7 +212,7 @@ if ($email_remove_duplicates) {
        next if ($line =~ m/^\s*$/);
 
        my ($name, $address) = parse_email($line);
-       $line = format_email($name, $address);
+       $line = format_email($name, $address, $email_usename);
 
        next if ($line =~ m/^\s*$/);
 
@@ -214,12 +244,10 @@ foreach my $file (@ARGV) {
        push(@files, $file);
        if (-f $file && $keywords) {
            open(FILE, "<$file") or die "$P: Can't open ${file}\n";
-           while (<FILE>) {
-               my $patch_line = $_;
-               foreach my $line (keys %keyword_hash) {
-                   if ($patch_line =~ m/^.*$keyword_hash{$line}/x) {
-                       push(@keyword_tvi, $line);
-                   }
+           my $text = do { local($/) ; <FILE> };
+           foreach my $line (keys %keyword_hash) {
+               if ($text =~ m/$keyword_hash{$line}/x) {
+                   push(@keyword_tvi, $line);
                }
            }
            close(FILE);
@@ -311,11 +339,11 @@ foreach my $file (@files) {
     }
 
     if ($email && $email_git) {
-       recent_git_signoffs($file);
+       vcs_file_signoffs($file);
     }
 
     if ($email && $email_git_blame) {
-       git_assign_blame($file);
+       vcs_file_blame($file);
     }
 }
 
@@ -331,7 +359,7 @@ if ($email) {
        if ($chief =~ m/^(.*):(.*)/) {
            my $email_address;
 
-           $email_address = format_email($1, $2);
+           $email_address = format_email($1, $2, $email_usename);
            if ($email_git_penguin_chiefs) {
                push(@email_to, [$email_address, 'chief penguin']);
            } else {
@@ -405,8 +433,9 @@ MAINTAINER field selection options:
     --git-min-signatures => number of signatures required (default: 1)
     --git-max-maintainers => maximum maintainers to add (default: 5)
     --git-min-percent => minimum percentage of commits required (default: 5)
-    --git-since => git history to use (default: 1-year-ago)
     --git-blame => use git blame to find modified commits for patch or file
+    --git-since => git history to use (default: 1-year-ago)
+    --hg-since => hg history to use (default: -365)
     --m => include maintainer(s) if any
     --n => include name 'Full Name <addr\@domain.tld>'
     --l => include list(s) if any
@@ -439,8 +468,8 @@ Notes:
           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,
-         no individual file within the directory or subdirectory
-         is matched.
+          no individual file within the directory or subdirectory
+          is matched.
       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.
@@ -451,6 +480,12 @@ Notes:
       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.
+  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
 EOT
 }
 
@@ -509,7 +544,7 @@ sub parse_email {
 }
 
 sub format_email {
-    my ($name, $address) = @_;
+    my ($name, $address, $usename) = @_;
 
     my $formatted_email;
 
@@ -522,11 +557,11 @@ sub format_email {
        $name = "\"$name\"";
     }
 
-    if ($email_usename) {
+    if ($usename) {
        if ("$name" eq "") {
            $formatted_email = "$address";
        } else {
-           $formatted_email = "$name <${address}>";
+           $formatted_email = "$name <$address>";
        }
     } else {
        $formatted_email = $address;
@@ -671,7 +706,7 @@ sub add_categories {
                        if ($tv =~ m/^(\C):\s*(.*)/) {
                            if ($1 eq "P") {
                                $name = $2;
-                               $pvalue = format_email($name, $address);
+                               $pvalue = format_email($name, $address, $email_usename);
                            }
                        }
                    }
@@ -714,9 +749,9 @@ sub push_email_address {
     }
 
     if (!$email_remove_duplicates) {
-       push(@email_to, [format_email($name, $address), $role]);
+       push(@email_to, [format_email($name, $address, $email_usename), $role]);
     } elsif (!email_inuse($name, $address)) {
-       push(@email_to, [format_email($name, $address), $role]);
+       push(@email_to, [format_email($name, $address, $email_usename), $role]);
        $email_hash_name{$name}++;
        $email_hash_address{$address}++;
     }
@@ -747,7 +782,7 @@ sub add_role {
     my ($line, $role) = @_;
 
     my ($name, $address) = parse_email($line);
-    my $email = format_email($name, $address);
+    my $email = format_email($name, $address, $email_usename);
 
     foreach my $entry (@email_to) {
        if ($email_remove_duplicates) {
@@ -784,7 +819,7 @@ sub which {
 }
 
 sub mailmap {
-    my @lines = @_;
+    my (@lines) = @_;
     my %hash;
 
     foreach my $line (@lines) {
@@ -793,14 +828,14 @@ sub mailmap {
            $hash{$name} = $address;
        } elsif ($address ne $hash{$name}) {
            $address = $hash{$name};
-           $line = format_email($name, $address);
+           $line = format_email($name, $address, $email_usename);
        }
        if (exists($mailmap{$name})) {
            my $obj = $mailmap{$name};
            foreach my $map_address (@$obj) {
                if (($map_address eq $address) &&
                    ($map_address ne $hash{$name})) {
-                   $line = format_email($name, $hash{$name});
+                   $line = format_email($name, $hash{$name}, $email_usename);
                }
            }
        }
@@ -809,34 +844,38 @@ sub mailmap {
     return @lines;
 }
 
-sub recent_git_signoffs {
-    my ($file) = @_;
-
-    my $sign_offs = "";
-    my $cmd = "";
-    my $output = "";
-    my $count = 0;
+sub git_execute_cmd {
+    my ($cmd) = @_;
     my @lines = ();
-    my %hash;
-    my $total_sign_offs;
 
-    if (which("git") eq "") {
-       warn("$P: git not found.  Add --nogit to options?\n");
-       return;
-    }
-    if (!(-d ".git")) {
-       warn("$P: .git directory not found.  Use a git repository for better results.\n");
-       warn("$P: perhaps 'git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git'\n");
-       return;
-    }
+    my $output = `$cmd`;
+    $output =~ s/^\s*//gm;
+    @lines = split("\n", $output);
 
-    $cmd = "git log --since=${email_git_since} -- ${file}";
+    return @lines;
+}
 
-    $output = `${cmd}`;
-    $output =~ s/^\s*//gm;
+sub hg_execute_cmd {
+    my ($cmd) = @_;
+    my @lines = ();
 
+    my $output = `$cmd`;
     @lines = split("\n", $output);
 
+    return @lines;
+}
+
+sub vcs_find_signers {
+    my ($cmd) = @_;
+    my @lines = ();
+    my $commits;
+
+    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
+
+    my $pattern = $VCS_cmds{"commit_pattern"};
+
+    $commits = grep(/$pattern/, @lines);       # of commits
+
     @lines = grep(/^[-_        a-z]+by:.*\@.*$/i, @lines);
     if (!$email_git_penguin_chiefs) {
        @lines = grep(!/${penguin_chiefs}/i, @lines);
@@ -844,135 +883,183 @@ sub recent_git_signoffs {
     # cut -f2- -d":"
     s/.*:\s*(.+)\s*/$1/ for (@lines);
 
-    $total_sign_offs = @lines;
+## Reformat email addresses (with names) to avoid badly written signatures
+
     foreach my $line (@lines) {
        my ($name, $address) = parse_email($line);
-       $line = format_email($name, $address);
-    }
-
-    if ($email_remove_duplicates) {
-       @lines = mailmap(@lines);
+       $line = format_email($name, $address, 1);
     }
 
-    @lines = sort(@lines);
-
-    # uniq -c
-    $hash{$_}++ for @lines;
-
-    # sort -rn
-    foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
-       my $sign_offs = $hash{$line};
-       my $role;
-
-       $count++;
-       last if ($sign_offs < $email_git_min_signatures ||
-                $count > $email_git_max_maintainers ||
-                $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
-       push_email_address($line, '');
-       $role = "git-signer";
-       if ($output_rolestats) {
-           my $percent = sprintf("%.0f", $sign_offs * 100 / $total_sign_offs);
-           $role = "$role:$sign_offs/$total_sign_offs=$percent%";
-       }
-       add_role($line, $role);
-    }
+    return ($commits, @lines);
 }
 
-sub save_commits {
-    my ($cmd, @commits) = @_;
-    my $output;
+sub vcs_save_commits {
+    my ($cmd) = @_;
     my @lines = ();
+    my @commits = ();
 
-    $output = `${cmd}`;
+    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
 
-    @lines = split("\n", $output);
     foreach my $line (@lines) {
-       if ($line =~ m/^(\w+) /) {
-           push (@commits, $1);
+       if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
+           push(@commits, $1);
        }
     }
+
     return @commits;
 }
 
-sub git_assign_blame {
+sub vcs_blame {
     my ($file) = @_;
-
-    my @lines = ();
-    my @commits = ();
     my $cmd;
-    my $output;
-    my %hash;
-    my $total_sign_offs;
-    my $count;
+    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);
 
-    if (@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" eq "$diff_file"));
-           $cmd = "git blame -l -L $diff_start,+$diff_length $file";
-           @commits = save_commits($cmd, @commits);
+           next if ("$file" ne "$diff_file");
+           for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
+               push(@commits, $all_commits[$i]);
+           }
        }
-    } else {
-       if (-f $file) {
-           $cmd = "git blame -l $file";
-           @commits = save_commits($cmd, @commits);
+    } 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);
     }
 
-    $total_sign_offs = 0;
-    @commits = uniq(@commits);
-    foreach my $commit (@commits) {
-       $cmd = "git log -1 ${commit}";
+    return @commits;
+}
 
-       $output = `${cmd}`;
-       $output =~ s/^\s*//gm;
-       @lines = split("\n", $output);
+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;
+}
 
-       @lines = grep(/^[-_     a-z]+by:.*\@.*$/i, @lines);
-       if (!$email_git_penguin_chiefs) {
-           @lines = grep(!/${penguin_chiefs}/i, @lines);
-       }
+sub vcs_assign {
+    my ($role, $divisor, @lines) = @_;
 
-       # cut -f2- -d":"
-       s/.*:\s*(.+)\s*/$1/ for (@lines);
+    my %hash;
+    my $count = 0;
 
-       $total_sign_offs += @lines;
+    return if (@lines <= 0);
 
-       if ($email_remove_duplicates) {
-           @lines = mailmap(@lines);
-       }
+    if ($divisor <= 0) {
+       warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
+       $divisor = 1;
+    }
 
-       $hash{$_}++ for @lines;
+    if ($email_remove_duplicates) {
+       @lines = mailmap(@lines);
     }
 
-    $count = 0;
+    @lines = sort(@lines);
+
+    # uniq -c
+    $hash{$_}++ for @lines;
+
+    # sort -rn
     foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
        my $sign_offs = $hash{$line};
-       my $role;
+       my $percent = $sign_offs * 100 / $divisor;
 
+       $percent = 100 if ($percent > 100);
        $count++;
        last if ($sign_offs < $email_git_min_signatures ||
                 $count > $email_git_max_maintainers ||
-                $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
+                $percent < $email_git_min_percent);
        push_email_address($line, '');
-       if ($from_filename) {
-           $role = "commits";
-       } else {
-           $role = "modified commits";
-       }
        if ($output_rolestats) {
-           my $percent = sprintf("%.0f", $sign_offs * 100 / $total_sign_offs);
-           $role = "$role:$sign_offs/$total_sign_offs=$percent%";
+           my $fmt_percent = sprintf("%.0f", $percent);
+           add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
+       } else {
+           add_role($line, $role);
        }
-       add_role($line, $role);
+    }
+}
+
+sub vcs_file_signoffs {
+    my ($file) = @_;
+
+    my @signers = ();
+    my $commits;
+
+    return if (!vcs_exists());
+
+    my $cmd = $VCS_cmds{"find_signers_cmd"};
+    $cmd =~ s/(\$\w+)/$1/eeg;          # interpolate $cmd
+
+    ($commits, @signers) = vcs_find_signers($cmd);
+    vcs_assign("commit_signer", $commits, @signers);
+}
+
+sub vcs_file_blame {
+    my ($file) = @_;
+
+    my @signers = ();
+    my @commits = ();
+    my $total_commits;
+
+    return if (!vcs_exists());
+
+    @commits = vcs_blame($file);
+    @commits = uniq(@commits);
+    $total_commits = @commits;
+
+    foreach my $commit (@commits) {
+       my $commit_count;
+       my @commit_signers = ();
+
+       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);
+    }
+
+    if ($from_filename) {
+       vcs_assign("commits", $total_commits, @signers);
+    } else {
+       vcs_assign("modified commits", $total_commits, @signers);
     }
 }
 
 sub uniq {
-    my @parms = @_;
+    my (@parms) = @_;
 
     my %saw;
     @parms = grep(!$saw{$_}++, @parms);
@@ -980,7 +1067,7 @@ sub uniq {
 }
 
 sub sort_and_uniq {
-    my @parms = @_;
+    my (@parms) = @_;
 
     my %saw;
     @parms = sort @parms;
@@ -996,9 +1083,9 @@ sub merge_email {
        my ($address, $role) = @$_;
        if (!$saw{$address}) {
            if ($output_roles) {
-               push @lines, "$address ($role)";
+               push(@lines, "$address ($role)");
            } else {
-               push @lines, $address;
+               push(@lines, $address);
            }
            $saw{$address} = 1;
        }
@@ -1008,7 +1095,7 @@ sub merge_email {
 }
 
 sub output {
-    my @parms = @_;
+    my (@parms) = @_;
 
     if ($output_multiline) {
        foreach my $line (@parms) {
@@ -1105,11 +1192,9 @@ sub rfc822_validlist ($) {
     if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
        $s =~ m/^$rfc822_char*$/) {
         while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
-            push @r, $1;
+            push(@r, $1);
         }
         return wantarray ? (scalar(@r), @r) : 1;
     }
-    else {
-        return wantarray ? () : 0;
-    }
+    return wantarray ? () : 0;
 }