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