Reviewed by Darin.
[WebKit-https.git] / WebKitTools / Scripts / prepare-ChangeLog
index 6d878dd..7d71afa 100755 (executable)
@@ -3,7 +3,7 @@
 
 #
 #  Copyright (C) 2000, 2001 Eazel, Inc.
-#  Copyright (C) 2002, 2003 Apple Computer, Inc.
+#  Copyright (C) 2002, 2003, 2004, 2005, 2006 Apple Computer, Inc.
 #
 #  prepare-ChangeLog is free software; you can redistribute it and/or
 #  modify it under the terms of the GNU General Public
@@ -22,7 +22,7 @@
 
 
 # Perl script to create a ChangeLog entry with names of files
-# and functions from a cvs diff.
+# and functions from a diff.
 #
 # Darin Adler <darin@bentspoon.com>, started 20 April 2000
 # Java support added by Maciej Stachowiak <mjs@eazel.com>
 #     already a partly written ChangeLog entry.
 #   Add command line option to put the ChangeLog into a separate
 #     file or just spew it out stdout.
-#   Figure out how to allow -z options from .cvsrc to work without
-#     letting other bad options work. Currently the -f disables
-#     everything from the .cvsrc.
-#   Add CVS version numbers for each file too (can't do that until
+#   Add SVN version numbers for commit (can't do that until
 #     the changes are checked in, though).
 #   Work around diff stupidity where deleting a function that starts
 #     with a comment makes diff think that the following function
 #     previous function.
 
 use strict;
+use warnings;
 
+use File::Basename;
+use File::Spec;
+use File::Temp;
+use FindBin;
 use Getopt::Long;
 
-my $spewDiff = $ENV{"PREPARE_CHANGELOG_DIFF"};
+sub canonicalizePath($);
+sub get_function_line_ranges($$);
+sub get_function_line_ranges_for_c($$);
+sub get_function_line_ranges_for_java($$);
+sub method_decl_to_selector($);
+sub processPaths(\@);
+
 my $openChangeLogs = 0;
-GetOptions("diff|d!" => \$spewDiff,
-          "open|o!" => \$openChangeLogs);
+my $showHelp = 0;
+my $updateChangeLogs = 1;
+my $spewDiff = $ENV{"PREPARE_CHANGELOG_DIFF"};
+my $parseOptionsResult =
+    GetOptions("diff|d!" => \$spewDiff,
+               "help|h!" => \$showHelp,
+               "open|o!" => \$openChangeLogs,
+               "update!" => \$updateChangeLogs);
+if (!$parseOptionsResult || $showHelp)
+  {
+    print STDERR basename($0) . " [-d|--diff] [-h|--help] [-o|--open] [svndir1 [svndir2 ...]]\n";
+    print STDERR "  -d|--diff      Spew diff to stdout when running\n";
+    print STDERR "  -h|--help      Show this help message\n";
+    print STDERR "  -o|--open      Open ChangeLogs in an editor when done\n";
+    print STDERR "  --[no-]update  Update ChangeLogs from svn before adding entry (default: update)\n";
+    exit 1;
+  }
+
+my %paths = processPaths(@ARGV);
 
 # Find the list of modified files
 my @changed_files;
@@ -69,27 +94,70 @@ my %changed_line_ranges;
 my %function_lists;
 my @conflict_files;
 
-my $CVS = "cvs";
+my $SVN = "svn";
+
+my %statusDescription = (
+    "A" => " Added.",
+    "D" => " Removed.",
+    "M" => "",
+    "R" => " Replaced.",
+);
+
+my $changedLayoutTests = 0;
 
-print STDERR "  Running cvs -n update to find changed, added, or removed files.\n";
-open UPDATE, "$CVS -qn update 2> /dev/stdout |" or die "The cvs update failed: $!.\n";
-while (<UPDATE>)
+my $DIFFOUT = new File::Temp(TEMPLATE => basename($0) . "-XXXXXXXX",
+                             DIR => ($ENV{'TMPDIR'} || "/tmp"),
+                             SUFFIX => ".diff");
+my $diffTempFile = $DIFFOUT->filename();
+my @diffFiles;
+
+print STDERR "  Running 'svn diff' to find changed, added, or removed files.\n";
+open SVNDIFF, "$SVN diff --diff-cmd diff -x -N '" . join("' '", keys %paths) . "'|"
+    or die "The svn diff failed: $!.\n";
+while (<SVNDIFF>)
   {
-    if (/^[MA] (.+)$/)
+    print $DIFFOUT $_;
+    if (/^Index: (.+)$/)
       {
-        my $file = $1;
-        $function_lists{$file} = "";
-        push @changed_files, $file if $file ne "ChangeLog" && $file !~ /\.nib$/;
+        push @diffFiles, $1;
       }
+  }
+close SVNDIFF;
+close $DIFFOUT;
 
