WebKitTools:
[WebKit-https.git] / WebKitTools / Scripts / svn-apply
1 #!/usr/bin/perl -w
2
3 # Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 #
9 # 1.  Redistributions of source code must retain the above copyright
10 #     notice, this list of conditions and the following disclaimer. 
11 # 2.  Redistributions in binary form must reproduce the above copyright
12 #     notice, this list of conditions and the following disclaimer in the
13 #     documentation and/or other materials provided with the distribution. 
14 # 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 #     its contributors may be used to endorse or promote products derived
16 #     from this software without specific prior written permission. 
17 #
18 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 # "patch" script for WebKit Open Source Project, used to apply patches.
30
31 # Differences from invoking "patch -p0":
32 #
33 #   Handles added files (does a svn add).
34 #   Handles removed files (does a svn rm).
35 #   Has mode where it will roll back to svn version numbers in the patch file so svn
36 #       can do a 3-way merge.
37 #   Paths from Index: lines are used rather than the paths on the patch lines, which
38 #       makes patches generated by "cvs diff" work (increasingly unimportant since we
39 #       use Subversion now).
40 #   ChangeLog patches use --fuzz=3 to prevent rejects.
41 #   Handles binary files (requires patches made by svn-create-patch).
42 #
43 # Missing features:
44 #
45 #   Handle property changes.
46 #   Handle file moves (requires patches made by svn-create-patch).
47 #   When doing a removal, check that old file matches what's being removed.
48 #   Notice a patch that's being applied at the "wrong level" and make it work anyway.
49 #   Do a dry run on the whole patch and don't do anything if part of the patch is
50 #       going to fail (probably too strict unless we do the ChangeLog thing).
51
52 use strict;
53 use Cwd;
54 use Getopt::Long;
55 use MIME::Base64;
56
57 my $merge = 0;
58 GetOptions("merge" => \$merge); 
59
60 my $startDir = getcwd();
61
62 my @patches;
63 my %versions;
64
65 my $indexPath;
66 my $patch;
67 while (<>) {
68     s/\r//g;
69     chomp;
70     if (/^Index: (.+)/) {
71         $indexPath = $1;
72         if ($patch) {
73             push @patches, $patch;
74             $patch = "";
75         }
76     }
77     if ($indexPath) {
78         # Fix paths on diff, ---, and +++ lines to match preceding Index: line.
79         s/\S+$/$indexPath/ if /^diff/;
80         s/^--- \S+/--- $indexPath/;
81         if (s/^\+\+\+ \S+/+++ $indexPath/) {
82             $indexPath = "";
83         }
84     }
85     if (/^--- .+\(revision (\d+)\)$/) {
86         $versions{$indexPath} = $1 if( $1 != 0 );
87     }
88     $patch .= $_;
89     $patch .= "\n";
90 }
91
92 push @patches, $patch if $patch;
93
94 if ($merge) {
95     for my $file (sort keys %versions) {
96         print "Getting version $versions{$file} of $file\n";
97         $file =~ m|^(([^/\n]*/)*)([^/\n]+)$| or die;
98         my ($prefix, $base) = ($1, $3);
99         if ($prefix) {
100             chdir $prefix or die;
101         }
102         system "svn update -r $versions{$file} $base";
103         chdir $startDir;
104     }
105 }
106
107 for $patch (@patches) {
108     patch($patch);
109 }
110
111 sub applyPatch
112 {
113     my ($patch, $fullpath, $options) = @_;
114     $options = [] if (! $options);
115     my $command = "patch " . join(" ", "-p0", @{$options});
116     open PATCH, "| $command" or die "Failed to patch $fullpath\n";
117     print PATCH $patch;
118     close PATCH;
119 }
120
121 sub patch
122 {
123     my ($patch) = @_;
124     return if !$patch;
125
126     $patch =~ m|^Index: ((([^/\n]*/)*)([^/\n]+))| or die "Failed to find Index: in \"$patch\"\n";
127     my ($fullpath, $prefix, $base) = ($1, $2, $4);
128
129     my $deletion = 0;
130     my $addition = 0;
131     my $isBinary = 0;
132
133     $addition = 1 if $patch =~ /\n--- .+\(revision 0\)\n/;
134     $deletion = 1 if $patch =~ /\n@@ .* \+0,0 @@/;
135     $isBinary = 1 if $patch =~ /\nCannot display: file marked as a binary type\./;
136
137     if (!$addition && !$deletion && !$isBinary) {
138         # Standard patch, patch tool can handle this.
139         if ($base eq "ChangeLog") {
140             my $changeLogDotOrigExisted = -f "${fullpath}.orig";
141             applyPatch($patch, $fullpath, ["--fuzz=3"]);
142             unlink("${fullpath}.orig") if (! $changeLogDotOrigExisted);
143         } else {
144             applyPatch($patch, $fullpath);
145         }
146     } else {
147         # Either a deletion, an addition or a binary change.
148
149         # Change directory down into the directory in question.
150         chdirAddingDirectoriesIfNeeded($prefix);
151
152         if ($isBinary) {
153             # Binary change
154             handleBinaryChange($base, $patch);
155         } elsif ($deletion) {
156             # Deletion.
157             system "svn", "rm", $base;
158         } else {
159             # Addition.
160             my $contents = $patch;
161             if ($contents !~ s/^(.*\n)*@@[^\n]+@@\n//) {
162                 # Empty contents.
163                 $contents = "";
164             } else {
165                 # Non-empty contents: Remove leading + signs.
166                 $contents =~ s/^\+//;
167                 $contents =~ s/\n\+/\n/g;
168             }
169             open FILE, ">", $base or die;
170             print FILE $contents;
171             close FILE;
172             system "svn", "add", "$base";
173         }
174
175         chdir $startDir if $prefix;
176     }
177 }
178
179 sub handleBinaryChange
180 {
181     my ($base, $contents) = @_;
182     if ($contents =~ m#((\n[A-Za-z0-9+/]{76})+\n[A-Za-z0-9+/=]{4,76}\n)\n#) {
183         # Addition or Modification
184         open FILE, ">", $base or die;
185         print FILE decode_base64($1);
186         close FILE;
187         open SVN, "svn stat '$base' |" or die;
188         my $svnStatus = <SVN>;
189         close SVN;
190         if (substr($svnStatus, 0 ,1) eq "?") {
191             # Addition
192             system "svn", "add", "$base";
193         } else {
194             # Modification
195             print $svnStatus;
196         }
197     } else {
198         # Deletion
199         system "svn", "rm", "$base";
200     }
201 }
202
203 sub chdirAddingDirectoriesIfNeeded
204 {
205     my $path = shift;
206     my @dirs = split('/', $path);
207     while (my $dir = shift @dirs) {
208         if (!-x $dir) {
209             mkdir $dir or die "Failed create required directory: $dir for path: $path\n";
210             system "svn", "add", "$dir";
211         }
212         chdir $dir or die "Failed to chdir to $dir\n";
213     }
214 }