scripts/get_maintainer.pl: support M: lines with names and multiple entries per M...
[sfrench/cifs-2.6.git] / scripts / get_maintainer.pl
index 0f3c0a5b5a5b278b9842b6d2a6e62463ba7815ed..7cf4309932f3075dea18ad191eda14dee190a929 100755 (executable)
@@ -13,7 +13,7 @@
 use strict;
 
 my $P = $0;
-my $V = '0.14';
+my $V = '0.16';
 
 use Getopt::Long qw(:config no_auto_abbrev);
 
@@ -34,7 +34,7 @@ my $scm = 0;
 my $web = 0;
 my $subsystem = 0;
 my $status = 0;
-my $onefile = 0;
+my $from_filename = 0;
 my $version = 0;
 my $help = 0;
 
@@ -55,6 +55,10 @@ foreach my $chief (@penguin_chief) {
 }
 my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)";
 
+# rfc822 - preloaded methods go here.
+my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
+my $rfc822_char = '[\\000-\\177]';
+
 if (!GetOptions(
                'email!' => \$email,
                'git!' => \$email_git,
@@ -72,7 +76,7 @@ if (!GetOptions(
                'status!' => \$status,
                'scm!' => \$scm,
                'web!' => \$web,
-               'f|file' => \$onefile,
+               'f|file' => \$from_filename,
                'v|version' => \$version,
                'h|help' => \$help,
                )) {
@@ -90,8 +94,6 @@ if ($version != 0) {
     exit 0;
 }
 
-my $infile = $ARGV[0];
-
 if ($#ARGV < 0) {
     usage();
     die "$P: argument missing: patchfile or -f file please\n";
@@ -139,35 +141,39 @@ while (<MAINT>) {
 }
 close(MAINT);
 
-## use the filename on the command line or find the filenames in the patchfile
+## use the filenames on the command line or find the filenames in the patchfiles
 
 my @files = ();
 
-if ($onefile) {
-    if (!(-f $infile)) {
-       die "$P: file '${infile}' not found\n";
+foreach my $file (@ARGV) {
+    next if ((-d $file));
+    if (!(-f $file)) {
+       die "$P: file '${file}' not found\n";
     }
-    push(@files, $infile);
-} else {
-    open(PATCH, "<$infile") or die "$P: Can't open ${infile}\n";
-    while (<PATCH>) {
-       if (m/^\+\+\+\s+(\S+)/) {
-           my $file = $1;
-           $file =~ s@^[^/]*/@@;
-           $file =~ s@\n@@;
-           push(@files, $file);
+    if ($from_filename) {
+       push(@files, $file);
+    } else {
+       my $file_cnt = @files;
+       open(PATCH, "<$file") or die "$P: Can't open ${file}\n";
+       while (<PATCH>) {
+           if (m/^\+\+\+\s+(\S+)/) {
+               my $filename = $1;
+               $filename =~ s@^[^/]*/@@;
+               $filename =~ s@\n@@;
+               push(@files, $filename);
+           }
        }
+       close(PATCH);
+       if ($file_cnt == @files) {
+           die "$P: file '${file}' doesn't appear to be a patch.  "
+               . "Add -f to options?\n";
+       }
+       @files = sort_and_uniq(@files);
     }
-    close(PATCH);
-    my $file_cnt = @files;
-    if ($file_cnt == 0) {
-       print STDERR "$P: file '${infile}' doesn't appear to be a patch.  "
-           . "Add -f to options?\n";
-    }
-    @files = sort_and_uniq(@files);
 }
 
 my @email_to = ();
+my @list_to = ();
 my @scm = ();
 my @web = ();
 my @subsystem = ();
@@ -181,7 +187,7 @@ foreach my $file (@files) {
 
     my $exclude = 0;
     foreach my $line (@typevalue) {
-       if ($line =~ m/^(\C):(.*)/) {
+       if ($line =~ m/^(\C):\s*(.*)/) {
            my $type = $1;
            my $value = $2;
            if ($type eq 'X') {
@@ -195,7 +201,7 @@ foreach my $file (@files) {
     if (!$exclude) {
        my $tvi = 0;
        foreach my $line (@typevalue) {
-           if ($line =~ m/^(\C):(.*)/) {
+           if ($line =~ m/^(\C):\s*(.*)/) {
                my $type = $1;
                my $value = $2;
                if ($type eq 'F') {
@@ -208,62 +214,58 @@ foreach my $file (@files) {
        }
     }
 
-    if ($email_git) {
+    if ($email && $email_git) {
        recent_git_signoffs($file);
     }
 
 }
 
-if ($email_git_penguin_chiefs) {
+if ($email) {
     foreach my $chief (@penguin_chief) {
        if ($chief =~ m/^(.*):(.*)/) {
-           my $chief_name = $1;
-           my $chief_addr = $2;
+           my $email_address;
            if ($email_usename) {
-               push(@email_to, format_email($chief_name, $chief_addr));
+               $email_address = format_email($1, $2);
            } else {
-               push(@email_to, $chief_addr);
+               $email_address = $2;
+           }
+           if ($email_git_penguin_chiefs) {
+               push(@email_to, $email_address);
+           } else {
+               @email_to = grep(!/${email_address}/, @email_to);
            }
        }
     }
 }
 
-if ($email) {
-    my $address_cnt = @email_to;
-    if ($address_cnt == 0 && $email_list) {
-       push(@email_to, "linux-kernel\@vger.kernel.org");
+if ($email || $email_list) {
+    my @to = ();
+    if ($email) {
+       @to = (@to, @email_to);
     }
-
-#Don't sort email address list, but do remove duplicates
-    @email_to = uniq(@email_to);
-    output(@email_to);
+    if ($email_list) {
+       @to = (@to, @list_to);
+    }
+    output(uniq(@to));
 }
 
 if ($scm) {
-    if (!$onefile) {
-       @scm = sort_and_uniq(@scm);
-    }
+    @scm = sort_and_uniq(@scm);
     output(@scm);
 }
 
 if ($status) {
-    if (!$onefile) {
-       @status = sort_and_uniq(@status);
-    }
+    @status = sort_and_uniq(@status);
     output(@status);
 }
 
 if ($subsystem) {
-    if (!$onefile) {
-       @subsystem = sort_and_uniq(@subsystem);
-    }
+    @subsystem = sort_and_uniq(@subsystem);
     output(@subsystem);
 }
 
 if ($web) {
-    if (!$onefile) {
-       @web = sort_and_uniq(@web);
-    }
+    @web = sort_and_uniq(@web);
     output(@web);
 }
 
@@ -314,10 +316,10 @@ Output type options:
   --multiline => print 1 entry per line
 
 Default options:
-  [--email --git --m --l --multiline]
+  [--email --git --m --n --l --multiline]
 
 Other options:
-  --version -> show version
+  --version => show version
   --help => show this help information
 
 EOT
@@ -373,35 +375,28 @@ sub add_categories {
     $index = $index - 1;
     while ($index >= 0) {
        my $tv = $typevalue[$index];
-       if ($tv =~ m/^(\C):(.*)/) {
+       if ($tv =~ m/^(\C):\s*(.*)/) {
            my $ptype = $1;
            my $pvalue = $2;
            if ($ptype eq "L") {
-               my $subscr = $pvalue;
-               if ($subscr =~ m/\s*\(subscribers-only\)/) {
+               my $list_address = $pvalue;
+               my $list_additional = "";
+               if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
+                   $list_address = $1;
+                   $list_additional = $2;
+               }
+               if ($list_additional =~ m/subscribers-only/) {
                    if ($email_subscriber_list) {
-                       $subscr =~ s/\s*\(subscribers-only\)//g;
-                       push(@email_to, $subscr);
+                       push(@list_to, $list_address);
                    }
                } else {
                    if ($email_list) {
-                       push(@email_to, $pvalue);
+                       push(@list_to, $list_address);
                    }
                }
            } elsif ($ptype eq "M") {
                if ($email_maintainer) {
-                   if ($index >= 0) {
-                       my $tv = $typevalue[$index - 1];
-                       if ($tv =~ m/^(\C):(.*)/) {
-                           if ($1 eq "P" && $email_usename) {
-                               push(@email_to, format_email($2, $pvalue));
-                           } else {
-                               push(@email_to, $pvalue);
-                           }
-                       }
-                   } else {
-                       push(@email_to, $pvalue);
-                   }
+                   push_email_addresses($pvalue);
                }
            } elsif ($ptype eq "T") {
                push(@scm, $pvalue);
@@ -419,10 +414,40 @@ sub add_categories {
     }
 }
 
+sub push_email_address {
+    my ($email_address) = @_;
+
+    my $email_name = "";
+    if ($email_address =~ m/([^<]+)<(.*\@.*)>$/) {
+       $email_name = $1;
+       $email_address = $2;
+    }
+
+    if ($email_usename && $email_name) {
+       push(@email_to, format_email($email_name, $email_address));
+    } else {
+       push(@email_to, $email_address);
+    }
+}
+
+sub push_email_addresses {
+    my ($address) = @_;
+
+    my @address_list = ();
+
+    if (@address_list = rfc822_validlist($address)) {
+       my $array_count = shift(@address_list);
+       while (my $entry = shift(@address_list)) {
+           push_email_address($entry);
+       }
+    }
+
+}
+
 sub which {
     my ($bin) = @_;
 
-    foreach my $path (split /:/, $ENV{PATH}) {
+    foreach my $path (split(/:/, $ENV{PATH})) {
        if (-e "$path/$bin") {
            return "$path/$bin";
        }
@@ -441,14 +466,22 @@ sub recent_git_signoffs {
     my @lines = ();
 
     if (which("git") eq "") {
-       die("$P: git not found.  Add --nogit to options?\n");
+       warn("$P: git not found.  Add --nogit to options?\n");
+       return;
+    }
+    if (!(-d ".git")) {
+       warn("$P: .git repository not found.\n");
+       warn("Use a .git repository for better results.\n");
+       warn("ie: git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
+       return;
     }
 
     $cmd = "git log --since=${email_git_since} -- ${file}";
-    $cmd .= " | grep -P '^    [-A-Za-z]+by:.*\\\@'";
+    $cmd .= " | grep -Ei \"^[-_        a-z]+by:.*\\\@.*\$\"";
     if (!$email_git_penguin_chiefs) {
-       $cmd .= " | grep -E -v \"${penguin_chiefs}\"";
+       $cmd .= " | grep -Ev \"${penguin_chiefs}\"";
     }
+    $cmd .= " | cut -f2- -d\":\"";
     $cmd .= " | sort | uniq -c | sort -rn";
 
     $output = `${cmd}`;
@@ -456,9 +489,9 @@ sub recent_git_signoffs {
 
     @lines = split("\n", $output);
     foreach my $line (@lines) {
-       if ($line =~ m/([0-9]+)\s+([-A-Za-z]+by:)\s+(.*)/) {
+       if ($line =~ m/([0-9]+)\s+(.*)/) {
            my $sign_offs = $1;
-           $line = $3;
+           $line = $2;
            $count++;
            if ($sign_offs < $email_git_min_signatures ||
                $count > $email_git_max_maintainers) {
@@ -467,24 +500,21 @@ sub recent_git_signoffs {
        } else {
            die("$P: Unexpected git output: ${line}\n");
        }
-       if ($line =~ m/(.*) <(.*)>/) {
+       if ($line =~ m/(.+)<(.+)>/) {
            my $git_name = $1;
            my $git_addr = $2;
-           $git_name =~ tr/^\"//;
-           $git_name =~ tr/\"$//;
            if ($email_usename) {
                push(@email_to, format_email($git_name, $git_addr));
            } else {
                push(@email_to, $git_addr);
            }
-       } elsif ($line =~ m/<(.*)>/) {
+       } elsif ($line =~ m/<(.+)>/) {
            my $git_addr = $1;
            push(@email_to, $git_addr);
        } else {
            push(@email_to, $line);
        }
     }
-    return $output;
 }
 
 sub uniq {
@@ -516,3 +546,97 @@ sub output {
        print("\n");
     }
 }
+
+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*$/) {
+        while($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
+            push @r, $1;
+        }
+        return wantarray ? (scalar(@r), @r) : 1;
+    }
+    else {
+        return wantarray ? () : 0;
+    }
+}