1#!/usr/bin/perl 2# Copyright (C) 2012-2014 Free Software Foundation, Inc. 3# 4# This file is part of GCC. 5# 6# GCC is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 3, or (at your option) 9# any later version. 10# 11# GCC is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with GCC; see the file COPYING. If not, write to 18# the Free Software Foundation, 51 Franklin Street, Fifth Floor, 19# Boston, MA 02110-1301, USA. 20 21# This script parses a .diff file generated with 'diff -up' or 'diff -cp' 22# and adds a skeleton ChangeLog file to the file. It does not try to be 23# very smart when parsing function names, but it produces a reasonable 24# approximation. 25# 26# Author: Diego Novillo <dnovillo@google.com> and 27# Cary Coutant <ccoutant@google.com> 28 29use File::Temp; 30use File::Copy qw(cp mv); 31 32$date = `date +%Y-%m-%d`; chop ($date); 33 34$dot_mklog_format_msg = 35 "The .mklog format is:\n" 36 . "NAME = ...\n" 37 . "EMAIL = ...\n"; 38 39# Create a .mklog to reflect your profile, if necessary. 40my $conf = "$ENV{HOME}/.mklog"; 41if (-f "$conf") { 42 open (CONF, "$conf") 43 or die "Could not open file '$conf' for reading: $!\n"; 44 while (<CONF>) { 45 if (m/^\s*NAME\s*=\s*(.*?)\s*$/) { 46 $name = $1; 47 } elsif (m/^\s*EMAIL\s*=\s*(.*?)\s*$/) { 48 $addr = $1; 49 } 50 } 51 if (!($name && $addr)) { 52 die "Could not read .mklog settings.\n" 53 . $dot_mklog_format_msg; 54 } 55} else { 56 $name = `git config user.name`; 57 chomp($name); 58 $addr = `git config user.email`; 59 chomp($addr); 60 61 if (!($name && $addr)) { 62 die "Could not read git user.name and user.email settings.\n" 63 . "Please add missing git settings, or create a .mklog file in" 64 . " $ENV{HOME}.\n" 65 . $dot_mklog_format_msg; 66 } 67} 68 69$gcc_root = $0; 70$gcc_root =~ s/[^\\\/]+$/../; 71 72#----------------------------------------------------------------------------- 73# Program starts here. You should not need to edit anything below this 74# line. 75#----------------------------------------------------------------------------- 76$inline = 0; 77if ($#ARGV == 1 && ("$ARGV[0]" eq "-i" || "$ARGV[0]" eq "--inline")) { 78 shift; 79 $inline = 1; 80} elsif ($#ARGV != 0) { 81 $prog = `basename $0`; chop ($prog); 82 print <<EOF; 83usage: $prog [ -i | --inline ] file.diff 84 85Generate ChangeLog template for file.diff. 86It assumes that patch has been created with -up or -cp. 87When -i is used, the ChangeLog template is followed by the contents of 88file.diff. 89When file.diff is -, read standard input. 90When -i is used and file.diff is not -, it writes to file.diff, otherwise it 91writes to stdout. 92EOF 93 exit 1; 94} 95 96$diff = $ARGV[0]; 97$dir = `dirname $diff`; chop ($dir); 98$basename = `basename $diff`; chop ($basename); 99$hdrline = "$date $name <$addr>"; 100 101sub get_clname ($) { 102 return ('ChangeLog', $_[0]) if ($_[0] !~ /[\/\\]/); 103 104 my $dirname = $_[0]; 105 while ($dirname) { 106 my $clname = "$dirname/ChangeLog"; 107 if (-f "$gcc_root/$clname") { 108 my $relname = substr ($_[0], length ($dirname) + 1); 109 return ($clname, $relname); 110 } else { 111 $dirname =~ s/[\/\\]?[^\/\\]*$//; 112 } 113 } 114 115 return ('Unknown ChangeLog', $_[0]); 116} 117 118sub remove_suffixes ($) { 119 my $filename = $_[0]; 120 $filename =~ s/^[ab]\///; 121 $filename =~ s/\.jj$//; 122 return $filename; 123} 124 125sub is_context_hunk_start { 126 return @_[0] =~ /^\*\*\*\*\*\** ([a-zA-Z0-9_].*)/; 127} 128 129sub is_unified_hunk_start { 130 return @_[0] =~ /^@@ .* @@ ([a-zA-Z0-9_].*)/; 131} 132 133# Check if line is a top-level declaration. 134# TODO: ignore preprocessor directives except maybe #define ? 135sub is_top_level { 136 my ($function, $is_context_diff) = (@_); 137 if (is_unified_hunk_start ($function) 138 || is_context_hunk_start ($function)) { 139 return 1; 140 } 141 if ($is_context_diff) { 142 $function =~ s/^..//; 143 } else { 144 $function =~ s/^.//; 145 } 146 return $function && $function !~ /^[\s{]/; 147} 148 149# Read contents of .diff file 150open (DFILE, $diff) or die "Could not open file $diff for reading"; 151chomp (my @diff_lines = <DFILE>); 152close (DFILE); 153 154# Array diff_lines is modified by the log generation, so save a copy in 155# orig_diff_lines if needed. 156if ($inline) { 157 @orig_diff_lines = @diff_lines; 158} 159 160# For every file in the .diff print all the function names in ChangeLog 161# format. 162%cl_entries = (); 163$change_msg = undef; 164$look_for_funs = 0; 165$clname = get_clname(''); 166$line_idx = 0; 167foreach (@diff_lines) { 168 # Stop processing functions if we found a new file. 169 # Remember both left and right names because one may be /dev/null. 170 # Don't be fooled by line markers in case of context diff. 171 if (!/\*\*\*$/ && /^[+*][+*][+*] +(\S+)/) { 172 $left = remove_suffixes ($1); 173 $look_for_funs = 0; 174 } 175 if (!/---$/ && /^--- +(\S+)?/) { 176 $right = remove_suffixes ($1); 177 $look_for_funs = 0; 178 } 179 180 # Check if the body of diff started. 181 # We should now have both left and right name, 182 # so we can decide filename. 183 184 if ($left && (/^\*{15}/ || /^@@ /)) { 185 # If we have not seen any function names in the previous file (ie, 186 # $change_msg is empty), we just write out a ':' before starting the next 187 # file. 188 if ($clname) { 189 $cl_entries{$clname} .= $change_msg ? "$change_msg" : ":\n"; 190 } 191 192 if ($left eq $right) { 193 $filename = $left; 194 } elsif($left eq '/dev/null') { 195 $filename = $right; 196 } elsif($right eq '/dev/null') { 197 $filename = $left; 198 } else { 199 print STDERR "Error: failed to parse diff for $left and $right\n"; 200 exit 1; 201 } 202 $left = $right = undef; 203 ($clname, $relname) = get_clname ($filename); 204 $cl_entries{$clname} .= "\t* $relname"; 205 $change_msg = ''; 206 $look_for_funs = $filename =~ '\.(c|cpp|C|cc|h|inc|def)$'; 207 } 208 209 # Context diffs have extra whitespace after first char; 210 # remove it to make matching easier. 211 if ($is_context_diff) { 212 s/^([-+! ]) /\1/; 213 } 214 215 # Remember the last line in a diff block that might start 216 # a new function. 217 if (/^[-+! ]([a-zA-Z0-9_].*)/) { 218 $save_fn = $1; 219 } 220 221 # Check if file is newly added. 222 # Two patterns: for context and unified diff. 223 if (/^\*\*\* 0 \*\*\*\*/ 224 || /^@@ -0,0 \+1.* @@/) { 225 $change_msg = $filename =~ /testsuite.*(?<!\.exp)$/ ? ": New test.\n" : ": New file.\n"; 226 $look_for_funs = 0; 227 } 228 229 # Check if file was removed. 230 # Two patterns: for context and unified diff. 231 if (/^--- 0 ----/ 232 || /^@@ -1.* \+0,0 @@/) { 233 $change_msg = ": Remove.\n"; 234 $look_for_funs = 0; 235 } 236 237 if (is_unified_hunk_start ($diff_lines[$line_idx])) { 238 $is_context_diff = 0; 239 } 240 elsif (is_context_hunk_start ($diff_lines[$line_idx])) { 241 $is_context_diff = 1; 242 } 243 244 # If we find a new function, print it in brackets. Special case if 245 # this is the first function in a file. 246 # 247 # Note that we don't try too hard to find good matches. This should 248 # return a superset of the actual set of functions in the .diff file. 249 # 250 # The first pattern works with context diff files (diff -c). The 251 # second pattern works with unified diff files (diff -u). 252 # 253 # The third pattern looks for the starts of functions or classes 254 # within a diff block both for context and unified diff files. 255 if ($look_for_funs 256 && (/^\*\*\*\*\*\** ([a-zA-Z0-9_].*)/ 257 || /^@@ .* @@ ([a-zA-Z0-9_].*)/ 258 || /^[-+! ](\{)/)) 259 { 260 $_ = $1; 261 my $fn; 262 if (/^\{/) { 263 # Beginning of a new function. 264 $_ = $save_fn; 265 } else { 266 $save_fn = ""; 267 } 268 if (/;$/) { 269 # No usable function name found. 270 } elsif (/^((class|struct|union|enum) [a-zA-Z0-9_]+)/) { 271 # Discard stuff after the class/struct/etc. tag. 272 $fn = $1; 273 } elsif (/([a-zA-Z0-9_][^(]*)\(/) { 274 # Discard template and function parameters. 275 $fn = $1; 276 1 while ($fn =~ s/<[^<>]*>//); 277 $fn =~ s/[ \t]*$//; 278 } 279 # Check is function really modified 280 $no_real_change = 0; 281 $idx = $line_idx; 282 # Skip line info in context diffs. 283 while ($idx <= $#diff_lines && $is_context_diff 284 && $diff_lines[$idx + 1] =~ /^[-\*]{3} [0-9]/) { 285 ++$idx; 286 } 287 # Check all lines till the first change 288 # for the presence of really changed function 289 do { 290 ++$idx; 291 $no_real_change = $idx > $#diff_lines 292 || is_top_level ($diff_lines[$idx], $is_context_diff); 293 } while (!$no_real_change && ($diff_lines[$idx] !~ /^[-+!]/)); 294 if ($fn && !$seen_names{$fn} && !$no_real_change) { 295 # If this is the first function in the file, we display it next 296 # to the filename, so we need an extra space before the opening 297 # brace. 298 if (!$change_msg) { 299 $change_msg .= " "; 300 } else { 301 $change_msg .= "\t"; 302 } 303 304 $change_msg .= "($fn):\n"; 305 $seen_names{$fn} = 1; 306 } 307 } 308 $line_idx++; 309} 310 311# If we have not seen any function names (ie, $change_msg is empty), we just 312# write out a ':'. This happens when there is only one file with no 313# functions. 314$cl_entries{$clname} .= $change_msg ? "$change_msg\n" : ":\n"; 315 316if ($inline && $diff ne "-") { 317 # Get a temp filename, rather than an open filehandle, because we use 318 # the open to truncate. 319 $tmp = mktemp("tmp.XXXXXXXX") or die "Could not create temp file: $!"; 320 321 # Copy the permissions to the temp file (in File::Copy module version 322 # 2.15 and later). 323 cp $diff, $tmp or die "Could not copy patch file to temp file: $!"; 324 325 # Open the temp file, clearing contents. 326 open (OUTPUTFILE, '>', $tmp) or die "Could not open temp file: $!"; 327} else { 328 *OUTPUTFILE = STDOUT; 329} 330 331# Print the log 332foreach my $clname (keys %cl_entries) { 333 print OUTPUTFILE "$clname:\n\n$hdrline\n\n$cl_entries{$clname}\n"; 334} 335 336if ($inline) { 337 # Append the patch to the log 338 foreach (@orig_diff_lines) { 339 print OUTPUTFILE "$_\n"; 340 } 341} 342 343if ($inline && $diff ne "-") { 344 # Close $tmp 345 close(OUTPUTFILE); 346 347 # Write new contents to $diff atomically 348 mv $tmp, $diff or die "Could not move temp file to patch file: $!"; 349} 350 351exit 0; 352