1#!/usr/bin/perl -w 2 3# Copyright (C) 2006, 2007, 2008, 2009 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 32use strict; 33use File::Basename; 34use File::Spec; 35use FindBin; 36use lib $FindBin::Bin; 37use Term::ReadKey; 38use VCSUtils; 39use webkitdirs; 40 41sub normalizeLineEndings($$); 42 43sub usage 44{ 45 print "Usage: [--help] [--regenerate-log] <log file>\n"; 46 exit 1; 47} 48 49my $help = checkForArgumentAndRemoveFromARGV("--help"); 50if ($help) { 51 usage(); 52} 53 54my $regenerateLog = checkForArgumentAndRemoveFromARGV("--regenerate-log"); 55my $log = $ARGV[0]; 56if (!$log) { 57 usage(); 58} 59 60my $baseDir = baseProductDir(); 61 62my $editor = $ENV{SVN_LOG_EDITOR}; 63if (!$editor) { 64 $editor = $ENV{CVS_LOG_EDITOR}; 65} 66if (!$editor) { 67 my $builtEditorApplication = "$baseDir/Release/Commit Log Editor.app/Contents/MacOS/Commit Log Editor"; 68 $editor = $builtEditorApplication if -x $builtEditorApplication; 69} 70if (!$editor) { 71 my $builtEditorApplication = "$baseDir/Debug/Commit Log Editor.app/Contents/MacOS/Commit Log Editor"; 72 $editor = $builtEditorApplication if -x $builtEditorApplication; 73} 74if (!$editor) { 75 my $installedEditorApplication = "$ENV{HOME}/Applications/Commit Log Editor.app/Contents/MacOS/Commit Log Editor"; 76 $editor = $installedEditorApplication if -x $installedEditorApplication; 77} 78if (!$editor) { 79 $editor = $ENV{EDITOR} || "/usr/bin/vi"; 80} 81 82my $inChangesToBeCommitted = !isGit(); 83my @changeLogs = (); 84my $logContents = ""; 85my $existingLog = 0; 86open LOG, $log or die; 87while (<LOG>) { 88 if (isGit()) { 89 if (/^# Changes to be committed:$/) { 90 $inChangesToBeCommitted = 1; 91 } elsif ($inChangesToBeCommitted && /^# \S/) { 92 $inChangesToBeCommitted = 0; 93 } 94 } 95 96 if (!isGit() || /^#/) { # 97 $logContents .= $_; 98 } else { 99 # $_ contains the current git log message 100 # (without the log comment info). We don't need it. 101 } 102 $existingLog = isGit() && !(/^#/ || /^\s*$/) unless $existingLog; 103 104 push @changeLogs, makeFilePathRelative($1) if $inChangesToBeCommitted && (/^M....(.*ChangeLog)\r?\n?$/ || /^#\tmodified: (.*ChangeLog)/) && !/-ChangeLog/; 105} 106close LOG; 107 108# We want to match the line endings of the existing log file in case they're 109# different from perl's line endings. 110my $endl = "\n"; 111$endl = $1 if $logContents =~ /(\r?\n)/; 112 113my $keepExistingLog = 1; 114if ($regenerateLog && $existingLog && scalar(@changeLogs) > 0) { 115 print "Existing log message detected, Use 'r' to regenerate log message from ChangeLogs, or any other key to keep the existing message.\n"; 116 ReadMode('cbreak'); 117 my $key = ReadKey(0); 118 ReadMode('normal'); 119 $keepExistingLog = 0 if ($key eq "r"); 120} 121 122# Don't change anything if there's already a log message 123# (as can happen with git-commit --amend) 124exec $editor, @ARGV if $existingLog && $keepExistingLog; 125 126my $topLevel = determineVCSRoot(); 127 128my %changeLogSort; 129my %changeLogContents; 130for my $changeLog (@changeLogs) { 131 open CHANGELOG, $changeLog or die "Can't open $changeLog"; 132 my $contents = ""; 133 my $blankLines = ""; 134 my $reviewedByLine = ""; 135 my $lineCount = 0; 136 my $date = ""; 137 my $author = ""; 138 my $email = ""; 139 my $hasAuthorInfoToWrite = 0; 140 while (<CHANGELOG>) { 141 if (/^\S/) { 142 last if $contents; 143 } 144 if (/\S/) { 145 my $previousLineWasBlank = 1 unless $blankLines eq ""; 146 my $line = $_; 147 my $currentLineBlankLines = $blankLines; 148 $blankLines = ""; 149 150 # Remove indentation spaces 151 $line =~ s/^ {8}//; 152 153 # Save the reviewed by line 154 if ($line =~ m/^Reviewed by .*/) { 155 $reviewedByLine = $line; 156 next; 157 } 158 159 # Grab the author and the date line 160 if ($line =~ m/^([0-9]{4}-[0-9]{2}-[0-9]{2})\s+(.*[^\s])\s+<(.*)>/ && $lineCount == 0) { 161 $date = $1; 162 $author = $2; 163 $email = $3; 164 $hasAuthorInfoToWrite = 1; 165 next; 166 } 167 168 $contents .= $currentLineBlankLines if $contents; 169 170 # Attempt to insert the "patch by" line, after the first blank line. 171 if ($previousLineWasBlank && $hasAuthorInfoToWrite && $lineCount > 0) { 172 my $authorAndCommitterAreSamePerson = $ENV{EMAIL_ADDRESS} && $email eq $ENV{EMAIL_ADDRESS}; 173 if (!$authorAndCommitterAreSamePerson) { 174 $contents .= "Patch by $author <$email> on $date\n"; 175 $hasAuthorInfoToWrite = 0; 176 } 177 } 178 179 # Attempt to insert the "reviewed by" line, after the first blank line. 180 if ($previousLineWasBlank && $reviewedByLine && $lineCount > 0) { 181 $contents .= $reviewedByLine . "\n"; 182 $reviewedByLine = ""; 183 } 184 185 186 $lineCount++; 187 $contents .= $line; 188 } else { 189 $blankLines .= $_; 190 } 191 } 192 if ($reviewedByLine) { 193 $contents .= "\n".$reviewedByLine; 194 } 195 close CHANGELOG; 196 197 $changeLog = File::Spec->abs2rel(File::Spec->rel2abs($changeLog), $topLevel); 198 199 my $label = dirname($changeLog); 200 $label = "top level" unless length $label; 201 202 my $sortKey = lc $label; 203 if ($label eq "top level") { 204 $sortKey = ""; 205 } elsif ($label eq "Tools") { 206 $sortKey = "-, just after top level"; 207 } elsif ($label eq "WebBrowser") { 208 $sortKey = lc "WebKit, WebBrowser after"; 209 } elsif ($label eq "WebCore") { 210 $sortKey = lc "WebFoundation, WebCore after"; 211 } elsif ($label eq "LayoutTests") { 212 $sortKey = lc "~, LayoutTests last"; 213 } 214 215 $changeLogSort{$sortKey} = $label; 216 $changeLogContents{$label} = $contents; 217} 218 219my $first = 1; 220open NEWLOG, ">$log.edit" or die; 221if (isGit() && scalar keys %changeLogSort == 0) { 222 # populate git commit message with WebKit-format ChangeLog entries unless explicitly disabled 223 my $branch = gitBranch(); 224 chomp(my $webkitGenerateCommitMessage = `git config --bool branch.$branch.webkitGenerateCommitMessage`); 225 if ($webkitGenerateCommitMessage eq "") { 226 chomp($webkitGenerateCommitMessage = `git config --bool core.webkitGenerateCommitMessage`); 227 } 228 if ($webkitGenerateCommitMessage ne "false") { 229 open CHANGELOG_ENTRIES, "-|", "prepare-ChangeLog --git-index --no-write" or die "prepare-ChangeLog failed: $!.\n"; 230 while (<CHANGELOG_ENTRIES>) { 231 print NEWLOG normalizeLineEndings($_, $endl); 232 } 233 close CHANGELOG_ENTRIES; 234 } 235} else { 236 for my $sortKey (sort keys %changeLogSort) { 237 my $label = $changeLogSort{$sortKey}; 238 if (keys %changeLogSort > 1) { 239 print NEWLOG normalizeLineEndings("\n", $endl) if !$first; 240 $first = 0; 241 print NEWLOG normalizeLineEndings("$label: ", $endl); 242 } 243 print NEWLOG normalizeLineEndings($changeLogContents{$label}, $endl); 244 } 245} 246print NEWLOG $logContents; 247close NEWLOG; 248 249system $editor, "$log.edit"; 250 251open NEWLOG, "$log.edit" or exit; 252my $foundComment = 0; 253while (<NEWLOG>) { 254 $foundComment = 1 if (/\S/ && !/^CVS:/); 255} 256close NEWLOG; 257 258if ($foundComment) { 259 open NEWLOG, "$log.edit" or die; 260 open LOG, ">$log" or die; 261 while (<NEWLOG>) { 262 print LOG; 263 } 264 close LOG; 265 close NEWLOG; 266} 267 268unlink "$log.edit"; 269 270sub normalizeLineEndings($$) 271{ 272 my ($string, $endl) = @_; 273 $string =~ s/\r?\n/$endl/g; 274 return $string; 275} 276