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