File Coverage

blib/script/makepatch
Criterion Covered Total %
statement 266 602 44.1
branch 95 364 26.1
condition 29 145 20.0
subroutine 22 34 64.7
pod n/a
total 412 1145 35.9


line stmt bran cond sub pod time code
1             #!/usr/local/bin/perl -w
2             # makepatch.pl -- generate a patch kit from two files or directories.
3             # Author : Johan Vromans
4             # Created On : Tue Jul 7 20:39:39 1992
5             # Last Modified By: Johan Vromans
6             # Last Modified On: Fri Oct 26 21:46:58 2012
7             # Update Count : 1196
8             # Status : Released
9              
10 1     1   1374 use strict;
  1         2  
  1         35  
11 1     1   727 use Getopt::Long 2.00;
  1         10555  
  1         26  
12 1     1   668 use IO qw(File);
  1         682  
  1         5  
13 1     1   8222 use File::Basename;
  1         3  
  1         61  
14 1     1   7 use File::Spec;
  1         1  
  1         23  
15 1     1   5 use File::Path;
  1         2  
  1         10593  
16              
17             ################ Common stuff ################
18              
19             my $my_package = 'Sciurix';
20             my $my_name = "makepatch";
21             my $my_version = "2.05";
22             my $data_version = '1.0';
23              
24             ################ Globals ################
25              
26             ## Options and defaults
27              
28             my $opt_diff = 'diff -c'; # default diff command
29             my $opt_sort; # sort entries. Default = 1
30             my $opt_follow = 0; # follow symbolic links
31             my $opt_automanifest = "MANIFEST";
32             my $opt_oldmanifest; # list of files of the old tree
33             my $opt_newmanifest; # list of files of the new tree
34             my $opt_nomanifest = 0; # suppress use of MANIFEST files
35             my $opt_patchlevel; # patchlevel.h file
36             my $opt_prefix = ''; # prefix to be added
37             my $opt_filelist = 0; # make file list
38             my $opt_infocmd; # info command
39             my $opt_exclude_standard = 1; # standard excludes
40             my $opt_exclude_rcs = 0; # exclude RCS files
41             my $opt_exclude_cvs = 0; # exclude CVS files
42             my $opt_exclude_sccs = 0; # exclude SCCS files
43             my $opt_ignore_rcs_keywords = 0; # exclude CVS/RCS keyword data
44             my @opt_exclude; # list of excludes (wildcards)
45             my @opt_exclude_regex; # list of excludes (regex)
46             my $opt_recurse = 1; # recurse
47             my @opt_descr = (); # description
48             my %opt_extract = (); # extraction rules
49              
50             # Development options (not shown with -help).
51             my $opt_trace = 0; # trace messages
52             my $opt_verbose = 0; # verbose info
53             my $opt_quiet = 0; # (almost?) no info
54             my $opt_debug = 0; # debugging messages
55             my $opt_test = 0; # testing
56              
57             ## Misc
58              
59             my $exclude_pat; # regex to exclude
60             my @workq = (); # pre/post work
61              
62             # Try to find a temp location.
63             my $TMPDIR = (File::Spec->can('tmpdir') && File::Spec->tmpdir)
64             || $ENV{TMPDIR}
65             || $ENV{TEMP}
66             || '/usr/tmp';
67              
68             my $dot_u = File::Spec::Unix->curdir; # UNIX current dir
69             my $dot = File::Spec->curdir; # current dir
70             my $dotdot = File::Spec->updir; # parent dir
71              
72             # Try to find something home-ish.
73             my $HOME = $ENV{HOME}
74             || ( ($^O eq 'MSWin32')
75             && ( $ENV{APPDATA}
76             || $ENV{USERPROFILE}
77             || $ENV{HOMEDRIVE} && $ENV{HOMEPATH}
78             && $ENV{HOMEDRIVE}.$ENV{HOMEPATH}
79             )
80             )
81             || $dot;
82              
83             # Try to find something null-ish.
84             my $DEVNULL = (File::Spec->can('devnull') && File::Spec->devnull)
85             || '/dev/null';
86             my $nulpat = quotemeta($DEVNULL); # pattern to match nul device
87              
88             my $timestamp = "".localtime(); # timestamp, in string format
89             my $unified = 0; # produce unified diff
90             my $skipped = 0; # number of files skipped.
91             my $excluded = 0; # number of files excluded.
92              
93             ## Subroutine prototypes
94              
95             sub app_options ();
96             sub app_parse_rc ($$$);
97             sub app_usage ($);
98             sub app_usage_filelist ($);
99             sub catfile ($$);
100             sub check_extract ($);
101             sub cleanup ();
102             sub cvs_excludes($$$);
103             sub cvs_ignore($);
104             sub debug (@);
105             sub dodiff ($$$$);
106             sub makepatch ();
107             sub extract ($$);
108             sub filelist ($);
109             sub make_filelist ($;$);
110             sub make_filelist_from_manifest ($);
111             sub message (@);
112             sub newfile ($$);
113             sub quotfn ($);
114             sub setup_excludes ();
115             sub showopts ($);
116             sub trace (@);
117             sub verbose (@);
118             sub wrapup (;$);
119             sub yesno ($);
120              
121             ################ INI files, program parameters ################
122              
123             app_options ();
124              
125             ################ Presets ################
126              
127             if ( $opt_exclude_sccs ) {
128             unshift (@opt_exclude, qw(p.* s.* SCCS));
129              
130             }
131              
132             if ( $opt_exclude_rcs ) {
133             unshift (@opt_exclude, ',*', '*,v', qw(RCS RCSLOG));
134             }
135              
136             if ( $opt_exclude_cvs ) {
137             # Load common .cvsignore, if present.
138             for ( catfile($HOME, ".cvsignore") ) {
139             unshift (@opt_exclude, cvs_ignore($_)) if -s $_;
140             }
141              
142             unshift (@opt_exclude, '.#*', '#*',
143             qw(_$* *$ CVS CVS.adm cvslog.*));
144              
145             }
146              
147             if ( $opt_exclude_standard ) {
148             # Common excludes.
149             # Mostly copied from 'Open Source Development with CVS', p. 170.
150             unshift (@opt_exclude,
151             qw(*~ *.a *.bak *.BAK *.elc *.exe *.gz *.ln *.o *.obj
152             *.olb *.old *.orig *.rej *.so *.Z
153             .del-* .make.state .nse_depinfo core
154             tags TAGS));
155             }
156              
157             setup_excludes ();
158              
159             if ( $opt_ignore_rcs_keywords ) {
160             # Note: We ignore 'Log' since that wouldn't work anyway.
161             $opt_diff .= ' ' .
162             q{'--ignore-matching-lines=\\$\\(} .
163             join('\\|', qw(Author Date Header Id Locker Name RCSfile
164             Revision Source State)) .
165             q{\\)[^$]*\\$'};
166             }
167              
168             ################ The Process ################
169              
170             # Handle --filelist. Special but obsolete case.
171             if ( $opt_filelist ) {
172             filelist ($ARGV[0]);
173             die ("Okay\n") if $opt_test;
174             exit (0);
175             }
176              
177             # Check temp dir.
178             unless ( -d $TMPDIR && -w $TMPDIR ) {
179             print STDERR <
180             Please use environment variable TMPDIR or TEMP to designate a writable
181             directory to hold temporary files.
182             EOD
183             die ("Cannot continue\n");
184             }
185              
186             # Create temp dir and names for temp files.
187             my $tmpdir = File::Spec->catdir ($TMPDIR, "mp$$.d");
188             mkdir ($tmpdir, 0777) or die ("tmpdir: $!\n");
189             my $thepatch = catfile ($tmpdir, ".mp$$.p");
190             my $tmpfile = catfile ($tmpdir, ".mp$$.t");
191             my $patch = new IO::File;
192              
193             # Attach cleanup handler.
194             $SIG{INT} = \&cleanup;
195             $SIG{QUIT} = \&cleanup;
196              
197             # The arguments.
198             my ($old, $new);
199             if ( $] >= 5.005 && $] < 5.008 ) {
200             # Use pseudo-hashes if possible.
201             my %fields = ( tag => 1, # old/new
202             name => 2, # given name on command line
203             root => 3, # real (physical) directory
204             base => 4, # basename (for archives)
205             man => 5, # name of manifest
206             manfn => 6, # same, real file name
207             files => 7, # list of files
208             );
209             $old = [ \%fields, "old", shift(@ARGV) ];
210             $new = [ \%fields, "new", shift(@ARGV) ];
211             }
212             else {
213             $old = { tag => "old", name => shift(@ARGV) };
214             $new = { tag => "new", name => shift(@ARGV) };
215             }
216              
217             # Unpack archives, if applicable.
218             # $old->{root} and $new->{root} are the real locations for the source trees.
219             check_extract ($old);
220             check_extract ($new);
221              
222             # The process.
223             makepatch ();
224              
225             # Wrap up.
226             wrapup ();
227              
228             die ("Okay\n") if $opt_test;
229              
230             # In case nothing went wrong...
231 1     1   672 END { cleanup (); }
232              
233             ################ Subroutines ################
234              
235 7 50   7   42 sub message (@) { print STDERR (@_) unless $opt_quiet; }
236 23 50   23   39 sub verbose (@) { print STDERR (@_) if $opt_verbose; }
237 13 50   13   52 sub debug (@) { print STDERR (@_) if $opt_debug; }
238 5 50   5   15 sub trace (@) { print STDERR (@_) if $opt_trace; }
239              
240             sub makepatch () {
241              
242             # This will bail out if the directory could not be created.
243 1 50   1   8 $patch->open(">$thepatch") || die ("$thepatch: $!\n");
244 1         104 binmode($patch);
245              
246 1 50 33     76 if ( -f $old->{root} && -f $new->{root} ) {
    50 33        
    50 33        
    50 33        
247             # Two files.
248 0         0 verbose ("Old file = $old->{root}.\n", "New file = $new->{root}.\n");
249             dodiff ($dot, $new->{root}, $dot, $old->{root}) &&
250             push (@workq, [ 'p', $old->{root}, -s $old->{root},
251 0 0       0 (stat($new->{root}))[9], (stat(_))[2] ]);
252             }
253             elsif ( -f $old->{root} && -d $new->{root} ) {
254             # File and dir -> File and dir/File.
255 0         0 $new->{root} = $new->{base} = catfile ($new->{root}, $old->{root});
256 0         0 verbose ("Old file = $old->{root}.\n", "New file = $new->{root}.\n");
257 0 0       0 if ( -f $new->{root} ) {
258             dodiff ($dot, $new->{root}, $dot, $old->{root}) &&
259             push (@workq, [ 'p', $old->{root}, -s $old->{root},
260 0 0       0 (stat($new->{root}))[9], (stat(_))[2] ]);
261             }
262             else {
263 0         0 unshift (@workq, [ 'r', $old->{root}, -s $old->{root}, 0 ]);
264             }
265             }
266             elsif ( -d $old->{root} && -f $new->{root} ) {
267             # Dir and file -> Dir/file and file.
268 0         0 $old->{root} = $old->{base} = catfile ($old->{root}, $new->{root});
269 0         0 verbose ("Old file = $old->{root}.\n", "New file = $new->{root}.\n");
270 0 0       0 if ( -f $old->{root} ) {
271             dodiff ($dot, $new->{root}, $dot, $old->{root}) &&
272             push (@workq, [ 'p', $old->{root}, -s $old->{root},
273 0 0       0 (stat($new->{root}))[9], (stat(_))[2] ]);
274             }
275             else {
276             newfile ($new->{root}, $old->{root}) &&
277             push (@workq, [ 'c', $old->{root}, 0,
278 0 0       0 (stat($new->{root}))[9], (stat(_))[2] ]);
279             }
280             }
281             elsif ( -d $old->{root} && -d $new->{root} ) {
282             # Two directories.
283 1 50 33     14 if ( $opt_nomanifest ) {
    50 33        
      33        
      33        
284 0         0 verbose ("Not using MANIFEST files.\n");
285 0         0 undef $opt_oldmanifest;
286 0         0 undef $opt_newmanifest;
287             }
288             elsif ( defined $opt_automanifest &&
289             !(defined $opt_oldmanifest || defined $opt_newmanifest) &&
290             (-s catfile($old->{root}, $opt_automanifest) &&
291             -s catfile($new->{root}, $opt_automanifest)) ) {
292 0         0 verbose ("Using standard $opt_automanifest files.\n");
293 0         0 $opt_oldmanifest = catfile($old->{root},$opt_automanifest);
294 0         0 $opt_newmanifest = catfile($new->{root},$opt_automanifest);
295 0         0 $new->{man} = $old->{man} = $opt_automanifest;
296 0         0 $old->{manfn} = $opt_oldmanifest;
297 0         0 $new->{manfn} = $opt_newmanifest;
298             }
299             else {
300 1         4 $old->{man} = $old->{manfn} = $opt_oldmanifest;
301 1         3 $new->{man} = $new->{manfn} = $opt_newmanifest;
302             }
303              
304 1         4 for ( $old, $new ) {
305 2 50       34 if ( defined ($_->{manfn}) ) {
306             my $t = $_->{name} eq $dot ? "current directory" :
307 0 0       0 $_->{name} eq $dotdot ? "parent directory" : $_->{base};
    0          
308 0         0 $_->{files} = [ make_filelist_from_manifest ($_->{manfn}) ];
309             message ("Manifest $_->{man} for $t contains ",
310 0         0 scalar(@{$_->{files}}), " file",
311 0 0       0 scalar(@{$_->{files}}) == 1 ? "" : "s", ".\n");
  0         0  
312             }
313             else {
314             my $t = $_->{name} eq $dot ? "current directory" :
315 2 50       25 $_->{name} eq $dotdot ? "parent directory" :
    50          
316             "directory $_->{base}";
317 2         9 message ("Building file list for $t ...\n");
318 2         7 $_->{files} = [ make_filelist ($_->{root}) ];
319             message (ucfirst($t)." contains ",
320 2         6 scalar(@{$_->{files}}), " file",
321 2 50       7 scalar(@{$_->{files}}) == 1 ? "" : "s", ".\n");
  2         8  
322             }
323             }
324              
325             # Handle patchlevel file first.
326 1 50       5 $opt_patchlevel = (grep (/patchlevel\.h/, @{$new->{files}}))[0]
  1         4  
327             unless defined $opt_patchlevel;
328              
329 1 50 33     5 if ( defined $opt_patchlevel && $opt_patchlevel ne "" ) {
330 0         0 my $oldpl = catfile ($old->{root}, $opt_patchlevel);
331 0         0 my $newpl = catfile ($new->{root}, $opt_patchlevel);
332 0 0       0 if ( ! -f $newpl ) {
333 0         0 die ("$newpl: $!\n");
334             }
335 0 0       0 if ( -f $oldpl ) {
336             push (@workq, [ dodiff ($new->{root}, $opt_patchlevel,
337 0 0       0 $old->{root}, $opt_patchlevel) ? 'p' : 'v',
338             $opt_patchlevel,
339             -s $oldpl,
340             (stat($newpl))[9], (stat(_))[2] ]);
341             # Remove patchlevel.h from the list of old files.
342 0         0 $old->{files} = [ grep ($_ ne $opt_patchlevel, @{$old->{files}}) ];
  0         0  
343             }
344             else {
345 0 0       0 newfile ($new->{root}, $opt_patchlevel) &&
346             push (@workq, [ 'c', $opt_patchlevel, 0,
347             (stat($newpl))[9], (stat(_))[2] ]);
348             }
349             # Remove patchlevel.h from the list of new files.
350 0         0 $new->{files} = [ grep ($_ ne $opt_patchlevel, @{$new->{files}}) ];
  0         0  
351             }
352             else {
353 1         2 undef $opt_patchlevel;
354             }
355              
356 1         2 my $o;
357             my $n;
358              
359 1         3 message ("Processing the filelists ...\n");
360 1   66     1 while ( scalar(@{$old->{files}}) + scalar(@{$new->{files}}) > 0
  3   66     12  
  3         44  
361             || defined $o || defined $n ) {
362              
363 2 50       5 $o = shift (@{$old->{files}}) unless defined $o;
  2         18  
364 2 50       8 $n = shift (@{$new->{files}}) unless defined $n;
  2         8  
365              
366 2 50 0     5 debug ("* ", $o || "(undef)", " <-> ", $n || "(undef)", " ",
      0        
367             "* $old->{files}->[0] <-> $new->{files}->[0]\n") if $opt_debug;
368 2 50 33     27 if ( defined $n && (!defined $o || $o gt $n) ) {
    50 33        
    50 33        
369             # New file.
370 0         0 debug ("*> New file: $n\n");
371             newfile ($new->{root}, $n) &&
372             push (@workq, [ 'c', $n, 0,
373 0 0       0 (stat(catfile($new->{root},$n)))[9],
374             (stat(_))[2] ]);
375 0         0 undef $n;
376             }
377             elsif ( !defined $n || $o lt $n ) {
378             # Obsolete (removed) file.
379 0         0 debug ("*> Obsolete: $o\n");
380 0         0 unshift (@workq, [ 'r', $o, -s catfile($old->{root},$o), 0 ]);
381 0         0 undef $o;
382             }
383             elsif ( $o eq $n ) {
384             # Same file.
385 2         15 debug ("*> Compare: $n\n");
386             dodiff ($new->{root}, $n, $old->{root}, $o) &&
387             push (@workq, [ 'p', $o, -s catfile($old->{root},$o),
388 2 50       7 (stat(catfile($new->{root},$n)))[9],
389             (stat(_))[2] ]);
390 2         9 undef $n;
391 2         5 undef $o;
392             }
393             }
394             }
395             else {
396 0         0 $patch->close;
397 0         0 app_usage (1);
398             }
399              
400 1         25 $patch->close;
401              
402             # For the sake of memory usage...
403 1         38 undef $old->{files};
404 1         9 undef $new->{files};
405             }
406              
407             sub cleanup () {
408             return unless defined $tmpdir;
409             return unless -d $tmpdir;
410             verbose ("Cleaning up...\n");
411             rmtree ($tmpdir);
412             die ("Okay\n") if $opt_test;
413             exit (0);
414             }
415              
416             sub shellpat($) {
417 22     22   30 my ($pat) = (@_);
418 22         73 my @a = split (/(\[[^\]]+\]|[*.?])/, $pat);
419             join ('',
420 22 50       32 (map { ($_ eq '*' ? '.*' :
  93 100       244  
    50          
    100          
421             ($_ eq '?' ? '.' :
422             ($_ eq '.' ? '\.' :
423             ($_ =~ /^\[/ ? $_ : quotemeta ($_)))))
424             } @a));
425             }
426              
427             sub setup_excludes () {
428             # Add --exclude wildcards to --exclude-regex list.
429 1 50   1   3 if ( @opt_exclude ) {
430 1         2 my $pat;
431 1         3 foreach $pat ( @opt_exclude ) {
432 22         38 push (@opt_exclude_regex, '(\A|/)'.shellpat($pat).'\Z');
433             }
434             }
435              
436             # Build regex from --exclude-regex list.
437 1 50       4 if ( @opt_exclude_regex ) {
438 1         2 $exclude_pat = '(';
439 1         1 my $re;
440 1         2 foreach $re ( @opt_exclude_regex ) {
441 22         36 verbose (" Exclude regex: ", $re, "\n");
442 22         21 eval { '' =~ /$re/ };
  22         238  
443 22 50       49 if ( $@ ) {
444 0         0 $@ =~ s/ at .* line.*$//;
445 0         0 die ("Invalid regex: $re $@");
446             }
447 22         43 $exclude_pat .= "($re)|";
448             }
449 1         3 chop ($exclude_pat);
450 1         3 $exclude_pat .= ')';
451 1         4 debug ("Exclude pattern: $exclude_pat\n");
452             }
453             }
454              
455             sub cvs_ignore($) {
456 0     0   0 my ($f) = @_;
457 0         0 my $fh = do { local *F; *F; };
  0         0  
  0         0  
458 0 0       0 unless ( open($fh, $f) ) {
459 0         0 warn("$f: $!\n");
460 0         0 return ();
461             }
462 0         0 local($/) = undef;
463 0         0 my $pat = <$fh>;
464 0         0 close($fh);
465              
466 0         0 $pat =~ s/[\n\r]+/\n/g;
467 0         0 $pat =~ s/\s+$//;
468 0         0 $pat =~ s/^\s+//;
469 0         0 split(/\n/, $pat);
470             }
471              
472             sub cvs_excludes($$$) {
473 0     0   0 my ($f, $dir, $disp) = @_;
474              
475 0         0 my @list = cvs_ignore($f);
476 0 0       0 return "" unless @list;
477              
478 0         0 for ( $dir, $disp ) {
479 0 0       0 $_ = "" unless defined $_;
480 0 0 0     0 $_ .= '/' if $_ && $_ !~ /\/$/;
481 0         0 $_ = '\A' . quotemeta($_);
482             }
483              
484 0         0 my $ret = "";
485 0         0 foreach my $pat ( @list ) {
486 0         0 my $re = shellpat($pat);
487 0         0 debug ("$f: '$pat' -> '$re'\n");
488 0         0 eval { '' =~ /$re/ };
  0         0  
489 0 0       0 if ( $@ ) {
490 0         0 $@ =~ s/ at .* line.*$//;
491 0         0 warn("$f: invalid pattern '$pat'");
492 0         0 next;
493             }
494 0         0 push(@opt_exclude_regex, $dir.$re.'\Z');
495 0         0 $ret .= "($re)|";
496             }
497 0 0       0 if ( $ret ) {
498 0         0 chop($ret);
499 0         0 $ret = '('.$disp.'('.$ret.')\Z)';
500             }
501 0         0 debug ("Exclude pattern ($f): $ret\n");
502 0         0 $ret;
503             }
504              
505             sub make_filelist ($;$) {
506 2     2   12 my ($dir, $disp) = @_;
507              
508             # Return a list of files, sorted, for this directory.
509             # Recurses if $opt_recurse.
510              
511 2         12 my $dh = new IO::File;
512 2         64 trace ("+ recurse $dir\n");
513 2 50       80 opendir ($dh, $dir) || die ("$dir: $!\n");
514 2         51 my @tmp = readdir ($dh);
515 2         37 closedir ($dh);
516 2         12 debug ("Dir $dir: ", scalar(@tmp), " entries\n");
517              
518 2         5 my @ret = ();
519 2         2 my $file;
520 2         3 my $excl = $exclude_pat;
521 2         5 for ( catfile($dir, ".cvsignore") ) {
522 2 50       36 $excl = '('.$excl.'|'.cvs_excludes($_,$dir,$disp).')' if -s $_;
523 2         16 debug("Exclude pattern: $excl\n");
524             }
525 2         5 foreach $file ( @tmp ) {
526              
527             # Skip unwanted files.
528 8 100       26 next if $file =~ /^\.\.?$/; # dot and dotdot
529 4 50       9 next if $file =~ /~$/; # editor backup files
530              
531 4         27 my $realname = catfile ($dir, $file);
532 4 50       12 my $display_name = defined $disp ? catfile($disp,$file) : $file;
533              
534             # Skip exclusions.
535 4 50 33     244 if ( defined $excl && $display_name =~ /$excl/mso ) {
536 0         0 verbose ("Excluding $display_name\n");
537 0         0 $excluded++;
538 0         0 next;
539             }
540              
541             # Push on the list.
542 4 50 0     96 if ( -d $realname && ( $opt_follow || ! -l $realname ) ) {
    50 33        
543 0 0       0 next unless $opt_recurse;
544             # Recurse.
545 0         0 push (@ret, make_filelist ($realname, $display_name));
546             }
547             elsif ( -f _ ) {
548 4         19 debug("+ file $display_name\n");
549 4         12 push (@ret, $display_name);
550             }
551             else {
552 0         0 verbose ("WARNING: Not a file: $realname -- skipped\n");
553 0         0 $skipped++;
554             }
555             }
556 2 50       10 @ret = sort @ret if $opt_sort;
557 2         15 @ret;
558             }
559              
560             sub make_filelist_from_manifest ($) {
561              
562             # Return a list of files, optionally sorted, from a manifest file.
563              
564 0     0   0 my ($man) = @_;
565 0         0 my $fh = new IO::File;
566 0         0 my @ret = ();
567 0         0 local ($_);
568              
569 0 0       0 $fh->open($man) || die ("$man: $!\n");
570 0         0 binmode($fh);
571 0         0 while ( <$fh> ) {
572 0 0 0     0 if ( $. == 2 && /^[-=_\s]*$/ ) {
573 0         0 @ret = ();
574 0         0 next;
575             }
576 0 0       0 next if /^#/;
577 0 0       0 next unless /\S/;
578 0 0       0 $_ = $1 if /^(\S+)\s/;
579 0 0 0     0 if ( defined $exclude_pat && /$exclude_pat/mso ) {
580 0         0 verbose ("Excluding $_\n");
581 0         0 $excluded++;
582 0         0 next;
583             }
584 0         0 push (@ret, $_);
585             }
586 0         0 $fh->close;
587 0 0       0 @ret = sort @ret if $opt_sort;
588 0         0 @ret;
589             }
590              
591             sub check_extract ($) {
592 2     2   3 my ($arg) = @_;
593              
594 2         7 my @exctrl = ('.+\.(tar\.gz|tgz)' => "gzip -d | tar xpf -",
595             '.+\.(tar\.bz2)' => "bzip2 -d | tar xpf -",
596             '.+\.(tar)' => "tar xf -",
597             '.+\.(zip)' => "unzip -",
598             );
599              
600             # Plug in user defined rules.
601 2 50       5 if ( %opt_extract ) {
602 0         0 my ($k, $v);
603 0         0 while ( ($k,$v) = each (%opt_extract) ) {
604 0         0 unshift (@exctrl, $v);
605 0         0 unshift (@exctrl, $k);
606             }
607             }
608              
609 2         9 $arg->{root} = File::Spec->canonpath ($arg->{name});
610 2         92 my $base = basename ($arg->{root});
611              
612 2         8 while ( @exctrl > 0 ) {
613 8         21 my $pat = shift (@exctrl);
614 8         11 my $cmd = shift (@exctrl);
615 8 50       116 if ( $base =~ /^$pat$/is ) {
616 0         0 extract ($arg, $cmd);
617             verbose ("Using $arg->{root} for $arg->{name}\n")
618 0 0       0 unless $arg->{root} eq $arg->{name};
619 0         0 return;
620             }
621             }
622 2         7 $arg->{root} = $arg->{base} = $arg->{name};
623             }
624              
625             sub extract ($$) {
626 0     0   0 my ($arg, $cmd) = @_;
627              
628 0         0 my $tmp = catfile ($tmpdir, $arg->{tag});
629 0         0 message ("Extracting $arg->{name} to $tmp...\n");
630              
631             # Create a temp directory.
632 0 0       0 mkdir ($tmp, 0777) || die ("Cannot mkdir $tmp [$!]\n");
633              
634             # Extract the kit.
635 0         0 $cmd = "( cd $tmp; $cmd ) < $arg->{name}";
636 0         0 trace ("+ $cmd\n");
637 0         0 my $ret = system ("$cmd 1>&2");
638 0 0 0     0 if ( $ret || ($? & 127) ) {
639 0 0       0 die ("Not okay 1\n") if $opt_test;
640 0         0 exit (1);
641             }
642              
643             # Inspect the directory.
644 0         0 my $dir = new IO::File;
645 0 0       0 opendir ($dir, $tmp) || die ("Cannot read $tmp [$!]\n");
646 0         0 my @files = grep ($_ !~ /^\.+$/, readdir ($dir));
647 0         0 closedir ($dir);
648              
649             # If we have only one directory, assume it is the root.
650 0 0 0     0 if ( @files == 1 && -d catfile($tmp,$files[0]) ) {
651 0         0 $arg->{base} = $files[0];
652 0         0 $arg->{root} = catfile($tmp,$files[0]);
653 0         0 return;
654             }
655             # Else, take the temp dir as root.
656 0         0 $arg->{root} = $tmp;
657 0         0 $arg->{base} = $arg->{name};
658             }
659              
660             sub catfile ($$) {
661 17     17   269 File::Spec->canonpath(File::Spec->catfile(@_));
662             }
663              
664             sub dot_file_u ($) {
665 6 50   6   81 $_[0] =~ s,\\,/,g if $^O =~ /^MSWin/i;
666 6         359 File::Spec::Unix->catfile($dot_u, File::Spec::Unix->canonpath(@_));
667             }
668              
669             sub dodiff ($$$$) {
670 2     2   5 my ($newdir, $new, $olddir, $old) = @_;
671 2         19 my $fh = new IO::File;
672 2         112 my $oldfn = catfile ($olddir, $old);
673 2         5 my $newfn = catfile ($newdir, $new);
674              
675             # Check for binary files.
676 2 50 33     140 if ( -s $oldfn && -B _ ) {
677 0         0 verbose ("WARNING: Binary file $oldfn -- skipped\n");
678 0         0 $skipped++;
679 0         0 return 0;
680             }
681 2 50 33     103 if ( -s $newfn && -B _ ) {
682 0         0 verbose ("WARNING: Binary file $newfn -- skipped\n");
683 0         0 $skipped++;
684 0         0 return 0;
685             }
686              
687             # Produce a patch hunk.
688 2         16 my $cmd = $opt_diff . ' ' . quotfn($oldfn) . ' ' . quotfn($newfn);
689 2         11 trace ("+ ", $cmd, "\n");
690              
691 2         8817 my $result = system ("$cmd > $tmpfile");
692 2 50       151 debug (sprintf ("+> result = 0x%x\n", $result)) if $result;
693              
694 2 50 33     41 if ( $result && $result < 128 ) {
695 0 0 0     0 wrapup (($result == 2 || $result == 3)
696             ? "User request" : "System error");
697 0 0       0 die ("Not okay 2\n") if $opt_test;
698 0         0 exit (1);
699             }
700 2 50       16 return 0 unless $result == 0x100; # no diffs
701 2         36 print $patch ($cmd, "\n");
702              
703             # Add output from user defined file information command.
704 2 50       19 if ( defined $opt_infocmd ) {
705 0         0 my $cmd = $opt_infocmd;
706 0         0 $cmd =~ s/\002P/$oldfn/eg;
  0         0  
707 0         0 $cmd =~ s/\003P/$newfn/eg;
  0         0  
708 0         0 print $patch (`$cmd`);
709             }
710              
711             # By prepending $dot to the names, we can use 'patch -p0' as well
712             # as 'patch -p1'.
713 2         31 print $patch ("Index: ", dot_file_u($old), "\n");
714              
715             # Try to find a prereq.
716             # The RCS code is based on a suggestion by jima@netcom.com, who also
717             # pointed out that patch requires blanks around the prereq string.
718 2 50       53 if ( $fh->open($oldfn) ) {
719 2         200 binmode($fh);
720 2         80 while ( <$fh> ) {
721 6 50       54 next unless (/(\@\(\#\)\@?|\$Header\:|\$Id\:)(.*)$/);
722 0 0       0 next unless $+ =~ /(\s\d+(\.\d+)*\s)/; # e.g. 5.4
723 0         0 print $patch ("Prereq: $1\n");
724 0         0 last;
725             }
726 2         65 $fh->close;
727             }
728             else {
729 0         0 warn ("$oldfn: $!\n");
730             }
731              
732             # Copy patch.
733 2 50       41 $fh->open($tmpfile) || die ("$tmpfile: $!\n");
734 2         94 binmode($fh);
735              
736             # Skip to beginning of patch. Adjust $unified if needed.
737 2         13 my $found = 0;
738 2         36 while ( <$fh> ) {
739 6 50       59 if ( /^\@\@/ ) {
    100          
740 0         0 $unified = 1;
741 0         0 $found = 1;
742 0         0 last;
743             }
744             elsif ( /^\*{15}/ ) {
745 2         14 $unified = 0;
746 2         3 $found = 1;
747 2         12 last;
748             }
749             }
750 2 50       6 unless ( $found ) {
751 0         0 die ("ALARM: No patch data found for $old\n",
752             "Something is wrong with your diff command \"$opt_diff\".\n",
753             "It should produce context or unified diff output.\n");
754             }
755              
756             # Replace patch header.
757 2 50       6 if ( $unified ) {
758 0         0 print $patch ("--- ", dot_file_u($old),
759             "\t" . localtime((stat($oldfn))[9]), "\n",
760             "+++ ", dot_file_u($new),
761             "\t" . localtime((stat($newfn))[9]), "\n",
762             $_);
763             }
764             else {
765 2         9 print $patch ("*** ", dot_file_u($old),
766             "\t" . localtime((stat($oldfn))[9]), "\n",
767             "--- ", dot_file_u($new),
768             "\t" . localtime((stat($newfn))[9]), "\n",
769             $_);
770             }
771              
772             # Copy rest.
773 2         40 print $patch ($_) while <$fh>;
774 2         24 print "\n"; # just in case
775 2         11 $fh->close;
776              
777 2         89 return 1;
778             }
779              
780             sub newfile ($$) {
781              
782             # In-line production of what diff would have produced.
783              
784 0     0   0 my ($newdir, $new) = @_;
785 0         0 my $fh = new IO::File;
786 0         0 my $newfn = catfile ($newdir, $new);
787              
788 0         0 my $lines = 0;
789 0 0       0 unless ( $fh->open($newfn) ) {
790 0         0 warn ("$newfn: $!\n");
791 0         0 $skipped++;
792 0         0 return 0;
793             }
794 0         0 binmode($fh);
795             # We cannot trust stdio here.
796 0 0 0     0 if ( -s $newfn && -B _ ) {
797 0         0 verbose ("WARNING: Binary file $new -- skipped\n");
798 0         0 $skipped++;
799 0         0 return 0;
800             }
801              
802 0         0 my $pos = $fh->getpos;
803 0         0 while ( <$fh> ) {
804 0         0 $lines++;
805             }
806 0         0 $fh->setpos($pos);
807              
808             # Avoid creating a patch if the new file is empty.
809 0 0       0 if ($lines == 0) {
810 0         0 return 1;
811             }
812              
813 0         0 my $cmd = $opt_diff . " " . $DEVNULL . " " . quotfn($newfn);
814 0         0 trace ("+ $cmd (inlined)\n");
815 0         0 print $patch ($cmd, "\n");
816              
817             # Add output from user defined file information command.
818 0 0       0 if ( defined $opt_infocmd ) {
819 0         0 my $cmd = $opt_infocmd;
820 0         0 $cmd =~ s/\002P/$newfn/eg;
  0         0  
821 0         0 $cmd =~ s/\003P/$newfn/eg;
  0         0  
822 0         0 print $patch (`$cmd`);
823             }
824              
825             # Prepending $dot, so we can use 'patch -p0' as well as 'patch -p1'.
826 0         0 $new = dot_file_u($new);
827              
828 0         0 print $patch ("Index: $new\n");
829              
830 0 0       0 $lines = "1,$lines" unless $lines == 1;
831              
832 0 0       0 if ( $unified ) {
833 0         0 print $patch ("--- ", $new, "\t" . localtime(0), "\n",
834             "+++ ", $new, "\t" . localtime((stat($fh))[9]), "\n",
835             "\@\@ -0,0 +", $lines, " \@\@\n");
836 0         0 while ( <$fh> ) {
837 0         0 print $patch ("+$_");
838             }
839             }
840             else {
841 0         0 print $patch ("*** ", $new, "\t" . localtime(0), "\n",
842             "--- ", $new, "\t" . localtime((stat($fh))[9]), "\n",
843             "***************\n",
844             "*** 0 ****\n",
845             "--- ", $lines, " ----\n");
846 0         0 while ( <$fh> ) {
847 0         0 print $patch ("+ $_");
848             }
849             }
850              
851 0         0 $fh->close;
852 0         0 return 1;
853             }
854              
855       0     sub remove_file ($$) {
856             # diff -c -N -r t1/f2 t2/f2
857             # *** t1/f2 Tue Jul 7 21:28:45 1992
858             # --- t2/f2 Thu Jan 1 01:00:00 1970
859             # ***************
860             # *** 1,1 ****
861             # - foo
862             # - bar
863             # --- 0 ----
864              
865             # diff -u -N -r t1/f2 t2/f2
866             # --- t1/f2 Tue Jul 7 21:28:45 1992
867             # +++ t2/f2 Thu Jan 1 01:00:00 1970
868             # @@ -1,1 +0,0 @@
869             # -foo
870             # -bar
871             }
872              
873             sub quotfn ($) {
874 6     6   21 my ($file) = @_;
875             # Protect file name.
876 6         23 $file =~ s/`/\\`/g;
877 6 50       51 ($^O =~ /^MSWin/i) ? "\"$file\"" : "'$file'";
878             }
879              
880             sub wrapup (;$) {
881 1     1   12 my ($reason) = @_;
882              
883 1 50       27 if ( defined $reason ) {
884 0         0 warn ("*** Aborted: $reason ***\n");
885 0         0 return;
886             }
887              
888 1 0       9 warn ("WARNING: $skipped file",
    0          
    50          
889             $skipped == 1 ? " was" : "s were", " skipped!",
890             $opt_verbose ? "" : " Use \"--verbose\" for more details.",
891             "\n") if $skipped;
892              
893             # Construct a description, if possible.
894 1 50       8 if ( @opt_descr == 0 ) {
895 0         0 my $old = $old->{base};
896 0         0 my $new = $new->{base};
897             # We can infer a name if the file name does not contain a
898             # directory part, and is not equal to . or ..
899 0 0 0     0 if ( $old ne $dot && $old ne $dotdot && basename($old) eq $old &&
      0        
      0        
      0        
      0        
900             $new ne $dot && $new ne $dotdot && basename($new) eq $new
901             ) {
902 0         0 @opt_descr = ("This is a patch for $old to update it to $new");
903             }
904             }
905              
906             # Get a description, unless provided.
907 1 50       15 if ( @opt_descr == 0 ) {
908 0         0 print STDERR ("Enter patch description, ",
909             "terminated with a single '.':\n>> ");
910 0         0 while ( ) {
911 0         0 chomp;
912 0 0       0 last if $_ eq ".";
913 0         0 push (@opt_descr, $_);
914 0         0 print STDERR (">> ");
915             }
916 0 0       0 print STDERR ("\n") unless $_ eq ".";
917             }
918 1         15 push (@opt_descr, "");
919              
920 1         18 message ("Collecting patches ...\n");
921              
922 1         8 my $removed = 0; # files removed
923 1         3 my $created = 0; # files added
924 1         2 my $patched = 0; # files patched
925 1         6 my $dremoved = 0; # directories removed
926 1         3 my $dcreated = 0; # directories created
927              
928 1         1 { my @goners = ();
  1         3  
929 1         2 my %dir_gone = ();
930 1         1 my @newcomers = ();
931 1         2 my %dir_ok = ();
932 1         3 foreach ( @workq ) {
933 2         8 my ($op, $fn) = @$_;
934 2 50       7 push (@newcomers, $fn) if $op eq 'c';
935 2 50       5 push (@goners, $fn) if $op eq 'r';
936 2 50       6 $patched++ if $op eq 'p';
937             }
938 1         2 $created = @newcomers;
939 1         6 $removed = @goners;
940 1         5 foreach ( sort @goners ) {
941             # WARNING: This code assumes you are running some Unix.
942 0         0 my @p = split (/\//, $_);
943 0         0 pop (@p);
944 0         0 foreach my $i ( (1-@p)..0 ) {
945 0         0 my $dir = join('/',@p[0..-$i]);
946 0 0       0 unless ( defined $dir_gone{$dir} ) {
947 0 0       0 unless ( -d catfile($new->{root},$dir) ) {
948 0         0 $dremoved++;
949 0         0 $dir_gone{$dir} = 1;
950             }
951             }
952             }
953             }
954 1         11 foreach ( reverse sort keys %dir_gone ) {
955 0         0 push (@workq, [ 'R', $_ ]);
956             }
957 1         5 foreach ( sort @newcomers ) {
958             # Explicitly create the new files since not all patch versions
959             # can handle creating new files.
960             # Create intermediate directories first.
961             # WARNING: This code assumes you are running some Unix.
962 0         0 my @p = split (/\//, $_);
963 0         0 pop (@p);
964 0         0 foreach my $i ( 0..(@p-1) ) {
965 0         0 my $dir = join('/',@p[0..$i]);
966 0 0       0 unless ( defined $dir_ok{$dir} ) {
967 0 0       0 unless ( -d catfile($old->{root},$dir) ) {
968             push (@workq, [ 'C', $dir, 0,
969 0         0 (stat(catfile($new->{root},$dir)))[9],
970             (stat(_))[2] ]);
971 0         0 $dcreated++;
972             }
973 0         0 $dir_ok{$dir} = 1;
974             }
975             }
976             }
977             }
978              
979 1         15 my $fh = new IO::File;
980 1 50       112 $fh->open(">$tmpfile") || die ("$tmpfile: $!\n");
981 1         23380 binmode($fh);
982              
983 1         13 foreach ( @opt_descr ) {
984 2         11 print $fh ("# ", $_, "\n");
985             }
986 1         3 print $fh <
987             # To apply this patch:
988             # STEP 1: Chdir to the source directory.
989             # STEP 2: Run the 'applypatch' program with this patch file as input.
990             #
991             # If you do not have 'applypatch', it is part of the 'makepatch' package
992             # that you can fetch from the Comprehensive Perl Archive Network:
993             # http://www.perl.com/CPAN/authors/Johan_Vromans/makepatch-x.y.tar.gz
994             # In the above URL, 'x' should be 2 or higher.
995             #
996             # To apply this patch without the use of 'applypatch':
997             # STEP 1: Chdir to the source directory.
998             EOD
999 1 50 33     14 if ( $removed || $created ) {
1000 0         0 my $cd = "";
1001 0         0 my $fd = "";
1002 0 0       0 $cd = "create" if $created;
1003 0 0       0 if ( $removed ) {
1004 0 0       0 $cd .= "/" if $cd;
1005 0         0 $cd .= "delete";
1006             }
1007 0         0 $fd = "files";
1008 0 0 0     0 if ( $dcreated || $dremoved ) {
1009 0 0       0 $fd .= "/" if $fd;
1010 0         0 $fd .= "directories";
1011             }
1012 0         0 print $fh <
1013             # If you have a decent Bourne-type shell:
1014             # STEP 2: Run the shell with this file as input.
1015             # If you don't have such a shell, you may need to manually $cd
1016             # the $fd as shown below.
1017             # STEP 3: Run the 'patch' program with this file as input.
1018             #
1019             # These are the commands needed to create/delete files/directories:
1020             #
1021             EOD
1022 0         0 foreach ( @workq ) {
1023 0         0 my ($op, $file, @args) = @$_;
1024 0 0       0 if ( $op eq 'C' ) {
1025 0         0 print $fh ("mkdir ", quotfn($file), "\n");
1026 0 0 0     0 if ( defined $args[2] && ($args[2] &= 0777) ) {
1027 0         0 printf $fh ("chmod 0%o %s\n", $args[2], quotfn($file))
1028             }
1029             }
1030             }
1031 0         0 foreach ( @workq ) {
1032 0         0 my ($op, $file, @args) = @$_;
1033 0 0       0 if ( $op eq 'r' ) {
    0          
    0          
1034 0         0 print $fh ("rm -f ", quotfn($file), "\n");
1035             }
1036             elsif ( $op eq 'R' ) {
1037 0         0 print $fh ("rmdir ", quotfn($file), "\n");
1038             }
1039             elsif ( $op eq 'c' ) {
1040 0         0 print $fh ("touch ", quotfn($file), "\n");
1041 0 0 0     0 if ( defined $args[2] && ($args[2] &= 0777) ) {
1042 0         0 printf $fh ("chmod 0%o %s\n", $args[2], quotfn($file))
1043             }
1044             }
1045             }
1046 0         0 print $fh <
1047             #
1048             # This command terminates the shell and need not be executed manually.
1049             exit
1050             #
1051             EOD
1052             }
1053             else {
1054 1         5 print $fh <
1055             # STEP 2: Run the 'patch' program with this file as input.
1056             #
1057             EOD
1058             }
1059 1         2 print $fh <
1060             #### End of Preamble ####
1061              
1062             #### Patch data follows ####
1063             EOD
1064             # Copy patch.
1065 1         10 $patch->open($thepatch);
1066 1         56 binmode($patch);
1067 1         37 while ( <$patch> ) {
1068 26         52 print $fh $_;
1069             }
1070 1         18 $patch->close;
1071              
1072             # Print a reassuring "End of Patch" note so people won't
1073             # wonder if their mailer truncated patches.
1074 1         30 print $fh ("#### End of Patch data ####\n\n",
1075             "#### ApplyPatch data follows ####\n",
1076             "# Data version : $data_version\n",
1077             "# Date generated : $timestamp\n",
1078             "# Generated by : $my_name $my_version\n");
1079 1 50       5 print $fh ("# Recurse directories : Yes\n") if $opt_recurse;
1080 1 50       12 print $fh ("# Excluded files : ",
1081             join("\n# ", @opt_exclude_regex), "\n")
1082             if @opt_exclude_regex;
1083              
1084 1         4 foreach ( @workq ) {
1085 2         7 my ($op, $file, @args) = @$_;
1086 2         6 $file = quotfn ($file);
1087 2         7 print $fh ("# ", $op, " ", $file);
1088 2 50 33     28 if ( defined ($args[2]) && ($op eq 'c' || $op eq 'C' || $op eq 'p') ) {
      33        
1089 2         10 $args[2] = sprintf ("0%o", $args[2]);
1090             }
1091 2 50       15 print $fh (" ", join(" ", @args)) if @args;
1092 2         7 print $fh ("\n");
1093             }
1094              
1095 1         2 print $fh ("#### End of ApplyPatch data ####\n");
1096 1         3 print $fh ("\n#### End of Patch kit [created: $timestamp] ####\n");
1097 1         5 $fh->close;
1098              
1099             # Checksum calculation.
1100             # Two checksums are calculated: one for the whole file (for compatibilty),
1101             # and one for just the patch data (so the preamble can be modified).
1102 1         112 my $lines = 0;
1103 1         3 my $bytes = 0;
1104 1         1 my $sum = 0;
1105 1         2 my $all_lines = 0;
1106 1         2 my $all_bytes = 0;
1107 1         1 my $all_sum = 0;
1108 1 50       5 $fh->open ($tmpfile) || die ("$tmpfile: $!\n");
1109 1         45 binmode($fh);
1110 1         19 binmode(STDOUT);
1111 1         16 while ( <$fh> ) {
1112 78 100       125 $lines = $bytes = $sum = 0
1113             if /^#### Patch data follows ####/;
1114 78         81 chomp;
1115 78         79 $_ .= "\n";
1116 78         67 $lines++;
1117 78         68 $all_lines++;
1118 78         71 $bytes += length ($_);
1119 78         78 $all_bytes += length ($_);
1120             # System V 'sum' checksum
1121 78         118 $sum = ($sum + unpack ("%16C*", $_)) % 65535;
1122 78         106 $all_sum = ($all_sum + unpack ("%16C*", $_)) % 65535;
1123 78         170 print STDOUT ($_);
1124             }
1125 1         6 $fh->close;
1126              
1127             # Checksum info for the patch data.
1128 1         36 $_ = "#### Patch checksum: $lines $bytes $sum ####\n";
1129 1         4 print STDOUT ($_);
1130 1         2 $all_lines++;
1131 1         2 $all_bytes += length ($_);
1132 1         4 $all_sum = ($all_sum + unpack ("%16C*", $_)) % 65535;
1133              
1134             # Overall checksum info.
1135 1         4 print STDOUT ("#### Checksum: $all_lines $all_bytes $all_sum ####\n");
1136              
1137 1 50       15 message (" $patched file",
1138             $patched == 1 ? "" : "s", " need to be patched.\n");
1139 1 50       4 if ( $created ) {
1140 0 0       0 message (" $created file", $created == 1 ? "" : "s");
1141 0 0       0 message (" and $dcreated director",
    0          
1142             $dcreated == 1 ? "y" : "ies") if $dcreated;
1143 0 0       0 message (" need", ($created+$dcreated != 1) ? "" : "s",
1144             " to be created.\n");
1145             }
1146 1 50       3 if ( $removed ) {
1147 0 0       0 message (" $removed file", $removed == 1 ? "" : "s");
1148 0 0       0 message (" and $dremoved director",
    0          
1149             $dremoved == 1 ? "y" : "ies") if $dremoved;
1150 0 0       0 message (" need", ($removed+$dremoved != 1) ? "" : "s",
1151             " to be removed.\n");
1152             }
1153 1 0       7 message (" $excluded file",
    50          
1154             $excluded == 1 ? " was" : "s were", " excluded.\n") if $excluded;
1155             }
1156              
1157             sub filelist ($) {
1158 0     0   0 my ($man) = @_;
1159 0         0 my @new = make_filelist_from_manifest ($man);
1160 0         0 foreach ( @new ) {
1161 0         0 print STDOUT ($opt_prefix, $_, "\n");
1162             }
1163             }
1164              
1165             sub app_options () {
1166 1     1   1 my $opt_manifest;
1167 1         2 my $opt_help = 0;
1168 1         1 my $opt_ident = 0;
1169 1         1 my $opt_rcfile;
1170              
1171             my @o = (
1172             "automanifest=s" => \$opt_automanifest,
1173             "debug!" => \$opt_debug,
1174             "description=s@" => \@opt_descr,
1175             "diff=s" => \$opt_diff,
1176             "exclude-regex=s@" => \@opt_exclude_regex,
1177             "exclude-standard!" => \$opt_exclude_standard,
1178             "exclude-rcs!" => \$opt_exclude_rcs,
1179             "exclude-sccs!" => \$opt_exclude_sccs,
1180             "exclude-cvs!" => \$opt_exclude_cvs,
1181 0     0   0 "exclude-vc!" => sub { $opt_exclude_rcs =
1182             $opt_exclude_cvs =
1183             $opt_exclude_sccs = $_[1] },
1184 1         10 "exclude=s@" => \@opt_exclude,
1185             "extract=s%" => \%opt_extract,
1186             "filelist|list!" => \$opt_filelist,
1187             "follow!" => \$opt_follow,
1188             "help" => \$opt_help,
1189             "ident!" => \$opt_ident,
1190             "ignore-cvs-keywords|ignore-rcs-keywords!"
1191             => \$opt_ignore_rcs_keywords,
1192             "infocmd=s" => \$opt_infocmd,
1193             "manifest=s" => \$opt_manifest,
1194             "newmanifest=s" => \$opt_newmanifest,
1195             "nomanifest!" => \$opt_nomanifest,
1196             "oldmanifest=s" => \$opt_oldmanifest,
1197             "patchlevel=s" => \$opt_patchlevel,
1198             "prefix=s" => \$opt_prefix,
1199             "quiet!" => \$opt_quiet,
1200             "sort!" => \$opt_sort,
1201             "recurse!" => \$opt_recurse,
1202             "test" => \$opt_test,
1203             "trace!" => \$opt_trace,
1204             "verbose!" => \$opt_verbose,
1205             );
1206              
1207 1         2 my $init;
1208              
1209             # Process ENV options.
1210 1 50       5 if ( defined ($init = $ENV{MAKEPATCHINIT}) ) {
1211 1         579 require Text::ParseWords;
1212 1         1730 local (@ARGV) = Text::ParseWords::shellwords ($init);
1213 1 50 33     141 unless ( GetOptions (@o, "rcfile=s" => \$opt_rcfile) &&
1214             @ARGV == 0 ) {
1215 0         0 warn ("Error in MAKEPATCHINIT\n");
1216 0         0 app_usage (1);
1217             }
1218             else {
1219 1         2489 trace ("+ INIT: $init\n");
1220             }
1221             }
1222              
1223 1 50       5 unless ( $opt_test ) {
1224             # Process ini file options.
1225             # First, try system wide file. Unix specific.
1226 0         0 app_parse_rc ("/etc/makepatchrc", 1, \@o);
1227 0         0 my $rcname = ".".$my_name."rc";
1228             # Then, try HOME .rc.
1229 0         0 app_parse_rc (catfile ($HOME, $rcname), 1, \@o);
1230             # Then try --rcfile, defaulting to .rc in current dir.
1231 0 0       0 if ( defined $opt_rcfile ) {
1232 0         0 app_parse_rc ($opt_rcfile, 0, \@o);
1233             }
1234             else {
1235 0         0 app_parse_rc (catfile ($dot, $rcname), 1, \@o);
1236             }
1237             }
1238              
1239             # Process command line options
1240 1 50 33     5 if ( !GetOptions (@o) || $opt_help ) {
1241 0         0 app_usage (1);
1242             }
1243              
1244             # Argument check.
1245 1 50       2285 if ( $opt_filelist ) {
1246 0 0       0 if ( defined $opt_manifest ) {
1247 0 0       0 app_usage (1) if @ARGV;
1248 0         0 @ARGV = ( $opt_manifest );
1249             }
1250             else {
1251 0 0       0 app_usage (1) unless @ARGV == 1;
1252             }
1253             }
1254             else {
1255 1 50       4 app_usage (1) unless @ARGV == 2;
1256             }
1257              
1258 1 50       3 $opt_trace = 1 if $opt_debug;
1259              
1260 1 50 33     5 print STDERR ("This is $my_name version $my_version\n")
1261             if $opt_verbose || $opt_ident;
1262              
1263 1 50       3 if ( $opt_prefix ne '' ) {
1264 0 0       0 die ("$0: option \"-prefix\" requires \"-filelist\"\n")
1265             unless $opt_filelist;
1266             }
1267              
1268 1 50       2 if ( defined $opt_sort ) {
1269 0 0       0 die ("$0: option \"-[no]sort\" requires \"-filelist\"\n")
1270             unless $opt_filelist;
1271             }
1272             else {
1273 1         3 $opt_sort = 1;
1274             }
1275              
1276 1 50       3 if ( $opt_filelist ) {
1277 0 0 0     0 die ("$0: option \"-filelist\" only uses \"-manifest\"\n")
1278             if defined $opt_oldmanifest || defined $opt_newmanifest;
1279             }
1280              
1281 1 50       3 if ( defined $opt_manifest ) {
1282 0 0 0     0 die ("$0: do not use \"-manifest\" with \"-oldmanifest\"".
1283             " or \"-newmanifest\"\n")
1284             if defined $opt_newmanifest || defined $opt_oldmanifest;
1285 0         0 $opt_newmanifest = $opt_oldmanifest = $opt_manifest;
1286             }
1287              
1288 1 50       2 if ( defined $opt_infocmd ) {
1289 0 0       0 die ("$0: \"-infocmd\" can not be used with \"-filelist\"\n")
1290             if $opt_filelist;
1291             # Protect %% sequences.
1292 0         0 $opt_infocmd =~ s/\%\%/\001/g;
1293             # Encode %o and %n sequences.
1294 0         0 $opt_infocmd =~ s/\%o([P])/\002$1/g;
1295 0         0 $opt_infocmd =~ s/\%n([P])/\003$1/g;
1296             # Restore %% sequences.
1297 0         0 $opt_infocmd =~ s/\001/%%/g;
1298 0         0 while ( $opt_infocmd =~ /(\%[on]\S)/g ) {
1299 0         0 warn ("Warning: $1 in info command may become ",
1300             "special in the future\n");
1301             }
1302             }
1303              
1304 1 50       3 $opt_verbose = 0 if $opt_quiet;
1305 1   33     4 $opt_trace ||= $opt_debug;
1306 1   33     11 $opt_verbose ||= $opt_trace;
1307             }
1308              
1309             sub app_parse_rc ($$$) {
1310 0     0     my ($file, $opt, $optref) = @_;
1311              
1312 0           my $rcfile = new IO::File;
1313 0 0         unless ( $rcfile->open($file) ) {
1314 0 0         die ("$file: $!\n") unless $opt;
1315 0           return;
1316             }
1317              
1318 0           require Text::ParseWords;
1319              
1320 0           local (@ARGV);
1321 0           my $ok = 1;
1322              
1323             # Intercept Getopt::Long warning messages.
1324 0           my $warn;
1325 0     0     $SIG{__WARN__} = sub { $warn = "@_"; };
  0            
1326              
1327             # Process the file.
1328 0           while ( <$rcfile> ) {
1329             # Skip blank and comment lines.
1330 0 0         next if /^\s*[;#]/;
1331 0 0         next unless /\S/;
1332              
1333             # Split.
1334 0           my @a = Text::ParseWords::shellwords ($_);
1335 0           $warn = '';
1336 0           trace ("+ RC: @a\n");
1337             # Handle.
1338 0           @ARGV = @a;
1339 0 0         unless ( GetOptions (@$optref) ) {
1340 0           chomp ($warn);
1341 0           print STDERR ("$warn -- at line $. in $file\n");
1342 0           $ok = 0;
1343             }
1344 0 0         if ( @ARGV > 0 ) {
1345 0           print STDERR ("Garbage \"@ARGV\"",
1346             " -- at line $. in $file\n");
1347 0           $ok = 0;
1348             }
1349             }
1350 0           $rcfile->close;
1351 0           $SIG{__WARN__} = 'DEFAULT';
1352 0 0         unless ( $ok ) {
1353 0           app_usage (1);
1354             }
1355 0           $ok;
1356             }
1357              
1358             sub app_usage ($) {
1359 0     0     my ($exit) = @_;
1360 0           print STDERR <
1361             This is $my_name version $my_version
1362              
1363             Usage: $0 [options] old-src new-src
1364              
1365             Makepatch options:
1366             -description XXX descriptive message about this patch
1367             -diff cmd diff command to use, default \"$opt_diff\"
1368             -patchlevel file file to use as patchlevel.h
1369             -man[ifest] file list of files for old and new dir, defaults to $opt_automanifest
1370             -nomanifest suppress use of MANIFEST files
1371             -automan[ifest] XXX assumend name for MANIFEST files
1372             -newman[ifest] file list of files for new dir
1373             -oldman[ifest] file list of files for old dir
1374             -follow follow symbolic links
1375             -infocmd cmd add output of cmd to each patch chunk
1376             -exclude pat exclude files according to wildcard pattern
1377             -exclude-regex pat exclude files and dirs matching regex pattern
1378             -exclude-vc exclude version control files (RCS, CVS, SCCS)
1379             -exclude-rcs exclude version control files for RCS
1380             -exclude-cvs exclude version control files for CVS
1381             -exclude-sccs exclude version control files for SCCS
1382             -ignore-cvs-keywords ignore diffs in CVS keyword data (same as RCS)
1383             -ignore-rcs-keywords ignore diffs in RCS keyword data (same as CVS)
1384             -extract PAT=RULE define an archive extraction rule
1385             -[no]recurse recurse through directories (default)
1386             -verbose verbose progress information
1387             -quiet no progress information
1388             -help this message
1389             EoU
1390 0 0         die ("Not okay 99\n") if $opt_test;
1391 0 0 0       exit $exit if defined $exit && $exit != 0;
1392             }
1393              
1394             sub app_usage_filelist ($) {
1395 0     0     my ($exit) = @_;
1396 0           print STDERR <
1397             This is $my_name version $my_version
1398              
1399             Usage: $0 -filelist [ options ] [ -manifest ] file
1400              
1401             Filelist options:
1402             -[file]list extract filenames from manifest file
1403             -prefix XXX add a prefix to these filenames
1404             -nosort do not sort manifest entries
1405             -man[ifest] file list of files
1406             -exclude pat exclude files according to wildcard pattern
1407             -exclude-regex pat exclude files and dirs matching regex pattern
1408             -exclude-vc exclude version control files (RCS, CVS, SCCS)
1409             -exclude-rcs exclude version control files for RCS
1410             -exclude-cvs exclude version control files for CVS
1411             -exclude-sccs exclude version control files for SCCS
1412             -verbose verbose output (default)
1413             -quiet no verbose output
1414             -help this message
1415             EoU
1416 0 0         die ("Not okay 99\n") if $opt_test;
1417 0 0 0       exit $exit if defined $exit && $exit != 0;
1418             }
1419              
1420             1;
1421              
1422             __END__