blob: 5a9edf560ee586d8c50e2aab4f0dddf85709ec07 [file] [log] [blame]
commit-queue@webkit.org026ee042018-01-04 07:18:18 +00001#!/usr/bin/env perl
thatcher021e4c92006-01-04 21:10:38 +00002
ddkilzerb3a42ce2007-09-26 02:18:22 +00003# Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved.
eric@webkit.org8bf6c2c2009-09-10 05:17:46 +00004# Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
ddkilzer@apple.com7308a5a2010-01-03 21:25:30 +00005# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com)
thatcher021e4c92006-01-04 21:10:38 +00006#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#
11# 1. Redistributions of source code must retain the above copyright
eric@webkit.orgf135b1c2009-06-25 07:45:30 +000012# notice, this list of conditions and the following disclaimer.
thatcher021e4c92006-01-04 21:10:38 +000013# 2. Redistributions in binary form must reproduce the above copyright
14# notice, this list of conditions and the following disclaimer in the
15# documentation and/or other materials provided with the distribution.
mjs@apple.com92047332014-03-15 04:08:27 +000016# 3. Neither the name of Apple Inc. ("Apple") nor the names of
thatcher021e4c92006-01-04 21:10:38 +000017# its contributors may be used to endorse or promote products derived
eric@webkit.orgf135b1c2009-06-25 07:45:30 +000018# from this software without specific prior written permission.
thatcher021e4c92006-01-04 21:10:38 +000019#
20# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31# "patch" script for WebKit Open Source Project, used to apply patches.
32
33# Differences from invoking "patch -p0":
34#
ddkilzercd674862006-07-17 03:59:42 +000035# Handles added files (does a svn add with logic to handle local changes).
ddkilzer576fd482006-06-25 19:39:22 +000036# Handles added directories (does a svn add).
ddkilzercd674862006-07-17 03:59:42 +000037# Handles removed files (does a svn rm with logic to handle local changes).
ddkilzer576fd482006-06-25 19:39:22 +000038# Handles removed directories--those with no more files or directories left in them
39# (does a svn rm).
darin75138392006-01-31 06:08:51 +000040# Has mode where it will roll back to svn version numbers in the patch file so svn
41# can do a 3-way merge.
42# Paths from Index: lines are used rather than the paths on the patch lines, which
43# makes patches generated by "cvs diff" work (increasingly unimportant since we
44# use Subversion now).
cjerdonek@webkit.orgb19ac5e2010-05-13 05:39:36 +000045# ChangeLog patches use --fuzz=3 to prevent rejects.
ddkilzer8816a4e2006-06-04 18:52:24 +000046# Handles binary files (requires patches made by svn-create-patch).
ddkilzerb88c3b92007-01-02 04:13:43 +000047# Handles copied and moved files (requires patches made by svn-create-patch).
ddkilzerb3a42ce2007-09-26 02:18:22 +000048# Handles git-diff patches (without binary changes) created at the top-level directory
thatcher021e4c92006-01-04 21:10:38 +000049#
50# Missing features:
51#
dbates@webkit.org1205b1a2010-04-11 19:09:29 +000052# Handle property changes.
ddkilzerb88c3b92007-01-02 04:13:43 +000053# Handle copied and moved directories (would require patches made by svn-create-patch).
darin75138392006-01-31 06:08:51 +000054# When doing a removal, check that old file matches what's being removed.
thatcher021e4c92006-01-04 21:10:38 +000055# Notice a patch that's being applied at the "wrong level" and make it work anyway.
darin75138392006-01-31 06:08:51 +000056# Do a dry run on the whole patch and don't do anything if part of the patch is
darin43efeff2006-06-25 20:01:03 +000057# going to fail (probably too strict unless we exclude ChangeLog).
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +000058# Handle git-diff patches with binary delta
thatcher021e4c92006-01-04 21:10:38 +000059
60use strict;
ddkilzer74829502006-06-19 00:00:53 +000061use warnings;
62
ddkilzercd674862006-07-17 03:59:42 +000063use Digest::MD5;
ddkilzer74829502006-06-19 00:00:53 +000064use File::Basename;
commit-queue@webkit.org161f92f2017-11-09 22:13:16 +000065use File::Copy qw(copy);
ddkilzer74829502006-06-19 00:00:53 +000066use File::Spec;
thatcher021e4c92006-01-04 21:10:38 +000067use Getopt::Long;
ddkilzer8816a4e2006-06-04 18:52:24 +000068use MIME::Base64;
ddkilzer43944572006-07-10 03:46:07 +000069use POSIX qw(strftime);
thatcher021e4c92006-01-04 21:10:38 +000070
eric@webkit.orgf135b1c2009-06-25 07:45:30 +000071use FindBin;
72use lib $FindBin::Bin;
73use VCSUtils;
74
ddkilzer74829502006-06-19 00:00:53 +000075sub addDirectoriesIfNeeded($);
76sub applyPatch($$;$);
ddkilzercd674862006-07-17 03:59:42 +000077sub checksum($);
ddkilzer74829502006-06-19 00:00:53 +000078sub handleBinaryChange($$);
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +000079sub handleGitBinaryChange($$);
ddkilzer576fd482006-06-25 19:39:22 +000080sub isDirectoryEmptyForRemoval($);
ddkilzer74829502006-06-19 00:00:53 +000081sub patch($);
ddkilzer576fd482006-06-25 19:39:22 +000082sub removeDirectoriesIfNeeded();
ddkilzer74829502006-06-19 00:00:53 +000083
eric@webkit.orgf135b1c2009-06-25 07:45:30 +000084# These should be replaced by an scm class/module:
85sub scmKnowsOfFile($);
86sub scmCopy($$);
87sub scmAdd($);
clopez@igalia.com031f1a22020-06-04 19:44:30 +000088sub scmAddQueued($);
89sub scmCommitQueueToggledExecutableBit();
90sub scmCommitQueueAdded();
eric@webkit.orgf135b1c2009-06-25 07:45:30 +000091sub scmRemove($);
clopez@igalia.com031f1a22020-06-04 19:44:30 +000092sub scmToggleExecutableBitQueued($$);
eric@webkit.orgf135b1c2009-06-25 07:45:30 +000093
thatcher021e4c92006-01-04 21:10:38 +000094my $merge = 0;
ddkilzer74829502006-06-19 00:00:53 +000095my $showHelp = 0;
mitz@apple.comc68fbd42008-05-02 17:53:59 +000096my $reviewer;
eric@webkit.orgf135b1c2009-06-25 07:45:30 +000097my $force = 0;
dburkart@apple.comd1d27b12016-04-18 17:13:04 +000098my $skipChangeLogs = 0;
clopez@igalia.com031f1a22020-06-04 19:44:30 +000099my @scmQueuedFilesToAdd = ();
100my %scmQueuedExecutableBits;
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000101
102my $optionParseSuccess = GetOptions(
103 "merge!" => \$merge,
104 "help!" => \$showHelp,
105 "reviewer=s" => \$reviewer,
dburkart@apple.comd1d27b12016-04-18 17:13:04 +0000106 "force!" => \$force,
107 "skip-changelogs" => \$skipChangeLogs
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000108);
109
110if (!$optionParseSuccess || $showHelp) {
dburkart@apple.comd1d27b12016-04-18 17:13:04 +0000111 print STDERR basename($0) . " [-h|--help] [--force] [-m|--merge] [-r|--reviewer name] [--skip-changelogs] patch1 [patch2 ...]\n";
ddkilzer74829502006-06-19 00:00:53 +0000112 exit 1;
113}
thatcher021e4c92006-01-04 21:10:38 +0000114
ddkilzer576fd482006-06-25 19:39:22 +0000115my %removeDirectoryIgnoreList = (
116 '.' => 1,
117 '..' => 1,
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000118 '.git' => 1,
ddkilzer576fd482006-06-25 19:39:22 +0000119 '.svn' => 1,
120 '_svn' => 1,
121);
thatcher021e4c92006-01-04 21:10:38 +0000122
cjerdonek@webkit.orgb19ac5e2010-05-13 05:39:36 +0000123my $epochTime = time(); # This is used to set the date in ChangeLog files.
ddkilzer@apple.com7308a5a2010-01-03 21:25:30 +0000124my $globalExitStatus = 0;
ddkilzer@apple.comd29b6db2009-09-03 19:07:21 +0000125
ddkilzer@apple.com7308a5a2010-01-03 21:25:30 +0000126my $repositoryRootPath = determineVCSRoot();
eric@webkit.orgefd9acc2009-09-01 10:24:36 +0000127
ddkilzer576fd482006-06-25 19:39:22 +0000128my %checkedDirectories;
thatcher021e4c92006-01-04 21:10:38 +0000129
cjerdonek@webkit.org21256f72010-04-29 10:33:18 +0000130# Need to use a typeglob to pass the file handle as a parameter,
131# otherwise get a bareword error.
132my @diffHashRefs = parsePatch(*ARGV);
thatcher021e4c92006-01-04 21:10:38 +0000133
cjerdonek@webkit.org21256f72010-04-29 10:33:18 +0000134print "Parsed " . @diffHashRefs . " diffs from patch file(s).\n";
ossy@webkit.org80b89d82015-06-01 17:13:26 +0000135die "No diff found." unless @diffHashRefs;
cjerdonek@webkit.org21256f72010-04-29 10:33:18 +0000136
137my $preparedPatchHash = prepareParsedPatch($force, @diffHashRefs);
138
139my @copyDiffHashRefs = @{$preparedPatchHash->{copyDiffHashRefs}};
140my @nonCopyDiffHashRefs = @{$preparedPatchHash->{nonCopyDiffHashRefs}};
141my %sourceRevisions = %{$preparedPatchHash->{sourceRevisionHash}};
thatcher021e4c92006-01-04 21:10:38 +0000142
143if ($merge) {
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000144 die "--merge is currently only supported for SVN" unless isSVN();
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000145 # How do we handle Git patches applied to an SVN checkout here?
cjerdonek@webkit.org21256f72010-04-29 10:33:18 +0000146 for my $file (sort keys %sourceRevisions) {
147 my $version = $sourceRevisions{$file};
eric@webkit.org14eb1392009-10-27 17:38:26 +0000148 print "Getting version $version of $file\n";
slewis@apple.com4875b632011-12-16 01:08:25 +0000149 my $escapedFile = escapeSubversionPath($file);
150 system("svn", "update", "-r", $version, $escapedFile) == 0 or die "Failed to run svn update -r $version $escapedFile.";
thatcher021e4c92006-01-04 21:10:38 +0000151 }
152}
153
cjerdonek@webkit.org21256f72010-04-29 10:33:18 +0000154# Handle copied and moved files first since moved files may have their
155# source deleted before the move.
156for my $copyDiffHashRef (@copyDiffHashRefs) {
157 my $indexPath = $copyDiffHashRef->{indexPath};
158 my $copiedFromPath = $copyDiffHashRef->{copiedFromPath};
159
160 addDirectoriesIfNeeded(dirname($indexPath));
161 scmCopy($copiedFromPath, $indexPath);
ddkilzerb88c3b92007-01-02 04:13:43 +0000162}
163
cjerdonek@webkit.org21256f72010-04-29 10:33:18 +0000164for my $diffHashRef (@nonCopyDiffHashRefs) {
165 patch($diffHashRef);
thatcher021e4c92006-01-04 21:10:38 +0000166}
167
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000168# For git we need to toggle the executable bit before adding the files
169# For SVN is the other way around.
170if (isGit()) {
171 scmCommitQueueToggledExecutableBit();
172 scmCommitQueueAdded();
173} elsif (isSVN()) {
174 scmCommitQueueAdded();
175 scmCommitQueueToggledExecutableBit();
176}
177
ddkilzer576fd482006-06-25 19:39:22 +0000178removeDirectoriesIfNeeded();
179
ddkilzer@apple.com7308a5a2010-01-03 21:25:30 +0000180exit $globalExitStatus;
ddkilzer74829502006-06-19 00:00:53 +0000181
ddkilzer576fd482006-06-25 19:39:22 +0000182sub addDirectoriesIfNeeded($)
183{
184 my ($path) = @_;
185 my @dirs = File::Spec->splitdir($path);
186 my $dir = ".";
187 while (scalar @dirs) {
188 $dir = File::Spec->catdir($dir, shift @dirs);
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000189 die "'$dir' exists, but is not a directory" if ( -e $dir && ! -d $dir);
190 if (isGit()) {
191 # Git removes a directory once the last file in it is removed. We need
192 # explicitly check for the existence of each directory along the path
193 # (and create it if it doesn't) so as to support patches that move all files in
194 # directory A to A/B. That is, we cannot depend on %checkedDirectories.
195 mkdir $dir if (! -e $dir);
196 } elsif (isSVN()) {
197 next if exists $checkedDirectories{$dir};
198 if (! -e $dir) {
199 mkdir $dir or die "Failed to create required directory '$dir' for path '$path'\n";
200 scmAdd($dir)
ddkilzer576fd482006-06-25 19:39:22 +0000201 }
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000202 elsif (-d $dir) {
203 # SVN prints "svn: warning: 'directory' is already under version control"
204 # if you try and add a directory which is already in the repository
205 # So we check first to see if the directory is under version control first.
206 if (!scmKnowsOfFile($dir)) {
207 scmAdd($dir);
208 }
209 }
ddkilzer576fd482006-06-25 19:39:22 +0000210 }
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000211 $checkedDirectories{$dir} = 1;
ddkilzer576fd482006-06-25 19:39:22 +0000212 }
213}
214
ddkilzer@apple.com7308a5a2010-01-03 21:25:30 +0000215# Args:
216# $patch: a patch string.
217# $pathRelativeToRoot: the path of the file to be patched, relative to the
218# repository root. This should normally be the path
219# found in the patch's "Index:" line.
220# $options: a reference to an array of options to pass to the patch command.
ddkilzer74829502006-06-19 00:00:53 +0000221sub applyPatch($$;$)
ddkilzer76ffa512006-06-03 17:59:05 +0000222{
ddkilzer@apple.com7308a5a2010-01-03 21:25:30 +0000223 my ($patch, $pathRelativeToRoot, $options) = @_;
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000224
ddkilzer@apple.com7308a5a2010-01-03 21:25:30 +0000225 my $optionalArgs = {options => $options, ensureForce => $force};
226
227 my $exitStatus = runPatchCommand($patch, $repositoryRootPath, $pathRelativeToRoot, $optionalArgs);
228
229 if ($exitStatus) {
230 $globalExitStatus = $exitStatus;
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000231 }
ddkilzer76ffa512006-06-03 17:59:05 +0000232}
233
ddkilzercd674862006-07-17 03:59:42 +0000234sub checksum($)
235{
236 my $file = shift;
237 open(FILE, $file) or die "Can't open '$file': $!";
238 binmode(FILE);
239 my $checksum = Digest::MD5->new->addfile(*FILE)->hexdigest();
240 close(FILE);
241 return $checksum;
242}
243
ddkilzer576fd482006-06-25 19:39:22 +0000244sub handleBinaryChange($$)
245{
246 my ($fullPath, $contents) = @_;
eric@webkit.org67d28472009-09-22 09:48:37 +0000247 # [A-Za-z0-9+/] is the class of allowed base64 characters.
248 # One or more lines, at most 76 characters in length.
249 # The last line is allowed to have up to two '=' characters at the end (to signify padding).
250 if ($contents =~ m#((\n[A-Za-z0-9+/]{76})*\n[A-Za-z0-9+/]{2,74}?[A-Za-z0-9+/=]{2}\n)#) {
ddkilzer576fd482006-06-25 19:39:22 +0000251 # Addition or Modification
eric@webkit.org14eb1392009-10-27 17:38:26 +0000252 open FILE, ">", $fullPath or die "Failed to open $fullPath.";
ddkilzer576fd482006-06-25 19:39:22 +0000253 print FILE decode_base64($1);
254 close FILE;
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000255 if (!scmKnowsOfFile($fullPath)) {
ddkilzer576fd482006-06-25 19:39:22 +0000256 # Addition
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000257 scmAddQueued($fullPath);
ddkilzer576fd482006-06-25 19:39:22 +0000258 }
259 } else {
260 # Deletion
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000261 scmRemove($fullPath);
ddkilzer576fd482006-06-25 19:39:22 +0000262 }
263}
264
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000265sub handleGitBinaryChange($$)
266{
cjerdonek@webkit.org498cdeb2010-05-09 07:21:35 +0000267 my ($fullPath, $diffHashRef) = @_;
268
269 my $contents = $diffHashRef->{svnConvertedText};
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000270
271 my ($binaryChunkType, $binaryChunk, $reverseBinaryChunkType, $reverseBinaryChunk) = decodeGitBinaryPatch($contents, $fullPath);
dburkart@apple.comc8f52232015-10-21 01:41:52 +0000272 if (!$binaryChunkType) {
273 die "$fullPath: unknown git binary patch format";
274 }
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000275
cjerdonek@webkit.org498cdeb2010-05-09 07:21:35 +0000276 my $isFileAddition = $diffHashRef->{isNew};
277 my $isFileDeletion = $diffHashRef->{isDeletion};
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000278
279 my $originalContents = "";
280 if (open FILE, $fullPath) {
281 die "$fullPath already exists" if $isFileAddition;
282
Hironori.Fujii@sony.com7fe48382019-01-08 01:37:58 +0000283 binmode(FILE);
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000284 $originalContents = join("", <FILE>);
285 close FILE;
286 }
commit-queue@webkit.orgbb8e4f12011-01-07 09:46:42 +0000287
288 if ($reverseBinaryChunkType eq "literal") {
289 die "Original content of $fullPath mismatches" if $originalContents ne $reverseBinaryChunk;
290 }
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000291
292 if ($isFileDeletion) {
293 scmRemove($fullPath);
294 } else {
295 # Addition or Modification
commit-queue@webkit.orgbb8e4f12011-01-07 09:46:42 +0000296 my $out = "";
297 if ($binaryChunkType eq "delta") {
298 $out = applyGitBinaryPatchDelta($binaryChunk, $originalContents);
299 } else {
300 $out = $binaryChunk;
301 }
302 if ($reverseBinaryChunkType eq "delta") {
303 die "Original content of $fullPath mismatches" if $originalContents ne applyGitBinaryPatchDelta($reverseBinaryChunk, $out);
304 }
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000305 open FILE, ">", $fullPath or die "Failed to open $fullPath.";
Hironori.Fujii@sony.com7fe48382019-01-08 01:37:58 +0000306 binmode(FILE);
commit-queue@webkit.orgbb8e4f12011-01-07 09:46:42 +0000307 print FILE $out;
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000308 close FILE;
309 if ($isFileAddition) {
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000310 scmAddQueued($fullPath);
hamaji@chromium.org0e18e9c2009-11-12 04:19:07 +0000311 }
312 }
313}
314
ddkilzer576fd482006-06-25 19:39:22 +0000315sub isDirectoryEmptyForRemoval($)
316{
317 my ($dir) = @_;
eric@webkit.org77092af2010-04-11 20:19:01 +0000318 return 1 unless -d $dir;
ddkilzer25b4b502006-06-25 23:28:14 +0000319 my $directoryIsEmpty = 1;
ddkilzer576fd482006-06-25 19:39:22 +0000320 opendir DIR, $dir or die "Could not open '$dir' to list files: $?";
ddkilzer25b4b502006-06-25 23:28:14 +0000321 for (my $item = readdir DIR; $item && $directoryIsEmpty; $item = readdir DIR) {
322 next if exists $removeDirectoryIgnoreList{$item};
dbates@webkit.org7a64a4b2010-09-09 21:07:44 +0000323 if (-d File::Spec->catdir($dir, $item)) {
ddkilzer25b4b502006-06-25 23:28:14 +0000324 $directoryIsEmpty = 0;
ddkilzerabfe3872006-06-25 19:42:14 +0000325 } else {
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000326 next if (scmWillDeleteFile(File::Spec->catdir($dir, $item)));
ddkilzer25b4b502006-06-25 23:28:14 +0000327 $directoryIsEmpty = 0;
ddkilzer576fd482006-06-25 19:39:22 +0000328 }
ddkilzer25b4b502006-06-25 23:28:14 +0000329 }
ddkilzer576fd482006-06-25 19:39:22 +0000330 closedir DIR;
ddkilzer25b4b502006-06-25 23:28:14 +0000331 return $directoryIsEmpty;
ddkilzer576fd482006-06-25 19:39:22 +0000332}
333
cjerdonek@webkit.org21256f72010-04-29 10:33:18 +0000334# Args:
335# $diffHashRef: a diff hash reference of the type returned by parsePatch().
ddkilzer74829502006-06-19 00:00:53 +0000336sub patch($)
thatcher021e4c92006-01-04 21:10:38 +0000337{
cjerdonek@webkit.org21256f72010-04-29 10:33:18 +0000338 my ($diffHashRef) = @_;
339
cjerdonek@webkit.org498cdeb2010-05-09 07:21:35 +0000340 # Make sure $patch is initialized to some value. A deletion can have no
341 # svnConvertedText property in the case of a deletion resulting from a
342 # Git rename.
343 my $patch = $diffHashRef->{svnConvertedText} || "";
cjerdonek@webkit.org056f91d2010-05-07 02:54:06 +0000344
cjerdonek@webkit.org507d0422010-05-04 06:14:20 +0000345 my $fullPath = $diffHashRef->{indexPath};
cjerdonek@webkit.org056f91d2010-05-07 02:54:06 +0000346 my $isBinary = $diffHashRef->{isBinary};
347 my $isGit = $diffHashRef->{isGit};
dbates@webkit.org4966ccc2011-07-19 19:00:58 +0000348 my $hasTextChunks = $patch && $diffHashRef->{numTextChunks};
thatcher021e4c92006-01-04 21:10:38 +0000349
darin2e888cc2006-02-04 18:51:59 +0000350 my $deletion = 0;
351 my $addition = 0;
thatcher021e4c92006-01-04 21:10:38 +0000352
cjerdonek@webkit.orga50fb9e2010-05-12 05:10:32 +0000353 $addition = 1 if ($diffHashRef->{isNew} || $patch =~ /\n@@ -0,0 .* @@/);
cjerdonek@webkit.orgc0216532010-05-09 02:52:26 +0000354 $deletion = 1 if ($diffHashRef->{isDeletion} || $patch =~ /\n@@ .* \+0,0 @@/);
darin2e888cc2006-02-04 18:51:59 +0000355
dburkart@apple.comd1d27b12016-04-18 17:13:04 +0000356 if (basename($fullPath) eq "ChangeLog" && $skipChangeLogs) {
357 print "Skipping '$fullPath' since --skip-changelogs was passed on the command line.";
358 return;
359 }
360
dbates@webkit.org4966ccc2011-07-19 19:00:58 +0000361 if (!$addition && !$deletion && !$isBinary && $hasTextChunks) {
thatcher021e4c92006-01-04 21:10:38 +0000362 # Standard patch, patch tool can handle this.
ddkilzer74829502006-06-19 00:00:53 +0000363 if (basename($fullPath) eq "ChangeLog") {
364 my $changeLogDotOrigExisted = -f "${fullPath}.orig";
dbates@webkit.org64ce91c2010-10-06 04:58:55 +0000365 my $changeLogHash = fixChangeLogPatch($patch);
366 my $newPatch = setChangeLogDateAndReviewer($changeLogHash->{patch}, $reviewer, $epochTime);
cjerdonek@webkit.orgb19ac5e2010-05-13 05:39:36 +0000367 applyPatch($newPatch, $fullPath, ["--fuzz=3"]);
ddkilzer74829502006-06-19 00:00:53 +0000368 unlink("${fullPath}.orig") if (! $changeLogDotOrigExisted);
ddkilzer8816a4e2006-06-04 18:52:24 +0000369 } else {
dbates@webkit.orgbd8724d2011-05-15 23:54:51 +0000370 applyPatch($patch, $fullPath);
ddkilzer76ffa512006-06-03 17:59:05 +0000371 }
thatcher021e4c92006-01-04 21:10:38 +0000372 } else {
ddkilzer8816a4e2006-06-04 18:52:24 +0000373 # Either a deletion, an addition or a binary change.
thatcher021e4c92006-01-04 21:10:38 +0000374
ddkilzer74829502006-06-19 00:00:53 +0000375 addDirectoriesIfNeeded(dirname($fullPath));
thatcher021e4c92006-01-04 21:10:38 +0000376
ddkilzer8816a4e2006-06-04 18:52:24 +0000377 if ($isBinary) {
cjerdonek@webkit.org056f91d2010-05-07 02:54:06 +0000378 if ($isGit) {
cjerdonek@webkit.org498cdeb2010-05-09 07:21:35 +0000379 handleGitBinaryChange($fullPath, $diffHashRef);
cjerdonek@webkit.org056f91d2010-05-07 02:54:06 +0000380 } else {
cjerdonek@webkit.orgc0216532010-05-09 02:52:26 +0000381 handleBinaryChange($fullPath, $patch) if $patch;
cjerdonek@webkit.org056f91d2010-05-07 02:54:06 +0000382 }
ddkilzer8816a4e2006-06-04 18:52:24 +0000383 } elsif ($deletion) {
ddkilzer@apple.com085f42f62018-05-27 16:50:42 +0000384 applyPatch($patch, $fullPath, ["--force"]) if ($patch && $hasTextChunks);
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000385 scmRemove($fullPath);
dbates@webkit.orge74c8152014-09-09 20:12:56 +0000386 } elsif ($addition && $hasTextChunks) {
ddkilzercd674862006-07-17 03:59:42 +0000387 # Addition
388 rename($fullPath, "$fullPath.orig") if -e $fullPath;
dbates@webkit.orge74c8152014-09-09 20:12:56 +0000389 applyPatch($patch, $fullPath);
ddkilzercd674862006-07-17 03:59:42 +0000390 unlink("$fullPath.orig") if -e "$fullPath.orig" && checksum($fullPath) eq checksum("$fullPath.orig");
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000391 scmAddQueued($fullPath);
ddkilzer@apple.com085f42f62018-05-27 16:50:42 +0000392 } elsif ($addition && !$hasTextChunks) {
393 # Add empty file.
394 die "\"$fullPath\" already exists" if -e $fullPath;
395 open(my $FH, ">>", $fullPath) or die "Could not open \"$fullPath\" for writing: $!";
396 close($FH);
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000397 scmAddQueued($fullPath);
commit-queue@webkit.orgbd272fd2019-08-28 03:15:33 +0000398 } elsif (!defined($diffHashRef->{executableBitDelta})) {
ddkilzer@apple.com085f42f62018-05-27 16:50:42 +0000399 die "Can't handle patch for \"$fullPath\".";
thatcher021e4c92006-01-04 21:10:38 +0000400 }
thatcher021e4c92006-01-04 21:10:38 +0000401 }
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000402 scmToggleExecutableBitQueued($fullPath, $diffHashRef->{executableBitDelta}) if defined($diffHashRef->{executableBitDelta});
thatcher021e4c92006-01-04 21:10:38 +0000403}
404
ddkilzer576fd482006-06-25 19:39:22 +0000405sub removeDirectoriesIfNeeded()
ddkilzer8816a4e2006-06-04 18:52:24 +0000406{
ddkilzer576fd482006-06-25 19:39:22 +0000407 foreach my $dir (reverse sort keys %checkedDirectories) {
408 if (isDirectoryEmptyForRemoval($dir)) {
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000409 scmRemove($dir);
ddkilzer8816a4e2006-06-04 18:52:24 +0000410 }
ddkilzer8816a4e2006-06-04 18:52:24 +0000411 }
412}
413
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000414# This could be made into a more general "status" call, except svn and git
415# have different ideas about "moving" files which might get confusing.
416sub scmWillDeleteFile($)
417{
418 my ($path) = @_;
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000419 if (isSVN()) {
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000420 my $svnOutput = svnStatus($path);
421 return 1 if $svnOutput && substr($svnOutput, 0, 1) eq "D";
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000422 } elsif (isGit()) {
dbates@webkit.orga33baaad2012-05-20 23:32:43 +0000423 my $command = runCommand("git", "diff-index", "--name-status", "HEAD", "--", $path);
424 return 1 if $command->{stdout} && substr($command->{stdout}, 0, 1) eq "D";
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000425 }
426 return 0;
427}
428
cjerdonek@webkit.org974ec942010-05-09 02:37:10 +0000429# Return whether the file at the given path is known to Git.
430#
431# This method outputs a message like the following to STDERR when
432# returning false:
433#
434# "error: pathspec 'test.png' did not match any file(s) known to git.
435# Did you forget to 'git add'?"
436sub gitKnowsOfFile($)
437{
438 my $path = shift;
439
440 `git ls-files --error-unmatch -- $path`;
cjerdonek@webkit.orga50fb9e2010-05-12 05:10:32 +0000441 my $exitStatus = exitStatus($?);
442 return $exitStatus == 0;
cjerdonek@webkit.org974ec942010-05-09 02:37:10 +0000443}
444
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000445sub scmKnowsOfFile($)
446{
447 my ($path) = @_;
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000448 if (isSVN()) {
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000449 my $svnOutput = svnStatus($path);
450 # This will match more than intended. ? might not be the first field in the status
451 if ($svnOutput && $svnOutput =~ m#\?\s+$path\n#) {
452 return 0;
453 }
454 # This does not handle errors well.
455 return 1;
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000456 } elsif (isGit()) {
cjerdonek@webkit.org974ec942010-05-09 02:37:10 +0000457 my @result = callSilently(\&gitKnowsOfFile, $path);
458 return $result[0];
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000459 }
460}
461
462sub scmCopy($$)
463{
464 my ($source, $destination) = @_;
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000465 if (isSVN()) {
slewis@apple.com4875b632011-12-16 01:08:25 +0000466 my $escapedSource = escapeSubversionPath($source);
467 my $escapedDestination = escapeSubversionPath($destination);
468 system("svn", "copy", $escapedSource, $escapedDestination) == 0 or die "Failed to svn copy $escapedSource $escapedDestination.";
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000469 } elsif (isGit()) {
commit-queue@webkit.org161f92f2017-11-09 22:13:16 +0000470 copy($source, $destination) or die "Failed to copy $source $destination.";
clopez@igalia.come7976922020-07-07 00:46:03 +0000471 system("git", "add", "-v", $destination) == 0 or die "Failed to git add $destination.";
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000472 }
473}
474
475sub scmAdd($)
476{
477 my ($path) = @_;
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000478 if (isSVN()) {
slewis@apple.com4875b632011-12-16 01:08:25 +0000479 my $escapedPath = escapeSubversionPath($path);
480 system("svn", "add", $escapedPath) == 0 or die "Failed to svn add $escapedPath.";
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000481 } elsif (isGit()) {
clopez@igalia.come7976922020-07-07 00:46:03 +0000482 system("git", "add", "-v", $path) == 0 or die "Failed to git add $path.";
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000483 }
484}
485
486sub scmRemove($)
487{
488 my ($path) = @_;
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000489 if (isSVN()) {
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000490 # SVN is very verbose when removing directories. Squelch all output except the last line.
491 my $svnOutput;
slewis@apple.com4875b632011-12-16 01:08:25 +0000492 my $escapedPath = escapeSubversionPath($path);
493 open SVN, "svn rm --force '$escapedPath' |" or die "svn rm --force '$escapedPath' failed!";
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000494 # Only print the last line. Subversion outputs all changed statuses below $dir
495 while (<SVN>) {
496 $svnOutput = $_;
497 }
498 close SVN;
499 print $svnOutput if $svnOutput;
ddkilzer@apple.com33e4fbc2009-09-26 22:57:25 +0000500 } elsif (isGit()) {
eric@webkit.org77092af2010-04-11 20:19:01 +0000501 # Git removes a directory if it becomes empty when the last file it contains is
502 # removed by `git rm`. In svn-apply this can happen when a directory is being
503 # removed in a patch, and all of the files inside of the directory are removed
504 # before attemping to remove the directory itself. In this case, Git will have
505 # already deleted the directory and `git rm` would exit with an error claiming
506 # there was no file. The --ignore-unmatch switch gracefully handles this case.
507 system("git", "rm", "--force", "--ignore-unmatch", $path) == 0 or die "Failed to git rm --force --ignore-unmatch $path.";
eric@webkit.orgf135b1c2009-06-25 07:45:30 +0000508 }
509}
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000510
511# Calling "git add" / "svn add" per each file its very slow on big patches
512# We queue the filenames to execute them at the end in a few commands.
513sub scmAddQueued($)
514{
515 my ($path) = @_;
516 if (isSVN()) {
517 push(@scmQueuedFilesToAdd, escapeSubversionPath($path));
518 } elsif (isGit()) {
519 push(@scmQueuedFilesToAdd, $path);
520 }
521}
522
523sub scmCommitQueueAdded()
524{
525 my @cmdBase;
526 if (isSVN()) {
527 @cmdBase = ("svn", "add")
528 } elsif (isGit()) {
clopez@igalia.come7976922020-07-07 00:46:03 +0000529 @cmdBase = ("git", "add", "-v")
clopez@igalia.com031f1a22020-06-04 19:44:30 +0000530 }
531
532 # When we are handling a very large patch (more than 1000 files modified)
533 # Instead of executing only one add command, we execute several commands
534 # with 1000 files each one. We do that to avoid running into E2BIG errors
535 # on the execve() syscall (argument list is too long).
536 while (@scmQueuedFilesToAdd) {
537 my @cmdGrouped = splice(@scmQueuedFilesToAdd, 0, 1000);
538 unshift (@cmdGrouped, @cmdBase);
539 system (@cmdGrouped);
540 }
541}
542
543sub scmToggleExecutableBitQueued($$)
544{
545 my ($path, $executableBitDelta) = @_;
546 $scmQueuedExecutableBits{$path} = $executableBitDelta;
547}
548
549sub scmCommitQueueToggledExecutableBit()
550{
551 foreach my $path (keys %scmQueuedExecutableBits) {
552 scmToggleExecutableBit($path, $scmQueuedExecutableBits{$path});
553 }
554}