blob: 288eaa07dfef5e7ba4bd0a1abc66cfee1685cfc9 [file] [log] [blame]
mjsb85f64e2005-06-17 07:49:31 +00001#!/usr/bin/perl -w
2# -*- Mode: perl; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3
4#
5# Copyright (C) 2000, 2001 Eazel, Inc.
ddkilzere149e7b72007-10-25 03:50:17 +00006# Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
treat@webkit.org1c86c8e2009-07-23 16:32:44 +00007# Copyright (C) 2009 Torch Mobile, Inc.
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00008# Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
mjsb85f64e2005-06-17 07:49:31 +00009#
10# prepare-ChangeLog is free software; you can redistribute it and/or
11# modify it under the terms of the GNU General Public
12# License as published by the Free Software Foundation; either
13# version 2 of the License, or (at your option) any later version.
14#
15# prepare-ChangeLog is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18# General Public License for more details.
19#
20# You should have received a copy of the GNU General Public
21# License along with this program; if not, write to the Free
22# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23#
24
25
26# Perl script to create a ChangeLog entry with names of files
thatcherbefc69c2006-01-08 04:38:04 +000027# and functions from a diff.
mjsb85f64e2005-06-17 07:49:31 +000028#
29# Darin Adler <darin@bentspoon.com>, started 20 April 2000
30# Java support added by Maciej Stachowiak <mjs@eazel.com>
31# Objective-C, C++ and Objective-C++ support added by Maciej Stachowiak <mjs@apple.com>
aroben363e7842007-05-15 23:35:59 +000032# Git support added by Adam Roben <aroben@apple.com>
treat@webkit.orgcaf90532009-06-23 23:10:55 +000033# --git-index flag added by Joe Mason <joe.mason@torchmobile.com>
mjsb85f64e2005-06-17 07:49:31 +000034
35
36#
37# TODO:
38# List functions that have been removed too.
39# Decide what a good logical order is for the changed files
40# other than a normal text "sort" (top level first?)
41# (group directories?) (.h before .c?)
42# Handle yacc source files too (other languages?).
43# Help merge when there are ChangeLog conflicts or if there's
44# already a partly written ChangeLog entry.
treat@webkit.org1c86c8e2009-07-23 16:32:44 +000045# Add command line option to put the ChangeLog into a separate file.
thatcherbefc69c2006-01-08 04:38:04 +000046# Add SVN version numbers for commit (can't do that until
mjsb85f64e2005-06-17 07:49:31 +000047# the changes are checked in, though).
48# Work around diff stupidity where deleting a function that starts
49# with a comment makes diff think that the following function
50# has been changed (if the following function starts with a comment
51# with the same first line, such as /**)
52# Work around diff stupidity where deleting an entire function and
53# the blank lines before it makes diff think you've changed the
54# previous function.
55
56use strict;
ddkilzer0507c8f2006-06-11 23:49:28 +000057use warnings;
mjsb85f64e2005-06-17 07:49:31 +000058
ddkilzer0507c8f2006-06-11 23:49:28 +000059use File::Basename;
60use File::Spec;
aroben8cc9bc62007-04-20 22:33:36 +000061use FindBin;
mjsb85f64e2005-06-17 07:49:31 +000062use Getopt::Long;
arobenc3e1ec22007-07-19 17:23:54 +000063use lib $FindBin::Bin;
ddkilzer1f0d4f02007-08-26 13:39:57 +000064use POSIX qw(strftime);
arobenc3e1ec22007-07-19 17:23:54 +000065use VCSUtils;
mjsb85f64e2005-06-17 07:49:31 +000066
ddkilzer1f0d4f02007-08-26 13:39:57 +000067sub changeLogDate($);
mrowe@apple.com264efc42009-11-08 03:59:28 +000068sub changeLogEmailAddressFromArgs($);
69sub changeLogNameFromArgs($);
haraken@chromium.org7daa6722011-12-09 23:26:42 +000070sub fetchBugDescriptionFromURL($);
arobenc3e1ec22007-07-19 17:23:54 +000071sub firstDirectoryOrCwd();
aroben84c7c6a2007-07-19 01:41:03 +000072sub diffFromToString();
ddkilzer33ab5032007-05-28 20:48:56 +000073sub diffCommand(@);
74sub statusCommand(@);
aroben363e7842007-05-15 23:35:59 +000075sub createPatchCommand($);
76sub diffHeaderFormat();
ddkilzer33ab5032007-05-28 20:48:56 +000077sub findOriginalFileFromSvn($);
eric@webkit.orgec02a6f2009-08-27 06:38:22 +000078sub determinePropertyChanges($$$);
79sub pluralizeAndList($$@);
ddkilzer33ab5032007-05-28 20:48:56 +000080sub generateFileList(\@\@\%);
eric@webkit.orgec02a6f2009-08-27 06:38:22 +000081sub isUnmodifiedStatus($);
aroben762ddbf2007-08-20 21:41:29 +000082sub isModifiedStatus($);
83sub isAddedStatus($);
aroben363e7842007-05-15 23:35:59 +000084sub isConflictStatus($);
eric@webkit.orgec02a6f2009-08-27 06:38:22 +000085sub statusDescription($$$$);
86sub propertyChangeDescription($);
aroben363e7842007-05-15 23:35:59 +000087sub extractLineRange($);
aroben762ddbf2007-08-20 21:41:29 +000088sub testListForChangeLog(@);
ddkilzerb7b3b912006-06-25 06:01:41 +000089sub get_function_line_ranges($$);
90sub get_function_line_ranges_for_c($$);
91sub get_function_line_ranges_for_java($$);
timothy@apple.com31f57e72008-10-14 19:17:08 +000092sub get_function_line_ranges_for_javascript($$);
aroben@apple.comd4acad02011-07-05 22:20:53 +000093sub get_function_line_ranges_for_perl($$);
aroben@apple.comec7621d2010-03-12 19:06:01 +000094sub get_selector_line_ranges_for_css($$);
ddkilzerb7b3b912006-06-25 06:01:41 +000095sub method_decl_to_selector($);
96sub processPaths(\@);
ddkilzer3b890b42007-10-02 20:38:36 +000097sub reviewerAndDescriptionForGitCommit($);
abarth@webkit.orgc93f2152009-06-16 08:06:58 +000098sub normalizeLineEndings($$);
levin@chromium.orga75fb0b2009-07-13 09:36:59 +000099sub decodeEntities($);
ddkilzerb7b3b912006-06-25 06:01:41 +0000100
ddkilzer1f0d4f02007-08-26 13:39:57 +0000101# Project time zone for Cupertino, CA, US
102my $changeLogTimeZone = "PST8PDT";
103
ojan@chromium.org7ad72992011-03-03 08:02:02 +0000104my $bugDescription;
eric@webkit.org97716132009-07-02 05:13:41 +0000105my $bugNumber;
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000106my $name;
107my $emailAddress;
ojan@chromium.org03d32662010-04-05 17:55:14 +0000108my $mergeBase = 0;
hausmann46f26492007-06-23 08:49:16 +0000109my $gitCommit = 0;
treat@webkit.orgcaf90532009-06-23 23:10:55 +0000110my $gitIndex = "";
hausmann46f26492007-06-23 08:49:16 +0000111my $gitReviewer = "";
mjsb85f64e2005-06-17 07:49:31 +0000112my $openChangeLogs = 0;
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000113my $writeChangeLogs = 1;
ddkilzer0507c8f2006-06-11 23:49:28 +0000114my $showHelp = 0;
115my $spewDiff = $ENV{"PREPARE_CHANGELOG_DIFF"};
hausmann46f26492007-06-23 08:49:16 +0000116my $updateChangeLogs = 1;
ddkilzer0507c8f2006-06-11 23:49:28 +0000117my $parseOptionsResult =
118 GetOptions("diff|d!" => \$spewDiff,
joepeck@webkit.orgc7bc5402010-12-11 02:02:47 +0000119 "bug|b:i" => \$bugNumber,
ojan@chromium.org7ad72992011-03-03 08:02:02 +0000120 "description:s" => \$bugDescription,
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000121 "name:s" => \$name,
122 "email:s" => \$emailAddress,
ojan@chromium.org03d32662010-04-05 17:55:14 +0000123 "merge-base:s" => \$mergeBase,
mrobinson@webkit.orgc3485182010-12-29 18:03:20 +0000124 "git-commit|g:s" => \$gitCommit,
treat@webkit.orgcaf90532009-06-23 23:10:55 +0000125 "git-index" => \$gitIndex,
hausmann46f26492007-06-23 08:49:16 +0000126 "git-reviewer:s" => \$gitReviewer,
ddkilzer0507c8f2006-06-11 23:49:28 +0000127 "help|h!" => \$showHelp,
ddkilzer322be072006-07-19 02:44:15 +0000128 "open|o!" => \$openChangeLogs,
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000129 "write!" => \$writeChangeLogs,
ddkilzer322be072006-07-19 02:44:15 +0000130 "update!" => \$updateChangeLogs);
aroben38e341c2007-04-28 06:57:49 +0000131if (!$parseOptionsResult || $showHelp) {
mrobinson@webkit.orgc3485182010-12-29 18:03:20 +0000132 print STDERR basename($0) . " [-b|--bug=<bugid>] [-d|--diff] [-h|--help] [-o|--open] [-g|--git-commit=<committish>] [--git-reviewer=<name>] [svndir1 [svndir2 ...]]\n";
133 print STDERR " -b|--bug Fill in the ChangeLog bug information from the given bug.\n";
ojan@chromium.org7ad72992011-03-03 08:02:02 +0000134 print STDERR " --description One-line description that matches the bug title.\n";
mrobinson@webkit.orgc3485182010-12-29 18:03:20 +0000135 print STDERR " -d|--diff Spew diff to stdout when running\n";
136 print STDERR " --merge-base Populate the ChangeLogs with the diff to this branch\n";
137 print STDERR " -g|--git-commit Populate the ChangeLogs from the specified git commit\n";
138 print STDERR " --git-index Populate the ChangeLogs from the git index only\n";
139 print STDERR " --git-reviewer When populating the ChangeLogs from a git commit claim that the spcified name reviewed the change.\n";
140 print STDERR " This option is useful when the git commit lacks a Signed-Off-By: line\n";
141 print STDERR " -h|--help Show this help message\n";
142 print STDERR " -o|--open Open ChangeLogs in an editor when done\n";
143 print STDERR " --[no-]update Update ChangeLogs from svn before adding entry (default: update)\n";
144 print STDERR " --[no-]write Write ChangeLogs to disk (otherwise send new entries to stdout) (default: write)\n";
commit-queue@webkit.orge766e9e2011-04-12 01:13:17 +0000145 print STDERR " --email= Specify the email address to be used in the patch\n";
ddkilzer0507c8f2006-06-11 23:49:28 +0000146 exit 1;
aroben38e341c2007-04-28 06:57:49 +0000147}
mjsb85f64e2005-06-17 07:49:31 +0000148
treat@webkit.orgcaf90532009-06-23 23:10:55 +0000149die "--git-commit and --git-index are incompatible." if ($gitIndex && $gitCommit);
150
ddkilzerb7b3b912006-06-25 06:01:41 +0000151my %paths = processPaths(@ARGV);
152
arobenc3e1ec22007-07-19 17:23:54 +0000153my $isGit = isGitDirectory(firstDirectoryOrCwd());
154my $isSVN = isSVNDirectory(firstDirectoryOrCwd());
155
156$isSVN || $isGit || die "Couldn't determine your version control system.";
aroben363e7842007-05-15 23:35:59 +0000157
weinig@apple.com5e65e9f52009-03-27 23:15:01 +0000158my $SVN = "svn";
159my $GIT = "git";
160
mjsb85f64e2005-06-17 07:49:31 +0000161# Find the list of modified files
162my @changed_files;
163my $changed_files_string;
164my %changed_line_ranges;
165my %function_lists;
166my @conflict_files;
167
ddkilzer0507c8f2006-06-11 23:49:28 +0000168
aroben762ddbf2007-08-20 21:41:29 +0000169my %supportedTestExtensions = map { $_ => 1 } qw(html shtml svg xml xhtml pl php);
170my @addedRegressionTests = ();
171my $didChangeRegressionTests = 0;
ddkilzer0507c8f2006-06-11 23:49:28 +0000172
ddkilzer33ab5032007-05-28 20:48:56 +0000173generateFileList(@changed_files, @conflict_files, %function_lists);
ddkilzer0507c8f2006-06-11 23:49:28 +0000174
ddkilzer@apple.com1595e802007-11-26 07:06:30 +0000175if (!@changed_files && !@conflict_files && !keys %function_lists) {
ddkilzer0507c8f2006-06-11 23:49:28 +0000176 print STDERR " No changes found.\n";
177 exit 1;
aroben38e341c2007-04-28 06:57:49 +0000178}
mjsb85f64e2005-06-17 07:49:31 +0000179
aroben38e341c2007-04-28 06:57:49 +0000180if (@conflict_files) {
mjsb85f64e2005-06-17 07:49:31 +0000181 print STDERR " The following files have conflicts. Run prepare-ChangeLog again after fixing the conflicts:\n";
182 print STDERR join("\n", @conflict_files), "\n";
183 exit 1;
aroben38e341c2007-04-28 06:57:49 +0000184}
mjsb85f64e2005-06-17 07:49:31 +0000185
aroben38e341c2007-04-28 06:57:49 +0000186if (@changed_files) {
mjsb85f64e2005-06-17 07:49:31 +0000187 $changed_files_string = "'" . join ("' '", @changed_files) . "'";
188
189 # For each file, build a list of modified lines.
190 # Use line numbers from the "after" side of each diff.
aroben363e7842007-05-15 23:35:59 +0000191 print STDERR " Reviewing diff to determine which lines changed.\n";
mjsb85f64e2005-06-17 07:49:31 +0000192 my $file;
ddkilzer33ab5032007-05-28 20:48:56 +0000193 open DIFF, "-|", diffCommand(@changed_files) or die "The diff failed: $!.\n";
aroben38e341c2007-04-28 06:57:49 +0000194 while (<DIFF>) {
aroben363e7842007-05-15 23:35:59 +0000195 $file = makeFilePathRelative($1) if $_ =~ diffHeaderFormat();
mjsb85f64e2005-06-17 07:49:31 +0000196 if (defined $file) {
aroben363e7842007-05-15 23:35:59 +0000197 my ($start, $end) = extractLineRange($_);
198 if ($start >= 0 && $end >= 0) {
199 push @{$changed_line_ranges{$file}}, [ $start, $end ];
aroben38e341c2007-04-28 06:57:49 +0000200 } elsif (/DO_NOT_COMMIT/) {
201 print STDERR "WARNING: file $file contains the string DO_NOT_COMMIT, line $.\n";
202 }
thatcherbefc69c2006-01-08 04:38:04 +0000203 }
aroben38e341c2007-04-28 06:57:49 +0000204 }
mjsb85f64e2005-06-17 07:49:31 +0000205 close DIFF;
aroben38e341c2007-04-28 06:57:49 +0000206}
mjsb85f64e2005-06-17 07:49:31 +0000207
208# For each source file, convert line range to function list.
aroben38e341c2007-04-28 06:57:49 +0000209if (%changed_line_ranges) {
mjsb85f64e2005-06-17 07:49:31 +0000210 print STDERR " Extracting affected function names from source files.\n";
aroben38e341c2007-04-28 06:57:49 +0000211 foreach my $file (keys %changed_line_ranges) {
mjsb85f64e2005-06-17 07:49:31 +0000212 # Find all the functions in the file.
213 open SOURCE, $file or next;
214 my @function_ranges = get_function_line_ranges(\*SOURCE, $file);
215 close SOURCE;
216
217 # Find all the modified functions.
218 my @functions;
219 my %saw_function;
220 my @change_ranges = (@{$changed_line_ranges{$file}}, []);
221 my @change_range = (0, 0);
aroben38e341c2007-04-28 06:57:49 +0000222 FUNCTION: foreach my $function_range_ref (@function_ranges) {
mjsb85f64e2005-06-17 07:49:31 +0000223 my @function_range = @$function_range_ref;
224
225 # Advance to successive change ranges.
aroben38e341c2007-04-28 06:57:49 +0000226 for (;; @change_range = @{shift @change_ranges}) {
mjsb85f64e2005-06-17 07:49:31 +0000227 last FUNCTION unless @change_range;
228
229 # If past this function, move on to the next one.
230 next FUNCTION if $change_range[0] > $function_range[1];
231
232 # If an overlap with this function range, record the function name.
233 if ($change_range[1] >= $function_range[0]
aroben38e341c2007-04-28 06:57:49 +0000234 and $change_range[0] <= $function_range[1]) {
235 if (!$saw_function{$function_range[2]}) {
mjsb85f64e2005-06-17 07:49:31 +0000236 $saw_function{$function_range[2]} = 1;
237 push @functions, $function_range[2];
aroben38e341c2007-04-28 06:57:49 +0000238 }
mjsb85f64e2005-06-17 07:49:31 +0000239 next FUNCTION;
aroben38e341c2007-04-28 06:57:49 +0000240 }
241 }
242 }
mjsb85f64e2005-06-17 07:49:31 +0000243
244 # Format the list of functions now.
245
246 if (@functions) {
247 $function_lists{$file} = "" if !defined $function_lists{$file};
248 $function_lists{$file} .= "\n (" . join("):\n (", @functions) . "):";
249 }
aroben38e341c2007-04-28 06:57:49 +0000250 }
251}
mjsb85f64e2005-06-17 07:49:31 +0000252
mjsb85f64e2005-06-17 07:49:31 +0000253# Get some parameters for the ChangeLog we are about to write.
ddkilzer1f0d4f02007-08-26 13:39:57 +0000254my $date = changeLogDate($changeLogTimeZone);
mrowe@apple.com264efc42009-11-08 03:59:28 +0000255$name = changeLogNameFromArgs($name);
256$emailAddress = changeLogEmailAddressFromArgs($emailAddress);
mjsb85f64e2005-06-17 07:49:31 +0000257
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000258print STDERR " Change author: $name <$emailAddress>.\n";
hausmann46f26492007-06-23 08:49:16 +0000259
haraken@chromium.org7daa6722011-12-09 23:26:42 +0000260# Remove trailing parenthesized notes from user name (bit of hack).
261$name =~ s/\(.*?\)\s*$//g;
262
eric@webkit.org97716132009-07-02 05:13:41 +0000263my $bugURL;
264if ($bugNumber) {
265 $bugURL = "https://bugs.webkit.org/show_bug.cgi?id=$bugNumber";
ojan@chromium.org7ad72992011-03-03 08:02:02 +0000266}
267
268if ($bugNumber && !$bugDescription) {
haraken@chromium.org7daa6722011-12-09 23:26:42 +0000269 $bugDescription = fetchBugDescriptionFromURL($bugURL);
eric@webkit.org97716132009-07-02 05:13:41 +0000270}
271
mjsb85f64e2005-06-17 07:49:31 +0000272# Find the change logs.
273my %has_log;
274my %files;
aroben38e341c2007-04-28 06:57:49 +0000275foreach my $file (sort keys %function_lists) {
mjsb85f64e2005-06-17 07:49:31 +0000276 my $prefix = $file;
277 my $has_log = 0;
aroben38e341c2007-04-28 06:57:49 +0000278 while ($prefix) {
mjsb85f64e2005-06-17 07:49:31 +0000279 $prefix =~ s-/[^/]+/?$-/- or $prefix = "";
280 $has_log = $has_log{$prefix};
aroben38e341c2007-04-28 06:57:49 +0000281 if (!defined $has_log) {
mjsb85f64e2005-06-17 07:49:31 +0000282 $has_log = -f "${prefix}ChangeLog";
283 $has_log{$prefix} = $has_log;
aroben38e341c2007-04-28 06:57:49 +0000284 }
mjsb85f64e2005-06-17 07:49:31 +0000285 last if $has_log;
aroben38e341c2007-04-28 06:57:49 +0000286 }
287 if (!$has_log) {
mjsb85f64e2005-06-17 07:49:31 +0000288 print STDERR "No ChangeLog found for $file.\n";
aroben38e341c2007-04-28 06:57:49 +0000289 } else {
mjsb85f64e2005-06-17 07:49:31 +0000290 push @{$files{$prefix}}, $file;
aroben38e341c2007-04-28 06:57:49 +0000291 }
292}
mjsb85f64e2005-06-17 07:49:31 +0000293
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000294# Build the list of ChangeLog prefixes in the correct project order
295my @prefixes;
296my %prefixesSort;
297foreach my $prefix (keys %files) {
298 my $prefixDir = substr($prefix, 0, length($prefix) - 1); # strip trailing /
299 my $sortKey = lc $prefix;
300 $sortKey = "top level" unless length $sortKey;
301
302 if ($prefixDir eq "top level") {
303 $sortKey = "";
304 } elsif ($prefixDir eq "Tools") {
305 $sortKey = "-, just after top level";
306 } elsif ($prefixDir eq "WebBrowser") {
307 $sortKey = lc "WebKit, WebBrowser after";
abarth@webkit.org8ac7e152011-01-08 09:35:14 +0000308 } elsif ($prefixDir eq "Source/WebCore") {
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000309 $sortKey = lc "WebFoundation, WebCore after";
310 } elsif ($prefixDir eq "LayoutTests") {
311 $sortKey = lc "~, LayoutTests last";
312 }
313
314 $prefixesSort{$sortKey} = $prefix;
315}
316foreach my $prefixSort (sort keys %prefixesSort) {
317 push @prefixes, $prefixesSort{$prefixSort};
318}
319
thatcherbefc69c2006-01-08 04:38:04 +0000320# Get the latest ChangeLog files from svn.
ddkilzere149e7b72007-10-25 03:50:17 +0000321my @logs = ();
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000322foreach my $prefix (@prefixes) {
ddkilzer@apple.com1242ec42008-01-29 23:03:12 +0000323 push @logs, File::Spec->catfile($prefix || ".", "ChangeLog");
aroben38e341c2007-04-28 06:57:49 +0000324}
aroben363e7842007-05-15 23:35:59 +0000325
ddkilzere149e7b72007-10-25 03:50:17 +0000326if (@logs && $updateChangeLogs && $isSVN) {
thatcherbefc69c2006-01-08 04:38:04 +0000327 print STDERR " Running 'svn update' to update ChangeLog files.\n";
ddkilzer7362d6e2007-10-26 15:38:48 +0000328 open ERRORS, "-|", $SVN, "update", @logs
ddkilzere149e7b72007-10-25 03:50:17 +0000329 or die "The svn update of ChangeLog files failed: $!.\n";
ddkilzer7362d6e2007-10-26 15:38:48 +0000330 my @conflictedChangeLogs;
331 while (my $line = <ERRORS>) {
332 print STDERR " ", $line;
pkasting@chromium.orgf189b702009-08-12 22:15:51 +0000333 push @conflictedChangeLogs, $1 if $line =~ m/^C\s+(.+?)[\r\n]*$/;
ddkilzer7362d6e2007-10-26 15:38:48 +0000334 }
mjsb85f64e2005-06-17 07:49:31 +0000335 close ERRORS;
ddkilzer7362d6e2007-10-26 15:38:48 +0000336
337 if (@conflictedChangeLogs) {
338 print STDERR " Attempting to merge conflicted ChangeLogs.\n";
339 my $resolveChangeLogsPath = File::Spec->catfile(dirname($0), "resolve-ChangeLogs");
340 open RESOLVE, "-|", $resolveChangeLogsPath, "--no-warnings", @conflictedChangeLogs
341 or die "Could not open resolve-ChangeLogs script: $!.\n";
342 print STDERR " $_" while <RESOLVE>;
343 close RESOLVE;
344 }
aroben38e341c2007-04-28 06:57:49 +0000345}
mjsb85f64e2005-06-17 07:49:31 +0000346
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000347# Generate new ChangeLog entries and (optionally) write out new ChangeLog files.
348foreach my $prefix (@prefixes) {
abarth@webkit.orgc93f2152009-06-16 08:06:58 +0000349 my $endl = "\n";
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000350 my @old_change_log;
351
352 if ($writeChangeLogs) {
353 my $changeLogPath = File::Spec->catfile($prefix || ".", "ChangeLog");
354 print STDERR " Editing the ${changeLogPath} file.\n";
355 open OLD_CHANGE_LOG, ${changeLogPath} or die "Could not open ${changeLogPath} file: $!.\n";
356 # It's less efficient to read the whole thing into memory than it would be
357 # to read it while we prepend to it later, but I like doing this part first.
358 @old_change_log = <OLD_CHANGE_LOG>;
359 close OLD_CHANGE_LOG;
360 # We want to match the ChangeLog's line endings in case it doesn't match
361 # the native line endings for this version of perl.
362 if ($old_change_log[0] =~ /(\r?\n)$/g) {
363 $endl = "$1";
364 }
365 open CHANGE_LOG, "> ${changeLogPath}" or die "Could not write ${changeLogPath}\n.";
366 } else {
367 open CHANGE_LOG, ">-" or die "Could not write to STDOUT\n.";
368 print substr($prefix, 0, length($prefix) - 1) . ":\n\n" unless (scalar @prefixes) == 1;
abarth@webkit.orgc93f2152009-06-16 08:06:58 +0000369 }
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000370
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000371 print CHANGE_LOG normalizeLineEndings("$date $name <$emailAddress>\n\n", $endl);
hausmann46f26492007-06-23 08:49:16 +0000372
ddkilzer3b890b42007-10-02 20:38:36 +0000373 my ($reviewer, $description) = reviewerAndDescriptionForGitCommit($gitCommit) if $gitCommit;
374 $reviewer = "NOBODY (OO" . "PS!)" if !$reviewer;
hausmann46f26492007-06-23 08:49:16 +0000375
abarth@webkit.orgc93f2152009-06-16 08:06:58 +0000376 print CHANGE_LOG normalizeLineEndings($description . "\n", $endl) if $description;
hausmann46f26492007-06-23 08:49:16 +0000377
mjs@apple.come4aafeb2009-07-08 22:58:31 +0000378 $bugDescription = "Need a short description and bug URL (OOPS!)" unless $bugDescription;
379 print CHANGE_LOG normalizeLineEndings(" $bugDescription\n", $endl) if $bugDescription;
380 print CHANGE_LOG normalizeLineEndings(" $bugURL\n", $endl) if $bugURL;
pkasting@chromium.org7f4f6982009-07-16 22:55:51 +0000381 print CHANGE_LOG normalizeLineEndings("\n", $endl);
eric@webkit.org97716132009-07-02 05:13:41 +0000382
aroben@apple.come9222dd2011-07-01 15:21:02 +0000383 print CHANGE_LOG normalizeLineEndings(" Reviewed by $reviewer.\n\n", $endl);
384
ddkilzer3b890b42007-10-02 20:38:36 +0000385 if ($prefix =~ m/WebCore/ || `pwd` =~ m/WebCore/) {
386 if ($didChangeRegressionTests) {
abarth@webkit.orgc93f2152009-06-16 08:06:58 +0000387 print CHANGE_LOG normalizeLineEndings(testListForChangeLog(sort @addedRegressionTests), $endl);
ddkilzer3b890b42007-10-02 20:38:36 +0000388 } else {
mjs@apple.come4aafeb2009-07-08 22:58:31 +0000389 print CHANGE_LOG normalizeLineEndings(" No new tests. (OOPS!)\n\n", $endl);
hausmann46f26492007-06-23 08:49:16 +0000390 }
mjsb85f64e2005-06-17 07:49:31 +0000391 }
392
aroben38e341c2007-04-28 06:57:49 +0000393 foreach my $file (sort @{$files{$prefix}}) {
pkasting@chromium.org7f4f6982009-07-16 22:55:51 +0000394 my $file_stem = substr $file, length $prefix;
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000395 print CHANGE_LOG normalizeLineEndings(" * $file_stem:$function_lists{$file}\n", $endl);
aroben38e341c2007-04-28 06:57:49 +0000396 }
treat@webkit.org1c86c8e2009-07-23 16:32:44 +0000397
398 if ($writeChangeLogs) {
399 print CHANGE_LOG normalizeLineEndings("\n", $endl), @old_change_log;
400 } else {
401 print CHANGE_LOG "\n";
402 }
403
mjsb85f64e2005-06-17 07:49:31 +0000404 close CHANGE_LOG;
aroben38e341c2007-04-28 06:57:49 +0000405}
mjsb85f64e2005-06-17 07:49:31 +0000406
mrowe@apple.comed88dfd2009-08-02 00:24:52 +0000407if ($writeChangeLogs) {
408 print STDERR "-- Please remember to include a detailed description in your ChangeLog entry. --\n-- See <http://webkit.org/coding/contributing.html> for more info --\n";
409}
410
mjsb85f64e2005-06-17 07:49:31 +0000411# Write out another diff.
aroben38e341c2007-04-28 06:57:49 +0000412if ($spewDiff && @changed_files) {
aroben363e7842007-05-15 23:35:59 +0000413 print STDERR " Running diff to help you write the ChangeLog entries.\n";
ddkilzer33ab5032007-05-28 20:48:56 +0000414 local $/ = undef; # local slurp mode
aroben363e7842007-05-15 23:35:59 +0000415 open DIFF, "-|", createPatchCommand($changed_files_string) or die "The diff failed: $!.\n";
ddkilzer33ab5032007-05-28 20:48:56 +0000416 print <DIFF>;
mjsb85f64e2005-06-17 07:49:31 +0000417 close DIFF;
aroben38e341c2007-04-28 06:57:49 +0000418}
mjsb85f64e2005-06-17 07:49:31 +0000419
420# Open ChangeLogs.
ddkilzere149e7b72007-10-25 03:50:17 +0000421if ($openChangeLogs && @logs) {
mjsb85f64e2005-06-17 07:49:31 +0000422 print STDERR " Opening the edited ChangeLog files.\n";
darin@apple.comdca1e7f2010-08-11 12:54:06 +0000423 my $editor = $ENV{CHANGE_LOG_EDITOR};
mjsb85f64e2005-06-17 07:49:31 +0000424 if ($editor) {
darin@apple.comdca1e7f2010-08-11 12:54:06 +0000425 system ((split ' ', $editor), @logs);
mjsb85f64e2005-06-17 07:49:31 +0000426 } else {
darin@apple.comdca1e7f2010-08-11 12:54:06 +0000427 $editor = $ENV{CHANGE_LOG_EDIT_APPLICATION};
428 if ($editor) {
429 system "open", "-a", $editor, @logs;
430 } else {
431 system "open", "-e", @logs;
432 }
mjsb85f64e2005-06-17 07:49:31 +0000433 }
aroben38e341c2007-04-28 06:57:49 +0000434}
mjsb85f64e2005-06-17 07:49:31 +0000435
436# Done.
437exit;
438
ddkilzerb7b3b912006-06-25 06:01:41 +0000439
ddkilzer1f0d4f02007-08-26 13:39:57 +0000440sub changeLogDate($)
441{
442 my ($timeZone) = @_;
443 my $savedTimeZone = $ENV{'TZ'};
444 # Set TZ temporarily so that localtime() is in that time zone
445 $ENV{'TZ'} = $timeZone;
446 my $date = strftime("%Y-%m-%d", localtime());
447 if (defined $savedTimeZone) {
448 $ENV{'TZ'} = $savedTimeZone;
449 } else {
450 delete $ENV{'TZ'};
451 }
452 return $date;
453}
454
mrowe@apple.com264efc42009-11-08 03:59:28 +0000455sub changeLogNameFromArgs($)
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000456{
457 my ($nameFromArgs) = @_;
mrowe@apple.com264efc42009-11-08 03:59:28 +0000458 # Silently allow --git-commit to win, we could warn if $nameFromArgs is defined.
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000459 return `$GIT log --max-count=1 --pretty=\"format:%an\" \"$gitCommit\"` if $gitCommit;
460
mrowe@apple.com264efc42009-11-08 03:59:28 +0000461 return $nameFromArgs || changeLogName();
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000462}
463
mrowe@apple.com264efc42009-11-08 03:59:28 +0000464sub changeLogEmailAddressFromArgs($)
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000465{
466 my ($emailAddressFromArgs) = @_;
467 # Silently allow --git-commit to win, we could warn if $emailAddressFromArgs is defined.
468 return `$GIT log --max-count=1 --pretty=\"format:%ae\" \"$gitCommit\"` if $gitCommit;
469
mrowe@apple.com264efc42009-11-08 03:59:28 +0000470 return $emailAddressFromArgs || changeLogEmailAddress();
eric@webkit.org2c1cc582009-07-01 23:31:37 +0000471}
472
haraken@chromium.org7daa6722011-12-09 23:26:42 +0000473sub fetchBugDescriptionFromURL($)
474{
475 my ($bugURL) = @_;
476
477 my $bugXMLURL = "$bugURL&ctype=xml";
478 # Perl has no built in XML processing, so we'll fetch and parse with curl and grep
479 # Pass --insecure because some cygwin installs have no certs we don't
480 # care about validating that bugs.webkit.org is who it says it is here.
481 my $descriptionLine = `curl --insecure --silent "$bugXMLURL" | grep short_desc`;
482 if ($descriptionLine !~ /<short_desc>(.*)<\/short_desc>/) {
483 # Maybe the reason the above did not work is because the curl that is installed doesn't
484 # support ssl at all.
485 if (`curl --version | grep ^Protocols` !~ /\bhttps\b/) {
486 print STDERR " Could not get description for bug $bugNumber.\n";
487 print STDERR " It looks like your version of curl does not support ssl.\n";
488 print STDERR " If you are using macports, this can be fixed with sudo port install curl +ssl.\n";
489 } else {
490 print STDERR " Bug $bugNumber has no bug description. Maybe you set wrong bug ID?\n";
491 print STDERR " The bug URL: $bugXMLURL\n";
492 }
493 exit 1;
494 }
495 my $bugDescription = decodeEntities($1);
496 print STDERR " Description from bug $bugNumber:\n \"$bugDescription\".\n";
497 return $bugDescription;
498}
499
ddkilzerb7b3b912006-06-25 06:01:41 +0000500sub get_function_line_ranges($$)
aroben38e341c2007-04-28 06:57:49 +0000501{
mjsb85f64e2005-06-17 07:49:31 +0000502 my ($file_handle, $file_name) = @_;
503
aroben@apple.comd4acad02011-07-05 22:20:53 +0000504 # Try to determine the source language based on the file extension.
505
506 return get_function_line_ranges_for_c($file_handle, $file_name) if $file_name =~ /\.(c|cpp|m|mm|h)$/;
507 return get_function_line_ranges_for_java($file_handle, $file_name) if $file_name =~ /\.java$/;
508 return get_function_line_ranges_for_javascript($file_handle, $file_name) if $file_name =~ /\.js$/;
509 return get_selector_line_ranges_for_css($file_handle, $file_name) if $file_name =~ /\.css$/;
510 return get_function_line_ranges_for_perl($file_handle, $file_name) if $file_name =~ /\.p[lm]$/;
aroben@apple.coma359c942011-12-02 18:00:43 +0000511 return get_function_line_ranges_for_python($file_handle, $file_name) if $file_name =~ /\.py$/ or $file_name =~ /master\.cfg$/;
aroben@apple.comd4acad02011-07-05 22:20:53 +0000512
513 # Try to determine the source language based on the script interpreter.
514
515 my $first_line = <$file_handle>;
516 seek($file_handle, 0, 0);
517
aroben@apple.comb7e35352011-11-22 16:52:44 +0000518 return () unless $first_line =~ m|^#!(?:/usr/bin/env\s+)?(\S+)|;
aroben@apple.comd4acad02011-07-05 22:20:53 +0000519 my $interpreter = $1;
520
521 return get_function_line_ranges_for_perl($file_handle, $file_name) if $interpreter =~ /perl$/;
aroben@apple.comb7e35352011-11-22 16:52:44 +0000522 return get_function_line_ranges_for_python($file_handle, $file_name) if $interpreter =~ /python$/;
aroben@apple.comd4acad02011-07-05 22:20:53 +0000523
mjsb85f64e2005-06-17 07:49:31 +0000524 return ();
aroben38e341c2007-04-28 06:57:49 +0000525}
mjsb85f64e2005-06-17 07:49:31 +0000526
527
ddkilzerb7b3b912006-06-25 06:01:41 +0000528sub method_decl_to_selector($)
aroben38e341c2007-04-28 06:57:49 +0000529{
mjsb85f64e2005-06-17 07:49:31 +0000530 (my $method_decl) = @_;
531
532 $_ = $method_decl;
533
aroben38e341c2007-04-28 06:57:49 +0000534 if ((my $comment_stripped) = m-([^/]*)(//|/*).*-) {
thatcherbefc69c2006-01-08 04:38:04 +0000535 $_ = $comment_stripped;
aroben38e341c2007-04-28 06:57:49 +0000536 }
mjsb85f64e2005-06-17 07:49:31 +0000537
538 s/,\s*...//;
539
aroben38e341c2007-04-28 06:57:49 +0000540 if (/:/) {
mjsb85f64e2005-06-17 07:49:31 +0000541 my @components = split /:/;
thatcherbefc69c2006-01-08 04:38:04 +0000542 pop @components if (scalar @components > 1);
543 $_ = (join ':', map {s/.*[^[:word:]]//; scalar $_;} @components) . ':';
aroben38e341c2007-04-28 06:57:49 +0000544 } else {
mjsb85f64e2005-06-17 07:49:31 +0000545 s/\s*$//;
thatcherbefc69c2006-01-08 04:38:04 +0000546 s/.*[^[:word:]]//;
aroben38e341c2007-04-28 06:57:49 +0000547 }
mjsb85f64e2005-06-17 07:49:31 +0000548
549 return $_;
aroben38e341c2007-04-28 06:57:49 +0000550}
mjsb85f64e2005-06-17 07:49:31 +0000551
552
553
554# Read a file and get all the line ranges of the things that look like C functions.
555# A function name is the last word before an open parenthesis before the outer
556# level open brace. A function starts at the first character after the last close
557# brace or semicolon before the function name and ends at the close brace.
558# Comment handling is simple-minded but will work for all but pathological cases.
559#
560# Result is a list of triples: [ start_line, end_line, function_name ].
561
ddkilzerb7b3b912006-06-25 06:01:41 +0000562sub get_function_line_ranges_for_c($$)
aroben38e341c2007-04-28 06:57:49 +0000563{
mjsb85f64e2005-06-17 07:49:31 +0000564 my ($file_handle, $file_name) = @_;
565
566 my @ranges;
567
568 my $in_comment = 0;
569 my $in_macro = 0;
570 my $in_method_declaration = 0;
571 my $in_parentheses = 0;
572 my $in_braces = 0;
573 my $brace_start = 0;
574 my $brace_end = 0;
575 my $skip_til_brace_or_semicolon = 0;
576
577 my $word = "";
578 my $interface_name = "";
579
580 my $potential_method_char = "";
581 my $potential_method_spec = "";
582
583 my $potential_start = 0;
584 my $potential_name = "";
585
586 my $start = 0;
587 my $name = "";
588
589 my $next_word_could_be_namespace = 0;
590 my $potential_namespace = "";
591 my @namespaces;
592
aroben38e341c2007-04-28 06:57:49 +0000593 while (<$file_handle>) {
mjsb85f64e2005-06-17 07:49:31 +0000594 # Handle continued multi-line comment.
aroben38e341c2007-04-28 06:57:49 +0000595 if ($in_comment) {
mjsb85f64e2005-06-17 07:49:31 +0000596 next unless s-.*\*/--;
597 $in_comment = 0;
aroben38e341c2007-04-28 06:57:49 +0000598 }
mjsb85f64e2005-06-17 07:49:31 +0000599
600 # Handle continued macro.
aroben38e341c2007-04-28 06:57:49 +0000601 if ($in_macro) {
mjsb85f64e2005-06-17 07:49:31 +0000602 $in_macro = 0 unless /\\$/;
603 next;
aroben38e341c2007-04-28 06:57:49 +0000604 }
mjsb85f64e2005-06-17 07:49:31 +0000605
606 # Handle start of macro (or any preprocessor directive).
aroben38e341c2007-04-28 06:57:49 +0000607 if (/^\s*\#/) {
mjsb85f64e2005-06-17 07:49:31 +0000608 $in_macro = 1 if /^([^\\]|\\.)*\\$/;
609 next;
aroben38e341c2007-04-28 06:57:49 +0000610 }
mjsb85f64e2005-06-17 07:49:31 +0000611
612 # Handle comments and quoted text.
aroben38e341c2007-04-28 06:57:49 +0000613 while (m-(/\*|//|\'|\")-) { # \' and \" keep emacs perl mode happy
mjsb85f64e2005-06-17 07:49:31 +0000614 my $match = $1;
aroben38e341c2007-04-28 06:57:49 +0000615 if ($match eq "/*") {
616 if (!s-/\*.*?\*/--) {
mjsb85f64e2005-06-17 07:49:31 +0000617 s-/\*.*--;
618 $in_comment = 1;
aroben38e341c2007-04-28 06:57:49 +0000619 }
620 } elsif ($match eq "//") {
mjsb85f64e2005-06-17 07:49:31 +0000621 s-//.*--;
aroben38e341c2007-04-28 06:57:49 +0000622 } else { # ' or "
623 if (!s-$match([^\\]|\\.)*?$match--) {
mjsb85f64e2005-06-17 07:49:31 +0000624 warn "mismatched quotes at line $. in $file_name\n";
625 s-$match.*--;
aroben38e341c2007-04-28 06:57:49 +0000626 }
627 }
628 }
mjsb85f64e2005-06-17 07:49:31 +0000629
630
631 # continued method declaration
aroben38e341c2007-04-28 06:57:49 +0000632 if ($in_method_declaration) {
mjsb85f64e2005-06-17 07:49:31 +0000633 my $original = $_;
634 my $method_cont = $_;
635
636 chomp $method_cont;
637 $method_cont =~ s/[;\{].*//;
638 $potential_method_spec = "${potential_method_spec} ${method_cont}";
639
640 $_ = $original;
aroben38e341c2007-04-28 06:57:49 +0000641 if (/;/) {
mjsb85f64e2005-06-17 07:49:31 +0000642 $potential_start = 0;
643 $potential_method_spec = "";
644 $potential_method_char = "";
645 $in_method_declaration = 0;
646 s/^[^;\{]*//;
aroben38e341c2007-04-28 06:57:49 +0000647 } elsif (/{/) {
mjsb85f64e2005-06-17 07:49:31 +0000648 my $selector = method_decl_to_selector ($potential_method_spec);
649 $potential_name = "${potential_method_char}\[${interface_name} ${selector}\]";
650
651 $potential_method_spec = "";
652 $potential_method_char = "";
653 $in_method_declaration = 0;
654
655 $_ = $original;
656 s/^[^;{]*//;
aroben38e341c2007-04-28 06:57:49 +0000657 } elsif (/\@end/) {
bdashdafd7942006-12-23 06:01:00 +0000658 $in_method_declaration = 0;
659 $interface_name = "";
660 $_ = $original;
aroben38e341c2007-04-28 06:57:49 +0000661 } else {
mjsb85f64e2005-06-17 07:49:31 +0000662 next;
aroben38e341c2007-04-28 06:57:49 +0000663 }
664 }
mjsb85f64e2005-06-17 07:49:31 +0000665
666
667 # start of method declaration
aroben38e341c2007-04-28 06:57:49 +0000668 if ((my $method_char, my $method_spec) = m&^([-+])([^0-9;][^;]*);?$&) {
mjsb85f64e2005-06-17 07:49:31 +0000669 my $original = $_;
670
aroben38e341c2007-04-28 06:57:49 +0000671 if ($interface_name) {
mjsb85f64e2005-06-17 07:49:31 +0000672 chomp $method_spec;
673 $method_spec =~ s/\{.*//;
aroben363e7842007-05-15 23:35:59 +0000674
mjsb85f64e2005-06-17 07:49:31 +0000675 $potential_method_char = $method_char;
676 $potential_method_spec = $method_spec;
677 $potential_start = $.;
678 $in_method_declaration = 1;
aroben38e341c2007-04-28 06:57:49 +0000679 } else {
mjsb85f64e2005-06-17 07:49:31 +0000680 warn "declaring a method but don't have interface on line $. in $file_name\n";
aroben38e341c2007-04-28 06:57:49 +0000681 }
mjsb85f64e2005-06-17 07:49:31 +0000682 $_ = $original;
683 if (/\{/) {
684 my $selector = method_decl_to_selector ($potential_method_spec);
685 $potential_name = "${potential_method_char}\[${interface_name} ${selector}\]";
686
687 $potential_method_spec = "";
688 $potential_method_char = "";
689 $in_method_declaration = 0;
690 $_ = $original;
691 s/^[^{]*//;
bdashdafd7942006-12-23 06:01:00 +0000692 } elsif (/\@end/) {
693 $in_method_declaration = 0;
694 $interface_name = "";
695 $_ = $original;
mjsb85f64e2005-06-17 07:49:31 +0000696 } else {
697 next;
698 }
aroben38e341c2007-04-28 06:57:49 +0000699 }
mjsb85f64e2005-06-17 07:49:31 +0000700
701
702 # Find function, interface and method names.
aroben38e341c2007-04-28 06:57:49 +0000703 while (m&((?:[[:word:]]+::)*operator(?:[ \t]*\(\)|[^()]*)|[[:word:]:~]+|[(){}:;])|\@(?:implementation|interface|protocol)\s+(\w+)[^{]*&g) {
mjsb85f64e2005-06-17 07:49:31 +0000704 # interface name
aroben38e341c2007-04-28 06:57:49 +0000705 if ($2) {
mjsb85f64e2005-06-17 07:49:31 +0000706 $interface_name = $2;
707 next;
aroben38e341c2007-04-28 06:57:49 +0000708 }
mjsb85f64e2005-06-17 07:49:31 +0000709
710 # Open parenthesis.
aroben38e341c2007-04-28 06:57:49 +0000711 if ($1 eq "(") {
mjsb85f64e2005-06-17 07:49:31 +0000712 $potential_name = $word unless $in_parentheses || $skip_til_brace_or_semicolon;
713 $in_parentheses++;
714 next;
aroben38e341c2007-04-28 06:57:49 +0000715 }
mjsb85f64e2005-06-17 07:49:31 +0000716
717 # Close parenthesis.
aroben38e341c2007-04-28 06:57:49 +0000718 if ($1 eq ")") {
mjsb85f64e2005-06-17 07:49:31 +0000719 $in_parentheses--;
720 next;
aroben38e341c2007-04-28 06:57:49 +0000721 }
mjsb85f64e2005-06-17 07:49:31 +0000722
723 # C++ constructor initializers
aroben38e341c2007-04-28 06:57:49 +0000724 if ($1 eq ":") {
mjsb85f64e2005-06-17 07:49:31 +0000725 $skip_til_brace_or_semicolon = 1 unless ($in_parentheses || $in_braces);
aroben38e341c2007-04-28 06:57:49 +0000726 }
mjsb85f64e2005-06-17 07:49:31 +0000727
728 # Open brace.
aroben38e341c2007-04-28 06:57:49 +0000729 if ($1 eq "{") {
mjsb85f64e2005-06-17 07:49:31 +0000730 $skip_til_brace_or_semicolon = 0;
731
732 if ($potential_namespace) {
733 push @namespaces, $potential_namespace;
734 $potential_namespace = "";
735 next;
736 }
737
738 # Promote potential name to real function name at the
739 # start of the outer level set of braces (function body?).
aroben38e341c2007-04-28 06:57:49 +0000740 if (!$in_braces and $potential_start) {
mjsb85f64e2005-06-17 07:49:31 +0000741 $start = $potential_start;
742 $name = $potential_name;
darin@apple.com3dd064f2010-08-30 03:26:19 +0000743 if (@namespaces && $name && (length($name) < 2 || substr($name,1,1) ne "[")) {
mjsb85f64e2005-06-17 07:49:31 +0000744 $name = join ('::', @namespaces, $name);
745 }
aroben38e341c2007-04-28 06:57:49 +0000746 }
mjsb85f64e2005-06-17 07:49:31 +0000747
748 $in_method_declaration = 0;
749
750 $brace_start = $. if (!$in_braces);
751 $in_braces++;
752 next;
aroben38e341c2007-04-28 06:57:49 +0000753 }
mjsb85f64e2005-06-17 07:49:31 +0000754
755 # Close brace.
aroben38e341c2007-04-28 06:57:49 +0000756 if ($1 eq "}") {
mjsb85f64e2005-06-17 07:49:31 +0000757 if (!$in_braces && @namespaces) {
758 pop @namespaces;
759 next;
760 }
761
762 $in_braces--;
763 $brace_end = $. if (!$in_braces);
764
765 # End of an outer level set of braces.
766 # This could be a function body.
aroben38e341c2007-04-28 06:57:49 +0000767 if (!$in_braces and $name) {
mjsb85f64e2005-06-17 07:49:31 +0000768 push @ranges, [ $start, $., $name ];
769 $name = "";
aroben38e341c2007-04-28 06:57:49 +0000770 }
mjsb85f64e2005-06-17 07:49:31 +0000771
772 $potential_start = 0;
773 $potential_name = "";
774 next;
aroben38e341c2007-04-28 06:57:49 +0000775 }
mjsb85f64e2005-06-17 07:49:31 +0000776
777 # Semicolon.
aroben38e341c2007-04-28 06:57:49 +0000778 if ($1 eq ";") {
mjsb85f64e2005-06-17 07:49:31 +0000779 $skip_til_brace_or_semicolon = 0;
780 $potential_start = 0;
781 $potential_name = "";
782 $in_method_declaration = 0;
783 next;
aroben38e341c2007-04-28 06:57:49 +0000784 }
mjsb85f64e2005-06-17 07:49:31 +0000785
786 # Ignore "const" method qualifier.
787 if ($1 eq "const") {
788 next;
789 }
790
791 if ($1 eq "namespace" || $1 eq "class" || $1 eq "struct") {
792 $next_word_could_be_namespace = 1;
793 next;
794 }
795
796 # Word.
797 $word = $1;
798 if (!$skip_til_brace_or_semicolon) {
aroben38e341c2007-04-28 06:57:49 +0000799 if ($next_word_could_be_namespace) {
800 $potential_namespace = $word;
801 $next_word_could_be_namespace = 0;
802 } elsif ($potential_namespace) {
803 $potential_namespace = "";
804 }
mjsb85f64e2005-06-17 07:49:31 +0000805
aroben38e341c2007-04-28 06:57:49 +0000806 if (!$in_parentheses) {
807 $potential_start = 0;
808 $potential_name = "";
809 }
810 if (!$potential_start) {
811 $potential_start = $.;
812 $potential_name = "";
813 }
mjsb85f64e2005-06-17 07:49:31 +0000814 }
aroben38e341c2007-04-28 06:57:49 +0000815 }
816 }
mjsb85f64e2005-06-17 07:49:31 +0000817
818 warn "missing close braces in $file_name (probable start at $brace_start)\n" if ($in_braces > 0);
819 warn "too many close braces in $file_name (probable start at $brace_end)\n" if ($in_braces < 0);
820
821 warn "mismatched parentheses in $file_name\n" if $in_parentheses;
822
823 return @ranges;
aroben38e341c2007-04-28 06:57:49 +0000824}
mjsb85f64e2005-06-17 07:49:31 +0000825
826
827
828# Read a file and get all the line ranges of the things that look like Java
829# classes, interfaces and methods.
830#
831# A class or interface name is the word that immediately follows
832# `class' or `interface' when followed by an open curly brace and not
833# a semicolon. It can appear at the top level, or inside another class
834# or interface block, but not inside a function block
835#
836# A class or interface starts at the first character after the first close
837# brace or after the function name and ends at the close brace.
838#
839# A function name is the last word before an open parenthesis before
840# an open brace rather than a semicolon. It can appear at top level or
841# inside a class or interface block, but not inside a function block.
842#
843# A function starts at the first character after the first close
844# brace or after the function name and ends at the close brace.
845#
846# Comment handling is simple-minded but will work for all but pathological cases.
847#
848# Result is a list of triples: [ start_line, end_line, function_name ].
849
ddkilzerb7b3b912006-06-25 06:01:41 +0000850sub get_function_line_ranges_for_java($$)
aroben38e341c2007-04-28 06:57:49 +0000851{
mjsb85f64e2005-06-17 07:49:31 +0000852 my ($file_handle, $file_name) = @_;
853
854 my @current_scopes;
855
856 my @ranges;
857
858 my $in_comment = 0;
859 my $in_macro = 0;
860 my $in_parentheses = 0;
861 my $in_braces = 0;
862 my $in_non_block_braces = 0;
863 my $class_or_interface_just_seen = 0;
864
865 my $word = "";
866
867 my $potential_start = 0;
868 my $potential_name = "";
869 my $potential_name_is_class_or_interface = 0;
870
871 my $start = 0;
872 my $name = "";
873 my $current_name_is_class_or_interface = 0;
874
aroben38e341c2007-04-28 06:57:49 +0000875 while (<$file_handle>) {
mjsb85f64e2005-06-17 07:49:31 +0000876 # Handle continued multi-line comment.
aroben38e341c2007-04-28 06:57:49 +0000877 if ($in_comment) {
mjsb85f64e2005-06-17 07:49:31 +0000878 next unless s-.*\*/--;
879 $in_comment = 0;
aroben38e341c2007-04-28 06:57:49 +0000880 }
mjsb85f64e2005-06-17 07:49:31 +0000881
882 # Handle continued macro.
aroben38e341c2007-04-28 06:57:49 +0000883 if ($in_macro) {
mjsb85f64e2005-06-17 07:49:31 +0000884 $in_macro = 0 unless /\\$/;
885 next;
aroben38e341c2007-04-28 06:57:49 +0000886 }
mjsb85f64e2005-06-17 07:49:31 +0000887
888 # Handle start of macro (or any preprocessor directive).
aroben38e341c2007-04-28 06:57:49 +0000889 if (/^\s*\#/) {
mjsb85f64e2005-06-17 07:49:31 +0000890 $in_macro = 1 if /^([^\\]|\\.)*\\$/;
891 next;
aroben38e341c2007-04-28 06:57:49 +0000892 }
mjsb85f64e2005-06-17 07:49:31 +0000893
894 # Handle comments and quoted text.
aroben38e341c2007-04-28 06:57:49 +0000895 while (m-(/\*|//|\'|\")-) { # \' and \" keep emacs perl mode happy
mjsb85f64e2005-06-17 07:49:31 +0000896 my $match = $1;
aroben38e341c2007-04-28 06:57:49 +0000897 if ($match eq "/*") {
898 if (!s-/\*.*?\*/--) {
mjsb85f64e2005-06-17 07:49:31 +0000899 s-/\*.*--;
900 $in_comment = 1;
aroben38e341c2007-04-28 06:57:49 +0000901 }
902 } elsif ($match eq "//") {
mjsb85f64e2005-06-17 07:49:31 +0000903 s-//.*--;
aroben38e341c2007-04-28 06:57:49 +0000904 } else { # ' or "
905 if (!s-$match([^\\]|\\.)*?$match--) {
mjsb85f64e2005-06-17 07:49:31 +0000906 warn "mismatched quotes at line $. in $file_name\n";
907 s-$match.*--;
aroben38e341c2007-04-28 06:57:49 +0000908 }
909 }
910 }
mjsb85f64e2005-06-17 07:49:31 +0000911
912 # Find function names.
aroben38e341c2007-04-28 06:57:49 +0000913 while (m-(\w+|[(){};])-g) {
mjsb85f64e2005-06-17 07:49:31 +0000914 # Open parenthesis.
aroben38e341c2007-04-28 06:57:49 +0000915 if ($1 eq "(") {
mjsb85f64e2005-06-17 07:49:31 +0000916 if (!$in_parentheses) {
917 $potential_name = $word;
918 $potential_name_is_class_or_interface = 0;
919 }
920 $in_parentheses++;
921 next;
aroben38e341c2007-04-28 06:57:49 +0000922 }
mjsb85f64e2005-06-17 07:49:31 +0000923
924 # Close parenthesis.
aroben38e341c2007-04-28 06:57:49 +0000925 if ($1 eq ")") {
mjsb85f64e2005-06-17 07:49:31 +0000926 $in_parentheses--;
927 next;
aroben38e341c2007-04-28 06:57:49 +0000928 }
mjsb85f64e2005-06-17 07:49:31 +0000929
930 # Open brace.
aroben38e341c2007-04-28 06:57:49 +0000931 if ($1 eq "{") {
mjsb85f64e2005-06-17 07:49:31 +0000932 # Promote potential name to real function name at the
933 # start of the outer level set of braces (function/class/interface body?).
934 if (!$in_non_block_braces
935 and (!$in_braces or $current_name_is_class_or_interface)
aroben38e341c2007-04-28 06:57:49 +0000936 and $potential_start) {
937 if ($name) {
mjsb85f64e2005-06-17 07:49:31 +0000938 push @ranges, [ $start, ($. - 1),
939 join ('.', @current_scopes) ];
aroben38e341c2007-04-28 06:57:49 +0000940 }
mjsb85f64e2005-06-17 07:49:31 +0000941
942
943 $current_name_is_class_or_interface = $potential_name_is_class_or_interface;
944
945 $start = $potential_start;
946 $name = $potential_name;
947
948 push (@current_scopes, $name);
aroben38e341c2007-04-28 06:57:49 +0000949 } else {
950 $in_non_block_braces++;
951 }
mjsb85f64e2005-06-17 07:49:31 +0000952
953 $potential_name = "";
954 $potential_start = 0;
955
956 $in_braces++;
957 next;
aroben38e341c2007-04-28 06:57:49 +0000958 }
mjsb85f64e2005-06-17 07:49:31 +0000959
960 # Close brace.
aroben38e341c2007-04-28 06:57:49 +0000961 if ($1 eq "}") {
mjsb85f64e2005-06-17 07:49:31 +0000962 $in_braces--;
963
964 # End of an outer level set of braces.
965 # This could be a function body.
aroben38e341c2007-04-28 06:57:49 +0000966 if (!$in_non_block_braces) {
967 if ($name) {
mjsb85f64e2005-06-17 07:49:31 +0000968 push @ranges, [ $start, $.,
969 join ('.', @current_scopes) ];
970
971 pop (@current_scopes);
972
aroben38e341c2007-04-28 06:57:49 +0000973 if (@current_scopes) {
mjsb85f64e2005-06-17 07:49:31 +0000974 $current_name_is_class_or_interface = 1;
975
976 $start = $. + 1;
977 $name = $current_scopes[$#current_scopes-1];
aroben38e341c2007-04-28 06:57:49 +0000978 } else {
mjsb85f64e2005-06-17 07:49:31 +0000979 $current_name_is_class_or_interface = 0;
980 $start = 0;
981 $name = "";
aroben38e341c2007-04-28 06:57:49 +0000982 }
mjsb85f64e2005-06-17 07:49:31 +0000983 }
aroben38e341c2007-04-28 06:57:49 +0000984 } else {
mjsb85f64e2005-06-17 07:49:31 +0000985 $in_non_block_braces-- if $in_non_block_braces;
aroben38e341c2007-04-28 06:57:49 +0000986 }
mjsb85f64e2005-06-17 07:49:31 +0000987
988 $potential_start = 0;
989 $potential_name = "";
990 next;
aroben38e341c2007-04-28 06:57:49 +0000991 }
mjsb85f64e2005-06-17 07:49:31 +0000992
993 # Semicolon.
aroben38e341c2007-04-28 06:57:49 +0000994 if ($1 eq ";") {
mjsb85f64e2005-06-17 07:49:31 +0000995 $potential_start = 0;
996 $potential_name = "";
997 next;
aroben38e341c2007-04-28 06:57:49 +0000998 }
mjsb85f64e2005-06-17 07:49:31 +0000999
aroben38e341c2007-04-28 06:57:49 +00001000 if ($1 eq "class" or $1 eq "interface") {
mjsb85f64e2005-06-17 07:49:31 +00001001 $class_or_interface_just_seen = 1;
1002 next;
aroben38e341c2007-04-28 06:57:49 +00001003 }
mjsb85f64e2005-06-17 07:49:31 +00001004
1005 # Word.
1006 $word = $1;
aroben38e341c2007-04-28 06:57:49 +00001007 if (!$in_parentheses) {
mjsb85f64e2005-06-17 07:49:31 +00001008 if ($class_or_interface_just_seen) {
1009 $potential_name = $word;
1010 $potential_start = $.;
1011 $class_or_interface_just_seen = 0;
1012 $potential_name_is_class_or_interface = 1;
1013 next;
1014 }
aroben38e341c2007-04-28 06:57:49 +00001015 }
1016 if (!$potential_start) {
mjsb85f64e2005-06-17 07:49:31 +00001017 $potential_start = $.;
1018 $potential_name = "";
aroben38e341c2007-04-28 06:57:49 +00001019 }
mjsb85f64e2005-06-17 07:49:31 +00001020 $class_or_interface_just_seen = 0;
aroben38e341c2007-04-28 06:57:49 +00001021 }
1022 }
mjsb85f64e2005-06-17 07:49:31 +00001023
1024 warn "mismatched braces in $file_name\n" if $in_braces;
1025 warn "mismatched parentheses in $file_name\n" if $in_parentheses;
1026
1027 return @ranges;
aroben38e341c2007-04-28 06:57:49 +00001028}
ddkilzerb7b3b912006-06-25 06:01:41 +00001029
timothy@apple.com31f57e72008-10-14 19:17:08 +00001030
1031
1032# Read a file and get all the line ranges of the things that look like
1033# JavaScript functions.
1034#
1035# A function name is the word that immediately follows `function' when
1036# followed by an open curly brace. It can appear at the top level, or
1037# inside other functions.
1038#
1039# An anonymous function name is the identifier chain immediately before
1040# an assignment with the equals operator or object notation that has a
1041# value starting with `function' followed by an open curly brace.
1042#
1043# A getter or setter name is the word that immediately follows `get' or
1044# `set' when followed by an open curly brace .
1045#
1046# Comment handling is simple-minded but will work for all but pathological cases.
1047#
1048# Result is a list of triples: [ start_line, end_line, function_name ].
1049
1050sub get_function_line_ranges_for_javascript($$)
1051{
1052 my ($fileHandle, $fileName) = @_;
1053
1054 my @currentScopes;
1055 my @currentIdentifiers;
1056 my @currentFunctionNames;
1057 my @currentFunctionDepths;
1058 my @currentFunctionStartLines;
1059
1060 my @ranges;
1061
1062 my $inComment = 0;
timothy@apple.com4d8680b2009-03-02 21:38:11 +00001063 my $inQuotedText = "";
timothy@apple.com31f57e72008-10-14 19:17:08 +00001064 my $parenthesesDepth = 0;
1065 my $bracesDepth = 0;
1066
1067 my $functionJustSeen = 0;
1068 my $getterJustSeen = 0;
1069 my $setterJustSeen = 0;
1070 my $assignmentJustSeen = 0;
1071
1072 my $word = "";
1073
1074 while (<$fileHandle>) {
1075 # Handle continued multi-line comment.
1076 if ($inComment) {
1077 next unless s-.*\*/--;
1078 $inComment = 0;
1079 }
1080
timothy@apple.com4d8680b2009-03-02 21:38:11 +00001081 # Handle continued quoted text.
1082 if ($inQuotedText ne "") {
1083 next if /\\$/;
1084 s-([^\\]|\\.)*?$inQuotedText--;
1085 $inQuotedText = "";
1086 }
1087
timothy@apple.com31f57e72008-10-14 19:17:08 +00001088 # Handle comments and quoted text.
1089 while (m-(/\*|//|\'|\")-) { # \' and \" keep emacs perl mode happy
1090 my $match = $1;
1091 if ($match eq '/*') {
1092 if (!s-/\*.*?\*/--) {
1093 s-/\*.*--;
1094 $inComment = 1;
1095 }
1096 } elsif ($match eq '//') {
1097 s-//.*--;
1098 } else { # ' or "
1099 if (!s-$match([^\\]|\\.)*?$match--) {
timothy@apple.com4d8680b2009-03-02 21:38:11 +00001100 $inQuotedText = $match if /\\$/;
1101 warn "mismatched quotes at line $. in $fileName\n" if $inQuotedText eq "";
timothy@apple.com31f57e72008-10-14 19:17:08 +00001102 s-$match.*--;
1103 }
1104 }
1105 }
1106
1107 # Find function names.
1108 while (m-(\w+|[(){}=:;])-g) {
1109 # Open parenthesis.
1110 if ($1 eq '(') {
1111 $parenthesesDepth++;
1112 next;
1113 }
1114
1115 # Close parenthesis.
1116 if ($1 eq ')') {
1117 $parenthesesDepth--;
1118 next;
1119 }
1120
1121 # Open brace.
1122 if ($1 eq '{') {
1123 push(@currentScopes, join(".", @currentIdentifiers));
1124 @currentIdentifiers = ();
1125
1126 $bracesDepth++;
1127 next;
1128 }
1129
1130 # Close brace.
1131 if ($1 eq '}') {
1132 $bracesDepth--;
1133
1134 if (@currentFunctionDepths and $bracesDepth == $currentFunctionDepths[$#currentFunctionDepths]) {
1135 pop(@currentFunctionDepths);
1136
1137 my $currentFunction = pop(@currentFunctionNames);
1138 my $start = pop(@currentFunctionStartLines);
1139
1140 push(@ranges, [$start, $., $currentFunction]);
1141 }
1142
1143 pop(@currentScopes);
1144 @currentIdentifiers = ();
1145
1146 next;
1147 }
1148
1149 # Semicolon.
1150 if ($1 eq ';') {
1151 @currentIdentifiers = ();
1152 next;
1153 }
1154
1155 # Function.
1156 if ($1 eq 'function') {
1157 $functionJustSeen = 1;
1158
1159 if ($assignmentJustSeen) {
1160 my $currentFunction = join('.', (@currentScopes, @currentIdentifiers));
1161 $currentFunction =~ s/\.{2,}/\./g; # Removes consecutive periods.
1162
1163 push(@currentFunctionNames, $currentFunction);
1164 push(@currentFunctionDepths, $bracesDepth);
1165 push(@currentFunctionStartLines, $.);
1166 }
1167
1168 next;
1169 }
1170
1171 # Getter prefix.
1172 if ($1 eq 'get') {
1173 $getterJustSeen = 1;
1174 next;
1175 }
1176
1177 # Setter prefix.
1178 if ($1 eq 'set') {
1179 $setterJustSeen = 1;
1180 next;
1181 }
1182
1183 # Assignment operator.
1184 if ($1 eq '=' or $1 eq ':') {
1185 $assignmentJustSeen = 1;
1186 next;
1187 }
1188
1189 next if $parenthesesDepth;
1190
1191 # Word.
1192 $word = $1;
1193 $word = "get $word" if $getterJustSeen;
1194 $word = "set $word" if $setterJustSeen;
1195
1196 if (($functionJustSeen and !$assignmentJustSeen) or $getterJustSeen or $setterJustSeen) {
1197 push(@currentIdentifiers, $word);
1198
1199 my $currentFunction = join('.', (@currentScopes, @currentIdentifiers));
1200 $currentFunction =~ s/\.{2,}/\./g; # Removes consecutive periods.
1201
1202 push(@currentFunctionNames, $currentFunction);
1203 push(@currentFunctionDepths, $bracesDepth);
1204 push(@currentFunctionStartLines, $.);
1205 } elsif ($word ne 'if' and $word ne 'for' and $word ne 'do' and $word ne 'while' and $word ne 'which' and $word ne 'var') {
1206 push(@currentIdentifiers, $word);
1207 }
1208
1209 $functionJustSeen = 0;
1210 $getterJustSeen = 0;
1211 $setterJustSeen = 0;
1212 $assignmentJustSeen = 0;
1213 }
1214 }
1215
1216 warn "mismatched braces in $fileName\n" if $bracesDepth;
1217 warn "mismatched parentheses in $fileName\n" if $parenthesesDepth;
1218
1219 return @ranges;
1220}
1221
aroben@apple.comd4acad02011-07-05 22:20:53 +00001222# Read a file and get all the line ranges of the things that look like Perl functions. Functions
1223# start on a line that starts with "sub ", and end on the first line starting with "}" thereafter.
1224#
1225# Result is a list of triples: [ start_line, end_line, function ].
1226
1227sub get_function_line_ranges_for_perl($$)
1228{
1229 my ($fileHandle, $fileName) = @_;
1230
1231 my @ranges;
1232
1233 my $currentFunction = "";
1234 my $start = 0;
1235
1236 while (<$fileHandle>) {
1237 if (/^sub\s+([^(\s]+)/) {
1238 # Skip over forward declarations, which don't contain a brace and end with a semicolon.
1239 next if !/{/ && /;$/;
1240
1241 if ($currentFunction) {
1242 warn "nested functions found at top-level at $fileName:$.\n";
1243 next;
1244 }
1245 $currentFunction = $1;
1246 $start = $.;
1247 }
1248 if (index($_, "}") == 0) {
1249 next unless $start;
1250 push(@ranges, [$start, $., $currentFunction]);
1251 $currentFunction = "";
1252 $start = 0;
1253 next;
1254 }
1255 }
1256
1257 return @ranges;
1258}
1259
aroben@apple.comb7e35352011-11-22 16:52:44 +00001260# Read a file and get all the line ranges of the things that look like Python classes, methods, or functions.
1261#
1262# FIXME: Maybe we should use Python's ast module to do the parsing for us?
1263#
1264# Result is a list of triples: [ start_line, end_line, function ].
1265
1266sub get_function_line_ranges_for_python($$)
1267{
1268 my ($fileHandle, $fileName) = @_;
1269
1270 my @ranges;
1271
1272 my @scopeStack = ({ line => 0, indent => -1, name => undef });
1273 while (<$fileHandle>) {
1274 next unless /^(\s*)(\S.*)$/;
1275 my $indent = length $1;
1276 my $rest = $2;
1277
1278 my $scope = $scopeStack[-1];
1279
1280 if ($indent <= $scope->{indent}) {
1281 # Find all the scopes that we have just exited.
1282 my $i = 0;
1283 for (; $i < @scopeStack; ++$i) {
1284 last if $indent <= $scopeStack[$i]->{indent};
1285 }
1286 my @poppedScopes = splice @scopeStack, $i;
1287
1288 # For each scope that was just exited, add a range that goes from the start of that
1289 # scope to the start of the next nested scope, or to the line just before this one for
1290 # the innermost scope.
1291 for ($i = 0; $i < @poppedScopes; ++$i) {
1292 my $lineAfterEnd = $i + 1 == @poppedScopes ? $. : $poppedScopes[$i + 1]->{line};
1293 push @ranges, [$poppedScopes[$i]->{line}, $lineAfterEnd - 1, $poppedScopes[$i]->{name}];
1294 }
1295 @scopeStack or warn "Popped off last scope at $fileName:$.\n";
1296
1297 # Set the now-current scope to start at the current line. Any lines within this scope
1298 # before this point should already have been added to @ranges.
1299 $scope = $scopeStack[-1];
1300 $scope->{line} = $.;
1301 }
1302
1303 next unless $rest =~ /(?:class|def)\s+(\w+)/;
1304 my $name = $1;
1305
1306 my $fullName = $scope->{name} ? join('.', $scope->{name}, $name) : $name;
1307 push @scopeStack, { line => $., indent => $indent, name => $fullName };
1308 }
1309
aroben@apple.comb7e35352011-11-22 16:52:44 +00001310 return @ranges;
1311}
1312
aroben@apple.comec7621d2010-03-12 19:06:01 +00001313# Read a file and get all the line ranges of the things that look like CSS selectors. A selector is
1314# anything before an opening brace on a line. A selector starts at the line containing the opening
1315# brace and ends at the closing brace.
1316# FIXME: Comments are parsed just like uncommented text.
1317#
1318# Result is a list of triples: [ start_line, end_line, selector ].
1319
1320sub get_selector_line_ranges_for_css($$)
1321{
1322 my ($fileHandle, $fileName) = @_;
1323
1324 my @ranges;
1325
1326 my $currentSelector = "";
1327 my $start = 0;
1328
1329 while (<$fileHandle>) {
1330 if (/^[ \t]*(.*[^ \t])[ \t]*{/) {
1331 $currentSelector = $1;
1332 $start = $.;
1333 }
1334 if (index($_, "}") >= 0) {
1335 unless ($start) {
1336 warn "mismatched braces in $fileName\n";
1337 next;
1338 }
1339 push(@ranges, [$start, $., $currentSelector]);
1340 $currentSelector = "";
1341 $start = 0;
1342 next;
1343 }
1344 }
1345
1346 return @ranges;
1347}
timothy@apple.com31f57e72008-10-14 19:17:08 +00001348
ddkilzerb7b3b912006-06-25 06:01:41 +00001349sub processPaths(\@)
aroben38e341c2007-04-28 06:57:49 +00001350{
ddkilzerb7b3b912006-06-25 06:01:41 +00001351 my ($paths) = @_;
1352 return ("." => 1) if (!@{$paths});
1353
1354 my %result = ();
1355
aroben38e341c2007-04-28 06:57:49 +00001356 for my $file (@{$paths}) {
ddkilzerb7b3b912006-06-25 06:01:41 +00001357 die "can't handle absolute paths like \"$file\"\n" if File::Spec->file_name_is_absolute($file);
1358 die "can't handle empty string path\n" if $file eq "";
1359 die "can't handle path with single quote in the name like \"$file\"\n" if $file =~ /'/; # ' (keep Xcode syntax highlighting happy)
1360
1361 my $untouchedFile = $file;
1362
1363 $file = canonicalizePath($file);
1364
1365 die "can't handle paths with .. like \"$untouchedFile\"\n" if $file =~ m|/\.\./|;
1366
1367 $result{$file} = 1;
aroben38e341c2007-04-28 06:57:49 +00001368 }
ddkilzerb7b3b912006-06-25 06:01:41 +00001369
1370 return ("." => 1) if ($result{"."});
1371
1372 # Remove any paths that also have a parent listed.
aroben38e341c2007-04-28 06:57:49 +00001373 for my $path (keys %result) {
1374 for (my $parent = dirname($path); $parent ne '.'; $parent = dirname($parent)) {
1375 if ($result{$parent}) {
ddkilzerb7b3b912006-06-25 06:01:41 +00001376 delete $result{$path};
1377 last;
aroben38e341c2007-04-28 06:57:49 +00001378 }
1379 }
1380 }
ddkilzerb7b3b912006-06-25 06:01:41 +00001381
1382 return %result;
aroben38e341c2007-04-28 06:57:49 +00001383}
aroben363e7842007-05-15 23:35:59 +00001384
aroben84c7c6a2007-07-19 01:41:03 +00001385sub diffFromToString()
1386{
arobenc3e1ec22007-07-19 17:23:54 +00001387 return "" if $isSVN;
ddkilzer3b890b42007-10-02 20:38:36 +00001388 return $gitCommit if $gitCommit =~ m/.+\.\..+/;
aroben84c7c6a2007-07-19 01:41:03 +00001389 return "\"$gitCommit^\" \"$gitCommit\"" if $gitCommit;
treat@webkit.orgcaf90532009-06-23 23:10:55 +00001390 return "--cached" if $gitIndex;
ojan@chromium.org03d32662010-04-05 17:55:14 +00001391 return $mergeBase if $mergeBase;
arobenc3e1ec22007-07-19 17:23:54 +00001392 return "HEAD" if $isGit;
aroben84c7c6a2007-07-19 01:41:03 +00001393}
1394
ddkilzer33ab5032007-05-28 20:48:56 +00001395sub diffCommand(@)
aroben363e7842007-05-15 23:35:59 +00001396{
ddkilzer33ab5032007-05-28 20:48:56 +00001397 my @paths = @_;
aroben363e7842007-05-15 23:35:59 +00001398
aroben84c7c6a2007-07-19 01:41:03 +00001399 my $pathsString = "'" . join("' '", @paths) . "'";
1400
1401 my $command;
arobenc3e1ec22007-07-19 17:23:54 +00001402 if ($isSVN) {
aroben@apple.com63af57d2010-03-10 22:19:15 +00001403 $command = "$SVN diff --diff-cmd diff -x -N $pathsString";
arobenc3e1ec22007-07-19 17:23:54 +00001404 } elsif ($isGit) {
eric@webkit.orge84536c2008-12-20 00:03:49 +00001405 $command = "$GIT diff --no-ext-diff -U0 " . diffFromToString();
ojan@chromium.org03d32662010-04-05 17:55:14 +00001406 $command .= " -- $pathsString" unless $gitCommit or $mergeBase;
aroben363e7842007-05-15 23:35:59 +00001407 }
1408
aroben84c7c6a2007-07-19 01:41:03 +00001409 return $command;
aroben363e7842007-05-15 23:35:59 +00001410}
1411
ddkilzer33ab5032007-05-28 20:48:56 +00001412sub statusCommand(@)
aroben363e7842007-05-15 23:35:59 +00001413{
ddkilzer33ab5032007-05-28 20:48:56 +00001414 my @files = @_;
aroben363e7842007-05-15 23:35:59 +00001415
paroga@webkit.orge7f34962011-05-22 18:08:02 +00001416 my $filesString = "\"" . join ("\" \"", @files) . "\"";
aroben363e7842007-05-15 23:35:59 +00001417 my $command;
arobenc3e1ec22007-07-19 17:23:54 +00001418 if ($isSVN) {
aroben363e7842007-05-15 23:35:59 +00001419 $command = "$SVN stat $filesString";
arobenc3e1ec22007-07-19 17:23:54 +00001420 } elsif ($isGit) {
aroben@apple.come84dcbb2010-10-21 17:03:47 +00001421 $command = "$GIT diff -r --name-status -M -C " . diffFromToString();
aroben84c7c6a2007-07-19 01:41:03 +00001422 $command .= " -- $filesString" unless $gitCommit;
aroben363e7842007-05-15 23:35:59 +00001423 }
1424
kmccullob7f3e552007-10-20 00:38:37 +00001425 return "$command 2>&1";
aroben363e7842007-05-15 23:35:59 +00001426}
1427
1428sub createPatchCommand($)
1429{
1430 my ($changedFilesString) = @_;
1431
aroben84c7c6a2007-07-19 01:41:03 +00001432 my $command;
arobenc3e1ec22007-07-19 17:23:54 +00001433 if ($isSVN) {
aroben84c7c6a2007-07-19 01:41:03 +00001434 $command = "'$FindBin::Bin/svn-create-patch' $changedFilesString";
arobenc3e1ec22007-07-19 17:23:54 +00001435 } elsif ($isGit) {
aroben@apple.come84dcbb2010-10-21 17:03:47 +00001436 $command = "$GIT diff -M -C " . diffFromToString();
aroben84c7c6a2007-07-19 01:41:03 +00001437 $command .= " -- $changedFilesString" unless $gitCommit;
1438 }
1439
1440 return $command;
aroben363e7842007-05-15 23:35:59 +00001441}
1442
1443sub diffHeaderFormat()
1444{
pkasting@chromium.orgf189b702009-08-12 22:15:51 +00001445 return qr/^Index: (\S+)[\r\n]*$/ if $isSVN;
arobenc3e1ec22007-07-19 17:23:54 +00001446 return qr/^diff --git a\/.+ b\/(.+)$/ if $isGit;
aroben363e7842007-05-15 23:35:59 +00001447}
1448
ddkilzer33ab5032007-05-28 20:48:56 +00001449sub findOriginalFileFromSvn($)
1450{
1451 my ($file) = @_;
1452 my $baseUrl;
1453 open INFO, "$SVN info . |" or die;
1454 while (<INFO>) {
pkasting@chromium.orgf189b702009-08-12 22:15:51 +00001455 if (/^URL: (.+?)[\r\n]*$/) {
ddkilzer33ab5032007-05-28 20:48:56 +00001456 $baseUrl = $1;
ddkilzer33ab5032007-05-28 20:48:56 +00001457 }
1458 }
1459 close INFO;
1460 my $sourceFile;
1461 open INFO, "$SVN info '$file' |" or die;
1462 while (<INFO>) {
pkasting@chromium.orgf189b702009-08-12 22:15:51 +00001463 if (/^Copied From URL: (.+?)[\r\n]*$/) {
ddkilzer33ab5032007-05-28 20:48:56 +00001464 $sourceFile = File::Spec->abs2rel($1, $baseUrl);
ddkilzer33ab5032007-05-28 20:48:56 +00001465 }
1466 }
1467 close INFO;
1468 return $sourceFile;
1469}
1470
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001471sub determinePropertyChanges($$$)
1472{
1473 my ($file, $isAdd, $original) = @_;
1474
1475 my %changes;
1476 if ($isAdd) {
1477 my %addedProperties;
1478 my %removedProperties;
1479 open PROPLIST, "$SVN proplist '$file' |" or die;
1480 while (<PROPLIST>) {
1481 $addedProperties{$1} = 1 if /^ (.+?)[\r\n]*$/ && $1 ne 'svn:mergeinfo';
1482 }
1483 close PROPLIST;
1484 if ($original) {
1485 open PROPLIST, "$SVN proplist '$original' |" or die;
1486 while (<PROPLIST>) {
1487 next unless /^ (.+?)[\r\n]*$/;
1488 my $property = $1;
1489 if (exists $addedProperties{$property}) {
1490 delete $addedProperties{$1};
1491 } else {
1492 $removedProperties{$1} = 1;
1493 }
1494 }
1495 }
1496 $changes{"A"} = [sort keys %addedProperties] if %addedProperties;
1497 $changes{"D"} = [sort keys %removedProperties] if %removedProperties;
1498 } else {
1499 open DIFF, "$SVN diff '$file' |" or die;
1500 while (<DIFF>) {
1501 if (/^Property changes on:/) {
1502 while (<DIFF>) {
1503 my $operation;
1504 my $property;
1505 if (/^Added: (\S*)/) {
1506 $operation = "A";
1507 $property = $1;
1508 } elsif (/^Modified: (\S*)/) {
1509 $operation = "M";
1510 $property = $1;
1511 } elsif (/^Deleted: (\S*)/) {
1512 $operation = "D";
1513 $property = $1;
1514 } elsif (/^Name: (\S*)/) {
1515 # Older versions of svn just say "Name" instead of the type
1516 # of property change.
1517 $operation = "C";
1518 $property = $1;
1519 }
1520 if ($operation) {
1521 $changes{$operation} = [] unless exists $changes{$operation};
1522 push @{$changes{$operation}}, $property;
1523 }
1524 }
1525 }
1526 }
1527 close DIFF;
1528 }
1529 return \%changes;
1530}
1531
1532sub pluralizeAndList($$@)
1533{
1534 my ($singular, $plural, @items) = @_;
1535
1536 return if @items == 0;
1537 return "$singular $items[0]" if @items == 1;
1538 return "$plural " . join(", ", @items[0 .. $#items - 1]) . " and " . $items[-1];
1539}
1540
ddkilzer33ab5032007-05-28 20:48:56 +00001541sub generateFileList(\@\@\%)
1542{
1543 my ($changedFiles, $conflictFiles, $functionLists) = @_;
1544 print STDERR " Running status to find changed, added, or removed files.\n";
1545 open STAT, "-|", statusCommand(keys %paths) or die "The status failed: $!.\n";
ddkilzer33ab5032007-05-28 20:48:56 +00001546 while (<STAT>) {
1547 my $status;
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001548 my $propertyStatus;
1549 my $propertyChanges;
ddkilzer33ab5032007-05-28 20:48:56 +00001550 my $original;
1551 my $file;
1552
arobenc3e1ec22007-07-19 17:23:54 +00001553 if ($isSVN) {
weinig@apple.com5e65e9f52009-03-27 23:15:01 +00001554 my $matches;
ddkilzer@apple.comf647b6d22009-09-02 15:24:23 +00001555 if (isSVNVersion16OrNewer()) {
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001556 $matches = /^([ ACDMR])([ CM]).{5} (.+?)[\r\n]*$/;
ddkilzer33ab5032007-05-28 20:48:56 +00001557 $status = $1;
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001558 $propertyStatus = $2;
1559 $file = $3;
weinig@apple.com5e65e9f52009-03-27 23:15:01 +00001560 } else {
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001561 $matches = /^([ ACDMR])([ CM]).{4} (.+?)[\r\n]*$/;
weinig@apple.com5e65e9f52009-03-27 23:15:01 +00001562 $status = $1;
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001563 $propertyStatus = $2;
1564 $file = $3;
weinig@apple.com5e65e9f52009-03-27 23:15:01 +00001565 }
1566 if ($matches) {
pkasting@chromium.org7f4f6982009-07-16 22:55:51 +00001567 $file = normalizePath($file);
ddkilzer33ab5032007-05-28 20:48:56 +00001568 $original = findOriginalFileFromSvn($file) if substr($_, 3, 1) eq "+";
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001569 my $isAdd = isAddedStatus($status);
1570 $propertyChanges = determinePropertyChanges($file, $isAdd, $original) if isModifiedStatus($propertyStatus) || $isAdd;
ddkilzer33ab5032007-05-28 20:48:56 +00001571 } else {
1572 print; # error output from svn stat
1573 }
arobenc3e1ec22007-07-19 17:23:54 +00001574 } elsif ($isGit) {
aroben84c7c6a2007-07-19 01:41:03 +00001575 if (/^([ADM])\t(.+)$/) {
1576 $status = $1;
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001577 $propertyStatus = " "; # git doesn't have properties
pkasting@chromium.org7f4f6982009-07-16 22:55:51 +00001578 $file = normalizePath($2);
aroben84c7c6a2007-07-19 01:41:03 +00001579 } elsif (/^([CR])[0-9]{1,3}\t([^\t]+)\t([^\t\n]+)$/) { # for example: R90% newfile oldfile
1580 $status = $1;
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001581 $propertyStatus = " ";
pkasting@chromium.org7f4f6982009-07-16 22:55:51 +00001582 $original = normalizePath($2);
1583 $file = normalizePath($3);
hausmann46f26492007-06-23 08:49:16 +00001584 } else {
aroben84c7c6a2007-07-19 01:41:03 +00001585 print; # error output from git diff
ddkilzer33ab5032007-05-28 20:48:56 +00001586 }
1587 }
1588
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001589 next if !$status || isUnmodifiedStatus($status) && isUnmodifiedStatus($propertyStatus);
ddkilzer33ab5032007-05-28 20:48:56 +00001590
1591 $file = makeFilePathRelative($file);
1592
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001593 if (isModifiedStatus($status) || isAddedStatus($status) || isModifiedStatus($propertyStatus)) {
ddkilzer33ab5032007-05-28 20:48:56 +00001594 my @components = File::Spec->splitdir($file);
aroben762ddbf2007-08-20 21:41:29 +00001595 if ($components[0] eq "LayoutTests") {
1596 $didChangeRegressionTests = 1;
ddkilzerfc34c912007-08-29 02:49:48 +00001597 push @addedRegressionTests, $file
1598 if isAddedStatus($status)
1599 && $file =~ /\.([a-zA-Z]+)$/
ddkilzer53a5e632007-08-29 21:25:00 +00001600 && $supportedTestExtensions{lc($1)}
darin@apple.com418bfed2009-09-25 21:07:59 +00001601 && !scalar(grep(/^resources$/i, @components))
1602 && !scalar(grep(/^script-tests$/i, @components));
aroben762ddbf2007-08-20 21:41:29 +00001603 }
ddkilzer33ab5032007-05-28 20:48:56 +00001604 push @{$changedFiles}, $file if $components[$#components] ne "ChangeLog";
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001605 } elsif (isConflictStatus($status) || isConflictStatus($propertyStatus)) {
ddkilzer33ab5032007-05-28 20:48:56 +00001606 push @{$conflictFiles}, $file;
1607 }
ddkilzer@apple.combc07ef42008-10-31 18:08:02 +00001608 if (basename($file) ne "ChangeLog") {
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001609 my $description = statusDescription($status, $propertyStatus, $original, $propertyChanges);
ddkilzer@apple.combc07ef42008-10-31 18:08:02 +00001610 $functionLists->{$file} = $description if defined $description;
1611 }
ddkilzer33ab5032007-05-28 20:48:56 +00001612 }
1613 close STAT;
1614}
1615
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001616sub isUnmodifiedStatus($)
1617{
1618 my ($status) = @_;
1619
1620 my %statusCodes = (
1621 " " => 1,
1622 );
1623
1624 return $statusCodes{$status};
1625}
1626
aroben762ddbf2007-08-20 21:41:29 +00001627sub isModifiedStatus($)
1628{
1629 my ($status) = @_;
1630
1631 my %statusCodes = (
1632 "M" => 1,
1633 );
1634
1635 return $statusCodes{$status};
1636}
1637
1638sub isAddedStatus($)
aroben363e7842007-05-15 23:35:59 +00001639{
1640 my ($status) = @_;
1641
aroben84c7c6a2007-07-19 01:41:03 +00001642 my %statusCodes = (
aroben363e7842007-05-15 23:35:59 +00001643 "A" => 1,
arobenc3e1ec22007-07-19 17:23:54 +00001644 "C" => $isGit,
aroben363e7842007-05-15 23:35:59 +00001645 "R" => 1,
1646 );
1647
aroben84c7c6a2007-07-19 01:41:03 +00001648 return $statusCodes{$status};
aroben363e7842007-05-15 23:35:59 +00001649}
1650
1651sub isConflictStatus($)
1652{
1653 my ($status) = @_;
1654
1655 my %svn = (
1656 "C" => 1,
1657 );
1658
1659 my %git = (
aroben84c7c6a2007-07-19 01:41:03 +00001660 "U" => 1,
aroben363e7842007-05-15 23:35:59 +00001661 );
1662
treat@webkit.orgcaf90532009-06-23 23:10:55 +00001663 return 0 if ($gitCommit || $gitIndex); # an existing commit or staged change cannot have conflicts
arobenc3e1ec22007-07-19 17:23:54 +00001664 return $svn{$status} if $isSVN;
1665 return $git{$status} if $isGit;
aroben363e7842007-05-15 23:35:59 +00001666}
1667
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001668sub statusDescription($$$$)
aroben363e7842007-05-15 23:35:59 +00001669{
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001670 my ($status, $propertyStatus, $original, $propertyChanges) = @_;
1671
1672 my $propertyDescription = defined $propertyChanges ? propertyChangeDescription($propertyChanges) : "";
aroben363e7842007-05-15 23:35:59 +00001673
1674 my %svn = (
ddkilzer33ab5032007-05-28 20:48:56 +00001675 "A" => defined $original ? " Copied from \%s." : " Added.",
aroben363e7842007-05-15 23:35:59 +00001676 "D" => " Removed.",
1677 "M" => "",
ddkilzer33ab5032007-05-28 20:48:56 +00001678 "R" => defined $original ? " Replaced with \%s." : " Replaced.",
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001679 " " => "",
aroben363e7842007-05-15 23:35:59 +00001680 );
aroben363e7842007-05-15 23:35:59 +00001681
aroben84c7c6a2007-07-19 01:41:03 +00001682 my %git = %svn;
1683 $git{"A"} = " Added.";
1684 $git{"C"} = " Copied from \%s.";
1685 $git{"R"} = " Renamed from \%s.";
1686
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001687 my $description;
1688 $description = sprintf($svn{$status}, $original) if $isSVN && exists $svn{$status};
1689 $description = sprintf($git{$status}, $original) if $isGit && exists $git{$status};
1690 return unless defined $description;
1691
eric@webkit.orgf6e89492009-09-08 15:51:30 +00001692 $description .= $propertyDescription unless isAddedStatus($status);
eric@webkit.orgec02a6f2009-08-27 06:38:22 +00001693 return $description;
1694}
1695
1696sub propertyChangeDescription($)
1697{
1698 my ($propertyChanges) = @_;
1699
1700 my %operations = (
1701 "A" => "Added",
1702 "M" => "Modified",
1703 "D" => "Removed",
1704 "C" => "Changed",
1705 );
1706
1707 my $description = "";
1708 while (my ($operation, $properties) = each %$propertyChanges) {
1709 my $word = $operations{$operation};
1710 my $list = pluralizeAndList("property", "properties", @$properties);
1711 $description .= " $word $list.";
1712 }
1713 return $description;
aroben363e7842007-05-15 23:35:59 +00001714}
1715
1716sub extractLineRange($)
1717{
1718 my ($string) = @_;
1719
1720 my ($start, $end) = (-1, -1);
1721
arobenc3e1ec22007-07-19 17:23:54 +00001722 if ($isSVN && $string =~ /^\d+(,\d+)?[acd](\d+)(,(\d+))?/) {
aroben363e7842007-05-15 23:35:59 +00001723 $start = $2;
1724 $end = $4 || $2;
aroben@apple.com7425bb12008-10-07 21:35:28 +00001725 } elsif ($isGit && $string =~ /^@@ -\d+(,\d+)? \+(\d+)(,(\d+))? @@/) {
1726 $start = $2;
1727 $end = defined($4) ? $4 + $2 - 1 : $2;
aroben363e7842007-05-15 23:35:59 +00001728 }
1729
1730 return ($start, $end);
1731}
1732
arobenc3e1ec22007-07-19 17:23:54 +00001733sub firstDirectoryOrCwd()
aroben363e7842007-05-15 23:35:59 +00001734{
aroben363e7842007-05-15 23:35:59 +00001735 my $dir = ".";
1736 my @dirs = keys(%paths);
ddkilzereee812d2007-05-20 14:12:06 +00001737
1738 $dir = -d $dirs[0] ? $dirs[0] : dirname($dirs[0]) if @dirs;
1739
arobenc3e1ec22007-07-19 17:23:54 +00001740 return $dir;
aroben363e7842007-05-15 23:35:59 +00001741}
aroben762ddbf2007-08-20 21:41:29 +00001742
1743sub testListForChangeLog(@)
1744{
1745 my (@tests) = @_;
1746
1747 return "" unless @tests;
1748
1749 my $leadString = " Test" . (@tests == 1 ? "" : "s") . ": ";
1750 my $list = $leadString;
1751 foreach my $i (0..$#tests) {
1752 $list .= " " x length($leadString) if $i;
1753 my $test = $tests[$i];
1754 $test =~ s/^LayoutTests\///;
1755 $list .= "$test\n";
1756 }
1757 $list .= "\n";
1758
1759 return $list;
1760}
ddkilzer3b890b42007-10-02 20:38:36 +00001761
1762sub reviewerAndDescriptionForGitCommit($)
1763{
1764 my ($commit) = @_;
1765
1766 my $description = '';
1767 my $reviewer;
1768
1769 my @args = qw(rev-list --pretty);
1770 push @args, '-1' if $commit !~ m/.+\.\..+/;
1771 my $gitLog;
1772 {
1773 local $/ = undef;
1774 open(GIT, "-|", $GIT, @args, $commit) || die;
1775 $gitLog = <GIT>;
1776 close(GIT);
1777 }
1778
1779 my @commitLogs = split(/^[Cc]ommit [a-f0-9]{40}/m, $gitLog);
1780 shift @commitLogs; # Remove initial blank commit log
1781 my $commitLogCount = 0;
1782 foreach my $commitLog (@commitLogs) {
1783 $description .= "\n" if $commitLogCount;
1784 $commitLogCount++;
1785 my $inHeader = 1;
eric@webkit.org40a261c2010-01-25 11:18:01 +00001786 my $commitLogIndent;
ddkilzer3b890b42007-10-02 20:38:36 +00001787 my @lines = split(/\n/, $commitLog);
1788 shift @lines; # Remove initial blank line
1789 foreach my $line (@lines) {
1790 if ($inHeader) {
1791 if (!$line) {
1792 $inHeader = 0;
ddkilzer3b890b42007-10-02 20:38:36 +00001793 }
1794 next;
hausmannff4297f2007-10-22 08:42:40 +00001795 } elsif ($line =~ /[Ss]igned-[Oo]ff-[Bb]y: (.+)/) {
1796 if (!$reviewer) {
1797 $reviewer = $1;
1798 } else {
1799 $reviewer .= ", " . $1;
1800 }
eric@webkit.org40a261c2010-01-25 11:18:01 +00001801 } elsif ($line =~ /^\s*$/) {
ddkilzer3b890b42007-10-02 20:38:36 +00001802 $description = $description . "\n";
1803 } else {
eric@webkit.org40a261c2010-01-25 11:18:01 +00001804 if (!defined($commitLogIndent)) {
1805 # Let the first line with non-white space determine
1806 # the global indent.
1807 $line =~ /^(\s*)\S/;
1808 $commitLogIndent = length($1);
1809 }
1810 # Strip at most the indent to preserve relative indents.
1811 $line =~ s/^\s{0,$commitLogIndent}//;
1812 $description = $description . (" " x 8) . $line . "\n";
ddkilzer3b890b42007-10-02 20:38:36 +00001813 }
1814 }
1815 }
lars077c3612007-10-09 13:52:51 +00001816 if (!$reviewer) {
1817 $reviewer = $gitReviewer;
1818 }
ddkilzer3b890b42007-10-02 20:38:36 +00001819
1820 return ($reviewer, $description);
1821}
abarth@webkit.orgc93f2152009-06-16 08:06:58 +00001822
1823sub normalizeLineEndings($$)
1824{
1825 my ($string, $endl) = @_;
1826 $string =~ s/\r?\n/$endl/g;
1827 return $string;
1828}
1829
levin@chromium.orga75fb0b2009-07-13 09:36:59 +00001830sub decodeEntities($)
1831{
1832 my ($text) = @_;
1833 $text =~ s/\&lt;/</g;
1834 $text =~ s/\&gt;/>/g;
1835 $text =~ s/\&quot;/\"/g;
1836 $text =~ s/\&apos;/\'/g;
1837 $text =~ s/\&amp;/\&/g;
1838 return $text;
1839}