diff options
Diffstat (limited to 'contrib/perl5/Porting/patchls')
-rwxr-xr-x | contrib/perl5/Porting/patchls | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/contrib/perl5/Porting/patchls b/contrib/perl5/Porting/patchls new file mode 100755 index 0000000..38c4dd1 --- /dev/null +++ b/contrib/perl5/Porting/patchls @@ -0,0 +1,539 @@ +#!/bin/perl -w +# +# patchls - patch listing utility +# +# Input is one or more patchfiles, output is a list of files to be patched. +# +# Copyright (c) 1997 Tim Bunce. All rights reserved. +# This program is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +# +# With thanks to Tom Horsley for the seed code. + + +use Getopt::Std; +use Text::Wrap qw(wrap $columns); +use Text::Tabs qw(expand unexpand); +use strict; +use vars qw($VERSION); + +$VERSION = 2.08; + +sub usage { +die qq{ + patchls [options] patchfile [ ... ] + + -h no filename headers (like grep), only the listing. + -l no listing (like grep), only the filename headers. + -i Invert: for each patched file list which patch files patch it. + -c Categorise the patch and sort by category (perl specific). + -m print formatted Meta-information (Subject,From,Msg-ID etc). + -p N strip N levels of directory Prefix (like patch), else automatic. + -v more verbose (-d for noisy debugging). + -n give a count of the number of patches applied to a file if >1. + -f F only list patches which patch files matching regexp F + (F has \$ appended unless it contains a /). + -e Expect patched files to Exist (relative to current directory) + Will print warnings for files which don't. Also affects -4 option. + other options for special uses: + -I just gather and display summary Information about the patches. + -4 write to stdout the PerForce commands to prepare for patching. + -5 like -4 but add "|| exit 1" after each command + -M T Like -m but only output listed meta tags (eg -M 'Title From') + -W N set wrap width to N (defaults to 70, use 0 for no wrap) + -X list patchfiles that may clash (i.e. patch the same file) + + patchls version $VERSION by Tim Bunce +} +} + +$::opt_p = undef; # undef != 0 +$::opt_d = 0; +$::opt_v = 0; +$::opt_m = 0; +$::opt_n = 0; +$::opt_i = 0; +$::opt_h = 0; +$::opt_l = 0; +$::opt_c = 0; +$::opt_f = ''; +$::opt_e = 0; + +# special purpose options +$::opt_I = 0; +$::opt_4 = 0; # output PerForce commands to prepare for patching +$::opt_5 = 0; +$::opt_M = ''; # like -m but only output these meta items (-M Title) +$::opt_W = 70; # set wrap width columns (see Text::Wrap module) +$::opt_C = 0; # 'Chip' mode (handle from/tags/article/bug files) undocumented +$::opt_X = 0; # list patchfiles that patch the same file + +usage unless @ARGV; + +getopts("dmnihlvecC45Xp:f:IM:W:") or usage; + +$columns = $::opt_W || 9999999; + +$::opt_m = 1 if $::opt_M; +$::opt_4 = 1 if $::opt_5; +$::opt_i = 1 if $::opt_X; + +# see get_meta_info() +my @show_meta = split(' ', $::opt_M || 'Title From Msg-ID Files'); +my %show_meta = map { ($_,1) } @show_meta; + +my %cat_title = ( + 'BUILD' => 'BUILD PROCESS', + 'CORE' => 'CORE LANGUAGE', + 'DOC' => 'DOCUMENTATION', + 'LIB' => 'LIBRARY', + 'PORT1' => 'PORTABILITY - WIN32', + 'PORT2' => 'PORTABILITY - GENERAL', + 'TEST' => 'TESTS', + 'UTIL' => 'UTILITIES', + 'OTHER' => 'OTHER CHANGES', + 'EXT' => 'EXTENSIONS', + 'UNKNOWN' => 'UNKNOWN - NO FILES PATCH', +); + + +sub get_meta_info { + my $ls = shift; + local($_) = shift; + if (/^From:\s+(.*\S)/i) {; + my $from = $1; # temporary measure for Chip Salzenberg + $from =~ s/chip\@(atlantic\.net|perlsupport\.com)/chip\@pobox.com/; + $from =~ s/\(Tim Bunce\) \(Tim Bunce\)/(Tim Bunce)/; + $ls->{From}{$from} = 1 + } + if (/^Subject:\s+(?:Re: )?(.*\S)/i) { + my $title = $1; + $title =~ s/\[(PATCH|PERL)[\w\. ]*\]:?//g; + $title =~ s/\b(PATCH|PERL)[\w\.]*://g; + $title =~ s/\bRe:\s+/ /g; + $title =~ s/\s+/ /g; + $title =~ s/^\s*(.*?)\s*$/$1/g; + $ls->{Title}{$title} = 1; + } + $ls->{'Msg-ID'}{$1}=1 if /^Message-Id:\s+(.*\S)/i; + $ls->{Date}{$1}=1 if /^Date:\s+(.*\S)/i; + $ls->{$1}{$2}=1 if $::opt_M && /^([-\w]+):\s+(.*\S)/; +} + + +# Style 1: +# *** perl-5.004/embed.h Sat May 10 03:39:32 1997 +# --- perl-5.004.fixed/embed.h Thu May 29 19:48:46 1997 +# *************** +# *** 308,313 **** +# --- 308,314 ---- +# +# Style 2: +# --- perl5.004001/mg.c Sun Jun 08 12:26:24 1997 +# +++ perl5.004-bc/mg.c Sun Jun 08 11:56:08 1997 +# @@ -656,9 +656,27 @@ +# or (rcs, note the different date format) +# --- 1.18 1997/05/23 19:22:04 +# +++ ./pod/perlembed.pod 1997/06/03 21:41:38 +# +# Variation: +# Index: embed.h + +my %ls; + +my $in; +my $ls; +my $prevline = ''; +my $prevtype = ''; +my (@removed, @added); +my $prologue = 1; # assume prologue till patch or /^exit\b/ seen + + +foreach my $argv (@ARGV) { + $in = $argv; + unless (open F, "<$in") { + warn "Unable to open $in: $!\n"; + next; + } + print "Reading $in...\n" if $::opt_v and @ARGV > 1; + $ls = $ls{$in} ||= { is_in => 1, in => $in }; + my $type; + while (<F>) { + unless (/^([-+*]{3}) / || /^(Index):/) { + # not an interesting patch line + # but possibly meta-information or prologue + if ($prologue) { + push @added, $1 if /^touch\s+(\S+)/; + push @removed, $1 if /^rm\s+(?:-f)?\s*(\S+)/; + $prologue = 0 if /^exit\b/; + } + get_meta_info($ls, $_) if $::opt_m; + next; + } + $type = $1; + next if /^--- [0-9,]+ ----$/ || /^\*\*\* [0-9,]+ \*\*\*\*$/; + $prologue = 0; + + print "Last: $prevline","This: ${_}Got: $type\n\n" if $::opt_d; + + # Some patches have Index lines but not diff headers + # Patch copes with this, so must we. It's also handy for + # documenting manual changes by simply adding Index: lines + # to the file which describes the problem being fixed. + if (/^Index:\s+(.*)/) { + my $f; + foreach $f (split(/ /, $1)) { add_file($ls, $f) } + next; + } + + if ( ($type eq '---' and $prevtype eq '***') # Style 1 + or ($type eq '+++' and $prevtype eq '---') # Style 2 + ) { + if (/^[-+*]{3} (\S+)\s*(.*?\d\d:\d\d:\d\d)?/) { # double check + add_file($ls, $1); + } + else { + warn "$in $.: parse error (prev $prevtype, type $type)\n$prevline$_"; + } + } + } + continue { + $prevline = $_; + $prevtype = $type || ''; + $type = ''; + } + + # special mode for patch sets from Chip + if ($in =~ m:[\\/]patch$:) { + my $is_chip; + my $chip; + my $dir; ($dir = $in) =~ s:[\\/]patch$::; + if (!$ls->{From} && (open(CHIP,"$dir/article") || open(CHIP,"$dir/bug"))) { + get_meta_info($ls, $_) while (<CHIP>); + $is_chip = 1; + } + if (open CHIP,"<$dir/from") { + chop($chip = <CHIP>); + $ls->{From} = { $chip => 1 }; + $is_chip = 1; + } + if (open CHIP,"<$dir/tag") { + chop($chip = <CHIP>); + $ls->{Title} = { $chip => 1 }; + $is_chip = 1; + } + $ls->{From} = { "Chip Salzenberg" => 1 } if $is_chip && !$ls->{From}; + } + + # if we don't have a title for -m then use the file name + $ls->{Title}{$in}=1 if $::opt_m + and !$ls->{Title} and $ls->{out}; + + $ls->{category} = $::opt_c + ? categorize_files([keys %{ $ls->{out} }], $::opt_v) : ''; +} +print scalar(@ARGV)." files read.\n" if $::opt_v and @ARGV > 1; + + +# --- Firstly we filter and sort as needed --- + +my @ls = values %ls; + +if ($::opt_f) { # filter out patches based on -f <regexp> + $::opt_f .= '$' unless $::opt_f =~ m:/:; + @ls = grep { + my $match = 0; + if ($_->{is_in}) { + my @out = keys %{ $_->{out} }; + $match=1 if grep { m/$::opt_f/o } @out; + } + else { + $match=1 if $_->{in} =~ m/$::opt_f/o; + } + $match; + } @ls; +} + +@ls = sort { + $a->{category} cmp $b->{category} || $a->{in} cmp $b->{in} +} @ls; + + +# --- Handle special modes --- + +if ($::opt_4) { + my $tail = ($::opt_5) ? "|| exit 1" : ""; + print map { "p4 delete $_$tail\n" } @removed if @removed; + print map { "p4 add $_$tail\n" } @added if @added; + my @patches = sort grep { $_->{is_in} } @ls; + my @no_outs = grep { keys %{$_->{out}} == 0 } @patches; + warn "Warning: Some files contain no patches:", + join("\n\t", '', map { $_->{in} } @no_outs), "\n" if @no_outs; + my %patched = map { ($_, 1) } map { keys %{$_->{out}} } @patches; + delete @patched{@added}; + my @patched = sort keys %patched; + foreach(@patched) { + my $edit = ($::opt_e && !-f $_) ? "add " : "edit"; + print "p4 $edit $_$tail\n"; + } + exit 0 unless $::opt_C; +} + + +if ($::opt_I) { + my $n_patches = 0; + my($in,$out); + my %all_out; + my @no_outs; + foreach $in (@ls) { + next unless $in->{is_in}; + ++$n_patches; + my @outs = keys %{$in->{out}}; + push @no_outs, $in unless @outs; + @all_out{@outs} = ($in->{in}) x @outs; + } + my @all_out = sort keys %all_out; + my @missing = grep { ! -f $_ } @all_out; + print "$n_patches patch files patch ".@all_out." files (".@missing." missing)\n"; + print @no_outs." patch files don't contain patches.\n" if @no_outs; + print "(use -v to list patches which patch 'missing' files)\n" + if (@missing || @no_outs) && !$::opt_v; + if ($::opt_v and @no_outs) { + print "Patch files which don't contain patches:\n"; + foreach $out (@no_outs) { + printf " %-20s\n", $out->{in}; + } + } + if ($::opt_v and @missing) { + print "Missing files:\n"; + foreach $out (@missing) { + printf " %-20s\t", $out unless $::opt_h; + print $all_out{$out} unless $::opt_l; + print "\n"; + } + } + print "Added files: @added\n" if @added; + print "Removed files: @removed\n" if @removed; + exit 0+@missing; +} + +unless ($::opt_c and $::opt_m) { + foreach $ls (@ls) { + next unless ($::opt_i) ? $ls->{is_out} : $ls->{is_in}; + next if $::opt_X and keys %{$ls->{out}} <= 1; + list_files_by_patch($ls); + } +} +else { + my $c = ''; + foreach $ls (@ls) { + next unless ($::opt_i) ? $ls->{is_out} : $ls->{is_in}; + print "\n ------ $cat_title{$ls->{category}} ------\n" + if $ls->{category} ne $c; + $c = $ls->{category}; + unless ($::opt_i) { + list_files_by_patch($ls); + } + else { + my $out = $ls->{in}; + print "\n$out patched by:\n"; + # find all the patches which patch $out and list them + my @p = grep { $_->{out}->{$out} } values %ls; + foreach $ls (@p) { + list_files_by_patch($ls, ''); + } + } + } + print "\n"; +} + +exit 0; + + +# --- + + +sub add_file { + my $ls = shift; + print "add_file '$_[0]'\n" if $::opt_d; + my $out = trim_name(shift); + + $ls->{out}->{$out} = 1; + + warn "$out patched but not present\n" if $::opt_e && !-f $out; + + # do the -i inverse as well, even if we're not doing -i + my $i = $ls{$out} ||= { + is_out => 1, + in => $out, + category => $::opt_c ? categorize_files([ $out ], $::opt_v) : '', + }; + $i->{out}->{$in} = 1; +} + + +sub trim_name { # reduce/tidy file paths from diff lines + my $name = shift; + $name = "$name ($in)" if $name eq "/dev/null"; + $name =~ s:\\:/:g; # adjust windows paths + $name =~ s://:/:g; # simplify (and make win \\share into absolute path) + if (defined $::opt_p) { + # strip on -p levels of directory prefix + my $dc = $::opt_p; + $name =~ s:^[^/]+/(.+)$:$1: while $dc-- > 0; + } + else { # try to strip off leading path to perl directory + # if absolute path, strip down to any *perl* directory first + $name =~ s:^/.*?perl.*?/::i; + $name =~ s:.*perl[-_]?5?[._]?[-_a-z0-9.+]*/::i; + $name =~ s:^\./::; + } + return $name; +} + + +sub list_files_by_patch { + my($ls, $name) = @_; + $name = $ls->{in} unless defined $name; + my @meta; + if ($::opt_m) { + my $meta; + foreach $meta (@show_meta) { + next unless $ls->{$meta}; + my @list = sort keys %{$ls->{$meta}}; + push @meta, sprintf "%7s: ", $meta; + if ($meta eq 'Title') { + @list = map { "\"$_\""; } @list; + push @list, "#$1" if $::opt_C && $ls->{in} =~ m:\b(\w\d+)/patch$:; + } + elsif ($meta eq 'From') { + # fix-up bizzare addresses from japan and ibm :-) + foreach(@list) { + s:\W+=?iso.*?<: <:; + s/\d\d-\w\w\w-\d{4}\s+\d\d:\S+\s*//; + } + } + elsif ($meta eq 'Msg-ID') { + my %from; # limit long threads to one msg-id per site + @list = map { + $from{(/@(.*?)>/ ? $1 : $_)}++ ? () : ($_); + } @list; + } + push @meta, my_wrap(""," ", join(", ",@list)."\n"); + } + $name = "\n$name" if @meta and $name; + } + # don't print the header unless the file contains something interesting + return if !@meta and !$ls->{out} and !$::opt_v; + if ($::opt_l) { # -l = no listing, just names + print "$ls->{in}"; + my $n = keys %{ $ls->{out} }; + print " ($n patches)" if $::opt_n and $n>1; + print "\n"; + return; + } + + # a twisty maze of little options + my $cat = ($ls->{category} and !$::opt_m) ? "\t$ls->{category}" : ""; + print "$name$cat: " unless ($::opt_h and !$::opt_v) or !"$name$cat"; + print join('',"\n",@meta) if @meta; + + return if $::opt_m && !$show_meta{Files}; + my @v = sort PATORDER keys %{ $ls->{out} }; + my $n = @v; + my $v = "@v"; + print $::opt_m ? " Files: ".my_wrap(""," ",$v) : $v; + print " ($n patches)" if $::opt_n and $n>1; + print "\n"; +} + + +sub my_wrap { + my $txt = eval { expand(wrap(@_)) }; # die's on long lines! + return $txt unless $@; + return expand("@_"); +} + + + +sub categorize_files { + my($files, $verb) = @_; + my(%c, $refine); + + foreach (@$files) { # assign a score to a file path + # the order of some of the tests is important + $c{TEST} += 5,next if m:^t/:; + $c{DOC} += 5,next if m:^pod/:; + $c{UTIL} += 10,next if m:^(utils|x2p|h2pl)/:; + $c{PORT1}+= 15,next if m:^win32:; + $c{PORT2} += 15,next + if m:^(cygwin32|os2|plan9|qnx|vms)/: + or m:^(hints|Porting|ext/DynaLoader)/: + or m:^README\.:; + $c{EXT} += 10,next + if m:^(ext|lib/ExtUtils)/:; + $c{LIB} += 10,next + if m:^(lib)/:; + $c{'CORE'} += 15,next + if m:^[^/]+[\._]([chH]|sym|pl)$:; + $c{BUILD} += 10,next + if m:^[A-Z]+$: or m:^[^/]+\.SH$: + or m:^(install|configure|configpm):i; + print "Couldn't categorise $_\n" if $::opt_v; + $c{OTHER} += 1; + } + if (keys %c > 1) { # sort to find category with highest score + refine: + ++$refine; + my @c = sort { $c{$b} <=> $c{$a} || $a cmp $b } keys %c; + my @v = map { $c{$_} } @c; + if (@v > 1 and $refine <= 1 and "@v" =~ /^(\d) \1/ + and $c[0] =~ m/^(DOC|TESTS|OTHER)/) { # rare + print "Tie, promoting $c[1] over $c[0]\n" if $::opt_d; + ++$c{$c[1]}; + goto refine; + } + print " ".@$files." patches: ", join(", ", map { "$_: $c{$_}" } @c),".\n" + if $verb; + return $c[0] || 'OTHER'; + } + else { + my($c, $v) = %c; + $c ||= 'UNKNOWN'; $v ||= 0; + print " ".@$files." patches: $c: $v\n" if $verb; + return $c; + } +} + + +sub PATORDER { # PATORDER sort by Chip Salzenberg + my ($i, $j); + + $i = ($a =~ m#^[A-Z]+$#); + $j = ($b =~ m#^[A-Z]+$#); + return $j - $i if $i != $j; + + $i = ($a =~ m#configure|hint#i) || ($a =~ m#[S_]H$#); + $j = ($b =~ m#configure|hint#i) || ($b =~ m#[S_]H$#); + return $j - $i if $i != $j; + + $i = ($a =~ m#\.pod$#); + $j = ($b =~ m#\.pod$#); + return $j - $i if $i != $j; + + $i = ($a =~ m#include/#); + $j = ($b =~ m#include/#); + return $j - $i if $i != $j; + + if ((($i = $a) =~ s#/+[^/]*$##) + && (($j = $b) =~ s#/+[^/]*$##)) { + return $i cmp $j if $i ne $j; + } + + $i = ($a =~ m#\.h$#); + $j = ($b =~ m#\.h$#); + return $j - $i if $i != $j; + + return $a cmp $b; +} + |