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