svn-apply should handle unified diffs
authordburkart@apple.com <dburkart@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Nov 2015 18:46:58 +0000 (18:46 +0000)
committerdburkart@apple.com <dburkart@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Nov 2015 18:46:58 +0000 (18:46 +0000)
https://bugs.webkit.org/show_bug.cgi?id=150650

Reviewed by Darin Adler.

* Scripts/VCSUtils.pm:
(parseUnifiedDiffHeader):
This method parses a unified diff header, and returns a information in the
style of parseGitDiffHeader and parseSvnDiffHeader.

(parseDiffHeader):
Teach parseDiffHeader to recognize unified diff headers.

(parseDiff):
Teach parseDiff to recognize unified diffs.

* Scripts/webkitperl/VCSUtils_unittest/parseDiffHeader.pl:
* Scripts/webkitperl/VCSUtils_unittest/parseUnifiedDiffHeader.pl: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@191900 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/VCSUtils.pm
Tools/Scripts/webkitperl/VCSUtils_unittest/parseDiffHeader.pl
Tools/Scripts/webkitperl/VCSUtils_unittest/parseUnifiedDiffHeader.pl [new file with mode: 0644]

index b7bd832..6786be6 100644 (file)
@@ -1,3 +1,24 @@
+2015-10-30  Dana Burkart  <dburkart@apple.com>
+
+        svn-apply should handle unified diffs
+        https://bugs.webkit.org/show_bug.cgi?id=150650
+
+        Reviewed by Darin Adler.
+
+        * Scripts/VCSUtils.pm:
+        (parseUnifiedDiffHeader):
+        This method parses a unified diff header, and returns a information in the
+        style of parseGitDiffHeader and parseSvnDiffHeader.
+
+        (parseDiffHeader):
+        Teach parseDiffHeader to recognize unified diff headers.
+
+        (parseDiff):
+        Teach parseDiff to recognize unified diffs.
+
+        * Scripts/webkitperl/VCSUtils_unittest/parseDiffHeader.pl:
+        * Scripts/webkitperl/VCSUtils_unittest/parseUnifiedDiffHeader.pl: Added.
+
 2015-11-02  Csaba Osztrogon√°c  <ossy@webkit.org>
 
         Fix the FTL JIT build with system LLVM on Linux
index f54e5ae..5460b92 100644 (file)
@@ -109,6 +109,7 @@ my $svnVersion;
 # Project time zone for Cupertino, CA, US
 my $changeLogTimeZone = "PST8PDT";
 
+my $unifiedDiffStartRegEx = qr#^--- ([abc]\/)?([^\r\n]+)#;
 my $gitDiffStartRegEx = qr#^diff --git [^\r\n]+#;
 my $gitDiffStartWithPrefixRegEx = qr#^diff --git \w/(.+) \w/([^\r\n]+)#; # We suppose that --src-prefix and --dst-prefix don't contain a non-word character (\W) and end with '/'.
 my $gitDiffStartWithoutPrefixNoSpaceRegEx = qr#^diff --git (\S+) (\S+)$#;
@@ -941,6 +942,103 @@ sub parseSvnDiffHeader($$)
     return (\%header, $_);
 }
 
