WebKitTools/Scripts/commit-log-editor
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 #!/usr/bin/perl -w
       
     2 
       
     3 # Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc.  All rights reserved.
       
     4 # Copyright (C) 2009 Torch Mobile Inc. All rights reserved.
       
     5 #
       
     6 # Redistribution and use in source and binary forms, with or without
       
     7 # modification, are permitted provided that the following conditions
       
     8 # are met:
       
     9 #
       
    10 # 1.  Redistributions of source code must retain the above copyright
       
    11 #     notice, this list of conditions and the following disclaimer.
       
    12 # 2.  Redistributions in binary form must reproduce the above copyright
       
    13 #     notice, this list of conditions and the following disclaimer in the
       
    14 #     documentation and/or other materials provided with the distribution.
       
    15 # 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
       
    16 #     its contributors may be used to endorse or promote products derived
       
    17 #     from this software without specific prior written permission.
       
    18 #
       
    19 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
       
    20 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
    21 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
       
    22 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
       
    23 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
       
    24 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
       
    25 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
       
    26 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
       
    28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    29 
       
    30 # Script to put change log comments in as default check-in comment.
       
    31 
       
    32 use strict;
       
    33 use File::Basename;
       
    34 use File::Spec;
       
    35 use FindBin;
       
    36 use lib $FindBin::Bin;
       
    37 use Term::ReadKey;
       
    38 use VCSUtils;
       
    39 use webkitdirs;
       
    40 
       
    41 sub normalizeLineEndings($$);
       
    42 sub removeLongestCommonPrefixEndingInDoubleNewline(\%);
       
    43 sub isCommitLogEditor($);
       
    44 
       
    45 sub usage
       
    46 {
       
    47     print "Usage: [--help] [--regenerate-log] <log file>\n";
       
    48     exit 1;
       
    49 }
       
    50 
       
    51 my $help = checkForArgumentAndRemoveFromARGV("--help");
       
    52 if ($help) {
       
    53     usage();
       
    54 }
       
    55 
       
    56 my $regenerateLog = checkForArgumentAndRemoveFromARGV("--regenerate-log");
       
    57 my $log = $ARGV[0];
       
    58 if (!$log) {
       
    59     usage();
       
    60 }
       
    61 
       
    62 my $baseDir = baseProductDir();
       
    63 
       
    64 my $editor = $ENV{SVN_LOG_EDITOR};
       
    65 if (!$editor || isCommitLogEditor($editor)) {
       
    66     $editor = $ENV{CVS_LOG_EDITOR};
       
    67 }
       
    68 if (!$editor || isCommitLogEditor($editor)) {
       
    69     my $builtEditorApplication = "$baseDir/Release/Commit Log Editor.app/Contents/MacOS/Commit Log Editor";
       
    70     $editor = $builtEditorApplication if -x $builtEditorApplication;
       
    71 }
       
    72 if (!$editor || isCommitLogEditor($editor)) {
       
    73     my $builtEditorApplication = "$baseDir/Debug/Commit Log Editor.app/Contents/MacOS/Commit Log Editor";
       
    74     $editor = $builtEditorApplication if -x $builtEditorApplication;
       
    75 }
       
    76 if (!$editor || isCommitLogEditor($editor)) {
       
    77     my $installedEditorApplication = "$ENV{HOME}/Applications/Commit Log Editor.app/Contents/MacOS/Commit Log Editor";
       
    78     $editor = $installedEditorApplication if -x $installedEditorApplication;
       
    79 }
       
    80 if (!$editor || isCommitLogEditor($editor)) {
       
    81     $editor = $ENV{EDITOR};
       
    82 }
       
    83 if (!$editor || isCommitLogEditor($editor)) {
       
    84     $editor = "/usr/bin/vi";
       
    85 }
       
    86 
       
    87 my $inChangesToBeCommitted = !isGit();
       
    88 my @changeLogs = ();
       
    89 my $logContents = "";
       
    90 my $existingLog = 0;
       
    91 open LOG, $log or die "Could not open the log file.";
       
    92 while (<LOG>) {
       
    93     if (isGit()) {
       
    94         if (/^# Changes to be committed:$/) {
       
    95             $inChangesToBeCommitted = 1;
       
    96         } elsif ($inChangesToBeCommitted && /^# \S/) {
       
    97             $inChangesToBeCommitted = 0;
       
    98         }
       
    99     }
       
   100 
       
   101     if (!isGit() || /^#/) { #
       
   102         $logContents .= $_;
       
   103     } else {
       
   104         # $_ contains the current git log message
       
   105         # (without the log comment info). We don't need it.
       
   106     }
       
   107     $existingLog = isGit() && !(/^#/ || /^\s*$/) unless $existingLog;
       
   108 
       
   109     push @changeLogs, makeFilePathRelative($1) if $inChangesToBeCommitted && (/^(?:M|A)....(.*ChangeLog)\r?\n?$/ || /^#\t(?:modified|new file):   (.*ChangeLog)$/) && !/-ChangeLog$/;
       
   110 }
       
   111 close LOG;
       
   112 
       
   113 # We want to match the line endings of the existing log file in case they're
       
   114 # different from perl's line endings.
       
   115 my $endl = "\n";
       
   116 $endl = $1 if $logContents =~ /(\r?\n)/;
       
   117 
       
   118 my $keepExistingLog = 1;
       
   119 if ($regenerateLog && $existingLog && scalar(@changeLogs) > 0) {
       
   120     print "Existing log message detected, Use 'r' to regenerate log message from ChangeLogs, or any other key to keep the existing message.\n";
       
   121     ReadMode('cbreak');
       
   122     my $key = ReadKey(0);
       
   123     ReadMode('normal');
       
   124     $keepExistingLog = 0 if ($key eq "r");
       
   125 }
       
   126 
       
   127 # Don't change anything if there's already a log message
       
   128 # (as can happen with git-commit --amend)
       
   129 exec $editor, @ARGV if $existingLog && $keepExistingLog;
       
   130 
       
   131 my $topLevel = determineVCSRoot();
       
   132 
       
   133 my %changeLogSort;
       
   134 my %changeLogContents;
       
   135 for my $changeLog (@changeLogs) {
       
   136     open CHANGELOG, $changeLog or die "Can't open $changeLog";
       
   137     my $contents = "";
       
   138     my $blankLines = "";
       
   139     my $reviewedByLine = "";
       
   140     my $lineCount = 0;
       
   141     my $date = "";
       
   142     my $author = "";
       
   143     my $email = "";
       
   144     my $hasAuthorInfoToWrite = 0;
       
   145     while (<CHANGELOG>) {
       
   146         if (/^\S/) {
       
   147             last if $contents;
       
   148         }
       
   149         if (/\S/) {
       
   150             my $previousLineWasBlank = 1 unless $blankLines eq "";
       
   151             my $line = $_;
       
   152             my $currentLineBlankLines = $blankLines;
       
   153             $blankLines = "";
       
   154 
       
   155             # Remove indentation spaces
       
   156             $line =~ s/^ {8}//;
       
   157 
       
   158             # Save the reviewed / rubber stamped by line.
       
   159             if ($line =~ m/^Reviewed by .*/ || $line =~ m/^Rubber[ \-]?stamped by .*/) {
       
   160                 $reviewedByLine = $line;
       
   161                 next;
       
   162             }
       
   163 
       
   164             # Grab the author and the date line
       
   165             if ($line =~ m/^([0-9]{4}-[0-9]{2}-[0-9]{2})\s+(.*[^\s])\s+<(.*)>/ && $lineCount == 0) {
       
   166                 $date = $1;
       
   167                 $author = $2;
       
   168                 $email = $3;
       
   169                 $hasAuthorInfoToWrite = 1;
       
   170                 next;
       
   171             }
       
   172 
       
   173             $contents .= $currentLineBlankLines if $contents;
       
   174 
       
   175             # Attempt to insert the "patch by" line, after the first blank line.
       
   176             if ($previousLineWasBlank && $hasAuthorInfoToWrite && $lineCount > 0) {
       
   177                 my $committerEmail = changeLogEmailAddress();
       
   178                 my $authorAndCommitterAreSamePerson = $email eq $committerEmail;
       
   179                 if (!$authorAndCommitterAreSamePerson) {
       
   180                     $contents .= "Patch by $author <$email> on $date\n";
       
   181                     $hasAuthorInfoToWrite = 0;
       
   182                 }
       
   183             }
       
   184 
       
   185             # Attempt to insert the "reviewed by" line, after the first blank line.
       
   186             if ($previousLineWasBlank && $reviewedByLine && $lineCount > 0) {
       
   187                 $contents .= $reviewedByLine . "\n";
       
   188                 $reviewedByLine = "";
       
   189             }
       
   190 
       
   191             $lineCount++;
       
   192             $contents .= $line;
       
   193         } else {
       
   194             $blankLines .= $_;
       
   195         }
       
   196     }
       
   197     if ($reviewedByLine) {
       
   198         $contents .= "\n".$reviewedByLine;
       
   199     }
       
   200     close CHANGELOG;
       
   201 
       
   202     $changeLog = File::Spec->abs2rel(File::Spec->rel2abs($changeLog), $topLevel);
       
   203 
       
   204     my $label = dirname($changeLog);
       
   205     $label = "top level" unless length $label;
       
   206 
       
   207     my $sortKey = lc $label;
       
   208     if ($label eq "top level") {
       
   209         $sortKey = "";
       
   210     } elsif ($label eq "LayoutTests") {
       
   211         $sortKey = lc "~, LayoutTests last";
       
   212     }
       
   213 
       
   214     $changeLogSort{$sortKey} = $label;
       
   215     $changeLogContents{$label} = $contents;
       
   216 }
       
   217 
       
   218 my $commonPrefix = removeLongestCommonPrefixEndingInDoubleNewline(%changeLogContents);
       
   219 
       
   220 my $first = 1;
       
   221 open NEWLOG, ">$log.edit" or die;
       
   222 if (isGit() && scalar keys %changeLogSort == 0) {
       
   223     # populate git commit message with WebKit-format ChangeLog entries unless explicitly disabled
       
   224     my $branch = gitBranch();
       
   225     chomp(my $webkitGenerateCommitMessage = `git config --bool branch.$branch.webkitGenerateCommitMessage`);
       
   226     if ($webkitGenerateCommitMessage eq "") {
       
   227         chomp($webkitGenerateCommitMessage = `git config --bool core.webkitGenerateCommitMessage`);
       
   228     }
       
   229     if ($webkitGenerateCommitMessage ne "false") {
       
   230         open CHANGELOG_ENTRIES, "-|", "$FindBin::Bin/prepare-ChangeLog --git-index --no-write" or die "prepare-ChangeLog failed: $!.\n";
       
   231         while (<CHANGELOG_ENTRIES>) {
       
   232             print NEWLOG normalizeLineEndings($_, $endl);
       
   233         }
       
   234         close CHANGELOG_ENTRIES;
       
   235     }
       
   236 } else {
       
   237     print NEWLOG normalizeLineEndings($commonPrefix, $endl);
       
   238     for my $sortKey (sort keys %changeLogSort) {
       
   239         my $label = $changeLogSort{$sortKey};
       
   240         if (keys %changeLogSort > 1) {
       
   241             print NEWLOG normalizeLineEndings("\n", $endl) if !$first;
       
   242             $first = 0;
       
   243             print NEWLOG normalizeLineEndings("$label: ", $endl);
       
   244         }
       
   245         print NEWLOG normalizeLineEndings($changeLogContents{$label}, $endl);
       
   246     }
       
   247 }
       
   248 print NEWLOG $logContents;
       
   249 close NEWLOG;
       
   250 
       
   251 system $editor, "$log.edit";
       
   252 
       
   253 open NEWLOG, "$log.edit" or exit;
       
   254 my $foundComment = 0;
       
   255 while (<NEWLOG>) {
       
   256     $foundComment = 1 if (/\S/ && !/^CVS:/);
       
   257 }
       
   258 close NEWLOG;
       
   259 
       
   260 if ($foundComment) {
       
   261     open NEWLOG, "$log.edit" or die;
       
   262     open LOG, ">$log" or die;
       
   263     while (<NEWLOG>) {
       
   264         print LOG;
       
   265     }
       
   266     close LOG;
       
   267     close NEWLOG;
       
   268 }
       
   269 
       
   270 unlink "$log.edit";
       
   271 
       
   272 sub normalizeLineEndings($$)
       
   273 {
       
   274     my ($string, $endl) = @_;
       
   275     $string =~ s/\r?\n/$endl/g;
       
   276     return $string;
       
   277 }
       
   278 
       
   279 sub removeLongestCommonPrefixEndingInDoubleNewline(\%)
       
   280 {
       
   281     my ($hashOfStrings) = @_;
       
   282 
       
   283     my @strings = values %{$hashOfStrings};
       
   284     return "" unless @strings > 1;
       
   285 
       
   286     my $prefix = shift @strings;
       
   287     my $prefixLength = length $prefix;
       
   288     foreach my $string (@strings) {
       
   289         while ($prefixLength) {
       
   290             last if substr($string, 0, $prefixLength) eq $prefix;
       
   291             --$prefixLength;
       
   292             $prefix = substr($prefix, 0, -1);
       
   293         }
       
   294         last unless $prefixLength;
       
   295     }
       
   296 
       
   297     return "" unless $prefixLength;
       
   298 
       
   299     my $lastDoubleNewline = rindex($prefix, "\n\n");
       
   300     return "" unless $lastDoubleNewline > 0;
       
   301 
       
   302     foreach my $key (keys %{$hashOfStrings}) {
       
   303         $hashOfStrings->{$key} = substr($hashOfStrings->{$key}, $lastDoubleNewline);
       
   304     }
       
   305     return substr($prefix, 0, $lastDoubleNewline + 2);
       
   306 }
       
   307 
       
   308 sub isCommitLogEditor($)
       
   309 {
       
   310     my $editor = shift;
       
   311     return $editor =~ m/commit-log-editor/;
       
   312 }