win32 tweaks
[p5sagit/p5-mst-13.2.git] / Porting / patchls
CommitLineData
08aa1457 1#!/bin/perl -w
2#
3e3baf6d 3# patchls - patch listing utility
08aa1457 4#
5# Input is one or more patchfiles, output is a list of files to be patched.
6#
3e3baf6d 7# Copyright (c) 1997 Tim Bunce. All rights reserved.
8# This program is free software; you can redistribute it and/or
9# modify it under the same terms as Perl itself.
10#
11# With thanks to Tom Horsley for the seed code.
fb73857a 12
08aa1457 13
3e3baf6d 14use Getopt::Std;
08aa1457 15use Text::Wrap qw(wrap $columns);
16use Text::Tabs qw(expand unexpand);
17use strict;
fb73857a 18use vars qw($VERSION);
19
43051805 20$VERSION = 2.05;
08aa1457 21
3e3baf6d 22sub usage {
43051805 23die qq{
3e3baf6d 24 patchls [options] patchfile [ ... ]
25
84902520 26 -h no filename headers (like grep), only the listing.
27 -l no listing (like grep), only the filename headers.
fb73857a 28 -i Invert: for each patched file list which patch files patch it.
84902520 29 -c Categorise the patch and sort by category (perl specific).
30 -m print formatted Meta-information (Subject,From,Msg-ID etc).
31 -p N strip N levels of directory Prefix (like patch), else automatic.
32 -v more verbose (-d for noisy debugging).
33 -f F only list patches which patch files matching regexp F
43051805 34 (F has \$ appended unless it contains a /).
35 -e Expect patched files to Exist (relative to current directory)
36 Will print warnings for files which don't. Also affects -4 option.
fb73857a 37 other options for special uses:
84902520 38 -I just gather and display summary Information about the patches.
fb73857a 39 -4 write to stdout the PerForce commands to prepare for patching.
43051805 40 -5 like -4 but add "|| exit 1" after each command
fb73857a 41 -M T Like -m but only output listed meta tags (eg -M 'Title From')
42 -W N set wrap width to N (defaults to 70, use 0 for no wrap)
43051805 43
44 patchls version $VERSION by Tim Bunce
3e3baf6d 45}
46}
47
3e3baf6d 48$::opt_p = undef; # undef != 0
08aa1457 49$::opt_d = 0;
50$::opt_v = 0;
51$::opt_m = 0;
52$::opt_i = 0;
53$::opt_h = 0;
54$::opt_l = 0;
55$::opt_c = 0;
84902520 56$::opt_f = '';
43051805 57$::opt_e = 0;
fb73857a 58
59# special purpose options
84902520 60$::opt_I = 0;
fb73857a 61$::opt_4 = 0; # output PerForce commands to prepare for patching
43051805 62$::opt_5 = 0;
fb73857a 63$::opt_M = ''; # like -m but only output these meta items (-M Title)
64$::opt_W = 70; # set wrap width columns (see Text::Wrap module)
43051805 65$::opt_C = 0; # 'Chip' mode (handle from/tags/article/bug files) undocumented
08aa1457 66
3e3baf6d 67usage unless @ARGV;
08aa1457 68
43051805 69getopts("mihlvecC45p:f:IM:W:") or usage;
fb73857a 70
71$columns = $::opt_W || 9999999;
72
73$::opt_m = 1 if $::opt_M;
43051805 74$::opt_4 = 1 if $::opt_5;
75my @show_meta = split(' ', $::opt_M || 'Title From Msg-ID'); # see get_meta_info()
08aa1457 76
3e3baf6d 77my %cat_title = (
84902520 78 'BUILD' => 'BUILD PROCESS',
79 'CORE' => 'CORE LANGUAGE',
3e3baf6d 80 'DOC' => 'DOCUMENTATION',
3e3baf6d 81 'LIB' => 'LIBRARY AND EXTENSIONS',
84902520 82 'PORT1' => 'PORTABILITY - WIN32',
fb73857a 83 'PORT2' => 'PORTABILITY - GENERAL',
84902520 84 'TEST' => 'TESTS',
85 'UTIL' => 'UTILITIES',
86 'OTHER' => 'OTHER CHANGES',
3e3baf6d 87);
08aa1457 88
43051805 89
90sub get_meta_info {
91 my $ls = shift;
92 local($_) = shift;
93 $ls->{From}{$1}=1 if /^From:\s+(.*\S)/i;
94 $ls->{Title}{$1}=1 if /^Subject:\s+(?:Re: )?(.*\S)/i;
95 $ls->{'Msg-ID'}{$1}=1 if /^Message-Id:\s+(.*\S)/i;
96 $ls->{Date}{$1}=1 if /^Date:\s+(.*\S)/i;
97 $ls->{$1}{$2}=1 if $::opt_M && /^([-\w]+):\s+(.*\S)/;
98}
99
08aa1457 100
101# Style 1:
102# *** perl-5.004/embed.h Sat May 10 03:39:32 1997
103# --- perl-5.004.fixed/embed.h Thu May 29 19:48:46 1997
104# ***************
105# *** 308,313 ****
106# --- 308,314 ----
107#
108# Style 2:
109# --- perl5.004001/mg.c Sun Jun 08 12:26:24 1997
110# +++ perl5.004-bc/mg.c Sun Jun 08 11:56:08 1997
111# @@ -656,9 +656,27 @@
112# or (rcs, note the different date format)
113# --- 1.18 1997/05/23 19:22:04
114# +++ ./pod/perlembed.pod 1997/06/03 21:41:38
115#
116# Variation:
117# Index: embed.h
118
43051805 119my %ls;
120
121my ($in, $prevline, $ls);
122my $prevtype = '';
123my (@removed, @added);
fb73857a 124my $prologue = 1; # assume prologue till patch or /^exit\b/ seen
08aa1457 125
43051805 126
08aa1457 127foreach my $argv (@ARGV) {
128 $in = $argv;
129 unless (open F, "<$in") {
130 warn "Unable to open $in: $!\n";
131 next;
132 }
133 print "Reading $in...\n" if $::opt_v and @ARGV > 1;
3e3baf6d 134 $ls = $ls{$in} ||= { is_in => 1, in => $in };
08aa1457 135 my $type;
136 while (<F>) {
137 unless (/^([-+*]{3}) / || /^(Index):/) {
fb73857a 138 # not an interesting patch line
139 # but possibly meta-information or prologue
140 if ($prologue) {
141 push @added, $1 if /^touch\s+(\S+)/;
142 push @removed, $1 if /^rm\s+(?:-f)?\s*(\S+)/;
143 $prologue = 0 if /^exit\b/;
144 }
43051805 145 get_meta_info($ls, $_) if $::opt_m;
08aa1457 146 next;
147 }
148 $type = $1;
149 next if /^--- [0-9,]+ ----$/ || /^\*\*\* [0-9,]+ \*\*\*\*$/;
fb73857a 150 $prologue = 0;
08aa1457 151
152 print "Last: $prevline","This: ${_}Got: $1\n\n" if $::opt_d;
153
154 # Some patches have Index lines but not diff headers
3e3baf6d 155 # Patch copes with this, so must we. It's also handy for
156 # documenting manual changes by simply adding Index: lines
157 # to the file which describes the problem bing fixed.
fb73857a 158 add_file($ls, $1), next if /^Index:\s+(\S+)/;
08aa1457 159
160 if ( ($type eq '---' and $prevtype eq '***') # Style 1
161 or ($type eq '+++' and $prevtype eq '---') # Style 2
162 ) {
fb73857a 163 if (/^[-+*]{3} (\S+)\s*(.*?\d\d:\d\d:\d\d)?/) { # double check
08aa1457 164 add_file($ls, $1);
165 }
166 else {
167 warn "$in $.: parse error (prev $prevtype, type $type)\n$prevline$_";
168 }
169 }
170 }
171 continue {
172 $prevline = $_;
173 $prevtype = $type;
174 $type = '';
175 }
43051805 176
177 # special mode for patch sets from Chip
178 if ($::opt_C && $in =~ m:[\\/]patch$:) {
179 my $chip;
180 my $dir; ($dir = $in) =~ s:[\\/]patch$::;
181 if (!$ls->{From} && (open(CHIP,"$dir/article") || open(CHIP,"$dir/bug"))) {
182 get_meta_info($ls, $_) while (<CHIP>);
183 }
184 if (open CHIP,"<$dir/from") {
185 chop($chip = <CHIP>);
186 $ls->{From} = { $chip => 1 };
187 }
188 if (open CHIP,"<$dir/tag") {
189 chop($chip = <CHIP>);
190 $ls->{Title} = { $chip => 1 };
191 }
192 $ls->{From} = { "Chip Salzenberg" => 1 } unless $ls->{From};
193 }
194
3e3baf6d 195 # if we don't have a title for -m then use the file name
196 $ls->{Title}{$in}=1 if $::opt_m
197 and !$ls->{Title} and $ls->{out};
198
199 $ls->{category} = $::opt_c
200 ? categorize_files([keys %{ $ls->{out} }], $::opt_v) : '';
08aa1457 201}
3e3baf6d 202print scalar(@ARGV)." files read.\n" if $::opt_v and @ARGV > 1;
203
204
fb73857a 205# --- Firstly we filter and sort as needed ---
206
207my @ls = values %ls;
08aa1457 208
84902520 209if ($::opt_f) { # filter out patches based on -f <regexp>
210 my $out;
211 $::opt_f .= '$' unless $::opt_f =~ m:/:;
212 @ls = grep {
213 my @out = keys %{$_->{out}};
214 my $match = 0;
215 for $out (@out) {
216 ++$match if $out =~ m/$::opt_f/o;
217 }
218 $match;
219 } @ls;
220}
221
fb73857a 222@ls = sort {
223 $a->{category} cmp $b->{category} || $a->{in} cmp $b->{in}
224} @ls;
225
226
227# --- Handle special modes ---
228
229if ($::opt_4) {
43051805 230 my $tail = ($::opt_5) ? "|| exit 1" : "";
231 print map { "p4 delete $_$tail\n" } @removed if @removed;
232 print map { "p4 add $_$tail\n" } @added if @added;
fb73857a 233 my @patches = grep { $_->{is_in} } @ls;
234 my %patched = map { ($_, 1) } map { keys %{$_->{out}} } @patches;
235 delete @patched{@added};
236 my @patched = sort keys %patched;
43051805 237 print map {
238 my $edit = ($::opt_e && !-f $_) ? "add " : "edit";
239 "p4 $edit $_$tail\n"
240 } @patched if @patched;
241 exit 0 unless $::opt_C;
fb73857a 242}
243
84902520 244if ($::opt_I) {
245 my $n_patches = 0;
246 my($in,$out);
247 my %all_out;
248 foreach $in (@ls) {
249 next unless $in->{is_in};
250 ++$n_patches;
251 my @outs = keys %{$in->{out}};
252 @all_out{@outs} = ($in->{in}) x @outs;
253 }
254 my @all_out = sort keys %all_out;
255 my @missing = grep { ! -f $_ } @all_out;
256 print "$n_patches patch files patch ".@all_out." files (".@missing." missing)\n";
fb73857a 257 print "(use -v to list patches which patch 'missing' files)\n"
258 if @missing && !$::opt_v;
84902520 259 if ($::opt_v and @missing) {
260 print "Missing files:\n";
261 foreach $out (@missing) {
262 printf " %-20s\t%s\n", $out, $all_out{$out};
263 }
264 }
fb73857a 265 print "Added files: @added\n" if @added;
266 print "Removed files: @removed\n" if @removed;
84902520 267 exit 0+@missing;
268}
269
08aa1457 270unless ($::opt_c and $::opt_m) {
3e3baf6d 271 foreach $ls (@ls) {
272 next unless ($::opt_i) ? $ls->{is_out} : $ls->{is_in};
08aa1457 273 list_files_by_patch($ls);
274 }
275}
276else {
277 my $c = '';
3e3baf6d 278 foreach $ls (@ls) {
279 next unless ($::opt_i) ? $ls->{is_out} : $ls->{is_in};
84902520 280 print "\n ------ $cat_title{$ls->{category}} ------\n"
281 if $ls->{category} ne $c;
08aa1457 282 $c = $ls->{category};
3e3baf6d 283 unless ($::opt_i) {
284 list_files_by_patch($ls);
285 }
286 else {
287 my $out = $ls->{in};
288 print "\n$out patched by:\n";
289 # find all the patches which patch $out and list them
290 my @p = grep { $_->{out}->{$out} } values %ls;
291 foreach $ls (@p) {
292 list_files_by_patch($ls, '');
293 }
294 }
08aa1457 295 }
296 print "\n";
297}
298
3e3baf6d 299exit 0;
300
301
302# ---
303
08aa1457 304
305sub add_file {
306 my $ls = shift;
307 my $out = trim_name(shift);
3e3baf6d 308
309 $ls->{out}->{$out} = 1;
310
43051805 311 warn "$out patched but not present\n" if $::opt_e && !-f $out;
312
3e3baf6d 313 # do the -i inverse as well, even if we're not doing -i
314 my $i = $ls{$out} ||= {
315 is_out => 1,
316 in => $out,
317 category => $::opt_c ? categorize_files([ $out ], $::opt_v) : '',
318 };
319 $i->{out}->{$in} = 1;
08aa1457 320}
321
322
323sub trim_name { # reduce/tidy file paths from diff lines
324 my $name = shift;
325 $name = "$name ($in)" if $name eq "/dev/null";
84902520 326 $name =~ s:\\:/:g; # adjust windows paths
327 $name =~ s://:/:g; # simplify (and make win \\share into absolute path)
08aa1457 328 if (defined $::opt_p) {
329 # strip on -p levels of directory prefix
330 my $dc = $::opt_p;
331 $name =~ s:^[^/]+/(.+)$:$1: while $dc-- > 0;
332 }
333 else { # try to strip off leading path to perl directory
334 # if absolute path, strip down to any *perl* directory first
335 $name =~ s:^/.*?perl.*?/::i;
84902520 336 $name =~ s:.*perl[-_]?5?[._]?[-_a-z0-9.+]*/::i;
08aa1457 337 $name =~ s:^\./::;
338 }
339 return $name;
340}
341
342
343sub list_files_by_patch {
3e3baf6d 344 my($ls, $name) = @_;
345 $name = $ls->{in} unless defined $name;
08aa1457 346 my @meta;
347 if ($::opt_m) {
fb73857a 348 my $meta;
349 foreach $meta (@show_meta) {
350 next unless $ls->{$meta};
351 my @list = sort keys %{$ls->{$meta}};
352 push @meta, sprintf "%7s: ", $meta;
353 if ($meta eq 'Title') {
43051805 354 @list = map { s/\[?(PATCH|PERL)\]?:?\s*//g; "\"$_\""; } @list;
355 push @list, "#$1" if $::opt_C && $ls->{in} =~ m:\b(\w\d+)/patch$:;
fb73857a 356 }
357 elsif ($meta eq 'From') {
358 # fix-up bizzare addresses from japan and ibm :-)
359 foreach(@list) {
360 s:\W+=?iso.*?<: <:;
361 s/\d\d-\w\w\w-\d{4}\s+\d\d:\S+\s*//;
362 }
363 }
364 elsif ($meta eq 'Msg-ID') {
365 my %from; # limit long threads to one msg-id per site
366 @list = map {
367 $from{(/@(.*?)>/ ? $1 : $_)}++ ? () : ($_);
368 } @list;
369 }
08aa1457 370 push @meta, my_wrap(""," ", join(", ",@list)."\n");
371 }
3e3baf6d 372 $name = "\n$name" if @meta and $name;
08aa1457 373 }
374 # don't print the header unless the file contains something interesting
3e3baf6d 375 return if !@meta and !$ls->{out};
43051805 376 print("$ls->{in}\n"),return if $::opt_l; # -l = no listing, just names
08aa1457 377
3e3baf6d 378 # a twisty maze of little options
379 my $cat = ($ls->{category} and !$::opt_m) ? "\t$ls->{category}" : "";
380 print "$name$cat: " unless ($::opt_h and !$::opt_v) or !"$name$cat";
08aa1457 381 print join('',"\n",@meta) if @meta;
382
3e3baf6d 383 my @v = sort PATORDER keys %{ $ls->{out} };
08aa1457 384 my $v = "@v\n";
385 print $::opt_m ? " Files: ".my_wrap(""," ",$v) : $v;
386}
387
388
389sub my_wrap {
84902520 390 my $txt = eval { expand(wrap(@_)) }; # die's on long lines!
391 return $txt unless $@;
392 return expand("@_");
08aa1457 393}
394
395
396
3e3baf6d 397sub categorize_files {
398 my($files, $verb) = @_;
08aa1457 399 my(%c, $refine);
3e3baf6d 400
401 foreach (@$files) { # assign a score to a file path
402 # the order of some of the tests is important
403 $c{TEST} += 5,next if m:^t/:;
404 $c{DOC} += 5,next if m:^pod/:;
405 $c{UTIL} += 10,next if m:^(utils|x2p|h2pl)/:;
84902520 406 $c{PORT1}+= 15,next if m:^win32:;
407 $c{PORT2} += 15,next
408 if m:^(cygwin32|os2|plan9|qnx|vms)/:
08aa1457 409 or m:^(hints|Porting|ext/DynaLoader)/:
410 or m:^README\.:;
3e3baf6d 411 $c{LIB} += 10,next
08aa1457 412 if m:^(lib|ext)/:;
3e3baf6d 413 $c{'CORE'} += 15,next
84902520 414 if m:^[^/]+[\._]([chH]|sym|pl)$:;
3e3baf6d 415 $c{BUILD} += 10,next
08aa1457 416 if m:^[A-Z]+$: or m:^[^/]+\.SH$:
84902520 417 or m:^(install|configure|configpm):i;
08aa1457 418 print "Couldn't categorise $_\n" if $::opt_v;
3e3baf6d 419 $c{OTHER} += 1;
420 }
421 if (keys %c > 1) { # sort to find category with highest score
422 refine:
423 ++$refine;
424 my @c = sort { $c{$b} <=> $c{$a} || $a cmp $b } keys %c;
425 my @v = map { $c{$_} } @c;
426 if (@v > 1 and $refine <= 1 and "@v" =~ /^(\d) \1/
427 and $c[0] =~ m/^(DOC|TESTS|OTHER)/) { # rare
428 print "Tie, promoting $c[1] over $c[0]\n" if $::opt_d;
429 ++$c{$c[1]};
430 goto refine;
431 }
432 print " ".@$files." patches: ", join(", ", map { "$_: $c{$_}" } @c),".\n"
433 if $verb;
434 return $c[0] || 'OTHER';
08aa1457 435 }
3e3baf6d 436 else {
437 my($c, $v) = %c;
438 $c ||= 'OTHER'; $v ||= 0;
439 print " ".@$files." patches: $c: $v\n" if $verb;
440 return $c;
08aa1457 441 }
08aa1457 442}
443
444
445sub PATORDER { # PATORDER sort by Chip Salzenberg
446 my ($i, $j);
447
448 $i = ($a =~ m#^[A-Z]+$#);
449 $j = ($b =~ m#^[A-Z]+$#);
450 return $j - $i if $i != $j;
451
452 $i = ($a =~ m#configure|hint#i) || ($a =~ m#[S_]H$#);
453 $j = ($b =~ m#configure|hint#i) || ($b =~ m#[S_]H$#);
454 return $j - $i if $i != $j;
455
456 $i = ($a =~ m#\.pod$#);
457 $j = ($b =~ m#\.pod$#);
458 return $j - $i if $i != $j;
459
460 $i = ($a =~ m#include/#);
461 $j = ($b =~ m#include/#);
462 return $j - $i if $i != $j;
463
464 if ((($i = $a) =~ s#/+[^/]*$##)
465 && (($j = $b) =~ s#/+[^/]*$##)) {
466 return $i cmp $j if $i ne $j;
467 }
468
469 $i = ($a =~ m#\.h$#);
470 $j = ($b =~ m#\.h$#);
471 return $j - $i if $i != $j;
472
473 return $a cmp $b;
474}
475