+# Parse the next Unified diff header from the given file handle, and advance
+# the handle so the last line read is the first line after the header.
+#
+# This subroutine dies if given leading junk.
+#
+# Args:
+#   $fileHandle: advanced so the last line read from the handle is the first
+#                line of the header to parse.  This should be a line
+#                beginning with "Index:".
+#   $line: the line last read from $fileHandle
+#
+# Returns ($headerHashRef, $lastReadLine):
+#   $headerHashRef: a hash reference representing a diff header, as follows--
+#     indexPath: the path of the target file, which is the path found in
+#                the "Index:" line.
+#     isNew: the value 1 if the diff is for a new file.
+#     isDeletion: the value 1 if the diff is a file deletion.
+#     svnConvertedText: the header text converted to a header with the paths
+#                       in some lines corrected.
+#   $lastReadLine: the line last read from $fileHandle.
+sub parseUnifiedDiffHeader($$)
+{
+    my ($fileHandle, $line) = @_;
+
+    $_ = $line;
+
+    my $currentPosition = tell($fileHandle);
+    my $indexLine;
+    my $indexPath;
+    if (/$unifiedDiffStartRegEx/) {
+        # Use $POSTMATCH to preserve the end-of-line character.
+        my $eol = $POSTMATCH;
+        
+        $indexPath = $2;
+
+        # In the case of an addition, we look at the next line for the index path
+        if ($indexPath eq "/dev/null") {
+            $_ = <$fileHandle>;
+            if (/^\+\+\+ ([abc]\/)?([^\t\n\r]+)/) {
+                $indexPath = $2;
+            } else {
+                die "Unrecognized unified diff format.";
+            }
+            $_ = $line;
+        }
+
+        $indexLine = "Index: $indexPath$eol"; # Convert to SVN format.
+    } else {
+        die("Could not parse leading \"---\" line: \"$line\".");
+    }
+
+    seek($fileHandle, $currentPosition, 0);
+
+    my $isDeletion;
+    my $isHeaderEnding;
+    my $isNew;
+    my $svnConvertedText = $indexLine;
+    while (1) {
+        # Temporarily strip off any end-of-line characters to simplify
+        # regex matching below.
+        s/([\n\r]+)$//;
+        my $eol = $1;
+        
+        if (/^--- \/dev\/null/) {
+            $isNew = 1;
+        } elsif (/^\+\+\+ \/dev\/null/) {
+            $isDeletion = 1;
+        }
+        
+        if (/^(---|\+\+\+) ([abc]\/)?([^\t\n\r]+)/) {
+            if ($1 eq "---") {
+                my $prependText = "";
+                $prependText = "new file mode 100644\n" if $isNew;
+                $_ = "${prependText}index 0000000..0000000\n$1 $3";
+            } else {
+                $_ = "$1 $3";
+                $isHeaderEnding = 1;
+            }
+        }
+        
+        $svnConvertedText .= "$_$eol"; # Also restore end-of-line characters.
+        
+        $currentPosition = tell($fileHandle);
+        $_ = <$fileHandle>; # Not defined if end-of-file reached.
+        last if (!defined($_) || /$unifiedDiffStartRegEx/ || $isHeaderEnding);
+    }
+    
+    my %header;
+    
+    $header{indexPath} = $indexPath;
+    $header{isDeletion} = $isDeletion if $isDeletion;
+    $header{isNew} = $isNew if $isNew;
+    $header{svnConvertedText} = $svnConvertedText;
+
+    return (\%header, $_);
+}
+
 # Parse the next diff header from the given file handle, and advance
 # the handle so the last line read is the first line after the header.
 #
@@ -979,6 +1077,7 @@ sub parseDiffHeader($$)
     my $header;  # This is a hash ref.
     my $isGit;
     my $isSvn;
+    my $isUnified;
     my $lastReadLine;
 
     if ($line =~ $svnDiffStartRegEx) {
@@ -987,12 +1086,16 @@ sub parseDiffHeader($$)
     } elsif ($line =~ $gitDiffStartRegEx) {
         $isGit = 1;
         ($header, $lastReadLine) = parseGitDiffHeader($fileHandle, $line);
+    } elsif ($line =~ $unifiedDiffStartRegEx) {
+        $isUnified = 1;
+        ($header, $lastReadLine) = parseUnifiedDiffHeader($fileHandle, $line);
     } else {
         die("First line of diff does not begin with \"Index:\" or \"diff --git\": \"$line\"");
     }
 
     $header->{isGit} = $isGit if $isGit;
     $header->{isSvn} = $isSvn if $isSvn;
+    $header->{isUnified} = $isUnified if $isUnified;
 
     return ($header, $lastReadLine);
 }
@@ -1077,6 +1180,10 @@ sub parseDiff($$;$)
             # all diffs in the patch are formatted the same (SVN or Git).
             $headerStartRegEx = $gitDiffStartRegEx;
         }
+        
+        if (!$headerHashRef && ($line =~ $unifiedDiffStartRegEx)) {
+            $headerStartRegEx = $unifiedDiffStartRegEx;
+        }
 
         if ($line =~ $svnPropertiesStartRegEx) {
             my $propertyPath = $1;
index 1ed0fd2..c23e000 100644 (file)
@@ -99,6 +99,29 @@ END
 undef],
     expectedNextLine => undef,
 },
+####
+#    Unified patch cases
+##
+{
+    diffName => "Unified: Modified file",
+    inputText => <<'END',
+--- Foo/bar.h
++++ Foo/bar.h
+END
+    expectedReturn => [
+{
+    svnConvertedText => <<'END',
+Index: Foo/bar.h
+index 0000000..0000000
+--- Foo/bar.h
++++ Foo/bar.h
+END
+    indexPath => 'Foo/bar.h',
+    isUnified => 1,
+},
+undef],
+    expectedNextLine => undef,
+}
 );
 
 my $testCasesCount = @testCaseHashRefs;
