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