#!/usr/bin/perl use strict; use Getopt::Long; use Cwd; my @generated_files = qw( proto.h configure config.h.in rsync.1 rsyncd.conf.5 ); my($no_cvs, $failures_only, $minor_updates, $prepare_source); my @auto_cmds; &Getopt::Long::Configure('bundling'); GetOptions( 'no-cvs|n' => \$no_cvs, 'failures-only|f' => \$failures_only, 'minor-updates|u' => \$minor_updates, 'prepare-source|p' => \$prepare_source, 'auto-cmd|a=s' => sub { push(@auto_cmds, $_[1]) }, ) or &usage; $" = '|'; my $auto_regex = @auto_cmds ? qr/^(@auto_cmds)$/i : qr/^never$/; my $interesting_fuzz = $minor_updates ? '\d' : '[2-9]'; $" = ' '; chdir('patches') if -d 'patches'; if (!-f 'verify-patches') { die <); close IN; mkdir('tmp', 0777) unless -d 'tmp'; chdir('tmp') or die "Unable to chdir to 'tmp'"; unlink('patches'); symlink($patches_dir, 'patches'); symlink($$, 'lock') or die "Unable to create lock file: $!\n"; mkdir('workdir') unless -d 'workdir'; open(OUT, '>exclude') or die $!; print OUT join("\n", 'CVS', @generated_files), "\n"; close OUT; unless ($no_cvs) { print "Using CVS to update the tmp/cvsdir copy of the source.\n"; system qq|cvs -qd "$root" co -P -d cvsdir rsync|; @_ = qw( configure configure.in config.h.in configure.in rsync.1 rsync.yo rsyncd.conf.5 rsyncd.conf.yo ); while (@_) { my $gen = 'cvsdir/' . shift(@_); my $src = 'cvsdir/' . shift(@_); if ((-M $gen) > (-M $src)) { system "touch $gen"; } } } @ARGV = glob('patches/*.diff') unless @ARGV; DIFF: foreach my $diff (@ARGV) { next unless $diff =~ /\.diff$/; next if $diff =~ /gzip-rsyncable[-_a-z]*\.diff$/; $diff =~ s#^(patches|\.\.)/##; my $conf_opts; open(IN, "patches/$diff") or die $!; while () { last if /^--- /; if (m#^\s+patch -p1 ; chomp $input; if ($input =~ s/^(-a|--auto-cmd=?)\s*//) { push(@auto_cmds, $input eq '' ? $default : $input); $" = '|'; $auto_regex = qr/^(@auto_cmds)$/i; $" = ' '; next; } $ans = $input if $input ne ''; } $first_time = 0; while ($ans =~ s/^\s*(!|\w)((?) { print $_; chomp; if (s/^patching file //) { push(@new, $_) unless -f "cvsdir/$_"; } elsif (s/.* saving rejects to file //) { push(@rejects, $_); } elsif (/No file to patch\.\s+Skipping patch/) { push(@rejects, 'skipped.file'); } elsif (/^Hunk #\d+ succeeded at \d+( with fuzz $interesting_fuzz)?/o) { $saw_fuzz ||= defined $1; $saw_offset = 1; } } close IN; return 'F,D,E' if @rejects; return 'D,E' if $saw_fuzz && !$failures_only; return 'D,U,N' if $saw_offset && !$failures_only; 'N'; } sub filter_diff { my($cmd) = @_; open(IN, '-|', $cmd) or die $!; while () { next if /^(diff -|Index: |Only in )/; s#^\Q--- cvsdir/\E([^\t]+).*#--- old/$1#; s#^\Q+++ workdir/\E([^\t]+).*#+++ new/$1#; print OUT $_; } close IN; } sub generate_new_patch { my($diff) = @_; foreach (@new) { system "touch -r workdir/$_ cvsdir/$_"; } open(IN, "patches/$diff") or die $!; open(OUT, '>new.patch') or die $!; while () { last if /^--- /; print OUT $_; } close IN; &filter_diff('diff --exclude-from=exclude -dupr cvsdir workdir'); if ($prepare_source) { # These are not included in the diff above so that patch will give # generated files a later timestamp than the source files. foreach my $fn (@generated_files) { &filter_diff("diff -dupW128 cvsdir/$fn workdir"); } } close OUT; foreach (@new) { unlink("cvsdir/$_"); } print "\nDiffing... "; if (system("diff patches/$diff new.patch >/dev/null") == 0) { print "new patch is identical to old.\n"; return 'N'; } print "New patch DIFFERS from old.\n"; 'E'; } sub restore_cvsdir { return unless $has_dependencies; $has_dependencies = 0; chdir('cvsdir') or die $!; foreach (glob('*.~[1-9]~'), glob('*/*.~[1-9]~')) { my $fn; ($fn = $_) =~ s/\.~1~$//; if ($fn eq $_) { unlink($_); } elsif (-r $_) { rename($_, $fn); } else { unlink($_); unlink($fn); } } chdir('..') or die $!; } sub usage { die <