1#!/usr/bin/perl 2# Copyright (C) 2012-2017 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" || -f "$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. 134sub is_top_level { 135 my ($function, $is_context_diff) = (@_); 136 if (is_unified_hunk_start ($function) 137 || is_context_hunk_start ($function)) { 138 return 1; 139 } 140 if ($is_context_diff) { 141 $function =~ s/^..//; 142 } else { 143 $function =~ s/^.//; 144 } 145 return $function && $function !~ /^[\s{#]/; 146} 147 148# Read contents of .diff file 149open (DFILE, $diff) or die "Could not open file $diff for reading"; 150chomp (my @diff_lines = <DFILE>); 151close (DFILE); 152 153# Array diff_lines is modified by the log generation, so save a copy in 154# orig_diff_lines if needed. 155if ($inline) { 156 @orig_diff_lines = @diff_lines; 157} 158 159# For every file in the .diff print all the function names in ChangeLog 160# format. 161%cl_entries = (); 162$change_msg = undef; 163$look_for_funs = 0; 164$clname = get_clname(''); 165$line_idx = 0; 166foreach (@diff_lines) { 167 # Stop processing functions if we found a new file. 168 # Remember both left and right names because one may be /dev/null. 169 # Don't be fooled by line markers in case of context diff. 170 if (!/\*\*\*$/ && /^[+*][+*][+*] +(\S+)/) { 171 $left = remove_suffixes ($1); 172 $look_for_funs = 0; 173 } 174 if (!/---$/ && /^--- +(\S+)?/) { 175 $right = remove_suffixes ($1); 176 $look_for_funs = 0; 177 } 178 179 # Check if the body of diff started. 180 # We should now have both left and right name, 181 # so we can decide filename. 182 183 if ($left && (/^\*{15}/ || /^@@ /)) { 184 # If we have not seen any function names in the previous file (ie, 185 # $change_msg is empty), we just write out a ':' before starting the next 186 # file. 187 if ($clname) { 188 $cl_entries{$clname} .= $change_msg ? "$change_msg" : ":\n"; 189 } 190 191 if ($left eq $right) { 192 $filename = $left; 193 } elsif($left eq '/dev/null') { 194 $filename = $right; 195 } elsif($right eq '/dev/null') { 196 $filename = $left; 197 } else { 198 my @ldirs = split /[\/\\]/, $left; 199 my @rdirs = split /[\/\\]/, $right; 200 201 $filename = ''; 202 while ((my $l = pop @ldirs) && (my $r = pop @rdirs)) { 203 last if ($l ne $r); 204 $filename = "$l/$filename"; 205 } 206 $filename =~ s/\/$//; 207 208 if (!$filename) { 209 print STDERR "Error: failed to parse diff for $left and $right\n"; 210 exit 1; 211 } 212 } 213 $left = $right = undef; 214 ($clname, $relname) = get_clname ($filename); 215 $cl_entries{$clname} .= "\t* $relname"; 216 $change_msg = ''; 217 $look_for_funs = $filename =~ '\.(c|cpp|C|cc|h|inc|def)$'; 218 } 219 220 # Context diffs have extra whitespace after first char; 221 # remove it to make matching easier. 222 if ($is_context_diff) { 223 s/^([-+! ]) /\1/; 224 } 225 226 # Remember the last line in a diff block that might start 227 # a new function. 228 if (/^[-+! ]([a-zA-Z0-9_].*)/) { 229 $save_fn = $1; 230 } 231 232 # Check if file is newly added. 233 # Two patterns: for context and unified diff. 234 if (/^\*\*\* 0 \*\*\*\*/ 235 || /^@@ -0,0 \+1.* @@/) { 236 $change_msg = $filename =~ /testsuite.*(?<!\.exp)$/ ? ": New test.\n" : ": New file.\n"; 237 $look_for_funs = 0; 238 } 239 240 # Check if file was removed. 241 # Two patterns: for context and unified diff. 242 if (/^--- 0 ----/ 243 || /^@@ -1.* \+0,0 @@/) { 244 $change_msg = ": Remove.\n"; 245 $look_for_funs = 0; 246 } 247 248 if (is_unified_hunk_start ($diff_lines[$line_idx])) { 249 $is_context_diff = 0; 250 } 251 elsif (is_context_hunk_start ($diff_lines[$line_idx])) { 252 $is_context_diff = 1; 253 } 254 255 # If we find a new function, print it in brackets. Special case if 256 # this is the first function in a file. 257 # 258 # Note that we don't try too hard to find good matches. This should 259 # return a superset of the actual set of functions in the .diff file. 260 # 261 # The first pattern works with context diff files (diff -c). The 262 # second pattern works with unified diff files (diff -u). 263 # 264 # The third pattern looks for the starts of functions or classes 265 # within a diff block both for context and unified diff files. 266 if ($look_for_funs 267 && (/^\*\*\*\*\*\** ([a-zA-Z0-9_].*)/ 268 || /^@@ .* @@ ([a-zA-Z0-9_].*)/ 269 || /^[-+! ](\{)/)) 270 { 271 $_ = $1; 272 my $fn; 273 if (/^\{/) { 274 # Beginning of a new function. 275 $_ = $save_fn; 276 } else { 277 $save_fn = ""; 278 } 279 if (/;$/) { 280 # No usable function name found. 281 } elsif (/^((class|struct|union|enum) [a-zA-Z0-9_]+)/) { 282 # Discard stuff after the class/struct/etc. tag. 283 $fn = $1; 284 } elsif (/([a-zA-Z0-9_][^(]*)\(/) { 285 # Discard template and function parameters. 286 $fn = $1; 287 1 while ($fn =~ s/<[^<>]*>//); 288 $fn =~ s/[ \t]*$//; 289 } 290 # Check is function really modified 291 $no_real_change = 0; 292 $idx = $line_idx; 293 # Skip line info in context diffs. 294 while ($idx <= $#diff_lines && $is_context_diff 295 && $diff_lines[$idx + 1] =~ /^[-\*]{3} [0-9]/) { 296 ++$idx; 297 } 298 # Check all lines till the first change 299 # for the presence of really changed function 300 do { 301 ++$idx; 302 $no_real_change = $idx > $#diff_lines 303 || is_top_level ($diff_lines[$idx], $is_context_diff); 304 } while (!$no_real_change && ($diff_lines[$idx] !~ /^[-+!]/)); 305 if ($fn && !$seen_names{$fn} && !$no_real_change) { 306 # If this is the first function in the file, we display it next 307 # to the filename, so we need an extra space before the opening 308 # brace. 309 if (!$change_msg) { 310 $change_msg .= " "; 311 } else { 312 $change_msg .= "\t"; 313 } 314 315 $change_msg .= "($fn):\n"; 316 $seen_names{$fn} = 1; 317 } 318 } 319 $line_idx++; 320} 321 322# If we have not seen any function names (ie, $change_msg is empty), we just 323# write out a ':'. This happens when there is only one file with no 324# functions. 325$cl_entries{$clname} .= $change_msg ? "$change_msg\n" : ":\n"; 326 327if ($inline && $diff ne "-") { 328 # Get a temp filename, rather than an open filehandle, because we use 329 # the open to truncate. 330 $tmp = mktemp("tmp.XXXXXXXX") or die "Could not create temp file: $!"; 331 332 # Copy the permissions to the temp file (in File::Copy module version 333 # 2.15 and later). 334 cp $diff, $tmp or die "Could not copy patch file to temp file: $!"; 335 336 # Open the temp file, clearing contents. 337 open (OUTPUTFILE, '>', $tmp) or die "Could not open temp file: $!"; 338} else { 339 *OUTPUTFILE = STDOUT; 340} 341 342# Print the log 343foreach my $clname (keys %cl_entries) { 344 print OUTPUTFILE "$clname:\n\n$hdrline\n\n$cl_entries{$clname}\n"; 345} 346 347if ($inline) { 348 # Append the patch to the log 349 foreach (@orig_diff_lines) { 350 print OUTPUTFILE "$_\n"; 351 } 352} 353 354if ($inline && $diff ne "-") { 355 # Close $tmp 356 close(OUTPUTFILE); 357 358 # Write new contents to $diff atomically 359 mv $tmp, $diff or die "Could not move temp file to patch file: $!"; 360} 361 362exit 0; 363