| #!/usr/bin/perl |
| # Copyright (C) 2012-2017 Free Software Foundation, Inc. |
| # |
| # This file is part of GCC. |
| # |
| # GCC is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 3, or (at your option) |
| # any later version. |
| # |
| # GCC is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with GCC; see the file COPYING. If not, write to |
| # the Free Software Foundation, 51 Franklin Street, Fifth Floor, |
| # Boston, MA 02110-1301, USA. |
| |
| # This script parses a .diff file generated with 'diff -up' or 'diff -cp' |
| # and adds a skeleton ChangeLog file to the file. It does not try to be |
| # very smart when parsing function names, but it produces a reasonable |
| # approximation. |
| # |
| # Author: Diego Novillo <dnovillo@google.com> and |
| # Cary Coutant <ccoutant@google.com> |
| |
| use File::Temp; |
| use File::Copy qw(cp mv); |
| |
| $date = `date +%Y-%m-%d`; chop ($date); |
| |
| $dot_mklog_format_msg = |
| "The .mklog format is:\n" |
| . "NAME = ...\n" |
| . "EMAIL = ...\n"; |
| |
| # Create a .mklog to reflect your profile, if necessary. |
| my $conf = "$ENV{HOME}/.mklog"; |
| if (-f "$conf") { |
| open (CONF, "$conf") |
| or die "Could not open file '$conf' for reading: $!\n"; |
| while (<CONF>) { |
| if (m/^\s*NAME\s*=\s*(.*?)\s*$/) { |
| $name = $1; |
| } elsif (m/^\s*EMAIL\s*=\s*(.*?)\s*$/) { |
| $addr = $1; |
| } |
| } |
| if (!($name && $addr)) { |
| die "Could not read .mklog settings.\n" |
| . $dot_mklog_format_msg; |
| } |
| } else { |
| $name = `git config user.name`; |
| chomp($name); |
| $addr = `git config user.email`; |
| chomp($addr); |
| |
| if (!($name && $addr)) { |
| die "Could not read git user.name and user.email settings.\n" |
| . "Please add missing git settings, or create a .mklog file in" |
| . " $ENV{HOME}.\n" |
| . $dot_mklog_format_msg; |
| } |
| } |
| |
| $gcc_root = $0; |
| $gcc_root =~ s/[^\\\/]+$/../; |
| |
| #----------------------------------------------------------------------------- |
| # Program starts here. You should not need to edit anything below this |
| # line. |
| #----------------------------------------------------------------------------- |
| $inline = 0; |
| if ($#ARGV == 1 && ("$ARGV[0]" eq "-i" || "$ARGV[0]" eq "--inline")) { |
| shift; |
| $inline = 1; |
| } elsif ($#ARGV != 0) { |
| $prog = `basename $0`; chop ($prog); |
| print <<EOF; |
| usage: $prog [ -i | --inline ] file.diff |
| |
| Generate ChangeLog template for file.diff. |
| It assumes that patch has been created with -up or -cp. |
| When -i is used, the ChangeLog template is followed by the contents of |
| file.diff. |
| When file.diff is -, read standard input. |
| When -i is used and file.diff is not -, it writes to file.diff, otherwise it |
| writes to stdout. |
| EOF |
| exit 1; |
| } |
| |
| $diff = $ARGV[0]; |
| $dir = `dirname $diff`; chop ($dir); |
| $basename = `basename $diff`; chop ($basename); |
| $hdrline = "$date $name <$addr>"; |
| |
| sub get_clname ($) { |
| return ('ChangeLog', $_[0]) if ($_[0] !~ /[\/\\]/); |
| |
| my $dirname = $_[0]; |
| while ($dirname) { |
| my $clname = "$dirname/ChangeLog"; |
| if (-f "$gcc_root/$clname" || -f "$clname") { |
| my $relname = substr ($_[0], length ($dirname) + 1); |
| return ($clname, $relname); |
| } else { |
| $dirname =~ s/[\/\\]?[^\/\\]*$//; |
| } |
| } |
| |
| return ('Unknown ChangeLog', $_[0]); |
| } |
| |
| sub remove_suffixes ($) { |
| my $filename = $_[0]; |
| $filename =~ s/^[ab]\///; |
| $filename =~ s/\.jj$//; |
| return $filename; |
| } |
| |
| sub is_context_hunk_start { |
| return @_[0] =~ /^\*\*\*\*\*\** ([a-zA-Z0-9_].*)/; |
| } |
| |
| sub is_unified_hunk_start { |
| return @_[0] =~ /^@@ .* @@ ([a-zA-Z0-9_].*)/; |
| } |
| |
| # Check if line is a top-level declaration. |
| sub is_top_level { |
| my ($function, $is_context_diff) = (@_); |
| if (is_unified_hunk_start ($function) |
| || is_context_hunk_start ($function)) { |
| return 1; |
| } |
| if ($is_context_diff) { |
| $function =~ s/^..//; |
| } else { |
| $function =~ s/^.//; |
| } |
| return $function && $function !~ /^[\s{#]/; |
| } |
| |
| # Read contents of .diff file |
| open (DFILE, $diff) or die "Could not open file $diff for reading"; |
| chomp (my @diff_lines = <DFILE>); |
| close (DFILE); |
| |
| # Array diff_lines is modified by the log generation, so save a copy in |
| # orig_diff_lines if needed. |
| if ($inline) { |
| @orig_diff_lines = @diff_lines; |
| } |
| |
| # For every file in the .diff print all the function names in ChangeLog |
| # format. |
| %cl_entries = (); |
| $change_msg = undef; |
| $look_for_funs = 0; |
| $clname = get_clname(''); |
| $line_idx = 0; |
| foreach (@diff_lines) { |
| # Stop processing functions if we found a new file. |
| # Remember both left and right names because one may be /dev/null. |
| # Don't be fooled by line markers in case of context diff. |
| if (!/\*\*\*$/ && /^[+*][+*][+*] +(\S+)/) { |
| $left = remove_suffixes ($1); |
| $look_for_funs = 0; |
| } |
| if (!/---$/ && /^--- +(\S+)?/) { |
| $right = remove_suffixes ($1); |
| $look_for_funs = 0; |
| } |
| |
| # Check if the body of diff started. |
| # We should now have both left and right name, |
| # so we can decide filename. |
| |
| if ($left && (/^\*{15}/ || /^@@ /)) { |
| # If we have not seen any function names in the previous file (ie, |
| # $change_msg is empty), we just write out a ':' before starting the next |
| # file. |
| if ($clname) { |
| $cl_entries{$clname} .= $change_msg ? "$change_msg" : ":\n"; |
| } |
| |
| if ($left eq $right) { |
| $filename = $left; |
| } elsif($left eq '/dev/null') { |
| $filename = $right; |
| } elsif($right eq '/dev/null') { |
| $filename = $left; |
| } else { |
| my @ldirs = split /[\/\\]/, $left; |
| my @rdirs = split /[\/\\]/, $right; |
| |
| $filename = ''; |
| while ((my $l = pop @ldirs) && (my $r = pop @rdirs)) { |
| last if ($l ne $r); |
| $filename = "$l/$filename"; |
| } |
| $filename =~ s/\/$//; |
| |
| if (!$filename) { |
| print STDERR "Error: failed to parse diff for $left and $right\n"; |
| exit 1; |
| } |
| } |
| $left = $right = undef; |
| ($clname, $relname) = get_clname ($filename); |
| $cl_entries{$clname} .= "\t* $relname"; |
| $change_msg = ''; |
| $look_for_funs = $filename =~ '\.(c|cpp|C|cc|h|inc|def)$'; |
| } |
| |
| # Context diffs have extra whitespace after first char; |
| # remove it to make matching easier. |
| if ($is_context_diff) { |
| s/^([-+! ]) /\1/; |
| } |
| |
| # Remember the last line in a diff block that might start |
| # a new function. |
| if (/^[-+! ]([a-zA-Z0-9_].*)/) { |
| $save_fn = $1; |
| } |
| |
| # Check if file is newly added. |
| # Two patterns: for context and unified diff. |
| if (/^\*\*\* 0 \*\*\*\*/ |
| || /^@@ -0,0 \+1.* @@/) { |
| $change_msg = $filename =~ /testsuite.*(?<!\.exp)$/ ? ": New test.\n" : ": New file.\n"; |
| $look_for_funs = 0; |
| } |
| |
| # Check if file was removed. |
| # Two patterns: for context and unified diff. |
| if (/^--- 0 ----/ |
| || /^@@ -1.* \+0,0 @@/) { |
| $change_msg = ": Remove.\n"; |
| $look_for_funs = 0; |
| } |
| |
| if (is_unified_hunk_start ($diff_lines[$line_idx])) { |
| $is_context_diff = 0; |
| } |
| elsif (is_context_hunk_start ($diff_lines[$line_idx])) { |
| $is_context_diff = 1; |
| } |
| |
| # If we find a new function, print it in brackets. Special case if |
| # this is the first function in a file. |
| # |
| # Note that we don't try too hard to find good matches. This should |
| # return a superset of the actual set of functions in the .diff file. |
| # |
| # The first pattern works with context diff files (diff -c). The |
| # second pattern works with unified diff files (diff -u). |
| # |
| # The third pattern looks for the starts of functions or classes |
| # within a diff block both for context and unified diff files. |
| if ($look_for_funs |
| && (/^\*\*\*\*\*\** ([a-zA-Z0-9_].*)/ |
| || /^@@ .* @@ ([a-zA-Z0-9_].*)/ |
| || /^[-+! ](\{)/)) |
| { |
| $_ = $1; |
| my $fn; |
| if (/^\{/) { |
| # Beginning of a new function. |
| $_ = $save_fn; |
| } else { |
| $save_fn = ""; |
| } |
| if (/;$/) { |
| # No usable function name found. |
| } elsif (/^((class|struct|union|enum) [a-zA-Z0-9_]+)/) { |
| # Discard stuff after the class/struct/etc. tag. |
| $fn = $1; |
| } elsif (/([a-zA-Z0-9_][^(]*)\(/) { |
| # Discard template and function parameters. |
| $fn = $1; |
| 1 while ($fn =~ s/<[^<>]*>//); |
| $fn =~ s/[ \t]*$//; |
| } |
| # Check is function really modified |
| $no_real_change = 0; |
| $idx = $line_idx; |
| # Skip line info in context diffs. |
| while ($idx <= $#diff_lines && $is_context_diff |
| && $diff_lines[$idx + 1] =~ /^[-\*]{3} [0-9]/) { |
| ++$idx; |
| } |
| # Check all lines till the first change |
| # for the presence of really changed function |
| do { |
| ++$idx; |
| $no_real_change = $idx > $#diff_lines |
| || is_top_level ($diff_lines[$idx], $is_context_diff); |
| } while (!$no_real_change && ($diff_lines[$idx] !~ /^[-+!]/)); |
| if ($fn && !$seen_names{$fn} && !$no_real_change) { |
| # If this is the first function in the file, we display it next |
| # to the filename, so we need an extra space before the opening |
| # brace. |
| if (!$change_msg) { |
| $change_msg .= " "; |
| } else { |
| $change_msg .= "\t"; |
| } |
| |
| $change_msg .= "($fn):\n"; |
| $seen_names{$fn} = 1; |
| } |
| } |
| $line_idx++; |
| } |
| |
| # If we have not seen any function names (ie, $change_msg is empty), we just |
| # write out a ':'. This happens when there is only one file with no |
| # functions. |
| $cl_entries{$clname} .= $change_msg ? "$change_msg\n" : ":\n"; |
| |
| if ($inline && $diff ne "-") { |
| # Get a temp filename, rather than an open filehandle, because we use |
| # the open to truncate. |
| $tmp = mktemp("tmp.XXXXXXXX") or die "Could not create temp file: $!"; |
| |
| # Copy the permissions to the temp file (in File::Copy module version |
| # 2.15 and later). |
| cp $diff, $tmp or die "Could not copy patch file to temp file: $!"; |
| |
| # Open the temp file, clearing contents. |
| open (OUTPUTFILE, '>', $tmp) or die "Could not open temp file: $!"; |
| } else { |
| *OUTPUTFILE = STDOUT; |
| } |
| |
| # Print the log |
| foreach my $clname (keys %cl_entries) { |
| print OUTPUTFILE "$clname:\n\n$hdrline\n\n$cl_entries{$clname}\n"; |
| } |
| |
| if ($inline) { |
| # Append the patch to the log |
| foreach (@orig_diff_lines) { |
| print OUTPUTFILE "$_\n"; |
| } |
| } |
| |
| if ($inline && $diff ne "-") { |
| # Close $tmp |
| close(OUTPUTFILE); |
| |
| # Write new contents to $diff atomically |
| mv $tmp, $diff or die "Could not move temp file to patch file: $!"; |
| } |
| |
| exit 0; |