-    push @conflict_files, $1 if /^[C] (.+)$/;
+if (@diffFiles)
+  {
+    my $diffFilesString = "'" . join ("' '", @diffFiles) . "'";
+    print STDERR "  Running 'svn stat' on changed, added, or removed files.\n";
+    open SVNSTAT, "$SVN stat $diffFilesString 2> /dev/stdout |" or die "The svn stat failed: $!.\n";
+    while(<SVNSTAT>)
+      {
+        if (/^([A-Z]).+\s+(.+)$/)
+          {
+              my $status = $1;
+              my $file = $2;
+              if ($status eq "A" || $status eq "M")
+                {
+                  my @components = File::Spec->splitdir($file);
+                  $changedLayoutTests = 1 if $components[0] eq "LayoutTests";
+                  push @changed_files, $file if $components[$#components] ne "ChangeLog";
+                }
+              push @conflict_files, $file if $status eq "C";
+              $function_lists{$file} = $statusDescription{$status} if exists $statusDescription{$status};
+          }
+        else
+          {
+            print;  # error output from svn stat
+          }
+      }
+    close SVNSTAT;
+  }
 
-    $function_lists{$1} = " Added." if /^A (.+)$/;
-    $function_lists{$1} = " Removed." if /^R (.+)$/;
-    
-    print unless /^[A-Z] ./ || /^cvs server: New directory/;
+if (!@diffFiles || !%function_lists)
+  {
+    print STDERR "  No changes found.\n";
+    exit 1;
   }
-close UPDATE;
 
 if (@conflict_files)
   {
@@ -104,19 +172,19 @@ if (@changed_files)
 
     # For each file, build a list of modified lines.
     # Use line numbers from the "after" side of each diff.
-    print STDERR "  Running cvs diff to determine which lines changed.\n";
+    print STDERR "  Reviewing 'svn diff' to determine which lines changed.\n";
     my $file;
-    open DIFF, "$CVS -fq diff -N $changed_files_string |" or die "The cvs diff failed: $!.\n";
+    open DIFF, "< $diffTempFile" or die "Opening $diffTempFile failed: $!.\n";
     while (<DIFF>)
       {
         $file = $1 if /^Index: (\S+)$/;
         if (defined $file) {
-          if (/^\d+(,\d+)?[acd](\d+)(,(\d+))?/)
-            { push @{$changed_line_ranges{$file}}, [ $2, $4 || $2 ]; }
-         elsif (/DO_NOT_COMMIT/) {
-           print STDERR "WARNING: file $file contains the string DO_NOT_COMMIT, line $.\n";
-         }
-       }
+          if (/^\d+(,\d+)?[acd](\d+)(,(\d+))?/) {
+            push @{$changed_line_ranges{$file}}, [ $2, $4 || $2 ];
+          } elsif (/DO_NOT_COMMIT/) {
+            print STDERR "WARNING: file $file contains the string DO_NOT_COMMIT, line $.\n";
+          }
+        }
       }
     close DIFF;
   }
@@ -175,12 +243,6 @@ if (%changed_line_ranges)
       }
   }
 
-if (!%function_lists)
-  {
-    print STDERR "  No changes found.\n";
-    exit 1;
-  }
-
 # Get some parameters for the ChangeLog we are about to write.
 my $date = sprintf "%d-%02d-%02d",
   1900 + (localtime $^T)[5], # year
@@ -225,16 +287,16 @@ foreach my $file (sort keys %function_lists)
       }
   }
 
-# Get the latest ChangeLog files from cvs.
+# Get the latest ChangeLog files from svn.
 my $logs = "";
 foreach my $prefix (sort keys %files)
   {
     $logs .= " ${prefix}ChangeLog";
   }
-if ($logs)
+if ($logs && $updateChangeLogs)
   {
-    print STDERR "  Running cvs update to update ChangeLog files.\n";
-    open ERRORS, "$CVS update$logs |" or die "The cvs update of ChangeLog files failed: $!.\n";
+    print STDERR "  Running 'svn update' to update ChangeLog files.\n";
+    open ERRORS, "$SVN update -q$logs |" or die "The svn update of ChangeLog files failed: $!.\n";
     print STDERR "    $_" while <ERRORS>;
     close ERRORS;
   }
@@ -250,9 +312,9 @@ foreach my $prefix (sort keys %files)
     close OLD_CHANGE_LOG;
     open CHANGE_LOG, "> ${prefix}ChangeLog" or die "Could not write ${prefix}ChangeLog\n.";
     print CHANGE_LOG "$date  $name  <$email_address>\n\n";
-    print CHANGE_LOG "        Reviewed by NOBODY (OOPS!).\n\n";
+    print CHANGE_LOG "        Reviewed by NOBODY (OO" . "PS!).\n\n";
     if ($prefix =~ m/WebCore/ || `pwd` =~ m/WebCore/) {
-        print CHANGE_LOG "        Test cases added: (NONE)\n\n";
+        print CHANGE_LOG "        WARNING: NO TEST CASES ADDED OR CHANGED\n\n" unless $changedLayoutTests;
     }
 
     foreach my $file (sort @{$files{$prefix}})
