Got rid of some cruft.
[rsync/rsync-patches.git] / verify-patches
1 #!/usr/bin/perl
2
3 use strict;
4 use Getopt::Long;
5 use Cwd;
6
7 my @generated_files = qw( proto.h configure config.h.in rsync.1 rsyncd.conf.5 );
8
9 my($no_cvs, $failures_only, $minor_updates, $prepare_source);
10 my @auto_cmds;
11
12 &Getopt::Long::Configure('bundling');
13 GetOptions(
14     'no-cvs|n' => \$no_cvs,
15     'failures-only|f' => \$failures_only,
16     'minor-updates|u' => \$minor_updates,
17     'prepare-source|p' => \$prepare_source,
18     'auto-cmd|a=s' => sub { push(@auto_cmds, $_[1]) },
19 ) or &usage;
20
21 $" = '|';
22 my $auto_regex = @auto_cmds ? qr/^(@auto_cmds)$/i : qr/^never$/;
23 my $interesting_fuzz = $minor_updates ? '\d' : '[2-9]';
24 $" = ' ';
25
26 chdir('patches') if -d 'patches';
27
28 if (!-f 'verify-patches') {
29     die <<EOT;
30 Please run this script from the root of the rsync dir or
31 from inside the patches subdir.
32 EOT
33 }
34
35 my $patches_dir = Cwd::cwd();
36
37 $ENV{'LC_COLLATE'} = 'C';
38 $| = 1;
39 my $CONF_OPTS = '--cache-file=../config.cache';
40
41 my($has_dependencies, @new, @rejects);
42
43 END {
44     &restore_cvsdir;
45     system "rsync -a --del cvsdir/ workdir/" if -d 'cvsdir';
46     my $pid = readlink('lock') || 0;
47     unlink('lock') if $pid == $$;
48 };
49
50 my $root;
51 open(IN, '../CVS/Root') or die $!;
52 chomp($root = <IN>);
53 close IN;
54
55 mkdir('tmp', 0777) unless -d 'tmp';
56 chdir('tmp') or die "Unable to chdir to 'tmp'";
57 unlink('patches');
58 symlink($patches_dir, 'patches');
59
60 symlink($$, 'lock') or die "Unable to create lock file: $!\n";
61
62 mkdir('workdir') unless -d 'workdir';
63 open(OUT, '>exclude') or die $!;
64 print OUT join("\n", 'CVS', @generated_files), "\n";
65 close OUT;
66
67 unless ($no_cvs) {
68     print "Using CVS to update the tmp/cvsdir copy of the source.\n";
69     system qq|cvs -qd "$root" co -P -d cvsdir rsync|;
70     @_ = qw( configure configure.in config.h.in configure.in
71              rsync.1 rsync.yo rsyncd.conf.5 rsyncd.conf.yo );
72     while (@_) {
73         my $gen = 'cvsdir/' . shift(@_);
74         my $src = 'cvsdir/' . shift(@_);
75         if ((-M $gen) > (-M $src)) {
76             system "touch $gen";
77         }
78     }
79 }
80
81 @ARGV = glob('patches/*.diff') unless @ARGV;
82
83 DIFF:
84 foreach my $diff (@ARGV) {
85     next unless $diff =~ /\.diff$/;
86     next if $diff =~ /gzip-rsyncable[-_a-z]*\.diff$/;
87     $diff =~ s#^(patches|\.\.)/##;
88
89     my $conf_opts;
90     open(IN, "patches/$diff") or die $!;
91     while (<IN>) {
92         last if /^--- /;
93         if (m#^\s+patch -p1 <patches/(\S+.diff)# && $1 ne $diff) {
94             my $dep = $1;
95             $has_dependencies = 1;
96             print "\nApplying dependency patch $dep...\n";
97             if (system("patch -f -d cvsdir -p1 -b -Vt <patches/$dep") != 0) {
98                 print "Unable to cleanly apply dependency patch -- skipping $diff\n";
99                 system "rm -f cvsdir/*.rej cvsdir/*/*.rej";
100                 &restore_cvsdir;
101                 next DIFF;
102             }
103             sleep(1); # Ensure later diffs get later times.
104         }
105         if (!defined($conf_opts) && s#^\s+\./configure\s+##) {
106             chomp($conf_opts = $_);
107             $conf_opts =~ s/\s*\(.*?\)//;
108         }
109     }
110     close IN;
111     $conf_opts = '' unless defined $conf_opts;
112
113     my $default = apply_patch($diff);
114
115     if ($prepare_source) {
116         print "\nPreparing the source...\n";
117         chdir('workdir') or die $!;
118         system "./prepare-source";
119         chdir('..') or die $!;
120     }
121
122     if ($default =~ s/^D,// || $default eq 'N') {
123         my $def = generate_new_patch($diff);
124         $default = 'U,N' if $default eq 'N' && $def eq 'E';
125         $default = 'N' if !$minor_updates && $default eq 'U,N';
126     }
127
128     my $first_time = 1;
129     PROMPT:
130     while (1) {
131         print "\n----------- $diff ------------\n",
132             "\nFix rejects, Diff create, Edit both diffs, Update patch,\n",
133             "Apply patch again, !(CMD), Build rsync, Next, Quit: [$default] ";
134         my $ans = $default;
135         if ($first_time && $default =~ /$auto_regex/) {
136             print $default, "\n";
137         } else {
138             my $input = <STDIN>;
139             chomp $input;
140             if ($input =~ s/^(-a|--auto-cmd=?)\s*//) {
141                 push(@auto_cmds, $input eq '' ? $default : $input);
142                 $" = '|';
143                 $auto_regex = qr/^(@auto_cmds)$/i;
144                 $" = ' ';
145                 next;
146             }
147             $ans = $input if $input ne '';
148         }
149         $first_time = 0;
150         while ($ans =~ s/^\s*(!|\w)((?<!!)[^;,]*|[^;]*)[;,]?//) {
151             my $cmd = "\U$1\E";
152             if ($cmd eq '!') {
153                 $cmd = $2 || $ENV{'SHELL'};
154                 chdir('workdir') or die $!;
155                 system $cmd;
156                 chdir('..') or die $!;
157                 $default = 'D';
158                 next;
159             }
160             if ($cmd eq 'A') {
161                 $default = apply_patch($diff);
162                 next;
163             }
164             if ($cmd eq 'B') {
165                 chdir('workdir') or die $!;
166                 my $cmd = "./prepare-source && ./configure $CONF_OPTS $conf_opts && make";
167                 print "Running: $cmd\n";
168                 system $cmd;
169                 chdir('..') or die $!;
170                 $default = '!make test';
171                 next;
172             }
173             if ($cmd eq 'D') {
174                 $default = generate_new_patch($diff);
175                 next;
176             }
177             if ($cmd eq 'E') {
178                 chdir('workdir') or die $!;
179                 system "vim -d ../patches/$diff ../new.patch";
180                 chdir('..') or die $!;
181                 $default = 'U,A,D';
182                 next;
183             }
184             if ($cmd eq 'F') {
185                 chdir('workdir') or die $!;
186                 system "vim @rejects";
187                 chdir('..') or die $!;
188                 $default = 'D,E';
189                 next;
190             }
191             if ($cmd eq 'N') {
192                 last PROMPT;
193             }
194             if ($cmd eq 'Q') {
195                 exit;
196             }
197             if ($cmd eq 'U') {
198                 system "cp -p new.patch patches/$diff";
199                 print "\nUpdated $diff from new.patch\n";
200                 $default = 'A';
201                 next;
202             }
203         }
204     }
205
206     &restore_cvsdir;
207 }
208
209 exit;
210
211
212 sub apply_patch
213 {
214     my($diff) = @_;
215
216     undef @new;
217     system "rsync -a --del --exclude='*~' cvsdir/ workdir/";
218     print "\nApplying patch $diff...\n";
219     undef @rejects;
220     my($saw_offset, $saw_fuzz);
221     open(IN, "patch -f -d workdir -p1 --no-backup-if-mismatch <patches/$diff |") or die $!;
222     while (<IN>) {
223         print $_;
224         chomp;
225         if (s/^patching file //) {
226             push(@new, $_) unless -f "cvsdir/$_";
227         } elsif (s/.* saving rejects to file //) {
228             push(@rejects, $_);
229         } elsif (/No file to patch\.\s+Skipping patch/) {
230             push(@rejects, 'skipped.file');
231         } elsif (/^Hunk #\d+ succeeded at \d+( with fuzz $interesting_fuzz)?/o) {
232             $saw_fuzz ||= defined $1;
233             $saw_offset = 1;
234         }
235     }
236     close IN;
237     return 'F,D,E' if @rejects;
238     return 'D,E' if $saw_fuzz && !$failures_only;
239     return 'D,U,N' if $saw_offset && !$failures_only;
240     'N';
241 }
242
243 sub filter_diff
244 {
245     my($cmd) = @_;
246     open(IN, '-|', $cmd) or die $!;
247     while (<IN>) {
248         next if /^(diff -|Index: |Only in )/;
249         s#^\Q--- cvsdir/\E([^\t]+).*#--- old/$1#;
250         s#^\Q+++ workdir/\E([^\t]+).*#+++ new/$1#;
251         print OUT $_;
252     }
253     close IN;
254 }
255
256 sub generate_new_patch
257 {
258     my($diff) = @_;
259
260     foreach (@new) {
261         system "touch -r workdir/$_ cvsdir/$_";
262     }
263     open(IN, "patches/$diff") or die $!;
264     open(OUT, '>new.patch') or die $!;
265     while (<IN>) {
266         last if /^--- /;
267         print OUT $_;
268     }
269     close IN;
270     &filter_diff('diff --exclude-from=exclude -dupr cvsdir workdir');
271     if ($prepare_source) {
272         # These are not included in the diff above so that patch will give
273         # generated files a later timestamp than the source files.
274         foreach my $fn (@generated_files) {
275             &filter_diff("diff -dup cvsdir/$fn workdir");
276         }
277     }
278     close OUT;
279     foreach (@new) {
280         unlink("cvsdir/$_");
281     }
282     print "\nDiffing... ";
283     if (system("diff patches/$diff new.patch >/dev/null") == 0) {
284         print "new patch is identical to old.\n";
285         return 'N';
286     }
287     print "New patch DIFFERS from old.\n";
288     'E';
289 }
290
291 sub restore_cvsdir
292 {
293     return unless $has_dependencies;
294     $has_dependencies = 0;
295
296     chdir('cvsdir') or die $!;
297     foreach (glob('*.~[1-9]~'), glob('*/*.~[1-9]~')) {
298         my $fn;
299         ($fn = $_) =~ s/\.~1~$//;
300         if ($fn eq $_) {
301             unlink($_);
302         } elsif (-r $_) {
303             rename($_, $fn);
304         } else {
305             unlink($_);
306             unlink($fn);
307         }
308     }
309     chdir('..') or die $!;
310 }
311
312 sub usage
313 {
314     die <<EOT;
315 Usage: $0 [OPTS] [DIFF-FILE...]
316  -a, --auto-cmd=REGEX  If default_cmd =~ /^(REGEX)\$/, enter it automatically
317  -f, --failures-only   Suggest skipping patches that don't have failing hunks
318  -n, --no-cvs          Don't update tmp/cvsdir at the start of the run
319  -p, --prepare-source  Run ./prepare-source and include generated files in diff
320  -u, --minor-updates   Suggest 'U' for even minor changes in the diff
321 EOT
322 }