diff --git a/Tools/Scripts/webkitperl/VCSUtils_unittest/parseUnifiedDiffHeader.pl b/Tools/Scripts/webkitperl/VCSUtils_unittest/parseUnifiedDiffHeader.pl
new file mode 100644 (file)
index 0000000..c2322d2
--- /dev/null
@@ -0,0 +1,190 @@
+#!/usr/bin/perl -w
+#
+# Copyright (C) 2015 Apple Inc.  All rights reserved.
+#
+# 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.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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.
+
+# Unit tests for parseUnifiedDiffHeader()
+
+use strict;
+use warnings;
+
+use Test::More;
+use VCSUtils;
+
+my @testCaseHashRefs = (
+{
+    diffName => "Modified file",
+    inputText => <<'END',
+--- Foo/bar.h
++++ Foo/bar.h
+@@ -304,6 +304,6 @@ void someKindOfFunction()
+leading
+text
+context
+-file contents
++new file contents
+trailing
+textcontext
+END
+    expectedReturn => [
+    {
+        svnConvertedText => <<'END',
+Index: Foo/bar.h
+index 0000000..0000000
+--- Foo/bar.h
++++ Foo/bar.h
+END
+        indexPath => 'Foo/bar.h',
+    },
+    "@@ -304,6 +304,6 @@ void someKindOfFunction()\n"],
+    expectedNextLine => "leading\n",
+},
+{
+    diffName => "Modified file with spaces in name",
+    inputText => <<'END',
+--- Foo/bar baz.h
++++ Foo/bar baz.h
+@@ -304,6 +304,6 @@ void someKindOfFunction()
+leading
+text
+context
+-file contents
++new file contents
+trailing
+textcontext
+END
+    expectedReturn => [
+    {
+        svnConvertedText => <<'END',
+Index: Foo/bar baz.h
+index 0000000..0000000
+--- Foo/bar baz.h
++++ Foo/bar baz.h
+END
+        indexPath => 'Foo/bar baz.h',
+    },
+    "@@ -304,6 +304,6 @@ void someKindOfFunction()\n"],
+    expectedNextLine => "leading\n",
+},
+{
+    diffName => "Modified file with git-style indices",
+    inputText => <<'END',
+--- a/Foo/bar.h
++++ b/Foo/bar.h
+@@ -304,6 +304,6 @@ void someKindOfFunction()
+leading
+text
+context
+-file contents
++new file contents
+trailing
+textcontext
+END
+    expectedReturn => [
+    {
+        svnConvertedText => <<'END',
+Index: Foo/bar.h
+index 0000000..0000000
+--- Foo/bar.h
++++ Foo/bar.h
+END
+        indexPath => 'Foo/bar.h',
+    },
+    "@@ -304,6 +304,6 @@ void someKindOfFunction()\n"],
+    expectedNextLine => "leading\n",
+},
+{
+    diffName => "Added file",
+    inputText => <<'END',
+--- /dev/null
++++ Foo/bar.h
+@@ -0,0 +1,6 @@
++leading
++text
++context
++new file contents
++trailing
++textcontext
+END
+    expectedReturn => [
+    {
+        svnConvertedText => <<'END',
+Index: Foo/bar.h
+new file mode 100644
+index 0000000..0000000
+--- /dev/null
++++ Foo/bar.h
+END
+        indexPath => 'Foo/bar.h',
+        isNew => 1,
+    },
+    "@@ -0,0 +1,6 @@\n"],
+    expectedNextLine => "+leading\n",
+},
+{
+    diffName => "Removed file",
+    inputText => <<'END',
+--- Foo/bar.h
++++ /dev/null
+@@ -0,0 +1,6 @@
+-leading
+-text
+-context
+-new file contents
+-trailing
+-textcontext
+END
+    expectedReturn => [
+    {
+        svnConvertedText => <<'END',
+Index: Foo/bar.h
+index 0000000..0000000
+--- Foo/bar.h
++++ /dev/null
+END
+        indexPath => 'Foo/bar.h',
+        isDeletion => 1,
+    },
+    "@@ -0,0 +1,6 @@\n"],
+    expectedNextLine => "-leading\n",
+},
+);
+
+
+my $testCasesCount = @testCaseHashRefs;
+plan(tests => 2 * $testCasesCount); # Total number of assertions.
+
+foreach my $testCase (@testCaseHashRefs) {
+    my $testNameStart = "parseUnifiedDiffHeader(): $testCase->{diffName}: comparing";
+
+    my $fileHandle;
+    open($fileHandle, "<", \$testCase->{inputText});
+    my $line = <$fileHandle>;
+
+    my @got = VCSUtils::parseUnifiedDiffHeader($fileHandle, $line);
+    my $expectedReturn = $testCase->{expectedReturn};
+
+    is_deeply(\@got, $expectedReturn, "$testNameStart return value.");
+
+    my $gotNextLine = <$fileHandle>;
+    is($gotNextLine, $testCase->{expectedNextLine},  "$testNameStart next read line.");
+}