15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#!/usr/bin/perl -w
25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Copyright (C) 2005, 2006, 2007 Apple Inc.  All rights reserved.
45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com)
65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Redistribution and use in source and binary forms, with or without
85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# modification, are permitted provided that the following conditions
95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# are met:
105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 1.  Redistributions of source code must retain the above copyright
12fff8884795cb540f87cf6e6d67b629519b00eb8bBen Murdoch#     notice, this list of conditions and the following disclaimer.
135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 2.  Redistributions in binary form must reproduce the above copyright
145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#     notice, this list of conditions and the following disclaimer in the
15fff8884795cb540f87cf6e6d67b629519b00eb8bBen Murdoch#     documentation and/or other materials provided with the distribution.
165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#     its contributors may be used to endorse or promote products derived
18fff8884795cb540f87cf6e6d67b629519b00eb8bBen Murdoch#     from this software without specific prior written permission.
195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# "unpatch" script for WebKit Open Source Project, used to remove patches.
325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Differences from invoking "patch -p0 -R":
345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
35fff8884795cb540f87cf6e6d67b629519b00eb8bBen Murdoch#   Handles added files (does a svn revert with additional logic to handle local changes).
365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Handles added directories (does a svn revert and a rmdir).
37fff8884795cb540f87cf6e6d67b629519b00eb8bBen Murdoch#   Handles removed files (does a svn revert with additional logic to handle local changes).
38fff8884795cb540f87cf6e6d67b629519b00eb8bBen Murdoch#   Handles removed directories (does a svn revert).
395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Paths from Index: lines are used rather than the paths on the patch lines, which
405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#       makes patches generated by "cvs diff" work (increasingly unimportant since we
415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#       use Subversion now).
425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   ChangeLog patches use --fuzz=3 to prevent rejects, and the entry date is reset in
435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#       the patch before it is applied (svn-apply sets it when applying a patch).
445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Handles binary files (requires patches made by svn-create-patch).
455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Handles copied and moved files (requires patches made by svn-create-patch).
465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Handles git-diff patches (without binary changes) created at the top-level directory
475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Missing features:
495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Handle property changes.
515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Handle copied and moved directories (would require patches made by svn-create-patch).
525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Use version numbers in the patch file and do a 3-way merge.
535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   When reversing an addition, check that the file matches what's being removed.
545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Notice a patch that's being unapplied at the "wrong level" and make it work anyway.
555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Do a dry run on the whole patch and don't do anything if part of the patch is
565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#       going to fail (probably too strict unless we exclude ChangeLog).
575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   Handle git-diff patches with binary changes
585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use strict;
605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use warnings;
615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use Cwd;
635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use Digest::MD5;
645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use Fcntl qw(:DEFAULT :seek);
655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use File::Basename;
665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use File::Spec;
675c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use File::Temp qw(tempfile);
685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use Getopt::Long;
695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
705c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use FindBin;
715c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use lib $FindBin::Bin;
725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)use VCSUtils;
735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub checksum($);
755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub patch($);
765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub revertDirectories();
775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub unapplyPatch($$;$);
785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub unsetChangeLogDate($$);
795c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
805c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my $force = 0;
815c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my $showHelp = 0;
825c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my $optionParseSuccess = GetOptions(
845c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    "force!" => \$force,
855c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    "help!" => \$showHelp
865c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles));
875c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
885c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)if (!$optionParseSuccess || $showHelp) {
895c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    print STDERR basename($0) . " [-h|--help] [--force] patch1 [patch2 ...]\n";
905c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    exit 1;
915c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
925c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
935c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my $globalExitStatus = 0;
945c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
955c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my $repositoryRootPath = determineVCSRoot();
965c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
975c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my @copiedFiles;
985c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my %directoriesToCheck;
995c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Need to use a typeglob to pass the file handle as a parameter,
1015c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# otherwise get a bareword error.
1025c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my @diffHashRefs = parsePatch(*ARGV);
1035c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1045c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)print "Parsed " . @diffHashRefs . " diffs from patch file(s).\n";
1055c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1065c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my $preparedPatchHash = prepareParsedPatch($force, @diffHashRefs);
1075c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1085c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my @copyDiffHashRefs = @{$preparedPatchHash->{copyDiffHashRefs}};
1095c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)my @nonCopyDiffHashRefs = @{$preparedPatchHash->{nonCopyDiffHashRefs}};
1105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)for my $diffHashRef (@nonCopyDiffHashRefs) {
1125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    patch($diffHashRef);
1135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Handle copied and moved files last since they may have had post-copy changes that have now been unapplied
1165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)for my $diffHashRef (@copyDiffHashRefs) {
1175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    patch($diffHashRef);
1185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)if (isSVN()) {
1215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    revertDirectories();
1225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)exit $globalExitStatus;
1255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub checksum($)
1275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
1285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $file = shift;
1295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    open(FILE, $file) or die "Can't open '$file': $!";
1305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    binmode(FILE);
1315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $checksum = Digest::MD5->new->addfile(*FILE)->hexdigest();
1325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    close(FILE);
1335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    return $checksum;
1345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Args:
1375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   $diffHashRef: a diff hash reference of the type returned by parsePatch().
1385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub patch($)
1395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
1405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my ($diffHashRef) = @_;
1415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    # Make sure $patch is initialized to some value.  There is no
1435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    # svnConvertedText when reversing an svn copy/move.
1445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $patch = $diffHashRef->{svnConvertedText} || "";
1455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $fullPath = $diffHashRef->{indexPath};
1475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $isSvnBinary = $diffHashRef->{isBinary} && $diffHashRef->{isSvn};
1485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $hasTextChunks = $patch && $diffHashRef->{numTextChunks};
1495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    $directoriesToCheck{dirname($fullPath)} = 1;
1515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $deletion = 0;
1535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $addition = 0;
1545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    $addition = 1 if ($diffHashRef->{isNew} || $diffHashRef->{copiedFromPath} || $patch =~ /\n@@ -0,0 .* @@/);
1565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    $deletion = 1 if ($diffHashRef->{isDeletion} || $patch =~ /\n@@ .* \+0,0 @@/);
1575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (!$addition && !$deletion && !$isSvnBinary && $hasTextChunks) {
1595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # Standard patch, patch tool can handle this.
1605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        if (basename($fullPath) eq "ChangeLog") {
1615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            my $changeLogDotOrigExisted = -f "${fullPath}.orig";
1625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            my $changeLogHash = fixChangeLogPatch($patch);
1635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            unapplyPatch(unsetChangeLogDate($fullPath, $changeLogHash->{patch}), $fullPath, ["--fuzz=3"]);
1645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            unlink("${fullPath}.orig") if (! $changeLogDotOrigExisted);
1655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        } else {
1665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            unapplyPatch($patch, $fullPath);
1675c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        }
1685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    } else {
1695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # Either a deletion, an addition or a binary change.
1705c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1715c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        my $escapedFullPath = escapeSubversionPath($fullPath);
1725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        # FIXME: Add support for Git binary files.
1735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        if ($isSvnBinary) {
1745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # Reverse binary change
1755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            unlink($fullPath) if (-e $fullPath);
1765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            system "svn", "revert", $escapedFullPath;
1775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        } elsif ($deletion) {
1785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # Reverse deletion
1795c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            rename($fullPath, "$fullPath.orig") if -e $fullPath;
1805c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1815c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            unapplyPatch($patch, $fullPath);
1825c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # If we don't ask for the filehandle here, we always get a warning.
1845c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            my ($fh, $tempPath) = tempfile(basename($fullPath) . "-XXXXXXXX",
1855c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                                           DIR => dirname($fullPath), UNLINK => 1);
1865c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            close($fh);
1875c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1885c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # Keep the version from the patch in case it's different from svn.
1895c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            rename($fullPath, $tempPath);
1905c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            system "svn", "revert", $escapedFullPath;
1915c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            rename($tempPath, $fullPath);
1925c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1935c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # This works around a bug in the svn client.
1945c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # [Issue 1960] file modifications get lost due to FAT 2s time resolution
1955c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # http://subversion.tigris.org/issues/show_bug.cgi?id=1960
1965c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            system "touch", $fullPath;
1975c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1985c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # Remove $fullPath.orig if it is the same as $fullPath
1995c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            unlink("$fullPath.orig") if -e "$fullPath.orig" && checksum($fullPath) eq checksum("$fullPath.orig");
2005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2015c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # Show status if the file is modifed
2025c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            system "svn", "stat", $escapedFullPath;
2035c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        } elsif ($addition) {
2045c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # Reverse addition
2055c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            #
2065c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            # FIXME: This should use the same logic as svn-apply's deletion
2075c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            #        code.  In particular, svn-apply's scmRemove() subroutine
2085c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            #        should be used here.
2095c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            unapplyPatch($patch, $fullPath, ["--force"]) if $patch;
2105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            unlink($fullPath) if -z $fullPath;
2115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            system "svn", "revert", $escapedFullPath;
2125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        }
2135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    }
2145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    scmToggleExecutableBit($fullPath, -1 * $diffHashRef->{executableBitDelta}) if defined($diffHashRef->{executableBitDelta});
2165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
2175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub revertDirectories()
2195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
2205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    chdir $repositoryRootPath;
2215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my %checkedDirectories;
2225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    foreach my $path (reverse sort keys %directoriesToCheck) {
2235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        my @dirs = File::Spec->splitdir($path);
2245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        while (scalar @dirs) {
2255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            my $dir = File::Spec->catdir(@dirs);
2265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            pop(@dirs);
2275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            next if (exists $checkedDirectories{$dir});
2285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            if (-d $dir) {
2295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                my $svnOutput = svnStatus($dir);
2305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                my $escapedDir = escapeSubversionPath($dir);
2315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                if ($svnOutput && $svnOutput =~ m#A\s+$dir\n#) {
2325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                   system "svn", "revert", $escapedDir;
2335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                   rmdir $dir;
2345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                }
2355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                elsif ($svnOutput && $svnOutput =~ m#D\s+$dir\n#) {
2365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                   system "svn", "revert", $escapedDir;
2375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                }
2385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                else {
2395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                    # Modification
2405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                    print $svnOutput if $svnOutput;
2415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                }
2425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                $checkedDirectories{$dir} = 1;
2435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            }
2445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            else {
2455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                die "'$dir' is not a directory";
2465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            }
2475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        }
2485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    }
2495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
2505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Args:
2525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   $patch: a patch string.
2535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   $pathRelativeToRoot: the path of the file to be patched, relative to the
2545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#                        repository root. This should normally be the path
2555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#                        found in the patch's "Index:" line.
2565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#   $options: a reference to an array of options to pass to the patch command.
2575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#             Do not include --reverse in this array.
2585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub unapplyPatch($$;$)
2595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
2605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my ($patch, $pathRelativeToRoot, $options) = @_;
2615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $optionalArgs = {options => $options, ensureForce => $force, shouldReverse => 1};
2635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $exitStatus = runPatchCommand($patch, $repositoryRootPath, $pathRelativeToRoot, $optionalArgs);
2655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if ($exitStatus) {
2675c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        $globalExitStatus = $exitStatus;
2685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    }
2695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
2705c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2715c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)sub unsetChangeLogDate($$)
2725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
2735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $fullPath = shift;
2745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $patch = shift;
2755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $newDate;
2765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    sysopen(CHANGELOG, $fullPath, O_RDONLY) or die "Failed to open $fullPath: $!";
2775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    sysseek(CHANGELOG, 0, SEEK_SET);
2785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    my $byteCount = sysread(CHANGELOG, $newDate, 10);
2795c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    die "Failed reading $fullPath: $!" if !$byteCount || $byteCount != 10;
2805c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    close(CHANGELOG);
2815c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    $patch =~ s/(\n\+)\d{4}-[^-]{2}-[^-]{2}(  )/$1$newDate$2/;
2825c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    return $patch;
2835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
284