diff options
Diffstat (limited to 'WebKitTools/Scripts/prepare-ChangeLog')
-rwxr-xr-x | WebKitTools/Scripts/prepare-ChangeLog | 1436 |
1 files changed, 0 insertions, 1436 deletions
diff --git a/WebKitTools/Scripts/prepare-ChangeLog b/WebKitTools/Scripts/prepare-ChangeLog deleted file mode 100755 index ff4ce80..0000000 --- a/WebKitTools/Scripts/prepare-ChangeLog +++ /dev/null @@ -1,1436 +0,0 @@ -#!/usr/bin/perl -w -# -*- Mode: perl; indent-tabs-mode: nil; c-basic-offset: 2 -*- - -# -# Copyright (C) 2000, 2001 Eazel, Inc. -# Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. -# -# prepare-ChangeLog is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# prepare-ChangeLog is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public -# License along with this program; if not, write to the Free -# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -# - - -# Perl script to create a ChangeLog entry with names of files -# and functions from a diff. -# -# Darin Adler <darin@bentspoon.com>, started 20 April 2000 -# Java support added by Maciej Stachowiak <mjs@eazel.com> -# Objective-C, C++ and Objective-C++ support added by Maciej Stachowiak <mjs@apple.com> -# Git support added by Adam Roben <aroben@apple.com> - - -# -# TODO: -# List functions that have been removed too. -# Decide what a good logical order is for the changed files -# other than a normal text "sort" (top level first?) -# (group directories?) (.h before .c?) -# Handle yacc source files too (other languages?). -# Help merge when there are ChangeLog conflicts or if there's -# already a partly written ChangeLog entry. -# Add command line option to put the ChangeLog into a separate -# file or just spew it out stdout. -# 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 -# has been changed (if the following function starts with a comment -# with the same first line, such as /**) -# Work around diff stupidity where deleting an entire function and -# the blank lines before it makes diff think you've changed the -# previous function. - -use strict; -use warnings; - -use File::Basename; -use File::Spec; -use FindBin; -use Getopt::Long; -use lib $FindBin::Bin; -use POSIX qw(strftime); -use VCSUtils; - -sub changeLogDate($); -sub firstDirectoryOrCwd(); -sub diffFromToString(); -sub diffCommand(@); -sub statusCommand(@); -sub createPatchCommand($); -sub diffHeaderFormat(); -sub findOriginalFileFromSvn($); -sub generateFileList(\@\@\%); -sub gitConfig($); -sub isModifiedStatus($); -sub isAddedStatus($); -sub isConflictStatus($); -sub statusDescription($$); -sub extractLineRange($); -sub canonicalizePath($); -sub testListForChangeLog(@); -sub get_function_line_ranges($$); -sub get_function_line_ranges_for_c($$); -sub get_function_line_ranges_for_java($$); -sub get_function_line_ranges_for_javascript($$); -sub method_decl_to_selector($); -sub processPaths(\@); -sub reviewerAndDescriptionForGitCommit($); - -# Project time zone for Cupertino, CA, US -my $changeLogTimeZone = "PST8PDT"; - -my $gitCommit = 0; -my $gitReviewer = ""; -my $openChangeLogs = 0; -my $showHelp = 0; -my $spewDiff = $ENV{"PREPARE_CHANGELOG_DIFF"}; -my $updateChangeLogs = 1; -my $parseOptionsResult = - GetOptions("diff|d!" => \$spewDiff, - "git-commit:s" => \$gitCommit, - "git-reviewer:s" => \$gitReviewer, - "help|h!" => \$showHelp, - "open|o!" => \$openChangeLogs, - "update!" => \$updateChangeLogs); -if (!$parseOptionsResult || $showHelp) { - print STDERR basename($0) . " [-d|--diff] [-h|--help] [-o|--open] [--git-commit=<committish>] [--git-reviewer=<name>] [svndir1 [svndir2 ...]]\n"; - print STDERR " -d|--diff Spew diff to stdout when running\n"; - print STDERR " --git-commit Populate the ChangeLogs from the specified git commit\n"; - print STDERR " --git-reviewer When populating the ChangeLogs from a git commit claim that the spcified name reviewed the change.\n"; - print STDERR " This option is useful when the git commit lacks a Signed-Off-By: line\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); - -my $isGit = isGitDirectory(firstDirectoryOrCwd()); -my $isSVN = isSVNDirectory(firstDirectoryOrCwd()); - -$isSVN || $isGit || die "Couldn't determine your version control system."; - -# Find the list of modified files -my @changed_files; -my $changed_files_string; -my %changed_line_ranges; -my %function_lists; -my @conflict_files; - -my $SVN = "svn"; -my $GIT = "git"; - -my %supportedTestExtensions = map { $_ => 1 } qw(html shtml svg xml xhtml pl php); -my @addedRegressionTests = (); -my $didChangeRegressionTests = 0; - -generateFileList(@changed_files, @conflict_files, %function_lists); - -if (!@changed_files && !@conflict_files && !keys %function_lists) { - print STDERR " No changes found.\n"; - exit 1; -} - -if (@conflict_files) { - print STDERR " The following files have conflicts. Run prepare-ChangeLog again after fixing the conflicts:\n"; - print STDERR join("\n", @conflict_files), "\n"; - exit 1; -} - -if (@changed_files) { - $changed_files_string = "'" . join ("' '", @changed_files) . "'"; - - # For each file, build a list of modified lines. - # Use line numbers from the "after" side of each diff. - print STDERR " Reviewing diff to determine which lines changed.\n"; - my $file; - open DIFF, "-|", diffCommand(@changed_files) or die "The diff failed: $!.\n"; - while (<DIFF>) { - $file = makeFilePathRelative($1) if $_ =~ diffHeaderFormat(); - if (defined $file) { - my ($start, $end) = extractLineRange($_); - if ($start >= 0 && $end >= 0) { - push @{$changed_line_ranges{$file}}, [ $start, $end ]; - } elsif (/DO_NOT_COMMIT/) { - print STDERR "WARNING: file $file contains the string DO_NOT_COMMIT, line $.\n"; - } - } - } - close DIFF; -} - -# For each source file, convert line range to function list. -if (%changed_line_ranges) { - print STDERR " Extracting affected function names from source files.\n"; - foreach my $file (keys %changed_line_ranges) { - # Only look for function names in certain source files. - next unless $file =~ /\.(c|cpp|m|mm|h|java|js)/; - - # Find all the functions in the file. - open SOURCE, $file or next; - my @function_ranges = get_function_line_ranges(\*SOURCE, $file); - close SOURCE; - - # Find all the modified functions. - my @functions; - my %saw_function; - my @change_ranges = (@{$changed_line_ranges{$file}}, []); - my @change_range = (0, 0); - FUNCTION: foreach my $function_range_ref (@function_ranges) { - my @function_range = @$function_range_ref; - - # Advance to successive change ranges. - for (;; @change_range = @{shift @change_ranges}) { - last FUNCTION unless @change_range; - - # If past this function, move on to the next one. - next FUNCTION if $change_range[0] > $function_range[1]; - - # If an overlap with this function range, record the function name. - if ($change_range[1] >= $function_range[0] - and $change_range[0] <= $function_range[1]) { - if (!$saw_function{$function_range[2]}) { - $saw_function{$function_range[2]} = 1; - push @functions, $function_range[2]; - } - next FUNCTION; - } - } - } - - # Format the list of functions now. - - if (@functions) { - $function_lists{$file} = "" if !defined $function_lists{$file}; - $function_lists{$file} .= "\n (" . join("):\n (", @functions) . "):"; - } - } -} - -# Get some parameters for the ChangeLog we are about to write. -my $date = changeLogDate($changeLogTimeZone); -my $name = $ENV{CHANGE_LOG_NAME} - || $ENV{REAL_NAME} - || gitConfig("user.name") - || (split /\s*,\s*/, (getpwuid $<)[6])[0] - || "set REAL_NAME environment variable"; -my $email_address = $ENV{CHANGE_LOG_EMAIL_ADDRESS} - || $ENV{EMAIL_ADDRESS} - || gitConfig("user.email") - || "set EMAIL_ADDRESS environment variable"; - -if ($gitCommit) { - $name = `$GIT log --max-count=1 --pretty=\"format:%an\" \"$gitCommit\"`; - $email_address = `$GIT log --max-count=1 --pretty=\"format:%ae\" \"$gitCommit\"`; -} - -# Remove trailing parenthesized notes from user name (bit of hack). -$name =~ s/\(.*?\)\s*$//g; - -# Find the change logs. -my %has_log; -my %files; -foreach my $file (sort keys %function_lists) { - my $prefix = $file; - my $has_log = 0; - while ($prefix) { - $prefix =~ s-/[^/]+/?$-/- or $prefix = ""; - $has_log = $has_log{$prefix}; - if (!defined $has_log) { - $has_log = -f "${prefix}ChangeLog"; - $has_log{$prefix} = $has_log; - } - last if $has_log; - } - if (!$has_log) { - print STDERR "No ChangeLog found for $file.\n"; - } else { - push @{$files{$prefix}}, $file; - } -} - -# Get the latest ChangeLog files from svn. -my @logs = (); -foreach my $prefix (sort keys %files) { - push @logs, File::Spec->catfile($prefix || ".", "ChangeLog"); -} - -if (@logs && $updateChangeLogs && $isSVN) { - print STDERR " Running 'svn update' to update ChangeLog files.\n"; - open ERRORS, "-|", $SVN, "update", @logs - or die "The svn update of ChangeLog files failed: $!.\n"; - my @conflictedChangeLogs; - while (my $line = <ERRORS>) { - print STDERR " ", $line; - push @conflictedChangeLogs, $1 if $line =~ m/^C\s+(.+)\s*$/; - } - close ERRORS; - - if (@conflictedChangeLogs) { - print STDERR " Attempting to merge conflicted ChangeLogs.\n"; - my $resolveChangeLogsPath = File::Spec->catfile(dirname($0), "resolve-ChangeLogs"); - open RESOLVE, "-|", $resolveChangeLogsPath, "--no-warnings", @conflictedChangeLogs - or die "Could not open resolve-ChangeLogs script: $!.\n"; - print STDERR " $_" while <RESOLVE>; - close RESOLVE; - } -} - -# Write out a new ChangeLog file. -foreach my $prefix (sort keys %files) { - my $changeLogPath = File::Spec->catfile($prefix || ".", "ChangeLog"); - print STDERR " Editing the ${changeLogPath} file.\n"; - open OLD_CHANGE_LOG, ${changeLogPath} or die "Could not open ${changeLogPath} file: $!.\n"; - # It's less efficient to read the whole thing into memory than it would be - # to read it while we prepend to it later, but I like doing this part first. - my @old_change_log = <OLD_CHANGE_LOG>; - close OLD_CHANGE_LOG; - open CHANGE_LOG, "> ${changeLogPath}" or die "Could not write ${changeLogPath}\n."; - print CHANGE_LOG "$date $name <$email_address>\n\n"; - - my ($reviewer, $description) = reviewerAndDescriptionForGitCommit($gitCommit) if $gitCommit; - $reviewer = "NOBODY (OO" . "PS!)" if !$reviewer; - - print CHANGE_LOG " Reviewed by $reviewer.\n\n"; - print CHANGE_LOG $description . "\n" if $description; - - if ($prefix =~ m/WebCore/ || `pwd` =~ m/WebCore/) { - if ($didChangeRegressionTests) { - print CHANGE_LOG testListForChangeLog(sort @addedRegressionTests); - } else { - print CHANGE_LOG " WARNING: NO TEST CASES ADDED OR CHANGED\n\n"; - } - } - - foreach my $file (sort @{$files{$prefix}}) { - my $file_stem = substr $file, length $prefix; - print CHANGE_LOG " * $file_stem:$function_lists{$file}\n"; - } - print CHANGE_LOG "\n", @old_change_log; - close CHANGE_LOG; -} - -# Write out another diff. -if ($spewDiff && @changed_files) { - print STDERR " Running diff to help you write the ChangeLog entries.\n"; - local $/ = undef; # local slurp mode - open DIFF, "-|", createPatchCommand($changed_files_string) or die "The diff failed: $!.\n"; - print <DIFF>; - close DIFF; -} - -# Open ChangeLogs. -if ($openChangeLogs && @logs) { - print STDERR " Opening the edited ChangeLog files.\n"; - my $editor = $ENV{"CHANGE_LOG_EDIT_APPLICATION"}; - if ($editor) { - system "open", "-a", $editor, @logs; - } else { - system "open", "-e", @logs; - } -} - -# Done. -exit; - -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 changeLogDate($) -{ - my ($timeZone) = @_; - my $savedTimeZone = $ENV{'TZ'}; - # Set TZ temporarily so that localtime() is in that time zone - $ENV{'TZ'} = $timeZone; - my $date = strftime("%Y-%m-%d", localtime()); - if (defined $savedTimeZone) { - $ENV{'TZ'} = $savedTimeZone; - } else { - delete $ENV{'TZ'}; - } - return $date; -} - -sub get_function_line_ranges($$) -{ - my ($file_handle, $file_name) = @_; - - if ($file_name =~ /\.(c|cpp|m|mm|h)$/) { - return get_function_line_ranges_for_c ($file_handle, $file_name); - } elsif ($file_name =~ /\.java$/) { - return get_function_line_ranges_for_java ($file_handle, $file_name); - } elsif ($file_name =~ /\.js$/) { - return get_function_line_ranges_for_javascript ($file_handle, $file_name); - } - return (); -} - - -sub method_decl_to_selector($) -{ - (my $method_decl) = @_; - - $_ = $method_decl; - - if ((my $comment_stripped) = m-([^/]*)(//|/*).*-) { - $_ = $comment_stripped; - } - - s/,\s*...//; - - if (/:/) { - my @components = split /:/; - pop @components if (scalar @components > 1); - $_ = (join ':', map {s/.*[^[:word:]]//; scalar $_;} @components) . ':'; - } else { - s/\s*$//; - s/.*[^[:word:]]//; - } - - return $_; -} - - - -# Read a file and get all the line ranges of the things that look like C functions. -# A function name is the last word before an open parenthesis before the outer -# level open brace. A function starts at the first character after the last close -# brace or semicolon before the function name and ends at the close brace. -# Comment handling is simple-minded but will work for all but pathological cases. -# -# Result is a list of triples: [ start_line, end_line, function_name ]. - -sub get_function_line_ranges_for_c($$) -{ - my ($file_handle, $file_name) = @_; - - my @ranges; - - my $in_comment = 0; - my $in_macro = 0; - my $in_method_declaration = 0; - my $in_parentheses = 0; - my $in_braces = 0; - my $brace_start = 0; - my $brace_end = 0; - my $skip_til_brace_or_semicolon = 0; - - my $word = ""; - my $interface_name = ""; - - my $potential_method_char = ""; - my $potential_method_spec = ""; - - my $potential_start = 0; - my $potential_name = ""; - - my $start = 0; - my $name = ""; - - my $next_word_could_be_namespace = 0; - my $potential_namespace = ""; - my @namespaces; - - while (<$file_handle>) { - # Handle continued multi-line comment. - if ($in_comment) { - next unless s-.*\*/--; - $in_comment = 0; - } - - # Handle continued macro. - if ($in_macro) { - $in_macro = 0 unless /\\$/; - next; - } - - # Handle start of macro (or any preprocessor directive). - if (/^\s*\#/) { - $in_macro = 1 if /^([^\\]|\\.)*\\$/; - next; - } - - # Handle comments and quoted text. - while (m-(/\*|//|\'|\")-) { # \' and \" keep emacs perl mode happy - my $match = $1; - if ($match eq "/*") { - if (!s-/\*.*?\*/--) { - s-/\*.*--; - $in_comment = 1; - } - } elsif ($match eq "//") { - s-//.*--; - } else { # ' or " - if (!s-$match([^\\]|\\.)*?$match--) { - warn "mismatched quotes at line $. in $file_name\n"; - s-$match.*--; - } - } - } - - - # continued method declaration - if ($in_method_declaration) { - my $original = $_; - my $method_cont = $_; - - chomp $method_cont; - $method_cont =~ s/[;\{].*//; - $potential_method_spec = "${potential_method_spec} ${method_cont}"; - - $_ = $original; - if (/;/) { - $potential_start = 0; - $potential_method_spec = ""; - $potential_method_char = ""; - $in_method_declaration = 0; - s/^[^;\{]*//; - } elsif (/{/) { - my $selector = method_decl_to_selector ($potential_method_spec); - $potential_name = "${potential_method_char}\[${interface_name} ${selector}\]"; - - $potential_method_spec = ""; - $potential_method_char = ""; - $in_method_declaration = 0; - - $_ = $original; - s/^[^;{]*//; - } elsif (/\@end/) { - $in_method_declaration = 0; - $interface_name = ""; - $_ = $original; - } else { - next; - } - } - - - # start of method declaration - if ((my $method_char, my $method_spec) = m&^([-+])([^0-9;][^;]*);?$&) { - my $original = $_; - - if ($interface_name) { - chomp $method_spec; - $method_spec =~ s/\{.*//; - - $potential_method_char = $method_char; - $potential_method_spec = $method_spec; - $potential_start = $.; - $in_method_declaration = 1; - } else { - warn "declaring a method but don't have interface on line $. in $file_name\n"; - } - $_ = $original; - if (/\{/) { - my $selector = method_decl_to_selector ($potential_method_spec); - $potential_name = "${potential_method_char}\[${interface_name} ${selector}\]"; - - $potential_method_spec = ""; - $potential_method_char = ""; - $in_method_declaration = 0; - $_ = $original; - s/^[^{]*//; - } elsif (/\@end/) { - $in_method_declaration = 0; - $interface_name = ""; - $_ = $original; - } else { - next; - } - } - - - # Find function, interface and method names. - while (m&((?:[[:word:]]+::)*operator(?:[ \t]*\(\)|[^()]*)|[[:word:]:~]+|[(){}:;])|\@(?:implementation|interface|protocol)\s+(\w+)[^{]*&g) { - # interface name - if ($2) { - $interface_name = $2; - next; - } - - # Open parenthesis. - if ($1 eq "(") { - $potential_name = $word unless $in_parentheses || $skip_til_brace_or_semicolon; - $in_parentheses++; - next; - } - - # Close parenthesis. - if ($1 eq ")") { - $in_parentheses--; - next; - } - - # C++ constructor initializers - if ($1 eq ":") { - $skip_til_brace_or_semicolon = 1 unless ($in_parentheses || $in_braces); - } - - # Open brace. - if ($1 eq "{") { - $skip_til_brace_or_semicolon = 0; - - if ($potential_namespace) { - push @namespaces, $potential_namespace; - $potential_namespace = ""; - next; - } - - # Promote potential name to real function name at the - # start of the outer level set of braces (function body?). - if (!$in_braces and $potential_start) { - $start = $potential_start; - $name = $potential_name; - if (@namespaces && (length($name) < 2 || substr($name,1,1) ne "[")) { - $name = join ('::', @namespaces, $name); - } - } - - $in_method_declaration = 0; - - $brace_start = $. if (!$in_braces); - $in_braces++; - next; - } - - # Close brace. - if ($1 eq "}") { - if (!$in_braces && @namespaces) { - pop @namespaces; - next; - } - - $in_braces--; - $brace_end = $. if (!$in_braces); - - # End of an outer level set of braces. - # This could be a function body. - if (!$in_braces and $name) { - push @ranges, [ $start, $., $name ]; - $name = ""; - } - - $potential_start = 0; - $potential_name = ""; - next; - } - - # Semicolon. - if ($1 eq ";") { - $skip_til_brace_or_semicolon = 0; - $potential_start = 0; - $potential_name = ""; - $in_method_declaration = 0; - next; - } - - # Ignore "const" method qualifier. - if ($1 eq "const") { - next; - } - - if ($1 eq "namespace" || $1 eq "class" || $1 eq "struct") { - $next_word_could_be_namespace = 1; - next; - } - - # Word. - $word = $1; - if (!$skip_til_brace_or_semicolon) { - if ($next_word_could_be_namespace) { - $potential_namespace = $word; - $next_word_could_be_namespace = 0; - } elsif ($potential_namespace) { - $potential_namespace = ""; - } - - if (!$in_parentheses) { - $potential_start = 0; - $potential_name = ""; - } - if (!$potential_start) { - $potential_start = $.; - $potential_name = ""; - } - } - } - } - - warn "missing close braces in $file_name (probable start at $brace_start)\n" if ($in_braces > 0); - warn "too many close braces in $file_name (probable start at $brace_end)\n" if ($in_braces < 0); - - warn "mismatched parentheses in $file_name\n" if $in_parentheses; - - return @ranges; -} - - - -# Read a file and get all the line ranges of the things that look like Java -# classes, interfaces and methods. -# -# A class or interface name is the word that immediately follows -# `class' or `interface' when followed by an open curly brace and not -# a semicolon. It can appear at the top level, or inside another class -# or interface block, but not inside a function block -# -# A class or interface starts at the first character after the first close -# brace or after the function name and ends at the close brace. -# -# A function name is the last word before an open parenthesis before -# an open brace rather than a semicolon. It can appear at top level or -# inside a class or interface block, but not inside a function block. -# -# A function starts at the first character after the first close -# brace or after the function name and ends at the close brace. -# -# Comment handling is simple-minded but will work for all but pathological cases. -# -# Result is a list of triples: [ start_line, end_line, function_name ]. - -sub get_function_line_ranges_for_java($$) -{ - my ($file_handle, $file_name) = @_; - - my @current_scopes; - - my @ranges; - - my $in_comment = 0; - my $in_macro = 0; - my $in_parentheses = 0; - my $in_braces = 0; - my $in_non_block_braces = 0; - my $class_or_interface_just_seen = 0; - - my $word = ""; - - my $potential_start = 0; - my $potential_name = ""; - my $potential_name_is_class_or_interface = 0; - - my $start = 0; - my $name = ""; - my $current_name_is_class_or_interface = 0; - - while (<$file_handle>) { - # Handle continued multi-line comment. - if ($in_comment) { - next unless s-.*\*/--; - $in_comment = 0; - } - - # Handle continued macro. - if ($in_macro) { - $in_macro = 0 unless /\\$/; - next; - } - - # Handle start of macro (or any preprocessor directive). - if (/^\s*\#/) { - $in_macro = 1 if /^([^\\]|\\.)*\\$/; - next; - } - - # Handle comments and quoted text. - while (m-(/\*|//|\'|\")-) { # \' and \" keep emacs perl mode happy - my $match = $1; - if ($match eq "/*") { - if (!s-/\*.*?\*/--) { - s-/\*.*--; - $in_comment = 1; - } - } elsif ($match eq "//") { - s-//.*--; - } else { # ' or " - if (!s-$match([^\\]|\\.)*?$match--) { - warn "mismatched quotes at line $. in $file_name\n"; - s-$match.*--; - } - } - } - - # Find function names. - while (m-(\w+|[(){};])-g) { - # Open parenthesis. - if ($1 eq "(") { - if (!$in_parentheses) { - $potential_name = $word; - $potential_name_is_class_or_interface = 0; - } - $in_parentheses++; - next; - } - - # Close parenthesis. - if ($1 eq ")") { - $in_parentheses--; - next; - } - - # Open brace. - if ($1 eq "{") { - # Promote potential name to real function name at the - # start of the outer level set of braces (function/class/interface body?). - if (!$in_non_block_braces - and (!$in_braces or $current_name_is_class_or_interface) - and $potential_start) { - if ($name) { - push @ranges, [ $start, ($. - 1), - join ('.', @current_scopes) ]; - } - - - $current_name_is_class_or_interface = $potential_name_is_class_or_interface; - - $start = $potential_start; - $name = $potential_name; - - push (@current_scopes, $name); - } else { - $in_non_block_braces++; - } - - $potential_name = ""; - $potential_start = 0; - - $in_braces++; - next; - } - - # Close brace. - if ($1 eq "}") { - $in_braces--; - - # End of an outer level set of braces. - # This could be a function body. - if (!$in_non_block_braces) { - if ($name) { - push @ranges, [ $start, $., - join ('.', @current_scopes) ]; - - pop (@current_scopes); - - if (@current_scopes) { - $current_name_is_class_or_interface = 1; - - $start = $. + 1; - $name = $current_scopes[$#current_scopes-1]; - } else { - $current_name_is_class_or_interface = 0; - $start = 0; - $name = ""; - } - } - } else { - $in_non_block_braces-- if $in_non_block_braces; - } - - $potential_start = 0; - $potential_name = ""; - next; - } - - # Semicolon. - if ($1 eq ";") { - $potential_start = 0; - $potential_name = ""; - next; - } - - if ($1 eq "class" or $1 eq "interface") { - $class_or_interface_just_seen = 1; - next; - } - - # Word. - $word = $1; - if (!$in_parentheses) { - if ($class_or_interface_just_seen) { - $potential_name = $word; - $potential_start = $.; - $class_or_interface_just_seen = 0; - $potential_name_is_class_or_interface = 1; - next; - } - } - if (!$potential_start) { - $potential_start = $.; - $potential_name = ""; - } - $class_or_interface_just_seen = 0; - } - } - - warn "mismatched braces in $file_name\n" if $in_braces; - warn "mismatched parentheses in $file_name\n" if $in_parentheses; - - return @ranges; -} - - - -# Read a file and get all the line ranges of the things that look like -# JavaScript functions. -# -# A function name is the word that immediately follows `function' when -# followed by an open curly brace. It can appear at the top level, or -# inside other functions. -# -# An anonymous function name is the identifier chain immediately before -# an assignment with the equals operator or object notation that has a -# value starting with `function' followed by an open curly brace. -# -# A getter or setter name is the word that immediately follows `get' or -# `set' when followed by an open curly brace . -# -# Comment handling is simple-minded but will work for all but pathological cases. -# -# Result is a list of triples: [ start_line, end_line, function_name ]. - -sub get_function_line_ranges_for_javascript($$) -{ - my ($fileHandle, $fileName) = @_; - - my @currentScopes; - my @currentIdentifiers; - my @currentFunctionNames; - my @currentFunctionDepths; - my @currentFunctionStartLines; - - my @ranges; - - my $inComment = 0; - my $parenthesesDepth = 0; - my $bracesDepth = 0; - - my $functionJustSeen = 0; - my $getterJustSeen = 0; - my $setterJustSeen = 0; - my $assignmentJustSeen = 0; - - my $word = ""; - - while (<$fileHandle>) { - # Handle continued multi-line comment. - if ($inComment) { - next unless s-.*\*/--; - $inComment = 0; - } - - # Handle comments and quoted text. - while (m-(/\*|//|\'|\")-) { # \' and \" keep emacs perl mode happy - my $match = $1; - if ($match eq '/*') { - if (!s-/\*.*?\*/--) { - s-/\*.*--; - $inComment = 1; - } - } elsif ($match eq '//') { - s-//.*--; - } else { # ' or " - if (!s-$match([^\\]|\\.)*?$match--) { - warn "mismatched quotes at line $. in $fileName\n"; - s-$match.*--; - } - } - } - - # Find function names. - while (m-(\w+|[(){}=:;])-g) { - # Open parenthesis. - if ($1 eq '(') { - $parenthesesDepth++; - next; - } - - # Close parenthesis. - if ($1 eq ')') { - $parenthesesDepth--; - next; - } - - # Open brace. - if ($1 eq '{') { - push(@currentScopes, join(".", @currentIdentifiers)); - @currentIdentifiers = (); - - $bracesDepth++; - next; - } - - # Close brace. - if ($1 eq '}') { - $bracesDepth--; - - if (@currentFunctionDepths and $bracesDepth == $currentFunctionDepths[$#currentFunctionDepths]) { - pop(@currentFunctionDepths); - - my $currentFunction = pop(@currentFunctionNames); - my $start = pop(@currentFunctionStartLines); - - push(@ranges, [$start, $., $currentFunction]); - } - - pop(@currentScopes); - @currentIdentifiers = (); - - next; - } - - # Semicolon. - if ($1 eq ';') { - @currentIdentifiers = (); - next; - } - - # Function. - if ($1 eq 'function') { - $functionJustSeen = 1; - - if ($assignmentJustSeen) { - my $currentFunction = join('.', (@currentScopes, @currentIdentifiers)); - $currentFunction =~ s/\.{2,}/\./g; # Removes consecutive periods. - - push(@currentFunctionNames, $currentFunction); - push(@currentFunctionDepths, $bracesDepth); - push(@currentFunctionStartLines, $.); - } - - next; - } - - # Getter prefix. - if ($1 eq 'get') { - $getterJustSeen = 1; - next; - } - - # Setter prefix. - if ($1 eq 'set') { - $setterJustSeen = 1; - next; - } - - # Assignment operator. - if ($1 eq '=' or $1 eq ':') { - $assignmentJustSeen = 1; - next; - } - - next if $parenthesesDepth; - - # Word. - $word = $1; - $word = "get $word" if $getterJustSeen; - $word = "set $word" if $setterJustSeen; - - if (($functionJustSeen and !$assignmentJustSeen) or $getterJustSeen or $setterJustSeen) { - push(@currentIdentifiers, $word); - - my $currentFunction = join('.', (@currentScopes, @currentIdentifiers)); - $currentFunction =~ s/\.{2,}/\./g; # Removes consecutive periods. - - push(@currentFunctionNames, $currentFunction); - push(@currentFunctionDepths, $bracesDepth); - push(@currentFunctionStartLines, $.); - } elsif ($word ne 'if' and $word ne 'for' and $word ne 'do' and $word ne 'while' and $word ne 'which' and $word ne 'var') { - push(@currentIdentifiers, $word); - } - - $functionJustSeen = 0; - $getterJustSeen = 0; - $setterJustSeen = 0; - $assignmentJustSeen = 0; - } - } - - warn "mismatched braces in $fileName\n" if $bracesDepth; - warn "mismatched parentheses in $fileName\n" if $parenthesesDepth; - - 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; -} - -sub diffFromToString() -{ - return "" if $isSVN; - return $gitCommit if $gitCommit =~ m/.+\.\..+/; - return "\"$gitCommit^\" \"$gitCommit\"" if $gitCommit; - return "HEAD" if $isGit; -} - -sub diffCommand(@) -{ - my @paths = @_; - - my $pathsString = "'" . join("' '", @paths) . "'"; - - my $command; - if ($isSVN) { - $command = "$SVN diff --diff-cmd diff -x -N $pathsString"; - } elsif ($isGit) { - $command = "$GIT diff -U0 " . diffFromToString(); - $command .= " -- $pathsString" unless $gitCommit; - } - - return $command; -} - -sub statusCommand(@) -{ - my @files = @_; - - my $filesString = "'" . join ("' '", @files) . "'"; - my $command; - if ($isSVN) { - $command = "$SVN stat $filesString"; - } elsif ($isGit) { - $command = "$GIT diff -r --name-status -C -C -M " . diffFromToString(); - $command .= " -- $filesString" unless $gitCommit; - } - - return "$command 2>&1"; -} - -sub createPatchCommand($) -{ - my ($changedFilesString) = @_; - - my $command; - if ($isSVN) { - $command = "'$FindBin::Bin/svn-create-patch' $changedFilesString"; - } elsif ($isGit) { - $command = "$GIT diff -C -C -M " . diffFromToString(); - $command .= " -- $changedFilesString" unless $gitCommit; - } - - return $command; -} - -sub diffHeaderFormat() -{ - return qr/^Index: (\S+)$/ if $isSVN; - return qr/^diff --git a\/.+ b\/(.+)$/ if $isGit; -} - -sub findOriginalFileFromSvn($) -{ - my ($file) = @_; - my $baseUrl; - open INFO, "$SVN info . |" or die; - while (<INFO>) { - if (/^URL: (.+)/) { - $baseUrl = $1; - last; - } - } - close INFO; - my $sourceFile; - open INFO, "$SVN info '$file' |" or die; - while (<INFO>) { - if (/^Copied From URL: (.+)/) { - $sourceFile = File::Spec->abs2rel($1, $baseUrl); - last; - } - } - close INFO; - return $sourceFile; -} - -sub generateFileList(\@\@\%) -{ - my ($changedFiles, $conflictFiles, $functionLists) = @_; - print STDERR " Running status to find changed, added, or removed files.\n"; - open STAT, "-|", statusCommand(keys %paths) or die "The status failed: $!.\n"; - my $inGitCommitSection = 0; - while (<STAT>) { - my $status; - my $original; - my $file; - - if ($isSVN) { - if (/^([ACDMR]).{5} (.+)$/) { - $status = $1; - $file = $2; - $original = findOriginalFileFromSvn($file) if substr($_, 3, 1) eq "+"; - } else { - print; # error output from svn stat - } - } elsif ($isGit) { - if (/^([ADM])\t(.+)$/) { - $status = $1; - $file = $2; - } elsif (/^([CR])[0-9]{1,3}\t([^\t]+)\t([^\t\n]+)$/) { # for example: R90% newfile oldfile - $status = $1; - $original = $2; - $file = $3; - } else { - print; # error output from git diff - } - } - - next unless $status; - - $file = makeFilePathRelative($file); - - if (isModifiedStatus($status) || isAddedStatus($status)) { - my @components = File::Spec->splitdir($file); - if ($components[0] eq "LayoutTests") { - $didChangeRegressionTests = 1; - push @addedRegressionTests, $file - if isAddedStatus($status) - && $file =~ /\.([a-zA-Z]+)$/ - && $supportedTestExtensions{lc($1)} - && !scalar(grep(/^resources$/i, @components)); - } - push @{$changedFiles}, $file if $components[$#components] ne "ChangeLog"; - } elsif (isConflictStatus($status)) { - push @{$conflictFiles}, $file; - } - if (basename($file) ne "ChangeLog") { - my $description = statusDescription($status, $original); - $functionLists->{$file} = $description if defined $description; - } - } - close STAT; -} - -sub gitConfig($) -{ - return unless $isGit; - - my ($config) = @_; - - my $result = `$GIT config $config`; - if (($? >> 8) != 0) { - $result = `$GIT repo-config $config`; - } - chomp $result; - return $result; -} - -sub isModifiedStatus($) -{ - my ($status) = @_; - - my %statusCodes = ( - "M" => 1, - ); - - return $statusCodes{$status}; -} - -sub isAddedStatus($) -{ - my ($status) = @_; - - my %statusCodes = ( - "A" => 1, - "C" => $isGit, - "R" => 1, - ); - - return $statusCodes{$status}; -} - -sub isConflictStatus($) -{ - my ($status) = @_; - - my %svn = ( - "C" => 1, - ); - - my %git = ( - "U" => 1, - ); - - return 0 if $gitCommit; # an existing commit cannot have conflicts - return $svn{$status} if $isSVN; - return $git{$status} if $isGit; -} - -sub statusDescription($$) -{ - my ($status, $original) = @_; - - my %svn = ( - "A" => defined $original ? " Copied from \%s." : " Added.", - "D" => " Removed.", - "M" => "", - "R" => defined $original ? " Replaced with \%s." : " Replaced.", - ); - - my %git = %svn; - $git{"A"} = " Added."; - $git{"C"} = " Copied from \%s."; - $git{"R"} = " Renamed from \%s."; - - return sprintf($svn{$status}, $original) if $isSVN && exists $svn{$status}; - return sprintf($git{$status}, $original) if $isGit && exists $git{$status}; - return undef; -} - -sub extractLineRange($) -{ - my ($string) = @_; - - my ($start, $end) = (-1, -1); - - if ($isSVN && $string =~ /^\d+(,\d+)?[acd](\d+)(,(\d+))?/) { - $start = $2; - $end = $4 || $2; - } elsif ($isGit && $string =~ /^@@ -\d+(,\d+)? \+(\d+)(,(\d+))? @@/) { - $start = $2; - $end = defined($4) ? $4 + $2 - 1 : $2; - } - - return ($start, $end); -} - -sub firstDirectoryOrCwd() -{ - my $dir = "."; - my @dirs = keys(%paths); - - $dir = -d $dirs[0] ? $dirs[0] : dirname($dirs[0]) if @dirs; - - return $dir; -} - -sub testListForChangeLog(@) -{ - my (@tests) = @_; - - return "" unless @tests; - - my $leadString = " Test" . (@tests == 1 ? "" : "s") . ": "; - my $list = $leadString; - foreach my $i (0..$#tests) { - $list .= " " x length($leadString) if $i; - my $test = $tests[$i]; - $test =~ s/^LayoutTests\///; - $list .= "$test\n"; - } - $list .= "\n"; - - return $list; -} - -sub reviewerAndDescriptionForGitCommit($) -{ - my ($commit) = @_; - - my $description = ''; - my $reviewer; - - my @args = qw(rev-list --pretty); - push @args, '-1' if $commit !~ m/.+\.\..+/; - my $gitLog; - { - local $/ = undef; - open(GIT, "-|", $GIT, @args, $commit) || die; - $gitLog = <GIT>; - close(GIT); - } - - my @commitLogs = split(/^[Cc]ommit [a-f0-9]{40}/m, $gitLog); - shift @commitLogs; # Remove initial blank commit log - my $commitLogCount = 0; - foreach my $commitLog (@commitLogs) { - $description .= "\n" if $commitLogCount; - $commitLogCount++; - my $inHeader = 1; - my @lines = split(/\n/, $commitLog); - shift @lines; # Remove initial blank line - foreach my $line (@lines) { - if ($inHeader) { - if (!$line) { - $inHeader = 0; - } - next; - } elsif ($line =~ /[Ss]igned-[Oo]ff-[Bb]y: (.+)/) { - if (!$reviewer) { - $reviewer = $1; - } else { - $reviewer .= ", " . $1; - } - } elsif (length $line == 0) { - $description = $description . "\n"; - } else { - $line =~ s/^\s*//; - $description = $description . " " . $line . "\n"; - } - } - } - if (!$reviewer) { - $reviewer = $gitReviewer; - } - - return ($reviewer, $description); -} |