diff options
Diffstat (limited to 'WebKitTools/Scripts/svn-apply')
| -rwxr-xr-x | WebKitTools/Scripts/svn-apply | 199 |
1 files changed, 83 insertions, 116 deletions
diff --git a/WebKitTools/Scripts/svn-apply b/WebKitTools/Scripts/svn-apply index f586211..33b2279 100755 --- a/WebKitTools/Scripts/svn-apply +++ b/WebKitTools/Scripts/svn-apply @@ -42,8 +42,7 @@ # Paths from Index: lines are used rather than the paths on the patch lines, which # makes patches generated by "cvs diff" work (increasingly unimportant since we # use Subversion now). -# ChangeLog patches use --fuzz=3 to prevent rejects, and the entry date is set in -# the patch to today's date using $changeLogTimeZone. +# ChangeLog patches use --fuzz=3 to prevent rejects. # Handles binary files (requires patches made by svn-create-patch). # Handles copied and moved files (requires patches made by svn-create-patch). # Handles git-diff patches (without binary changes) created at the top-level directory @@ -80,7 +79,6 @@ sub handleGitBinaryChange($$); sub isDirectoryEmptyForRemoval($); sub patch($); sub removeDirectoriesIfNeeded(); -sub setChangeLogDateAndReviewer($$); # These should be replaced by an scm class/module: sub scmKnowsOfFile($); @@ -88,10 +86,6 @@ sub scmCopy($$); sub scmAdd($); sub scmRemove($); - -# Project time zone for Cupertino, CA, US -my $changeLogTimeZone = "PST8PDT"; - my $merge = 0; my $showHelp = 0; my $reviewer; @@ -117,78 +111,47 @@ my %removeDirectoryIgnoreList = ( '_svn' => 1, ); +my $epochTime = time(); # This is used to set the date in ChangeLog files. my $globalExitStatus = 0; my $repositoryRootPath = determineVCSRoot(); my %checkedDirectories; -my %copiedFiles; -my @patches; -my %versions; - -my $copiedFromPath; -my $filter; -my $indexPath; -my $patch; -while (<>) { - s/([\n\r]+)$//mg; - my $eol = $1; - if (!defined($indexPath) && m#^diff --git \w/#) { - $filter = \&gitdiff2svndiff; - } - $_ = &$filter($_) if $filter; - if (/^Index: (.+)/) { - $indexPath = $1; - if ($patch) { - if (!$copiedFromPath) { - push @patches, $patch; - } - $copiedFromPath = ""; - $patch = ""; - } - } - if ($indexPath) { - # Fix paths on diff, ---, and +++ lines to match preceding Index: line. - s/\S+$/$indexPath/ if /^diff/; - s/^--- \S+/--- $indexPath/; - if (/^--- .+\(from (\S+):(\d+)\)$/) { - $copiedFromPath = $1; - $copiedFiles{$indexPath} = $copiedFromPath; - $versions{$copiedFromPath} = $2 if ($2 != 0); - } - elsif (/^--- .+\(revision (\d+)\)$/) { - $versions{$indexPath} = $1 if ($1 != 0); - } - if (s/^\+\+\+ \S+/+++ $indexPath/) { - $indexPath = ""; - } - } - $patch .= $_; - $patch .= $eol; -} -if ($patch && !$copiedFromPath) { - push @patches, $patch; -} +# Need to use a typeglob to pass the file handle as a parameter, +# otherwise get a bareword error. +my @diffHashRefs = parsePatch(*ARGV); + +print "Parsed " . @diffHashRefs . " diffs from patch file(s).\n"; + +my $preparedPatchHash = prepareParsedPatch($force, @diffHashRefs); + +my @copyDiffHashRefs = @{$preparedPatchHash->{copyDiffHashRefs}}; +my @nonCopyDiffHashRefs = @{$preparedPatchHash->{nonCopyDiffHashRefs}}; +my %sourceRevisions = %{$preparedPatchHash->{sourceRevisionHash}}; if ($merge) { die "--merge is currently only supported for SVN" unless isSVN(); # How do we handle Git patches applied to an SVN checkout here? - for my $file (sort keys %versions) { - my $version = $versions{$file}; + for my $file (sort keys %sourceRevisions) { + my $version = $sourceRevisions{$file}; print "Getting version $version of $file\n"; system("svn", "update", "-r", $version, $file) == 0 or die "Failed to run svn update -r $version $file."; } } -# Handle copied and moved files first since moved files may have their source deleted before the move. -for my $file (keys %copiedFiles) { - addDirectoriesIfNeeded(dirname($file)); - scmCopy($copiedFiles{$file}, $file); +# Handle copied and moved files first since moved files may have their +# source deleted before the move. +for my $copyDiffHashRef (@copyDiffHashRefs) { + my $indexPath = $copyDiffHashRef->{indexPath}; + my $copiedFromPath = $copyDiffHashRef->{copiedFromPath}; + + addDirectoriesIfNeeded(dirname($indexPath)); + scmCopy($copiedFromPath, $indexPath); } -for $patch (@patches) { - patch($patch); +for my $diffHashRef (@nonCopyDiffHashRefs) { + patch($diffHashRef); } removeDirectoriesIfNeeded(); @@ -276,14 +239,16 @@ sub handleBinaryChange($$) sub handleGitBinaryChange($$) { - my ($fullPath, $contents) = @_; + my ($fullPath, $diffHashRef) = @_; + + my $contents = $diffHashRef->{svnConvertedText}; my ($binaryChunkType, $binaryChunk, $reverseBinaryChunkType, $reverseBinaryChunk) = decodeGitBinaryPatch($contents, $fullPath); # FIXME: support "delta" type. die "only literal type is supported now" if ($binaryChunkType ne "literal" || $reverseBinaryChunkType ne "literal"); - my $isFileAddition = $contents =~ /\nnew file mode \d+\n/; - my $isFileDeletion = $contents =~ /\ndeleted file mode \d+\n/; + my $isFileAddition = $diffHashRef->{isNew}; + my $isFileDeletion = $diffHashRef->{isDeletion}; my $originalContents = ""; if (open FILE, $fullPath) { @@ -310,6 +275,7 @@ sub handleGitBinaryChange($$) sub isDirectoryEmptyForRemoval($) { my ($dir) = @_; + return 1 unless -d $dir; my $directoryIsEmpty = 1; opendir DIR, $dir or die "Could not open '$dir' to list files: $?"; for (my $item = readdir DIR; $item && $directoryIsEmpty; $item = readdir DIR) { @@ -325,37 +291,36 @@ sub isDirectoryEmptyForRemoval($) return $directoryIsEmpty; } +# Args: +# $diffHashRef: a diff hash reference of the type returned by parsePatch(). sub patch($) { - my ($patch) = @_; - return if !$patch; - - unless ($patch =~ m|^Index: ([^\r\n]+)|) { - my $separator = '-' x 67; - warn "Failed to find 'Index:' in:\n$separator\n$patch\n$separator\n"; - die unless $force; - return; - } - my $fullPath = $1; + my ($diffHashRef) = @_; + + # Make sure $patch is initialized to some value. A deletion can have no + # svnConvertedText property in the case of a deletion resulting from a + # Git rename. + my $patch = $diffHashRef->{svnConvertedText} || ""; + + my $fullPath = $diffHashRef->{indexPath}; + my $isBinary = $diffHashRef->{isBinary}; + my $isGit = $diffHashRef->{isGit}; my $deletion = 0; my $addition = 0; - my $isBinary = 0; - my $isGitBinary = 0; - $addition = 1 if ($patch =~ /\n--- .+\(revision 0\)\r?\n/ || $patch =~ /\n@@ -0,0 .* @@/) && !exists($copiedFiles{$fullPath}); - $deletion = 1 if $patch =~ /\n@@ .* \+0,0 @@/; - $isBinary = 1 if $patch =~ /\nCannot display: file marked as a binary type\./; - $isGitBinary = 1 if $patch =~ /\nGIT binary patch\n/; + $addition = 1 if ($diffHashRef->{isNew} || $patch =~ /\n@@ -0,0 .* @@/); + $deletion = 1 if ($diffHashRef->{isDeletion} || $patch =~ /\n@@ .* \+0,0 @@/); - if (!$addition && !$deletion && !$isBinary && !$isGitBinary) { + if (!$addition && !$deletion && !$isBinary) { # Standard patch, patch tool can handle this. if (basename($fullPath) eq "ChangeLog") { my $changeLogDotOrigExisted = -f "${fullPath}.orig"; - applyPatch(setChangeLogDateAndReviewer(fixChangeLogPatch($patch), $reviewer), $fullPath, ["--fuzz=3"]); + my $newPatch = setChangeLogDateAndReviewer(fixChangeLogPatch($patch), $reviewer, $epochTime); + applyPatch($newPatch, $fullPath, ["--fuzz=3"]); unlink("${fullPath}.orig") if (! $changeLogDotOrigExisted); } else { - applyPatch($patch, $fullPath); + applyPatch($patch, $fullPath) if $patch; } } else { # Either a deletion, an addition or a binary change. @@ -363,25 +328,26 @@ sub patch($) addDirectoriesIfNeeded(dirname($fullPath)); if ($isBinary) { - # Binary change - handleBinaryChange($fullPath, $patch); - } elsif ($isGitBinary) { - # Git binary change - handleGitBinaryChange($fullPath, $patch); + if ($isGit) { + handleGitBinaryChange($fullPath, $diffHashRef); + } else { + handleBinaryChange($fullPath, $patch) if $patch; + } } elsif ($deletion) { - # Deletion - applyPatch($patch, $fullPath, ["--force"]); + applyPatch($patch, $fullPath, ["--force"]) if $patch; scmRemove($fullPath); } else { # Addition rename($fullPath, "$fullPath.orig") if -e $fullPath; - applyPatch($patch, $fullPath); + applyPatch($patch, $fullPath) if $patch; unlink("$fullPath.orig") if -e "$fullPath.orig" && checksum($fullPath) eq checksum("$fullPath.orig"); scmAdd($fullPath); # What is this for? system("svn", "stat", "$fullPath.orig") if isSVN() && -e "$fullPath.orig"; } } + + scmToggleExecutableBit($fullPath, $diffHashRef->{executableBitDelta}) if defined($diffHashRef->{executableBitDelta}); } sub removeDirectoriesIfNeeded() @@ -393,26 +359,6 @@ sub removeDirectoriesIfNeeded() } } -sub setChangeLogDateAndReviewer($$) -{ - my $patch = shift; - my $reviewer = shift; - my $savedTimeZone = $ENV{'TZ'}; - # Set TZ temporarily so that localtime() is in that time zone - $ENV{'TZ'} = $changeLogTimeZone; - my $newDate = strftime("%Y-%m-%d", localtime()); - if (defined $savedTimeZone) { - $ENV{'TZ'} = $savedTimeZone; - } else { - delete $ENV{'TZ'}; - } - $patch =~ s/(\n\+)\d{4}-[^-]{2}-[^-]{2}( )/$1$newDate$2/; - if (defined($reviewer)) { - $patch =~ s/NOBODY \(OOPS!\)/$reviewer/; - } - return $patch; -} - # This could be made into a more general "status" call, except svn and git # have different ideas about "moving" files which might get confusing. sub scmWillDeleteFile($) @@ -428,6 +374,22 @@ sub scmWillDeleteFile($) return 0; } +# Return whether the file at the given path is known to Git. +# +# This method outputs a message like the following to STDERR when +# returning false: +# +# "error: pathspec 'test.png' did not match any file(s) known to git. +# Did you forget to 'git add'?" +sub gitKnowsOfFile($) +{ + my $path = shift; + + `git ls-files --error-unmatch -- $path`; + my $exitStatus = exitStatus($?); + return $exitStatus == 0; +} + sub scmKnowsOfFile($) { my ($path) = @_; @@ -440,9 +402,8 @@ sub scmKnowsOfFile($) # This does not handle errors well. return 1; } elsif (isGit()) { - `git ls-files --error-unmatch -- $path`; - my $exitCode = $? >> 8; - return $exitCode == 0; + my @result = callSilently(\&gitKnowsOfFile, $path); + return $result[0]; } } @@ -481,6 +442,12 @@ sub scmRemove($) close SVN; print $svnOutput if $svnOutput; } elsif (isGit()) { - system("git", "rm", "--force", $path) == 0 or die "Failed to git rm --force $path."; + # Git removes a directory if it becomes empty when the last file it contains is + # removed by `git rm`. In svn-apply this can happen when a directory is being + # removed in a patch, and all of the files inside of the directory are removed + # before attemping to remove the directory itself. In this case, Git will have + # already deleted the directory and `git rm` would exit with an error claiming + # there was no file. The --ignore-unmatch switch gracefully handles this case. + system("git", "rm", "--force", "--ignore-unmatch", $path) == 0 or die "Failed to git rm --force --ignore-unmatch $path."; } } |
