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