@@ -267,8 +329,8 @@ foreach my $prefix (sort keys %files)
 # Write out another diff.
 if ($spewDiff && @changed_files)
   {
-    print STDERR "  Running cvs diff -u to help you write the ChangeLog entries.\n";
-    open DIFF, "$CVS -q diff -uN $changed_files_string |" or die "The cvs diff failed: $!.\n";
+    print STDERR "  Running 'svn diff' to help you write the ChangeLog entries.\n";
+    open DIFF, "'$FindBin::Bin/svn-create-patch' $changed_files_string |" or die "The svn diff failed: $!.\n";
     while (<DIFF>) { print; }
     close DIFF;
   }
@@ -288,7 +350,30 @@ if ($openChangeLogs && $logs)
 # Done.
 exit;
 
-sub get_function_line_ranges
+sub canonicalizePath($)
+  {
+    my ($file) = @_;
+
+    # Remove extra slashes and '.' directories in path
+    $file = File::Spec->canonpath($file);
+
+    # Remove '..' directories in path
+    my @dirs = ();
+    foreach my $dir (File::Spec->splitdir($file))
+      {
+        if ($dir eq '..' && $#dirs >= 0 && $dirs[$#dirs] ne '..')
+          {
+            pop(@dirs);
+          }
+        else
+          {
+            push(@dirs, $dir);
+          }
+      }
+    return ($#dirs >= 0) ? File::Spec->catdir(@dirs) : ".";
+  }
+
+sub get_function_line_ranges($$)
   {
     my ($file_handle, $file_name) = @_;
 
@@ -301,7 +386,7 @@ sub get_function_line_ranges
   }
 
 
-sub method_decl_to_selector
+sub method_decl_to_selector($)
   {
     (my $method_decl) = @_;
 
@@ -309,7 +394,7 @@ sub method_decl_to_selector
 
     if ((my $comment_stripped) = m-([^/]*)(//|/*).*-) 
       {
-       $_ = $comment_stripped;
+        $_ = $comment_stripped;
       }
 
     s/,\s*...//;
@@ -317,11 +402,11 @@ sub method_decl_to_selector
     if (/:/) 
       {
         my @components = split /:/;
-       pop @components if (scalar @components > 1);
-       $_ = (join ':', map {s/.*[^[:word:]]//; scalar $_;} @components) . ':';
+        pop @components if (scalar @components > 1);
+        $_ = (join ':', map {s/.*[^[:word:]]//; scalar $_;} @components) . ':';
       } else {
         s/\s*$//;
-       s/.*[^[:word:]]//;
+        s/.*[^[:word:]]//;
       }
 
     return $_;
@@ -337,7 +422,7 @@ sub method_decl_to_selector
 #
 # Result is a list of triples: [ start_line, end_line, function_name ].
 
-sub get_function_line_ranges_for_c
+sub get_function_line_ranges_for_c($$)
   {
     my ($file_handle, $file_name) = @_;
 
@@ -446,6 +531,10 @@ sub get_function_line_ranges_for_c
   
                   $_ = $original;
                   s/^[^;{]*//;
+                } elsif (/\@end/) {
+                  $in_method_declaration = 0;
+                  $interface_name = "";
+                  $_ = $original;
                 } else {
                   next;
                 }
@@ -479,6 +568,10 @@ sub get_function_line_ranges_for_c
               $in_method_declaration = 0;
               $_ = $original;
               s/^[^{]*//;
+            } elsif (/\@end/) {
+              $in_method_declaration = 0;
+              $interface_name = "";
+              $_ = $original;
             } else {
               next;
             }
@@ -643,7 +736,7 @@ sub get_function_line_ranges_for_c
 #
 # Result is a list of triples: [ start_line, end_line, function_name ].
 
-sub get_function_line_ranges_for_java
+sub get_function_line_ranges_for_java($$)
   {
     my ($file_handle, $file_name) = @_;
 
@@ -852,3 +945,43 @@ sub get_function_line_ranges_for_java
 
     return @ranges;
   }
+
+sub processPaths(\@)
+  {
+    my ($paths) = @_;
+    return ("." => 1) if (!@{$paths});
+
+    my %result = ();
+
+    for my $file (@{$paths})
+      {
+        die "can't handle absolute paths like \"$file\"\n" if File::Spec->file_name_is_absolute($file);
+        die "can't handle empty string path\n" if $file eq "";
+        die "can't handle path with single quote in the name like \"$file\"\n" if $file =~ /'/; # ' (keep Xcode syntax highlighting happy)
+
+        my $untouchedFile = $file;
+
+        $file = canonicalizePath($file);
+
+        die "can't handle paths with .. like \"$untouchedFile\"\n" if $file =~ m|/\.\./|;
+
+        $result{$file} = 1;
+      }
+
+    return ("." => 1) if ($result{"."});
+
+    # Remove any paths that also have a parent listed.
+    for my $path (keys %result)
+      {
+        for (my $parent = dirname($path); $parent ne '.'; $parent = dirname($parent))
+         {
+            if ($result{$parent})
+              {
+                delete $result{$path};
+                last;
+              }
+          }
+      }
+
+    return %result;
+  }