diff -r 000000000000 -r 4f2f89ce4247 WebKitTools/Scripts/svn-unapply --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebKitTools/Scripts/svn-unapply Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,277 @@ +#!/usr/bin/perl -w + +# Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. +# Copyright (C) 2009 Cameron McCormack +# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of +# its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# "unpatch" script for WebKit Open Source Project, used to remove patches. + +# Differences from invoking "patch -p0 -R": +# +# Handles added files (does a svn revert with additional logic to handle local changes). +# Handles added directories (does a svn revert and a rmdir). +# Handles removed files (does a svn revert with additional logic to handle local changes). +# Handles removed directories (does a svn revert). +# 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 reset in +# the patch before it is applied (svn-apply sets it when applying a patch). +# 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 +# +# Missing features: +# +# Handle property changes. +# Handle copied and moved directories (would require patches made by svn-create-patch). +# Use version numbers in the patch file and do a 3-way merge. +# When reversing an addition, check that the file matches what's being removed. +# Notice a patch that's being unapplied at the "wrong level" and make it work anyway. +# Do a dry run on the whole patch and don't do anything if part of the patch is +# going to fail (probably too strict unless we exclude ChangeLog). +# Handle git-diff patches with binary changes + +use strict; +use warnings; + +use Cwd; +use Digest::MD5; +use Fcntl qw(:DEFAULT :seek); +use File::Basename; +use File::Spec; +use File::Temp qw(tempfile); +use Getopt::Long; + +use FindBin; +use lib $FindBin::Bin; +use VCSUtils; + +sub checksum($); +sub patch($); +sub revertDirectories(); +sub unapplyPatch($$;$); +sub unsetChangeLogDate($$); + +my $force = 0; +my $showHelp = 0; + +my $optionParseSuccess = GetOptions( + "force!" => \$force, + "help!" => \$showHelp +); + +if (!$optionParseSuccess || $showHelp) { + print STDERR basename($0) . " [-h|--help] [--force] patch1 [patch2 ...]\n"; + exit 1; +} + +my $globalExitStatus = 0; + +my $repositoryRootPath = determineVCSRoot(); + +my @copiedFiles; +my %directoriesToCheck; + +# 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}}; + +for my $diffHashRef (@nonCopyDiffHashRefs) { + patch($diffHashRef); +} + +# Handle copied and moved files last since they may have had post-copy changes that have now been unapplied +for my $diffHashRef (@copyDiffHashRefs) { + patch($diffHashRef); +} + +if (isSVN()) { + revertDirectories(); +} + +exit $globalExitStatus; + +sub checksum($) +{ + my $file = shift; + open(FILE, $file) or die "Can't open '$file': $!"; + binmode(FILE); + my $checksum = Digest::MD5->new->addfile(*FILE)->hexdigest(); + close(FILE); + return $checksum; +} + +# Args: +# $diffHashRef: a diff hash reference of the type returned by parsePatch(). +sub patch($) +{ + my ($diffHashRef) = @_; + + my $patch = $diffHashRef->{svnConvertedText}; + + my $fullPath = $diffHashRef->{indexPath}; + my $isSvnBinary = $diffHashRef->{isBinary} && $diffHashRef->{isSvn}; + + $directoriesToCheck{dirname($fullPath)} = 1; + + my $deletion = 0; + my $addition = 0; + + $addition = 1 if ($diffHashRef->{isNew} || $diffHashRef->{copiedFromPath} || $patch =~ /\n@@ -0,0 .* @@/); + $deletion = 1 if ($diffHashRef->{isDeletion} || $patch =~ /\n@@ .* \+0,0 @@/); + + if (!$addition && !$deletion && !$isSvnBinary) { + # Standard patch, patch tool can handle this. + if (basename($fullPath) eq "ChangeLog") { + my $changeLogDotOrigExisted = -f "${fullPath}.orig"; + unapplyPatch(unsetChangeLogDate($fullPath, fixChangeLogPatch($patch)), $fullPath, ["--fuzz=3"]); + unlink("${fullPath}.orig") if (! $changeLogDotOrigExisted); + } else { + unapplyPatch($patch, $fullPath); + } + } else { + # Either a deletion, an addition or a binary change. + + # FIXME: Add support for Git binary files. + if ($isSvnBinary) { + # Reverse binary change + unlink($fullPath) if (-e $fullPath); + system "svn", "revert", $fullPath; + } elsif ($deletion) { + # Reverse deletion + rename($fullPath, "$fullPath.orig") if -e $fullPath; + + unapplyPatch($patch, $fullPath); + + # If we don't ask for the filehandle here, we always get a warning. + my ($fh, $tempPath) = tempfile(basename($fullPath) . "-XXXXXXXX", + DIR => dirname($fullPath), UNLINK => 1); + close($fh); + + # Keep the version from the patch in case it's different from svn. + rename($fullPath, $tempPath); + system "svn", "revert", $fullPath; + rename($tempPath, $fullPath); + + # This works around a bug in the svn client. + # [Issue 1960] file modifications get lost due to FAT 2s time resolution + # http://subversion.tigris.org/issues/show_bug.cgi?id=1960 + system "touch", $fullPath; + + # Remove $fullPath.orig if it is the same as $fullPath + unlink("$fullPath.orig") if -e "$fullPath.orig" && checksum($fullPath) eq checksum("$fullPath.orig"); + + # Show status if the file is modifed + system "svn", "stat", $fullPath; + } else { + # Reverse addition + # + # FIXME: This should use the same logic as svn-apply's deletion + # code. In particular, svn-apply's scmRemove() subroutine + # should be used here. + unapplyPatch($patch, $fullPath, ["--force"]); + unlink($fullPath) if -z $fullPath; + system "svn", "revert", $fullPath; + } + } + + scmToggleExecutableBit($fullPath, -1 * $diffHashRef->{executableBitDelta}) if defined($diffHashRef->{executableBitDelta}); +} + +sub revertDirectories() +{ + chdir $repositoryRootPath; + my %checkedDirectories; + foreach my $path (reverse sort keys %directoriesToCheck) { + my @dirs = File::Spec->splitdir($path); + while (scalar @dirs) { + my $dir = File::Spec->catdir(@dirs); + pop(@dirs); + next if (exists $checkedDirectories{$dir}); + if (-d $dir) { + my $svnOutput = svnStatus($dir); + if ($svnOutput && $svnOutput =~ m#A\s+$dir\n#) { + system "svn", "revert", $dir; + rmdir $dir; + } + elsif ($svnOutput && $svnOutput =~ m#D\s+$dir\n#) { + system "svn", "revert", $dir; + } + else { + # Modification + print $svnOutput if $svnOutput; + } + $checkedDirectories{$dir} = 1; + } + else { + die "'$dir' is not a directory"; + } + } + } +} + +# Args: +# $patch: a patch string. +# $pathRelativeToRoot: the path of the file to be patched, relative to the +# repository root. This should normally be the path +# found in the patch's "Index:" line. +# $options: a reference to an array of options to pass to the patch command. +# Do not include --reverse in this array. +sub unapplyPatch($$;$) +{ + my ($patch, $pathRelativeToRoot, $options) = @_; + + my $optionalArgs = {options => $options, ensureForce => $force, shouldReverse => 1}; + + my $exitStatus = runPatchCommand($patch, $repositoryRootPath, $pathRelativeToRoot, $optionalArgs); + + if ($exitStatus) { + $globalExitStatus = $exitStatus; + } +} + +sub unsetChangeLogDate($$) +{ + my $fullPath = shift; + my $patch = shift; + my $newDate; + sysopen(CHANGELOG, $fullPath, O_RDONLY) or die "Failed to open $fullPath: $!"; + sysseek(CHANGELOG, 0, SEEK_SET); + my $byteCount = sysread(CHANGELOG, $newDate, 10); + die "Failed reading $fullPath: $!" if !$byteCount || $byteCount != 10; + close(CHANGELOG); + $patch =~ s/(\n\+)\d{4}-[^-]{2}-[^-]{2}( )/$1$newDate$2/; + return $patch; +}