10bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch# Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved. 2d0825bca7fe65beaee391d30da42e937db621564Steve Block# Copyright (C) 2009, 2010 Chris Jerdonek (chris.jerdonek@gmail.com) 32daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# Copyright (C) 2010, 2011 Research In Motion Limited. All rights reserved. 4563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 5563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Redistribution and use in source and binary forms, with or without 6563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# modification, are permitted provided that the following conditions 7563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# are met: 8563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 9563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 1. Redistributions of source code must retain the above copyright 10563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# notice, this list of conditions and the following disclaimer. 11563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 2. Redistributions in binary form must reproduce the above copyright 12563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# notice, this list of conditions and the following disclaimer in the 13563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# documentation and/or other materials provided with the distribution. 14563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# its contributors may be used to endorse or promote products derived 16563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# from this software without specific prior written permission. 17563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 18563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 29563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Module to share code to work with various version control systems. 30231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Blockpackage VCSUtils; 31563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 32563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse strict; 33563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse warnings; 340bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 350bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdochuse Cwd qw(); # "qw()" prevents warnings about redefining getcwd() with "use POSIX;" 36d0825bca7fe65beaee391d30da42e937db621564Steve Blockuse English; # for $POSTMATCH, etc. 370bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdochuse File::Basename; 38563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse File::Spec; 39d0825bca7fe65beaee391d30da42e937db621564Steve Blockuse POSIX; 40563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 41563af33bc48281d19dce701398dbb88cb54fd7ecCary ClarkBEGIN { 42231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block use Exporter (); 43231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); 44231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block $VERSION = 1.00; 45231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block @ISA = qw(Exporter); 46231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block @EXPORT = qw( 47cad810f21b803229eb11403f9209855525a25d57Steve Block &applyGitBinaryPatchDelta 486c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen &callSilently 49cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block &canonicalizePath 50643ca7872b450ea4efacab6188849e5aac2ba161Steve Block &changeLogEmailAddress 51643ca7872b450ea4efacab6188849e5aac2ba161Steve Block &changeLogName 52231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &chdirReturningRelativePath 53cad810f21b803229eb11403f9209855525a25d57Steve Block &decodeGitBinaryChunk 54643ca7872b450ea4efacab6188849e5aac2ba161Steve Block &decodeGitBinaryPatch 55231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &determineSVNRoot 56231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &determineVCSRoot 57d0825bca7fe65beaee391d30da42e937db621564Steve Block &exitStatus 58cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block &fixChangeLogPatch 59231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &gitBranch 60cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block &gitdiff2svndiff 61231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &isGit 6281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch &isGitSVN 63231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &isGitBranchBuild 64231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &isGitDirectory 65231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &isSVN 66231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &isSVNDirectory 67231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &isSVNVersion16OrNewer 68231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &makeFilePathRelative 69dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block &mergeChangeLogs 70cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block &normalizePath 712daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch &parseFirstEOL 72d0825bca7fe65beaee391d30da42e937db621564Steve Block &parsePatch 73231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &pathRelativeToSVNRepositoryRootForPath 7421939df44de1705786c545cd1bf519d47250322dBen Murdoch &prepareParsedPatch 75e14391e94c850b8bd03680c23b38978db68687a8John Reck &removeEOL 76d0825bca7fe65beaee391d30da42e937db621564Steve Block &runPatchCommand 77e14391e94c850b8bd03680c23b38978db68687a8John Reck &scmMoveOrRenameFile 78e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke &scmToggleExecutableBit 796c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen &setChangeLogDateAndReviewer 80231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block &svnRevisionForDirectory 81cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block &svnStatus 82e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block &toWindowsLineEndings 83231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block ); 84231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block %EXPORT_TAGS = ( ); 85231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block @EXPORT_OK = (); 86563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 87563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 88563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkour @EXPORT_OK; 89563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 90563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkmy $gitBranch; 91231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Blockmy $gitRoot; 92231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Blockmy $isGit; 9381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdochmy $isGitSVN; 94563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkmy $isGitBranchBuild; 95231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Blockmy $isSVN; 96231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Blockmy $svnVersion; 97563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 986c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Project time zone for Cupertino, CA, US 996c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsenmy $changeLogTimeZone = "PST8PDT"; 1006c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 1012daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdochmy $chunkRangeRegEx = qr#^\@\@ -(\d+),(\d+) \+\d+,(\d+) \@\@$#; # e.g. @@ -2,6 +2,18 @@ 1026c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsenmy $gitDiffStartRegEx = qr#^diff --git (\w/)?(.+) (\w/)?([^\r\n]+)#; 1036c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsenmy $svnDiffStartRegEx = qr#^Index: ([^\r\n]+)#; 104e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarkemy $svnPropertiesStartRegEx = qr#^Property changes on: ([^\r\n]+)#; # $1 is normally the same as the index path. 1056c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsenmy $svnPropertyStartRegEx = qr#^(Modified|Name|Added|Deleted): ([^\r\n]+)#; # $2 is the name of the property. 1065abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrickmy $svnPropertyValueStartRegEx = qr#^ (\+|-|Merged|Reverse-merged) ([^\r\n]+)#; # $2 is the start of the property's value (which may span multiple lines). 1076c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 108d0825bca7fe65beaee391d30da42e937db621564Steve Block# This method is for portability. Return the system-appropriate exit 109d0825bca7fe65beaee391d30da42e937db621564Steve Block# status of a child process. 110d0825bca7fe65beaee391d30da42e937db621564Steve Block# 111d0825bca7fe65beaee391d30da42e937db621564Steve Block# Args: pass the child error status returned by the last pipe close, 112d0825bca7fe65beaee391d30da42e937db621564Steve Block# for example "$?". 113d0825bca7fe65beaee391d30da42e937db621564Steve Blocksub exitStatus($) 114d0825bca7fe65beaee391d30da42e937db621564Steve Block{ 115d0825bca7fe65beaee391d30da42e937db621564Steve Block my ($returnvalue) = @_; 116d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($^O eq "MSWin32") { 117d0825bca7fe65beaee391d30da42e937db621564Steve Block return $returnvalue >> 8; 118d0825bca7fe65beaee391d30da42e937db621564Steve Block } 119d0825bca7fe65beaee391d30da42e937db621564Steve Block return WEXITSTATUS($returnvalue); 120d0825bca7fe65beaee391d30da42e937db621564Steve Block} 121d0825bca7fe65beaee391d30da42e937db621564Steve Block 1226c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Call a function while suppressing STDERR, and return the return values 1236c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# as an array. 1246c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsensub callSilently($@) { 1256c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my ($func, @args) = @_; 1266c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 1276c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # The following pattern was taken from here: 1286c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # http://www.sdsc.edu/~moreland/courses/IntroPerl/docs/manual/pod/perlfunc/open.html 1296c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # 1306c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Also see this Perl documentation (search for "open OLDERR"): 1316c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # http://perldoc.perl.org/functions/open.html 1326c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen open(OLDERR, ">&STDERR"); 1336c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen close(STDERR); 1346c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my @returnValue = &$func(@args); 1356c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen open(STDERR, ">&OLDERR"); 1366c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen close(OLDERR); 1376c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 1386c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen return @returnValue; 1396c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen} 1406c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 141e8b154fd68f9b33be40a3590e58347f353835f5cSteve Blocksub toWindowsLineEndings 142e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block{ 143e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block my ($text) = @_; 144e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block $text =~ s/\n/\r\n/g; 145e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block return $text; 146e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block} 147e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block 148a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch# Note, this method will not error if the file corresponding to the $source path does not exist. 149a94275402997c11dd2e778633dacf4b7e630a35dBen Murdochsub scmMoveOrRenameFile 150a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch{ 151a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch my ($source, $destination) = @_; 152a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return if ! -e $source; 153a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch if (isSVN()) { 154a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch system("svn", "move", $source, $destination); 155a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch } elsif (isGit()) { 156a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch system("git", "mv", $source, $destination); 157a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch } 158a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch} 159a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch 16021939df44de1705786c545cd1bf519d47250322dBen Murdoch# Note, this method will not error if the file corresponding to the path does not exist. 16121939df44de1705786c545cd1bf519d47250322dBen Murdochsub scmToggleExecutableBit 16221939df44de1705786c545cd1bf519d47250322dBen Murdoch{ 16321939df44de1705786c545cd1bf519d47250322dBen Murdoch my ($path, $executableBitDelta) = @_; 16421939df44de1705786c545cd1bf519d47250322dBen Murdoch return if ! -e $path; 16521939df44de1705786c545cd1bf519d47250322dBen Murdoch if ($executableBitDelta == 1) { 16621939df44de1705786c545cd1bf519d47250322dBen Murdoch scmAddExecutableBit($path); 16721939df44de1705786c545cd1bf519d47250322dBen Murdoch } elsif ($executableBitDelta == -1) { 16821939df44de1705786c545cd1bf519d47250322dBen Murdoch scmRemoveExecutableBit($path); 16921939df44de1705786c545cd1bf519d47250322dBen Murdoch } 17021939df44de1705786c545cd1bf519d47250322dBen Murdoch} 17121939df44de1705786c545cd1bf519d47250322dBen Murdoch 17221939df44de1705786c545cd1bf519d47250322dBen Murdochsub scmAddExecutableBit($) 17321939df44de1705786c545cd1bf519d47250322dBen Murdoch{ 17421939df44de1705786c545cd1bf519d47250322dBen Murdoch my ($path) = @_; 17521939df44de1705786c545cd1bf519d47250322dBen Murdoch 17621939df44de1705786c545cd1bf519d47250322dBen Murdoch if (isSVN()) { 17721939df44de1705786c545cd1bf519d47250322dBen Murdoch system("svn", "propset", "svn:executable", "on", $path) == 0 or die "Failed to run 'svn propset svn:executable on $path'."; 17821939df44de1705786c545cd1bf519d47250322dBen Murdoch } elsif (isGit()) { 17921939df44de1705786c545cd1bf519d47250322dBen Murdoch chmod(0755, $path); 18021939df44de1705786c545cd1bf519d47250322dBen Murdoch } 18121939df44de1705786c545cd1bf519d47250322dBen Murdoch} 18221939df44de1705786c545cd1bf519d47250322dBen Murdoch 18321939df44de1705786c545cd1bf519d47250322dBen Murdochsub scmRemoveExecutableBit($) 18421939df44de1705786c545cd1bf519d47250322dBen Murdoch{ 18521939df44de1705786c545cd1bf519d47250322dBen Murdoch my ($path) = @_; 18621939df44de1705786c545cd1bf519d47250322dBen Murdoch 18721939df44de1705786c545cd1bf519d47250322dBen Murdoch if (isSVN()) { 18821939df44de1705786c545cd1bf519d47250322dBen Murdoch system("svn", "propdel", "svn:executable", $path) == 0 or die "Failed to run 'svn propdel svn:executable $path'."; 18921939df44de1705786c545cd1bf519d47250322dBen Murdoch } elsif (isGit()) { 19021939df44de1705786c545cd1bf519d47250322dBen Murdoch chmod(0664, $path); 19121939df44de1705786c545cd1bf519d47250322dBen Murdoch } 19221939df44de1705786c545cd1bf519d47250322dBen Murdoch} 19321939df44de1705786c545cd1bf519d47250322dBen Murdoch 194563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub isGitDirectory($) 195563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{ 196563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark my ($dir) = @_; 197cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block return system("cd $dir && git rev-parse > " . File::Spec->devnull() . " 2>&1") == 0; 198563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 199563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 200563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub isGit() 201563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{ 202563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $isGit if defined $isGit; 203563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 204563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark $isGit = isGitDirectory("."); 205563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $isGit; 206563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 207563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 20881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdochsub isGitSVN() 20981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch{ 21081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch return $isGitSVN if defined $isGitSVN; 21181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch 21281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch # There doesn't seem to be an officially documented way to determine 21381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch # if you're in a git-svn checkout. The best suggestions seen so far 21481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch # all use something like the following: 21581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch my $output = `git config --get svn-remote.svn.fetch 2>& 1`; 21681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch $isGitSVN = $output ne ''; 21781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch return $isGitSVN; 21881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch} 21981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch 220563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub gitBranch() 221563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{ 222563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark unless (defined $gitBranch) { 223563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark chomp($gitBranch = `git symbolic-ref -q HEAD`); 224d0825bca7fe65beaee391d30da42e937db621564Steve Block $gitBranch = "" if exitStatus($?); 225563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark $gitBranch =~ s#^refs/heads/##; 226563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark $gitBranch = "" if $gitBranch eq "master"; 227563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark } 228563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 229563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $gitBranch; 230563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 231563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 232563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub isGitBranchBuild() 233563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{ 234563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark my $branch = gitBranch(); 235563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark chomp(my $override = `git config --bool branch.$branch.webKitBranchBuild`); 236563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return 1 if $override eq "true"; 237563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return 0 if $override eq "false"; 238563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 239563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark unless (defined $isGitBranchBuild) { 240563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark chomp(my $gitBranchBuild = `git config --bool core.webKitBranchBuild`); 241563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark $isGitBranchBuild = $gitBranchBuild eq "true"; 242563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark } 243563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 244563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $isGitBranchBuild; 245563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 246563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 247563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub isSVNDirectory($) 248563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{ 249563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark my ($dir) = @_; 250563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 251563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return -d File::Spec->catdir($dir, ".svn"); 252563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 253563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 254563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub isSVN() 255563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{ 256563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $isSVN if defined $isSVN; 257563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 258563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark $isSVN = isSVNDirectory("."); 259563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $isSVN; 260563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 261563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 262231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Blocksub svnVersion() 263231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block{ 264231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block return $svnVersion if defined $svnVersion; 265231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 266231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block if (!isSVN()) { 267231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block $svnVersion = 0; 268231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block } else { 269231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block chomp($svnVersion = `svn --version --quiet`); 270231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block } 271231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block return $svnVersion; 272231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block} 273231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 274231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Blocksub isSVNVersion16OrNewer() 275231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block{ 276231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block my $version = svnVersion(); 277231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block return eval "v$version" ge v1.6; 278231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block} 279231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 2800bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdochsub chdirReturningRelativePath($) 2810bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{ 2820bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my ($directory) = @_; 2830bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $previousDirectory = Cwd::getcwd(); 2840bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch chdir $directory; 2850bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $newDirectory = Cwd::getcwd(); 2860bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch return "." if $newDirectory eq $previousDirectory; 2870bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch return File::Spec->abs2rel($previousDirectory, $newDirectory); 2880bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch} 2890bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 2900bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdochsub determineGitRoot() 2910bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{ 2920bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch chomp(my $gitDir = `git rev-parse --git-dir`); 2930bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch return dirname($gitDir); 2940bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch} 2950bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 2960bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdochsub determineSVNRoot() 2970bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{ 2980bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $last = ''; 2990bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $path = '.'; 3000bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $parent = '..'; 301231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block my $repositoryRoot; 3020bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $repositoryUUID; 3030bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch while (1) { 304231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block my $thisRoot; 3050bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $thisUUID; 3060bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch # Ignore error messages in case we've run past the root of the checkout. 307cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block open INFO, "svn info '$path' 2> " . File::Spec->devnull() . " |" or die; 3080bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch while (<INFO>) { 309231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block if (/^Repository Root: (.+)/) { 310231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block $thisRoot = $1; 311231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block } 3120bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch if (/^Repository UUID: (.+)/) { 3130bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch $thisUUID = $1; 314231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block } 315231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block if ($thisRoot && $thisUUID) { 316231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block local $/ = undef; 317231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block <INFO>; # Consume the rest of the input. 3180bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch } 3190bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch } 3200bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch close INFO; 3210bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 3220bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch # It's possible (e.g. for developers of some ports) to have a WebKit 3230bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch # checkout in a subdirectory of another checkout. So abort if the 324231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block # repository root or the repository UUID suddenly changes. 3250bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch last if !$thisUUID; 326231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block $repositoryUUID = $thisUUID if !$repositoryUUID; 3270bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch last if $thisUUID ne $repositoryUUID; 3280bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 329231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block last if !$thisRoot; 330231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block $repositoryRoot = $thisRoot if !$repositoryRoot; 331231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block last if $thisRoot ne $repositoryRoot; 332231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 3330bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch $last = $path; 3340bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch $path = File::Spec->catdir($parent, $path); 3350bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch } 3360bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 3370bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch return File::Spec->rel2abs($last); 3380bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch} 3390bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 3400bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdochsub determineVCSRoot() 3410bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{ 3420bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch if (isGit()) { 3430bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch return determineGitRoot(); 3440bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch } 345231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 346231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block if (!isSVN()) { 347231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block # Some users have a workflow where svn-create-patch, svn-apply and 348231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block # svn-unapply are used outside of multiple svn working directores, 349231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block # so warn the user and assume Subversion is being used in this case. 350231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block warn "Unable to determine VCS root; assuming Subversion"; 351231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block $isSVN = 1; 3520bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch } 353231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 354231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block return determineSVNRoot(); 3550bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch} 3560bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 357563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub svnRevisionForDirectory($) 358563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{ 359563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark my ($dir) = @_; 360563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark my $revision; 361563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 362563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark if (isSVNDirectory($dir)) { 363563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark my $svnInfo = `LC_ALL=C svn info $dir | grep Revision:`; 364563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark ($revision) = ($svnInfo =~ m/Revision: (\d+).*/g); 365563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark } elsif (isGitDirectory($dir)) { 366563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark my $gitLog = `cd $dir && LC_ALL=C git log --grep='git-svn-id: ' -n 1 | grep git-svn-id:`; 367563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark ($revision) = ($gitLog =~ m/ +git-svn-id: .+@(\d+) /g); 368563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark } 369563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark die "Unable to determine current SVN revision in $dir" unless (defined $revision); 370563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $revision; 371563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 372563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 3730bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdochsub pathRelativeToSVNRepositoryRootForPath($) 3740bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{ 3750bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my ($file) = @_; 3760bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $relativePath = File::Spec->abs2rel($file); 3770bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 3780bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $svnInfo; 3790bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch if (isSVN()) { 3800bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch $svnInfo = `LC_ALL=C svn info $relativePath`; 3810bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch } elsif (isGit()) { 3820bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch $svnInfo = `LC_ALL=C git svn info $relativePath`; 3830bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch } 3840bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 3850bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch $svnInfo =~ /.*^URL: (.*?)$/m; 3860bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $svnURL = $1; 3870bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 3880bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch $svnInfo =~ /.*^Repository Root: (.*?)$/m; 3890bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch my $repositoryRoot = $1; 3900bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 3910bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch $svnURL =~ s/$repositoryRoot\///; 3920bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch return $svnURL; 3930bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch} 3940bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch 395563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub makeFilePathRelative($) 396563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{ 397563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark my ($path) = @_; 398563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $path unless isGit(); 399563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 400563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark unless (defined $gitRoot) { 401563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark chomp($gitRoot = `git rev-parse --show-cdup`); 402563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark } 403563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark return $gitRoot . $path; 404563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark} 405563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark 406cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Blocksub normalizePath($) 407cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block{ 408cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block my ($path) = @_; 409cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block $path =~ s/\\/\//g; 410cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block return $path; 411cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block} 412cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 413cad810f21b803229eb11403f9209855525a25d57Steve Blocksub adjustPathForRecentRenamings($) 414cad810f21b803229eb11403f9209855525a25d57Steve Block{ 415cad810f21b803229eb11403f9209855525a25d57Steve Block my ($fullPath) = @_; 416cad810f21b803229eb11403f9209855525a25d57Steve Block 41765f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch if ($fullPath =~ m|^WebCore/| 41865f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch || $fullPath =~ m|^JavaScriptCore/| 41965f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch || $fullPath =~ m|^WebKit/| 42065f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch || $fullPath =~ m|^WebKit2/|) { 421cad810f21b803229eb11403f9209855525a25d57Steve Block return "Source/$fullPath"; 422cad810f21b803229eb11403f9209855525a25d57Steve Block } 423cad810f21b803229eb11403f9209855525a25d57Steve Block return $fullPath; 424cad810f21b803229eb11403f9209855525a25d57Steve Block} 425cad810f21b803229eb11403f9209855525a25d57Steve Block 426cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Blocksub canonicalizePath($) 427cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block{ 428cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block my ($file) = @_; 429cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 430cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # Remove extra slashes and '.' directories in path 431cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block $file = File::Spec->canonpath($file); 432cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 433cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # Remove '..' directories in path 434cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block my @dirs = (); 435cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block foreach my $dir (File::Spec->splitdir($file)) { 436cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block if ($dir eq '..' && $#dirs >= 0 && $dirs[$#dirs] ne '..') { 437cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block pop(@dirs); 438cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } else { 439cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block push(@dirs, $dir); 440cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } 441cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } 442cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block return ($#dirs >= 0) ? File::Spec->catdir(@dirs) : "."; 443cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block} 444cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 445643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub removeEOL($) 446643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 447643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my ($line) = @_; 448e14391e94c850b8bd03680c23b38978db68687a8John Reck return "" unless $line; 449643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 450643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $line =~ s/[\r\n]+$//g; 451643ca7872b450ea4efacab6188849e5aac2ba161Steve Block return $line; 452643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 453643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 4542daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdochsub parseFirstEOL($) 4552daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch{ 4562daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my ($fileHandle) = @_; 4572daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch 4582daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch # Make input record separator the new-line character to simplify regex matching below. 4592daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my $savedInputRecordSeparator = $INPUT_RECORD_SEPARATOR; 4602daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch $INPUT_RECORD_SEPARATOR = "\n"; 4612daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my $firstLine = <$fileHandle>; 4622daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch $INPUT_RECORD_SEPARATOR = $savedInputRecordSeparator; 4632daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch 4642daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch return unless defined($firstLine); 4652daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch 4662daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my $eol; 4672daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch if ($firstLine =~ /\r\n/) { 4682daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch $eol = "\r\n"; 4692daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch } elsif ($firstLine =~ /\r/) { 4702daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch $eol = "\r"; 4712daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch } elsif ($firstLine =~ /\n/) { 4722daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch $eol = "\n"; 4732daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch } 4742daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch return $eol; 4752daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch} 4762daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch 4772daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdochsub firstEOLInFile($) 4782daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch{ 4792daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my ($file) = @_; 4802daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my $eol; 4812daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch if (open(FILE, $file)) { 4822daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch $eol = parseFirstEOL(*FILE); 4832daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch close(FILE); 4842daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch } 4852daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch return $eol; 4862daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch} 4872daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch 488cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Blocksub svnStatus($) 489cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block{ 490cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block my ($fullPath) = @_; 491cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block my $svnStatus; 492cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block open SVN, "svn status --non-interactive --non-recursive '$fullPath' |" or die; 493cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block if (-d $fullPath) { 494cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # When running "svn stat" on a directory, we can't assume that only one 495cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # status will be returned (since any files with a status below the 496cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # directory will be returned), and we can't assume that the directory will 497cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # be first (since any files with unknown status will be listed first). 498cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block my $normalizedFullPath = File::Spec->catdir(File::Spec->splitdir($fullPath)); 499cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block while (<SVN>) { 500cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # Input may use a different EOL sequence than $/, so avoid chomp. 501cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block $_ = removeEOL($_); 502cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block my $normalizedStatPath = File::Spec->catdir(File::Spec->splitdir(substr($_, 7))); 503cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block if ($normalizedFullPath eq $normalizedStatPath) { 504cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block $svnStatus = "$_\n"; 505cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block last; 506cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } 507cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } 508cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # Read the rest of the svn command output to avoid a broken pipe warning. 509cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block local $/ = undef; 510cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block <SVN>; 511cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } 512cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block else { 513cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block # Files will have only one status returned. 514cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block $svnStatus = removeEOL(<SVN>) . "\n"; 515cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } 516cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block close SVN; 517cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block return $svnStatus; 518cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block} 519cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 52021939df44de1705786c545cd1bf519d47250322dBen Murdoch# Return whether the given file mode is executable in the source control 52121939df44de1705786c545cd1bf519d47250322dBen Murdoch# sense. We make this determination based on whether the executable bit 52221939df44de1705786c545cd1bf519d47250322dBen Murdoch# is set for "others" rather than the stronger condition that it be set 52321939df44de1705786c545cd1bf519d47250322dBen Murdoch# for the user, group, and others. This is sufficient for distinguishing 52421939df44de1705786c545cd1bf519d47250322dBen Murdoch# the default behavior in Git and SVN. 52521939df44de1705786c545cd1bf519d47250322dBen Murdoch# 52621939df44de1705786c545cd1bf519d47250322dBen Murdoch# Args: 52721939df44de1705786c545cd1bf519d47250322dBen Murdoch# $fileMode: A number or string representing a file mode in octal notation. 52821939df44de1705786c545cd1bf519d47250322dBen Murdochsub isExecutable($) 529cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block{ 53021939df44de1705786c545cd1bf519d47250322dBen Murdoch my $fileMode = shift; 531d0825bca7fe65beaee391d30da42e937db621564Steve Block 53221939df44de1705786c545cd1bf519d47250322dBen Murdoch return $fileMode % 2; 533cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block} 534cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 53521939df44de1705786c545cd1bf519d47250322dBen Murdoch# Parse the next Git diff header from the given file handle, and advance 53621939df44de1705786c545cd1bf519d47250322dBen Murdoch# the handle so the last line read is the first line after the header. 537d0825bca7fe65beaee391d30da42e937db621564Steve Block# 53821939df44de1705786c545cd1bf519d47250322dBen Murdoch# This subroutine dies if given leading junk. 539d0825bca7fe65beaee391d30da42e937db621564Steve Block# 540d0825bca7fe65beaee391d30da42e937db621564Steve Block# Args: 54121939df44de1705786c545cd1bf519d47250322dBen Murdoch# $fileHandle: advanced so the last line read from the handle is the first 54221939df44de1705786c545cd1bf519d47250322dBen Murdoch# line of the header to parse. This should be a line 54321939df44de1705786c545cd1bf519d47250322dBen Murdoch# beginning with "diff --git". 544d0825bca7fe65beaee391d30da42e937db621564Steve Block# $line: the line last read from $fileHandle 545d0825bca7fe65beaee391d30da42e937db621564Steve Block# 546d0825bca7fe65beaee391d30da42e937db621564Steve Block# Returns ($headerHashRef, $lastReadLine): 54721939df44de1705786c545cd1bf519d47250322dBen Murdoch# $headerHashRef: a hash reference representing a diff header, as follows-- 5486c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# copiedFromPath: the path from which the file was copied or moved if 5496c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# the diff is a copy or move. 55021939df44de1705786c545cd1bf519d47250322dBen Murdoch# executableBitDelta: the value 1 or -1 if the executable bit was added or 55121939df44de1705786c545cd1bf519d47250322dBen Murdoch# removed, respectively. New and deleted files have 55221939df44de1705786c545cd1bf519d47250322dBen Murdoch# this value only if the file is executable, in which 55321939df44de1705786c545cd1bf519d47250322dBen Murdoch# case the value is 1 and -1, respectively. 55421939df44de1705786c545cd1bf519d47250322dBen Murdoch# indexPath: the path of the target file. 55521939df44de1705786c545cd1bf519d47250322dBen Murdoch# isBinary: the value 1 if the diff is for a binary file. 5566c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# isDeletion: the value 1 if the diff is a file deletion. 5576c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# isCopyWithChanges: the value 1 if the file was copied or moved and 5586c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# the target file was changed in some way after being 5596c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# copied or moved (e.g. if its contents or executable 5606c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# bit were changed). 5616c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# isNew: the value 1 if the diff is for a new file. 5626c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# shouldDeleteSource: the value 1 if the file was copied or moved and 5636c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# the source file was deleted -- i.e. if the copy 5646c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# was actually a move. 56521939df44de1705786c545cd1bf519d47250322dBen Murdoch# svnConvertedText: the header text with some lines converted to SVN 56621939df44de1705786c545cd1bf519d47250322dBen Murdoch# format. Git-specific lines are preserved. 56721939df44de1705786c545cd1bf519d47250322dBen Murdoch# $lastReadLine: the line last read from $fileHandle. 56821939df44de1705786c545cd1bf519d47250322dBen Murdochsub parseGitDiffHeader($$) 569d0825bca7fe65beaee391d30da42e937db621564Steve Block{ 570d0825bca7fe65beaee391d30da42e937db621564Steve Block my ($fileHandle, $line) = @_; 571d0825bca7fe65beaee391d30da42e937db621564Steve Block 57221939df44de1705786c545cd1bf519d47250322dBen Murdoch $_ = $line; 573d0825bca7fe65beaee391d30da42e937db621564Steve Block 574d0825bca7fe65beaee391d30da42e937db621564Steve Block my $indexPath; 5756c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (/$gitDiffStartRegEx/) { 57621939df44de1705786c545cd1bf519d47250322dBen Murdoch # The first and second paths can differ in the case of copies 57721939df44de1705786c545cd1bf519d47250322dBen Murdoch # and renames. We use the second file path because it is the 57821939df44de1705786c545cd1bf519d47250322dBen Murdoch # destination path. 579cad810f21b803229eb11403f9209855525a25d57Steve Block $indexPath = adjustPathForRecentRenamings($4); 58021939df44de1705786c545cd1bf519d47250322dBen Murdoch # Use $POSTMATCH to preserve the end-of-line character. 58121939df44de1705786c545cd1bf519d47250322dBen Murdoch $_ = "Index: $indexPath$POSTMATCH"; # Convert to SVN format. 582d0825bca7fe65beaee391d30da42e937db621564Steve Block } else { 58321939df44de1705786c545cd1bf519d47250322dBen Murdoch die("Could not parse leading \"diff --git\" line: \"$line\"."); 584d0825bca7fe65beaee391d30da42e937db621564Steve Block } 585d0825bca7fe65beaee391d30da42e937db621564Steve Block 58621939df44de1705786c545cd1bf519d47250322dBen Murdoch my $copiedFromPath; 58721939df44de1705786c545cd1bf519d47250322dBen Murdoch my $foundHeaderEnding; 58821939df44de1705786c545cd1bf519d47250322dBen Murdoch my $isBinary; 5896c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $isDeletion; 5906c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $isNew; 59121939df44de1705786c545cd1bf519d47250322dBen Murdoch my $newExecutableBit = 0; 59221939df44de1705786c545cd1bf519d47250322dBen Murdoch my $oldExecutableBit = 0; 5936c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $shouldDeleteSource = 0; 5946c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $similarityIndex = 0; 59521939df44de1705786c545cd1bf519d47250322dBen Murdoch my $svnConvertedText; 59621939df44de1705786c545cd1bf519d47250322dBen Murdoch while (1) { 59721939df44de1705786c545cd1bf519d47250322dBen Murdoch # Temporarily strip off any end-of-line characters to simplify 59821939df44de1705786c545cd1bf519d47250322dBen Murdoch # regex matching below. 59921939df44de1705786c545cd1bf519d47250322dBen Murdoch s/([\n\r]+)$//; 60021939df44de1705786c545cd1bf519d47250322dBen Murdoch my $eol = $1; 60121939df44de1705786c545cd1bf519d47250322dBen Murdoch 6026c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (/^(deleted file|old) mode (\d+)/) { 60321939df44de1705786c545cd1bf519d47250322dBen Murdoch $oldExecutableBit = (isExecutable($2) ? 1 : 0); 6046c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $isDeletion = 1 if $1 eq "deleted file"; 6056c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } elsif (/^new( file)? mode (\d+)/) { 60621939df44de1705786c545cd1bf519d47250322dBen Murdoch $newExecutableBit = (isExecutable($2) ? 1 : 0); 6076c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $isNew = 1 if $1; 6086c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } elsif (/^similarity index (\d+)%/) { 6096c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $similarityIndex = $1; 6106c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } elsif (/^copy from (\S+)/) { 6116c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $copiedFromPath = $1; 6126c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } elsif (/^rename from (\S+)/) { 6136c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # FIXME: Record this as a move rather than as a copy-and-delete. 6146c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # This will simplify adding rename support to svn-unapply. 6156c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Otherwise, the hash for a deletion would have to know 6166c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # everything about the file being deleted in order to 6176c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # support undoing itself. Recording as a move will also 6186c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # permit us to use "svn move" and "git move". 6196c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $copiedFromPath = $1; 6206c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $shouldDeleteSource = 1; 62121939df44de1705786c545cd1bf519d47250322dBen Murdoch } elsif (/^--- \S+/) { 62221939df44de1705786c545cd1bf519d47250322dBen Murdoch $_ = "--- $indexPath"; # Convert to SVN format. 62321939df44de1705786c545cd1bf519d47250322dBen Murdoch } elsif (/^\+\+\+ \S+/) { 62421939df44de1705786c545cd1bf519d47250322dBen Murdoch $_ = "+++ $indexPath"; # Convert to SVN format. 62521939df44de1705786c545cd1bf519d47250322dBen Murdoch $foundHeaderEnding = 1; 6266c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } elsif (/^GIT binary patch$/ ) { 6276c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $isBinary = 1; 6286c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $foundHeaderEnding = 1; 62921939df44de1705786c545cd1bf519d47250322dBen Murdoch # The "git diff" command includes a line of the form "Binary files 63021939df44de1705786c545cd1bf519d47250322dBen Murdoch # <path1> and <path2> differ" if the --binary flag is not used. 63121939df44de1705786c545cd1bf519d47250322dBen Murdoch } elsif (/^Binary files / ) { 63221939df44de1705786c545cd1bf519d47250322dBen Murdoch die("Error: the Git diff contains a binary file without the binary data in ". 63321939df44de1705786c545cd1bf519d47250322dBen Murdoch "line: \"$_\". Be sure to use the --binary flag when invoking \"git diff\" ". 63421939df44de1705786c545cd1bf519d47250322dBen Murdoch "with diffs containing binary files."); 63521939df44de1705786c545cd1bf519d47250322dBen Murdoch } 63621939df44de1705786c545cd1bf519d47250322dBen Murdoch 63721939df44de1705786c545cd1bf519d47250322dBen Murdoch $svnConvertedText .= "$_$eol"; # Also restore end-of-line characters. 63821939df44de1705786c545cd1bf519d47250322dBen Murdoch 63921939df44de1705786c545cd1bf519d47250322dBen Murdoch $_ = <$fileHandle>; # Not defined if end-of-file reached. 64021939df44de1705786c545cd1bf519d47250322dBen Murdoch 6416c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen last if (!defined($_) || /$gitDiffStartRegEx/ || $foundHeaderEnding); 64221939df44de1705786c545cd1bf519d47250322dBen Murdoch } 64321939df44de1705786c545cd1bf519d47250322dBen Murdoch 64421939df44de1705786c545cd1bf519d47250322dBen Murdoch my $executableBitDelta = $newExecutableBit - $oldExecutableBit; 64521939df44de1705786c545cd1bf519d47250322dBen Murdoch 646d0825bca7fe65beaee391d30da42e937db621564Steve Block my %header; 647d0825bca7fe65beaee391d30da42e937db621564Steve Block 6486c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $header{copiedFromPath} = $copiedFromPath if $copiedFromPath; 64921939df44de1705786c545cd1bf519d47250322dBen Murdoch $header{executableBitDelta} = $executableBitDelta if $executableBitDelta; 65021939df44de1705786c545cd1bf519d47250322dBen Murdoch $header{indexPath} = $indexPath; 65121939df44de1705786c545cd1bf519d47250322dBen Murdoch $header{isBinary} = $isBinary if $isBinary; 6526c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $header{isCopyWithChanges} = 1 if ($copiedFromPath && ($similarityIndex != 100 || $executableBitDelta)); 6536c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $header{isDeletion} = $isDeletion if $isDeletion; 6546c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $header{isNew} = $isNew if $isNew; 6556c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $header{shouldDeleteSource} = $shouldDeleteSource if $shouldDeleteSource; 65621939df44de1705786c545cd1bf519d47250322dBen Murdoch $header{svnConvertedText} = $svnConvertedText; 65721939df44de1705786c545cd1bf519d47250322dBen Murdoch 65821939df44de1705786c545cd1bf519d47250322dBen Murdoch return (\%header, $_); 65921939df44de1705786c545cd1bf519d47250322dBen Murdoch} 66021939df44de1705786c545cd1bf519d47250322dBen Murdoch 66121939df44de1705786c545cd1bf519d47250322dBen Murdoch# Parse the next SVN diff header from the given file handle, and advance 66221939df44de1705786c545cd1bf519d47250322dBen Murdoch# the handle so the last line read is the first line after the header. 66321939df44de1705786c545cd1bf519d47250322dBen Murdoch# 66421939df44de1705786c545cd1bf519d47250322dBen Murdoch# This subroutine dies if given leading junk or if it could not detect 66521939df44de1705786c545cd1bf519d47250322dBen Murdoch# the end of the header block. 66621939df44de1705786c545cd1bf519d47250322dBen Murdoch# 66721939df44de1705786c545cd1bf519d47250322dBen Murdoch# Args: 66821939df44de1705786c545cd1bf519d47250322dBen Murdoch# $fileHandle: advanced so the last line read from the handle is the first 66921939df44de1705786c545cd1bf519d47250322dBen Murdoch# line of the header to parse. This should be a line 67021939df44de1705786c545cd1bf519d47250322dBen Murdoch# beginning with "Index:". 67121939df44de1705786c545cd1bf519d47250322dBen Murdoch# $line: the line last read from $fileHandle 67221939df44de1705786c545cd1bf519d47250322dBen Murdoch# 67321939df44de1705786c545cd1bf519d47250322dBen Murdoch# Returns ($headerHashRef, $lastReadLine): 67421939df44de1705786c545cd1bf519d47250322dBen Murdoch# $headerHashRef: a hash reference representing a diff header, as follows-- 67521939df44de1705786c545cd1bf519d47250322dBen Murdoch# copiedFromPath: the path from which the file was copied if the diff 67621939df44de1705786c545cd1bf519d47250322dBen Murdoch# is a copy. 67721939df44de1705786c545cd1bf519d47250322dBen Murdoch# indexPath: the path of the target file, which is the path found in 67821939df44de1705786c545cd1bf519d47250322dBen Murdoch# the "Index:" line. 67921939df44de1705786c545cd1bf519d47250322dBen Murdoch# isBinary: the value 1 if the diff is for a binary file. 6806c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# isNew: the value 1 if the diff is for a new file. 68121939df44de1705786c545cd1bf519d47250322dBen Murdoch# sourceRevision: the revision number of the source, if it exists. This 68221939df44de1705786c545cd1bf519d47250322dBen Murdoch# is the same as the revision number the file was copied 68321939df44de1705786c545cd1bf519d47250322dBen Murdoch# from, in the case of a file copy. 68421939df44de1705786c545cd1bf519d47250322dBen Murdoch# svnConvertedText: the header text converted to a header with the paths 68521939df44de1705786c545cd1bf519d47250322dBen Murdoch# in some lines corrected. 68621939df44de1705786c545cd1bf519d47250322dBen Murdoch# $lastReadLine: the line last read from $fileHandle. 68721939df44de1705786c545cd1bf519d47250322dBen Murdochsub parseSvnDiffHeader($$) 68821939df44de1705786c545cd1bf519d47250322dBen Murdoch{ 68921939df44de1705786c545cd1bf519d47250322dBen Murdoch my ($fileHandle, $line) = @_; 69021939df44de1705786c545cd1bf519d47250322dBen Murdoch 69121939df44de1705786c545cd1bf519d47250322dBen Murdoch $_ = $line; 69221939df44de1705786c545cd1bf519d47250322dBen Murdoch 6936c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $indexPath; 6946c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (/$svnDiffStartRegEx/) { 695cad810f21b803229eb11403f9209855525a25d57Steve Block $indexPath = adjustPathForRecentRenamings($1); 6966c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } else { 69721939df44de1705786c545cd1bf519d47250322dBen Murdoch die("First line of SVN diff does not begin with \"Index \": \"$_\""); 69821939df44de1705786c545cd1bf519d47250322dBen Murdoch } 69921939df44de1705786c545cd1bf519d47250322dBen Murdoch 70021939df44de1705786c545cd1bf519d47250322dBen Murdoch my $copiedFromPath; 701d0825bca7fe65beaee391d30da42e937db621564Steve Block my $foundHeaderEnding; 70221939df44de1705786c545cd1bf519d47250322dBen Murdoch my $isBinary; 7036c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $isNew; 704d0825bca7fe65beaee391d30da42e937db621564Steve Block my $sourceRevision; 70521939df44de1705786c545cd1bf519d47250322dBen Murdoch my $svnConvertedText; 70621939df44de1705786c545cd1bf519d47250322dBen Murdoch while (1) { 707d0825bca7fe65beaee391d30da42e937db621564Steve Block # Temporarily strip off any end-of-line characters to simplify 708d0825bca7fe65beaee391d30da42e937db621564Steve Block # regex matching below. 709d0825bca7fe65beaee391d30da42e937db621564Steve Block s/([\n\r]+)$//; 710d0825bca7fe65beaee391d30da42e937db621564Steve Block my $eol = $1; 711d0825bca7fe65beaee391d30da42e937db621564Steve Block 712d0825bca7fe65beaee391d30da42e937db621564Steve Block # Fix paths on ""---" and "+++" lines to match the leading 713d0825bca7fe65beaee391d30da42e937db621564Steve Block # index line. 7146c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (s/^--- \S+/--- $indexPath/) { 715d0825bca7fe65beaee391d30da42e937db621564Steve Block # --- 716d0825bca7fe65beaee391d30da42e937db621564Steve Block if (/^--- .+\(revision (\d+)\)/) { 71721939df44de1705786c545cd1bf519d47250322dBen Murdoch $sourceRevision = $1; 7186c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $isNew = 1 if !$sourceRevision; # if revision 0. 719d0825bca7fe65beaee391d30da42e937db621564Steve Block if (/\(from (\S+):(\d+)\)$/) { 720d0825bca7fe65beaee391d30da42e937db621564Steve Block # The "from" clause is created by svn-create-patch, in 721d0825bca7fe65beaee391d30da42e937db621564Steve Block # which case there is always also a "revision" clause. 72221939df44de1705786c545cd1bf519d47250322dBen Murdoch $copiedFromPath = $1; 723d0825bca7fe65beaee391d30da42e937db621564Steve Block die("Revision number \"$2\" in \"from\" clause does not match " . 724d0825bca7fe65beaee391d30da42e937db621564Steve Block "source revision number \"$sourceRevision\".") if ($2 != $sourceRevision); 725d0825bca7fe65beaee391d30da42e937db621564Steve Block } 726d0825bca7fe65beaee391d30da42e937db621564Steve Block } 727d0825bca7fe65beaee391d30da42e937db621564Steve Block } elsif (s/^\+\+\+ \S+/+++ $indexPath/) { 728d0825bca7fe65beaee391d30da42e937db621564Steve Block $foundHeaderEnding = 1; 72921939df44de1705786c545cd1bf519d47250322dBen Murdoch } elsif (/^Cannot display: file marked as a binary type.$/) { 73021939df44de1705786c545cd1bf519d47250322dBen Murdoch $isBinary = 1; 73121939df44de1705786c545cd1bf519d47250322dBen Murdoch $foundHeaderEnding = 1; 732d0825bca7fe65beaee391d30da42e937db621564Steve Block } 733d0825bca7fe65beaee391d30da42e937db621564Steve Block 734d0825bca7fe65beaee391d30da42e937db621564Steve Block $svnConvertedText .= "$_$eol"; # Also restore end-of-line characters. 73521939df44de1705786c545cd1bf519d47250322dBen Murdoch 73621939df44de1705786c545cd1bf519d47250322dBen Murdoch $_ = <$fileHandle>; # Not defined if end-of-file reached. 73721939df44de1705786c545cd1bf519d47250322dBen Murdoch 7386c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen last if (!defined($_) || /$svnDiffStartRegEx/ || $foundHeaderEnding); 73921939df44de1705786c545cd1bf519d47250322dBen Murdoch } 740d0825bca7fe65beaee391d30da42e937db621564Steve Block 741d0825bca7fe65beaee391d30da42e937db621564Steve Block if (!$foundHeaderEnding) { 742d0825bca7fe65beaee391d30da42e937db621564Steve Block die("Did not find end of header block corresponding to index path \"$indexPath\"."); 743d0825bca7fe65beaee391d30da42e937db621564Steve Block } 744d0825bca7fe65beaee391d30da42e937db621564Steve Block 74521939df44de1705786c545cd1bf519d47250322dBen Murdoch my %header; 74621939df44de1705786c545cd1bf519d47250322dBen Murdoch 74721939df44de1705786c545cd1bf519d47250322dBen Murdoch $header{copiedFromPath} = $copiedFromPath if $copiedFromPath; 748d0825bca7fe65beaee391d30da42e937db621564Steve Block $header{indexPath} = $indexPath; 74921939df44de1705786c545cd1bf519d47250322dBen Murdoch $header{isBinary} = $isBinary if $isBinary; 7506c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $header{isNew} = $isNew if $isNew; 75121939df44de1705786c545cd1bf519d47250322dBen Murdoch $header{sourceRevision} = $sourceRevision if $sourceRevision; 752d0825bca7fe65beaee391d30da42e937db621564Steve Block $header{svnConvertedText} = $svnConvertedText; 753d0825bca7fe65beaee391d30da42e937db621564Steve Block 75421939df44de1705786c545cd1bf519d47250322dBen Murdoch return (\%header, $_); 755d0825bca7fe65beaee391d30da42e937db621564Steve Block} 756d0825bca7fe65beaee391d30da42e937db621564Steve Block 75721939df44de1705786c545cd1bf519d47250322dBen Murdoch# Parse the next diff header from the given file handle, and advance 75821939df44de1705786c545cd1bf519d47250322dBen Murdoch# the handle so the last line read is the first line after the header. 75921939df44de1705786c545cd1bf519d47250322dBen Murdoch# 76021939df44de1705786c545cd1bf519d47250322dBen Murdoch# This subroutine dies if given leading junk or if it could not detect 76121939df44de1705786c545cd1bf519d47250322dBen Murdoch# the end of the header block. 76221939df44de1705786c545cd1bf519d47250322dBen Murdoch# 76321939df44de1705786c545cd1bf519d47250322dBen Murdoch# Args: 76421939df44de1705786c545cd1bf519d47250322dBen Murdoch# $fileHandle: advanced so the last line read from the handle is the first 76521939df44de1705786c545cd1bf519d47250322dBen Murdoch# line of the header to parse. For SVN-formatted diffs, this 76621939df44de1705786c545cd1bf519d47250322dBen Murdoch# is a line beginning with "Index:". For Git, this is a line 76721939df44de1705786c545cd1bf519d47250322dBen Murdoch# beginning with "diff --git". 76821939df44de1705786c545cd1bf519d47250322dBen Murdoch# $line: the line last read from $fileHandle 76921939df44de1705786c545cd1bf519d47250322dBen Murdoch# 77021939df44de1705786c545cd1bf519d47250322dBen Murdoch# Returns ($headerHashRef, $lastReadLine): 77121939df44de1705786c545cd1bf519d47250322dBen Murdoch# $headerHashRef: a hash reference representing a diff header 77221939df44de1705786c545cd1bf519d47250322dBen Murdoch# copiedFromPath: the path from which the file was copied if the diff 77321939df44de1705786c545cd1bf519d47250322dBen Murdoch# is a copy. 77421939df44de1705786c545cd1bf519d47250322dBen Murdoch# executableBitDelta: the value 1 or -1 if the executable bit was added or 77521939df44de1705786c545cd1bf519d47250322dBen Murdoch# removed, respectively. New and deleted files have 77621939df44de1705786c545cd1bf519d47250322dBen Murdoch# this value only if the file is executable, in which 77721939df44de1705786c545cd1bf519d47250322dBen Murdoch# case the value is 1 and -1, respectively. 77821939df44de1705786c545cd1bf519d47250322dBen Murdoch# indexPath: the path of the target file. 77921939df44de1705786c545cd1bf519d47250322dBen Murdoch# isBinary: the value 1 if the diff is for a binary file. 78021939df44de1705786c545cd1bf519d47250322dBen Murdoch# isGit: the value 1 if the diff is Git-formatted. 78121939df44de1705786c545cd1bf519d47250322dBen Murdoch# isSvn: the value 1 if the diff is SVN-formatted. 78221939df44de1705786c545cd1bf519d47250322dBen Murdoch# sourceRevision: the revision number of the source, if it exists. This 78321939df44de1705786c545cd1bf519d47250322dBen Murdoch# is the same as the revision number the file was copied 78421939df44de1705786c545cd1bf519d47250322dBen Murdoch# from, in the case of a file copy. 78521939df44de1705786c545cd1bf519d47250322dBen Murdoch# svnConvertedText: the header text with some lines converted to SVN 78621939df44de1705786c545cd1bf519d47250322dBen Murdoch# format. Git-specific lines are preserved. 78721939df44de1705786c545cd1bf519d47250322dBen Murdoch# $lastReadLine: the line last read from $fileHandle. 78821939df44de1705786c545cd1bf519d47250322dBen Murdochsub parseDiffHeader($$) 78921939df44de1705786c545cd1bf519d47250322dBen Murdoch{ 79021939df44de1705786c545cd1bf519d47250322dBen Murdoch my ($fileHandle, $line) = @_; 79121939df44de1705786c545cd1bf519d47250322dBen Murdoch 79221939df44de1705786c545cd1bf519d47250322dBen Murdoch my $header; # This is a hash ref. 79321939df44de1705786c545cd1bf519d47250322dBen Murdoch my $isGit; 79421939df44de1705786c545cd1bf519d47250322dBen Murdoch my $isSvn; 79521939df44de1705786c545cd1bf519d47250322dBen Murdoch my $lastReadLine; 79621939df44de1705786c545cd1bf519d47250322dBen Murdoch 7976c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if ($line =~ $svnDiffStartRegEx) { 79821939df44de1705786c545cd1bf519d47250322dBen Murdoch $isSvn = 1; 79921939df44de1705786c545cd1bf519d47250322dBen Murdoch ($header, $lastReadLine) = parseSvnDiffHeader($fileHandle, $line); 8006c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } elsif ($line =~ $gitDiffStartRegEx) { 80121939df44de1705786c545cd1bf519d47250322dBen Murdoch $isGit = 1; 80221939df44de1705786c545cd1bf519d47250322dBen Murdoch ($header, $lastReadLine) = parseGitDiffHeader($fileHandle, $line); 80321939df44de1705786c545cd1bf519d47250322dBen Murdoch } else { 80421939df44de1705786c545cd1bf519d47250322dBen Murdoch die("First line of diff does not begin with \"Index:\" or \"diff --git\": \"$line\""); 80521939df44de1705786c545cd1bf519d47250322dBen Murdoch } 80621939df44de1705786c545cd1bf519d47250322dBen Murdoch 80721939df44de1705786c545cd1bf519d47250322dBen Murdoch $header->{isGit} = $isGit if $isGit; 80821939df44de1705786c545cd1bf519d47250322dBen Murdoch $header->{isSvn} = $isSvn if $isSvn; 80921939df44de1705786c545cd1bf519d47250322dBen Murdoch 81021939df44de1705786c545cd1bf519d47250322dBen Murdoch return ($header, $lastReadLine); 81121939df44de1705786c545cd1bf519d47250322dBen Murdoch} 81221939df44de1705786c545cd1bf519d47250322dBen Murdoch 81321939df44de1705786c545cd1bf519d47250322dBen Murdoch# FIXME: The %diffHash "object" should not have an svnConvertedText property. 81421939df44de1705786c545cd1bf519d47250322dBen Murdoch# Instead, the hash object should store its information in a 81521939df44de1705786c545cd1bf519d47250322dBen Murdoch# structured way as properties. This should be done in a way so 81621939df44de1705786c545cd1bf519d47250322dBen Murdoch# that, if necessary, the text of an SVN or Git patch can be 81721939df44de1705786c545cd1bf519d47250322dBen Murdoch# reconstructed from the information in those hash properties. 81821939df44de1705786c545cd1bf519d47250322dBen Murdoch# 81921939df44de1705786c545cd1bf519d47250322dBen Murdoch# A %diffHash is a hash representing a source control diff of a single 82021939df44de1705786c545cd1bf519d47250322dBen Murdoch# file operation (e.g. a file modification, copy, or delete). 82121939df44de1705786c545cd1bf519d47250322dBen Murdoch# 82221939df44de1705786c545cd1bf519d47250322dBen Murdoch# These hashes appear, for example, in the parseDiff(), parsePatch(), 82321939df44de1705786c545cd1bf519d47250322dBen Murdoch# and prepareParsedPatch() subroutines of this package. 82421939df44de1705786c545cd1bf519d47250322dBen Murdoch# 82521939df44de1705786c545cd1bf519d47250322dBen Murdoch# The corresponding values are-- 82621939df44de1705786c545cd1bf519d47250322dBen Murdoch# 82721939df44de1705786c545cd1bf519d47250322dBen Murdoch# copiedFromPath: the path from which the file was copied if the diff 82821939df44de1705786c545cd1bf519d47250322dBen Murdoch# is a copy. 829e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# executableBitDelta: the value 1 or -1 if the executable bit was added or 830e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# removed from the target file, respectively. 83121939df44de1705786c545cd1bf519d47250322dBen Murdoch# indexPath: the path of the target file. For SVN-formatted diffs, 83221939df44de1705786c545cd1bf519d47250322dBen Murdoch# this is the same as the path in the "Index:" line. 83321939df44de1705786c545cd1bf519d47250322dBen Murdoch# isBinary: the value 1 if the diff is for a binary file. 8346c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# isDeletion: the value 1 if the diff is known from the header to be a deletion. 83521939df44de1705786c545cd1bf519d47250322dBen Murdoch# isGit: the value 1 if the diff is Git-formatted. 8366c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# isNew: the value 1 if the dif is known from the header to be a new file. 83721939df44de1705786c545cd1bf519d47250322dBen Murdoch# isSvn: the value 1 if the diff is SVN-formatted. 83821939df44de1705786c545cd1bf519d47250322dBen Murdoch# sourceRevision: the revision number of the source, if it exists. This 83921939df44de1705786c545cd1bf519d47250322dBen Murdoch# is the same as the revision number the file was copied 84021939df44de1705786c545cd1bf519d47250322dBen Murdoch# from, in the case of a file copy. 84121939df44de1705786c545cd1bf519d47250322dBen Murdoch# svnConvertedText: the diff with some lines converted to SVN format. 84221939df44de1705786c545cd1bf519d47250322dBen Murdoch# Git-specific lines are preserved. 84321939df44de1705786c545cd1bf519d47250322dBen Murdoch 844d0825bca7fe65beaee391d30da42e937db621564Steve Block# Parse one diff from a patch file created by svn-create-patch, and 845d0825bca7fe65beaee391d30da42e937db621564Steve Block# advance the file handle so the last line read is the first line 846d0825bca7fe65beaee391d30da42e937db621564Steve Block# of the next header block. 847d0825bca7fe65beaee391d30da42e937db621564Steve Block# 848d0825bca7fe65beaee391d30da42e937db621564Steve Block# This subroutine preserves any leading junk encountered before the header. 849d0825bca7fe65beaee391d30da42e937db621564Steve Block# 850e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# Composition of an SVN diff 851e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# 852e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# There are three parts to an SVN diff: the header, the property change, and 853e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# the binary contents, in that order. Either the header or the property change 854e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# may be ommitted, but not both. If there are binary changes, then you always 855e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# have all three. 856e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke# 857d0825bca7fe65beaee391d30da42e937db621564Steve Block# Args: 858d0825bca7fe65beaee391d30da42e937db621564Steve Block# $fileHandle: a file handle advanced to the first line of the next 859d0825bca7fe65beaee391d30da42e937db621564Steve Block# header block. Leading junk is okay. 860d0825bca7fe65beaee391d30da42e937db621564Steve Block# $line: the line last read from $fileHandle. 8612daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# $optionsHashRef: a hash reference representing optional options to use 8622daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# when processing a diff. 8632daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# shouldNotUseIndexPathEOL: whether to use the line endings in the diff instead 8642daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# instead of the line endings in the target file; the 8652daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# value of 1 if svnConvertedText should use the line 8662daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# endings in the diff. 867d0825bca7fe65beaee391d30da42e937db621564Steve Block# 8686c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Returns ($diffHashRefs, $lastReadLine): 8696c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $diffHashRefs: A reference to an array of references to %diffHash hashes. 8706c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# See the %diffHash documentation above. 871d0825bca7fe65beaee391d30da42e937db621564Steve Block# $lastReadLine: the line last read from $fileHandle 8722daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdochsub parseDiff($$;$) 873d0825bca7fe65beaee391d30da42e937db621564Steve Block{ 8746c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # FIXME: Adjust this method so that it dies if the first line does not 8756c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # match the start of a diff. This will require a change to 8766c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # parsePatch() so that parsePatch() skips over leading junk. 8772daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my ($fileHandle, $line, $optionsHashRef) = @_; 878d0825bca7fe65beaee391d30da42e937db621564Steve Block 8796c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $headerStartRegEx = $svnDiffStartRegEx; # SVN-style header for the default 880d0825bca7fe65beaee391d30da42e937db621564Steve Block 881d0825bca7fe65beaee391d30da42e937db621564Steve Block my $headerHashRef; # Last header found, as returned by parseDiffHeader(). 882e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke my $svnPropertiesHashRef; # Last SVN properties diff found, as returned by parseSvnDiffProperties(). 883d0825bca7fe65beaee391d30da42e937db621564Steve Block my $svnText; 8842daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my $indexPathEOL; 885d0825bca7fe65beaee391d30da42e937db621564Steve Block while (defined($line)) { 8866c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (!$headerHashRef && ($line =~ $gitDiffStartRegEx)) { 887d0825bca7fe65beaee391d30da42e937db621564Steve Block # Then assume all diffs in the patch are Git-formatted. This 888d0825bca7fe65beaee391d30da42e937db621564Steve Block # block was made to be enterable at most once since we assume 889d0825bca7fe65beaee391d30da42e937db621564Steve Block # all diffs in the patch are formatted the same (SVN or Git). 8906c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $headerStartRegEx = $gitDiffStartRegEx; 891d0825bca7fe65beaee391d30da42e937db621564Steve Block } 892d0825bca7fe65beaee391d30da42e937db621564Steve Block 893e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke if ($line =~ $svnPropertiesStartRegEx) { 894e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke my $propertyPath = $1; 895e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke if ($svnPropertiesHashRef || $headerHashRef && ($propertyPath ne $headerHashRef->{indexPath})) { 896e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # This is the start of the second diff in the while loop, which happens to 897e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # be a property diff. If $svnPropertiesHasRef is defined, then this is the 898e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # second consecutive property diff, otherwise it's the start of a property 899e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # diff for a file that only has property changes. 900e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke last; 901e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke } 902e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke ($svnPropertiesHashRef, $line) = parseSvnDiffProperties($fileHandle, $line); 903e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke next; 904e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke } 905d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($line !~ $headerStartRegEx) { 906d0825bca7fe65beaee391d30da42e937db621564Steve Block # Then we are in the body of the diff. 9072daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch if ($indexPathEOL && $line !~ /$chunkRangeRegEx/) { 9082daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch # The chunk range is part of the body of the diff, but its line endings should't be 9092daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch # modified or patch(1) will complain. So, we only modify non-chunk range lines. 9102daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch $line =~ s/\r\n|\r|\n/$indexPathEOL/g; 9112daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch } 912d0825bca7fe65beaee391d30da42e937db621564Steve Block $svnText .= $line; 913d0825bca7fe65beaee391d30da42e937db621564Steve Block $line = <$fileHandle>; 914d0825bca7fe65beaee391d30da42e937db621564Steve Block next; 915d0825bca7fe65beaee391d30da42e937db621564Steve Block } # Otherwise, we found a diff header. 916d0825bca7fe65beaee391d30da42e937db621564Steve Block 917e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke if ($svnPropertiesHashRef || $headerHashRef) { 918e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # Then either we just processed an SVN property change or this 919e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # is the start of the second diff header of this while loop. 920d0825bca7fe65beaee391d30da42e937db621564Steve Block last; 921d0825bca7fe65beaee391d30da42e937db621564Steve Block } 922d0825bca7fe65beaee391d30da42e937db621564Steve Block 923d0825bca7fe65beaee391d30da42e937db621564Steve Block ($headerHashRef, $line) = parseDiffHeader($fileHandle, $line); 9242daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch if (!$optionsHashRef || !$optionsHashRef->{shouldNotUseIndexPathEOL}) { 9252daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch $indexPathEOL = firstEOLInFile($headerHashRef->{indexPath}) if !$headerHashRef->{isNew} && !$headerHashRef->{isBinary}; 9262daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch } 927d0825bca7fe65beaee391d30da42e937db621564Steve Block 928d0825bca7fe65beaee391d30da42e937db621564Steve Block $svnText .= $headerHashRef->{svnConvertedText}; 929d0825bca7fe65beaee391d30da42e937db621564Steve Block } 930d0825bca7fe65beaee391d30da42e937db621564Steve Block 9316c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my @diffHashRefs; 9326c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 9336c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if ($headerHashRef->{shouldDeleteSource}) { 9346c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my %deletionHash; 9356c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $deletionHash{indexPath} = $headerHashRef->{copiedFromPath}; 9366c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $deletionHash{isDeletion} = 1; 9376c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen push @diffHashRefs, \%deletionHash; 9386c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 9396c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if ($headerHashRef->{copiedFromPath}) { 9406c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my %copyHash; 9416c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $copyHash{copiedFromPath} = $headerHashRef->{copiedFromPath}; 9426c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $copyHash{indexPath} = $headerHashRef->{indexPath}; 9436c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $copyHash{sourceRevision} = $headerHashRef->{sourceRevision} if $headerHashRef->{sourceRevision}; 944e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke if ($headerHashRef->{isSvn}) { 945e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke $copyHash{executableBitDelta} = $svnPropertiesHashRef->{executableBitDelta} if $svnPropertiesHashRef->{executableBitDelta}; 946e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke } 9476c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen push @diffHashRefs, \%copyHash; 9486c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 949e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke 950e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # Note, the order of evaluation for the following if conditional has been explicitly chosen so that 951e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # it evaluates to false when there is no headerHashRef (e.g. a property change diff for a file that 952e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # only has property changes). 953e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke if ($headerHashRef->{isCopyWithChanges} || (%$headerHashRef && !$headerHashRef->{copiedFromPath})) { 9546c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Then add the usual file modification. 9556c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my %diffHash; 956e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # FIXME: We should expand this code to support other properties. In the future, 957e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # parseSvnDiffProperties may return a hash whose keys are the properties. 958e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke if ($headerHashRef->{isSvn}) { 959e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # SVN records the change to the executable bit in a separate property change diff 960e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # that follows the contents of the diff, except for binary diffs. For binary 961e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # diffs, the property change diff follows the diff header. 962e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke $diffHash{executableBitDelta} = $svnPropertiesHashRef->{executableBitDelta} if $svnPropertiesHashRef->{executableBitDelta}; 963e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke } elsif ($headerHashRef->{isGit}) { 964e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # Git records the change to the executable bit in the header of a diff. 965e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke $diffHash{executableBitDelta} = $headerHashRef->{executableBitDelta} if $headerHashRef->{executableBitDelta}; 966e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke } 9676c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $diffHash{indexPath} = $headerHashRef->{indexPath}; 9686c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $diffHash{isBinary} = $headerHashRef->{isBinary} if $headerHashRef->{isBinary}; 9696c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $diffHash{isDeletion} = $headerHashRef->{isDeletion} if $headerHashRef->{isDeletion}; 9706c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $diffHash{isGit} = $headerHashRef->{isGit} if $headerHashRef->{isGit}; 9716c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $diffHash{isNew} = $headerHashRef->{isNew} if $headerHashRef->{isNew}; 9726c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $diffHash{isSvn} = $headerHashRef->{isSvn} if $headerHashRef->{isSvn}; 9736c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (!$headerHashRef->{copiedFromPath}) { 9746c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # If the file was copied, then we have already incorporated the 9756c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # sourceRevision information into the change. 9766c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $diffHash{sourceRevision} = $headerHashRef->{sourceRevision} if $headerHashRef->{sourceRevision}; 9776c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 9786c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # FIXME: Remove the need for svnConvertedText. See the %diffHash 9796c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # code comments above for more information. 980e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # 981e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # Note, we may not always have SVN converted text since we intend 982e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # to deprecate it in the future. For example, a property change 983e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # diff for a file that only has property changes will not return 984e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # any SVN converted text. 985e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke $diffHash{svnConvertedText} = $svnText if $svnText; 9866c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen push @diffHashRefs, \%diffHash; 9876c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 9886c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 989e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke if (!%$headerHashRef && $svnPropertiesHashRef) { 990e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # A property change diff for a file that only has property changes. 991e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke my %propertyChangeHash; 992e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke $propertyChangeHash{executableBitDelta} = $svnPropertiesHashRef->{executableBitDelta} if $svnPropertiesHashRef->{executableBitDelta}; 993e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke $propertyChangeHash{indexPath} = $svnPropertiesHashRef->{propertyPath}; 994e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke $propertyChangeHash{isSvn} = 1; 995e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke push @diffHashRefs, \%propertyChangeHash; 996e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke } 997e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke 9986c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen return (\@diffHashRefs, $line); 9996c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen} 10006c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 1001e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# Parse an SVN property change diff from the given file handle, and advance 1002e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# the handle so the last line read is the first line after this diff. 1003e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# 1004e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# For the case of an SVN binary diff, the binary contents will follow the 1005e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# the property changes. 1006e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# 1007e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# This subroutine dies if the first line does not begin with "Property changes on" 1008e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# or if the separator line that follows this line is missing. 1009e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# 1010e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# Args: 1011e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# $fileHandle: advanced so the last line read from the handle is the first 1012e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# line of the footer to parse. This line begins with 1013e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# "Property changes on". 1014e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# $line: the line last read from $fileHandle. 1015e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# 1016e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# Returns ($propertyHashRef, $lastReadLine): 1017e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# $propertyHashRef: a hash reference representing an SVN diff footer. 1018e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# propertyPath: the path of the target file. 1019e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# executableBitDelta: the value 1 or -1 if the executable bit was added or 1020e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# removed from the target file, respectively. 1021e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block# $lastReadLine: the line last read from $fileHandle. 1022e78cbe89e6f337f2f1fe40315be88f742b547151Steve Blocksub parseSvnDiffProperties($$) 1023e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block{ 1024e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block my ($fileHandle, $line) = @_; 1025e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block 1026e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block $_ = $line; 1027e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block 1028e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block my %footer; 1029e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke if (/$svnPropertiesStartRegEx/) { 1030e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block $footer{propertyPath} = $1; 1031e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block } else { 1032e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block die("Failed to find start of SVN property change, \"Property changes on \": \"$_\""); 1033e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block } 1034e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block 1035e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # We advance $fileHandle two lines so that the next line that 1036e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # we process is $svnPropertyStartRegEx in a well-formed footer. 1037e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # A well-formed footer has the form: 1038e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # Property changes on: FileA 1039e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # ___________________________________________________________________ 1040e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # Added: svn:executable 1041e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # + * 1042e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block $_ = <$fileHandle>; # Not defined if end-of-file reached. 1043e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block my $separator = "_" x 67; 1044e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block if (defined($_) && /^$separator[\r\n]+$/) { 1045e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block $_ = <$fileHandle>; 1046e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block } else { 1047e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block die("Failed to find separator line: \"$_\"."); 1048e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block } 1049e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block 1050e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # FIXME: We should expand this to support other SVN properties 1051e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # (e.g. return a hash of property key-values that represents 1052e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # all properties). 1053e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # 1054e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # Notice, we keep processing until we hit end-of-file or some 1055e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # line that does not resemble $svnPropertyStartRegEx, such as 1056e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # the empty line that precedes the start of the binary contents 1057e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # of a patch, or the start of the next diff (e.g. "Index:"). 1058e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block my $propertyHashRef; 1059e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block while (defined($_) && /$svnPropertyStartRegEx/) { 1060e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block ($propertyHashRef, $_) = parseSvnProperty($fileHandle, $_); 1061e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block if ($propertyHashRef->{name} eq "svn:executable") { 1062e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # Notice, for SVN properties, propertyChangeDelta is always non-zero 1063e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block # because a property can only be added or removed. 1064e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block $footer{executableBitDelta} = $propertyHashRef->{propertyChangeDelta}; 1065e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block } 1066e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block } 1067e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block 1068e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block return(\%footer, $_); 1069e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block} 1070e78cbe89e6f337f2f1fe40315be88f742b547151Steve Block 10716c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Parse the next SVN property from the given file handle, and advance the handle so the last 10726c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# line read is the first line after the property. 10736c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# 10746c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# This subroutine dies if the first line is not a valid start of an SVN property, 10756c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# or the property is missing a value, or the property change type (e.g. "Added") 10766c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# does not correspond to the property value type (e.g. "+"). 10776c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# 10786c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Args: 10796c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $fileHandle: advanced so the last line read from the handle is the first 10806c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# line of the property to parse. This should be a line 10816c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# that matches $svnPropertyStartRegEx. 10826c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $line: the line last read from $fileHandle. 10836c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# 10846c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Returns ($propertyHashRef, $lastReadLine): 10856c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $propertyHashRef: a hash reference representing a SVN property. 10866c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# name: the name of the property. 10876c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# value: the last property value. For instance, suppose the property is "Modified". 10886c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Then it has both a '-' and '+' property value in that order. Therefore, 10896c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# the value of this key is the value of the '+' property by ordering (since 10906c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# it is the last value). 10916c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# propertyChangeDelta: the value 1 or -1 if the property was added or 10926c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# removed, respectively. 10936c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $lastReadLine: the line last read from $fileHandle. 10946c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsensub parseSvnProperty($$) 10956c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen{ 10966c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my ($fileHandle, $line) = @_; 10976c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 10986c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $_ = $line; 10996c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11006c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $propertyName; 11016c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $propertyChangeType; 11026c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (/$svnPropertyStartRegEx/) { 11036c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyChangeType = $1; 11046c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyName = $2; 11056c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } else { 11066c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen die("Failed to find SVN property: \"$_\"."); 11076c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 11086c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11096c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $_ = <$fileHandle>; # Not defined if end-of-file reached. 11106c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11116c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # The "svn diff" command neither inserts newline characters between property values 11126c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # nor between successive properties. 11136c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # 11146c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # FIXME: We do not support property values that contain tailing newline characters 11156c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # as it is difficult to disambiguate these trailing newlines from the empty 11166c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # line that precedes the contents of a binary patch. 11176c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $propertyValue; 11186c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $propertyValueType; 11196c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen while (defined($_) && /$svnPropertyValueStartRegEx/) { 11206c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Note, a '-' property may be followed by a '+' property in the case of a "Modified" 11216c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # or "Name" property. We only care about the ending value (i.e. the '+' property) 11226c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # in such circumstances. So, we take the property value for the property to be its 11236c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # last parsed property value. 11246c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # 11256c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # FIXME: We may want to consider strictly enforcing a '-', '+' property ordering or 11266c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # add error checking to prevent '+', '+', ..., '+' and other invalid combinations. 11276c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyValueType = $1; 11286c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen ($propertyValue, $_) = parseSvnPropertyValue($fileHandle, $_); 11296c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 11306c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11316c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (!$propertyValue) { 11326c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen die("Failed to find the property value for the SVN property \"$propertyName\": \"$_\"."); 11336c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 11346c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11356c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $propertyChangeDelta; 11365abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick if ($propertyValueType eq "+" || $propertyValueType eq "Merged") { 11376c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyChangeDelta = 1; 11385abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick } elsif ($propertyValueType eq "-" || $propertyValueType eq "Reverse-merged") { 11396c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyChangeDelta = -1; 11406c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } else { 11416c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen die("Not reached."); 11426c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 11436c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11446c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # We perform a simple validation that an "Added" or "Deleted" property 11456c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # change type corresponds with a "+" and "-" value type, respectively. 11466c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $expectedChangeDelta; 11476c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if ($propertyChangeType eq "Added") { 11486c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $expectedChangeDelta = 1; 11496c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } elsif ($propertyChangeType eq "Deleted") { 11506c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $expectedChangeDelta = -1; 11516c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 11526c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11536c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if ($expectedChangeDelta && $propertyChangeDelta != $expectedChangeDelta) { 11546c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen die("The final property value type found \"$propertyValueType\" does not " . 11556c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen "correspond to the property change type found \"$propertyChangeType\"."); 11566c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 11576c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11586c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my %propertyHash; 11596c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyHash{name} = $propertyName; 11606c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyHash{propertyChangeDelta} = $propertyChangeDelta; 11616c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyHash{value} = $propertyValue; 11626c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen return (\%propertyHash, $_); 11636c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen} 11646c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11656c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Parse the value of an SVN property from the given file handle, and advance 11666c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# the handle so the last line read is the first line after the property value. 11676c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# 11686c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# This subroutine dies if the first line is an invalid SVN property value line 11696c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# (i.e. a line that does not begin with " +" or " -"). 11706c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# 11716c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Args: 11726c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $fileHandle: advanced so the last line read from the handle is the first 11736c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# line of the property value to parse. This should be a line 11746c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# beginning with " +" or " -". 11756c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $line: the line last read from $fileHandle. 11766c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# 11776c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Returns ($propertyValue, $lastReadLine): 11786c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $propertyValue: the value of the property. 11796c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $lastReadLine: the line last read from $fileHandle. 11806c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsensub parseSvnPropertyValue($$) 11816c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen{ 11826c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my ($fileHandle, $line) = @_; 11836c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11846c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $_ = $line; 11856c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11866c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $propertyValue; 11876c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $eol; 11886c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (/$svnPropertyValueStartRegEx/) { 11896c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyValue = $2; # Does not include the end-of-line character(s). 11906c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $eol = $POSTMATCH; 11916c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } else { 11925abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick die("Failed to find property value beginning with '+', '-', 'Merged', or 'Reverse-merged': \"$_\"."); 11936c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 11946c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 11956c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen while (<$fileHandle>) { 1196e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block if (/^[\r\n]+$/ || /$svnPropertyValueStartRegEx/ || /$svnPropertyStartRegEx/) { 11976c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Note, we may encounter an empty line before the contents of a binary patch. 11986c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Also, we check for $svnPropertyValueStartRegEx because a '-' property may be 11996c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # followed by a '+' property in the case of a "Modified" or "Name" property. 12006c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # We check for $svnPropertyStartRegEx because it indicates the start of the 12016c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # next property to parse. 12026c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen last; 12036c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 1204d0825bca7fe65beaee391d30da42e937db621564Steve Block 12056c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Temporarily strip off any end-of-line characters. We add the end-of-line characters 12066c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # from the previously processed line to the start of this line so that the last line 12076c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # of the property value does not end in end-of-line characters. 12086c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen s/([\n\r]+)$//; 12096c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $propertyValue .= "$eol$_"; 12106c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $eol = $1; 12116c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 12126c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 12136c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen return ($propertyValue, $_); 1214d0825bca7fe65beaee391d30da42e937db621564Steve Block} 1215d0825bca7fe65beaee391d30da42e937db621564Steve Block 1216d0825bca7fe65beaee391d30da42e937db621564Steve Block# Parse a patch file created by svn-create-patch. 1217d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1218d0825bca7fe65beaee391d30da42e937db621564Steve Block# Args: 1219d0825bca7fe65beaee391d30da42e937db621564Steve Block# $fileHandle: A file handle to the patch file that has not yet been 1220d0825bca7fe65beaee391d30da42e937db621564Steve Block# read from. 12212daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# $optionsHashRef: a hash reference representing optional options to use 12222daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# when processing a diff. 12232daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# shouldNotUseIndexPathEOL: whether to use the line endings in the diff instead 12242daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# instead of the line endings in the target file; the 12252daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# value of 1 if svnConvertedText should use the line 12262daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch# endings in the diff. 1227d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1228d0825bca7fe65beaee391d30da42e937db621564Steve Block# Returns: 122921939df44de1705786c545cd1bf519d47250322dBen Murdoch# @diffHashRefs: an array of diff hash references. 123021939df44de1705786c545cd1bf519d47250322dBen Murdoch# See the %diffHash documentation above. 12312daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdochsub parsePatch($;$) 1232d0825bca7fe65beaee391d30da42e937db621564Steve Block{ 12332daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch my ($fileHandle, $optionsHashRef) = @_; 1234d0825bca7fe65beaee391d30da42e937db621564Steve Block 12356c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $newDiffHashRefs; 1236d0825bca7fe65beaee391d30da42e937db621564Steve Block my @diffHashRefs; # return value 1237d0825bca7fe65beaee391d30da42e937db621564Steve Block 1238d0825bca7fe65beaee391d30da42e937db621564Steve Block my $line = <$fileHandle>; 1239d0825bca7fe65beaee391d30da42e937db621564Steve Block 1240d0825bca7fe65beaee391d30da42e937db621564Steve Block while (defined($line)) { # Otherwise, at EOF. 1241d0825bca7fe65beaee391d30da42e937db621564Steve Block 12422daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch ($newDiffHashRefs, $line) = parseDiff($fileHandle, $line, $optionsHashRef); 1243d0825bca7fe65beaee391d30da42e937db621564Steve Block 12446c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen push @diffHashRefs, @$newDiffHashRefs; 1245d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1246d0825bca7fe65beaee391d30da42e937db621564Steve Block 1247d0825bca7fe65beaee391d30da42e937db621564Steve Block return @diffHashRefs; 1248d0825bca7fe65beaee391d30da42e937db621564Steve Block} 1249d0825bca7fe65beaee391d30da42e937db621564Steve Block 125021939df44de1705786c545cd1bf519d47250322dBen Murdoch# Prepare the results of parsePatch() for use in svn-apply and svn-unapply. 125121939df44de1705786c545cd1bf519d47250322dBen Murdoch# 125221939df44de1705786c545cd1bf519d47250322dBen Murdoch# Args: 125321939df44de1705786c545cd1bf519d47250322dBen Murdoch# $shouldForce: Whether to continue processing if an unexpected 125421939df44de1705786c545cd1bf519d47250322dBen Murdoch# state occurs. 125521939df44de1705786c545cd1bf519d47250322dBen Murdoch# @diffHashRefs: An array of references to %diffHashes. 125621939df44de1705786c545cd1bf519d47250322dBen Murdoch# See the %diffHash documentation above. 125721939df44de1705786c545cd1bf519d47250322dBen Murdoch# 125821939df44de1705786c545cd1bf519d47250322dBen Murdoch# Returns $preparedPatchHashRef: 125921939df44de1705786c545cd1bf519d47250322dBen Murdoch# copyDiffHashRefs: A reference to an array of the $diffHashRefs in 126021939df44de1705786c545cd1bf519d47250322dBen Murdoch# @diffHashRefs that represent file copies. The original 126121939df44de1705786c545cd1bf519d47250322dBen Murdoch# ordering is preserved. 126221939df44de1705786c545cd1bf519d47250322dBen Murdoch# nonCopyDiffHashRefs: A reference to an array of the $diffHashRefs in 126321939df44de1705786c545cd1bf519d47250322dBen Murdoch# @diffHashRefs that do not represent file copies. 126421939df44de1705786c545cd1bf519d47250322dBen Murdoch# The original ordering is preserved. 126521939df44de1705786c545cd1bf519d47250322dBen Murdoch# sourceRevisionHash: A reference to a hash of source path to source 126621939df44de1705786c545cd1bf519d47250322dBen Murdoch# revision number. 126721939df44de1705786c545cd1bf519d47250322dBen Murdochsub prepareParsedPatch($@) 126821939df44de1705786c545cd1bf519d47250322dBen Murdoch{ 126921939df44de1705786c545cd1bf519d47250322dBen Murdoch my ($shouldForce, @diffHashRefs) = @_; 127021939df44de1705786c545cd1bf519d47250322dBen Murdoch 127121939df44de1705786c545cd1bf519d47250322dBen Murdoch my %copiedFiles; 127221939df44de1705786c545cd1bf519d47250322dBen Murdoch 127321939df44de1705786c545cd1bf519d47250322dBen Murdoch # Return values 127421939df44de1705786c545cd1bf519d47250322dBen Murdoch my @copyDiffHashRefs = (); 127521939df44de1705786c545cd1bf519d47250322dBen Murdoch my @nonCopyDiffHashRefs = (); 127621939df44de1705786c545cd1bf519d47250322dBen Murdoch my %sourceRevisionHash = (); 127721939df44de1705786c545cd1bf519d47250322dBen Murdoch for my $diffHashRef (@diffHashRefs) { 127821939df44de1705786c545cd1bf519d47250322dBen Murdoch my $copiedFromPath = $diffHashRef->{copiedFromPath}; 127921939df44de1705786c545cd1bf519d47250322dBen Murdoch my $indexPath = $diffHashRef->{indexPath}; 128021939df44de1705786c545cd1bf519d47250322dBen Murdoch my $sourceRevision = $diffHashRef->{sourceRevision}; 128121939df44de1705786c545cd1bf519d47250322dBen Murdoch my $sourcePath; 128221939df44de1705786c545cd1bf519d47250322dBen Murdoch 128321939df44de1705786c545cd1bf519d47250322dBen Murdoch if (defined($copiedFromPath)) { 128421939df44de1705786c545cd1bf519d47250322dBen Murdoch # Then the diff is a copy operation. 128521939df44de1705786c545cd1bf519d47250322dBen Murdoch $sourcePath = $copiedFromPath; 128621939df44de1705786c545cd1bf519d47250322dBen Murdoch 128721939df44de1705786c545cd1bf519d47250322dBen Murdoch # FIXME: Consider printing a warning or exiting if 128821939df44de1705786c545cd1bf519d47250322dBen Murdoch # exists($copiedFiles{$indexPath}) is true -- i.e. if 128921939df44de1705786c545cd1bf519d47250322dBen Murdoch # $indexPath appears twice as a copy target. 129021939df44de1705786c545cd1bf519d47250322dBen Murdoch $copiedFiles{$indexPath} = $sourcePath; 129121939df44de1705786c545cd1bf519d47250322dBen Murdoch 129221939df44de1705786c545cd1bf519d47250322dBen Murdoch push @copyDiffHashRefs, $diffHashRef; 129321939df44de1705786c545cd1bf519d47250322dBen Murdoch } else { 129421939df44de1705786c545cd1bf519d47250322dBen Murdoch # Then the diff is not a copy operation. 129521939df44de1705786c545cd1bf519d47250322dBen Murdoch $sourcePath = $indexPath; 129621939df44de1705786c545cd1bf519d47250322dBen Murdoch 129721939df44de1705786c545cd1bf519d47250322dBen Murdoch push @nonCopyDiffHashRefs, $diffHashRef; 129821939df44de1705786c545cd1bf519d47250322dBen Murdoch } 129921939df44de1705786c545cd1bf519d47250322dBen Murdoch 130021939df44de1705786c545cd1bf519d47250322dBen Murdoch if (defined($sourceRevision)) { 130121939df44de1705786c545cd1bf519d47250322dBen Murdoch if (exists($sourceRevisionHash{$sourcePath}) && 130221939df44de1705786c545cd1bf519d47250322dBen Murdoch ($sourceRevisionHash{$sourcePath} != $sourceRevision)) { 130321939df44de1705786c545cd1bf519d47250322dBen Murdoch if (!$shouldForce) { 130421939df44de1705786c545cd1bf519d47250322dBen Murdoch die "Two revisions of the same file required as a source:\n". 130521939df44de1705786c545cd1bf519d47250322dBen Murdoch " $sourcePath:$sourceRevisionHash{$sourcePath}\n". 130621939df44de1705786c545cd1bf519d47250322dBen Murdoch " $sourcePath:$sourceRevision"; 130721939df44de1705786c545cd1bf519d47250322dBen Murdoch } 130821939df44de1705786c545cd1bf519d47250322dBen Murdoch } 130921939df44de1705786c545cd1bf519d47250322dBen Murdoch $sourceRevisionHash{$sourcePath} = $sourceRevision; 131021939df44de1705786c545cd1bf519d47250322dBen Murdoch } 131121939df44de1705786c545cd1bf519d47250322dBen Murdoch } 131221939df44de1705786c545cd1bf519d47250322dBen Murdoch 131321939df44de1705786c545cd1bf519d47250322dBen Murdoch my %preparedPatchHash; 131421939df44de1705786c545cd1bf519d47250322dBen Murdoch 131521939df44de1705786c545cd1bf519d47250322dBen Murdoch $preparedPatchHash{copyDiffHashRefs} = \@copyDiffHashRefs; 131621939df44de1705786c545cd1bf519d47250322dBen Murdoch $preparedPatchHash{nonCopyDiffHashRefs} = \@nonCopyDiffHashRefs; 131721939df44de1705786c545cd1bf519d47250322dBen Murdoch $preparedPatchHash{sourceRevisionHash} = \%sourceRevisionHash; 131821939df44de1705786c545cd1bf519d47250322dBen Murdoch 131921939df44de1705786c545cd1bf519d47250322dBen Murdoch return \%preparedPatchHash; 132021939df44de1705786c545cd1bf519d47250322dBen Murdoch} 132121939df44de1705786c545cd1bf519d47250322dBen Murdoch 13226c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Return localtime() for the project's time zone, given an integer time as 13236c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# returned by Perl's time() function. 13246c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsensub localTimeInProjectTimeZone($) 13256c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen{ 13266c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $epochTime = shift; 13276c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 13286c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Change the time zone temporarily for the localtime() call. 13296c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $savedTimeZone = $ENV{'TZ'}; 13306c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $ENV{'TZ'} = $changeLogTimeZone; 13316c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my @localTime = localtime($epochTime); 13326c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (defined $savedTimeZone) { 13336c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $ENV{'TZ'} = $savedTimeZone; 13346c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } else { 13356c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen delete $ENV{'TZ'}; 13366c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 13376c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 13386c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen return @localTime; 13396c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen} 13406c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 13416c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Set the reviewer and date in a ChangeLog patch, and return the new patch. 13426c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# 13436c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# Args: 13446c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $patch: a ChangeLog patch as a string. 13456c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $reviewer: the name of the reviewer, or undef if the reviewer should not be set. 13466c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen# $epochTime: an integer time as returned by Perl's time() function. 13476c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsensub setChangeLogDateAndReviewer($$$) 13486c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen{ 13496c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my ($patch, $reviewer, $epochTime) = @_; 13506c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 13516c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my @localTime = localTimeInProjectTimeZone($epochTime); 13526c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $newDate = strftime("%Y-%m-%d", @localTime); 13536c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 13546c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen my $firstChangeLogLineRegEx = qr#(\n\+)\d{4}-[^-]{2}-[^-]{2}( )#; 13556c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $patch =~ s/$firstChangeLogLineRegEx/$1$newDate$2/; 13566c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 13576c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if (defined($reviewer)) { 13586c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # We include a leading plus ("+") in the regular expression to make 13596c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # the regular expression less likely to match text in the leading junk 13606c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # for the patch, if the patch has leading junk. 13616c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen $patch =~ s/(\n\+.*)NOBODY \(OOPS!\)/$1$reviewer/; 13626c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen } 13636c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 13646c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen return $patch; 13656c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen} 13666c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 1367d0825bca7fe65beaee391d30da42e937db621564Steve Block# If possible, returns a ChangeLog patch equivalent to the given one, 1368d0825bca7fe65beaee391d30da42e937db621564Steve Block# but with the newest ChangeLog entry inserted at the top of the 1369d0825bca7fe65beaee391d30da42e937db621564Steve Block# file -- i.e. no leading context and all lines starting with "+". 1370d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1371d0825bca7fe65beaee391d30da42e937db621564Steve Block# If given a patch string not representable as a patch with the above 1372d0825bca7fe65beaee391d30da42e937db621564Steve Block# properties, it returns the input back unchanged. 1373d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1374d0825bca7fe65beaee391d30da42e937db621564Steve Block# WARNING: This subroutine can return an inequivalent patch string if 1375d0825bca7fe65beaee391d30da42e937db621564Steve Block# both the beginning of the new ChangeLog file matches the beginning 1376d0825bca7fe65beaee391d30da42e937db621564Steve Block# of the source ChangeLog, and the source beginning was modified. 1377d0825bca7fe65beaee391d30da42e937db621564Steve Block# Otherwise, it is guaranteed to return an equivalent patch string, 1378d0825bca7fe65beaee391d30da42e937db621564Steve Block# if it returns. 1379d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1380d0825bca7fe65beaee391d30da42e937db621564Steve Block# Applying this subroutine to ChangeLog patches allows svn-apply to 1381d0825bca7fe65beaee391d30da42e937db621564Steve Block# insert new ChangeLog entries at the top of the ChangeLog file. 1382d0825bca7fe65beaee391d30da42e937db621564Steve Block# svn-apply uses patch with --fuzz=3 to do this. We need to apply 1383d0825bca7fe65beaee391d30da42e937db621564Steve Block# this subroutine because the diff(1) command is greedy when matching 1384d0825bca7fe65beaee391d30da42e937db621564Steve Block# lines. A new ChangeLog entry with the same date and author as the 1385d0825bca7fe65beaee391d30da42e937db621564Steve Block# previous will match and cause the diff to have lines of starting 1386d0825bca7fe65beaee391d30da42e937db621564Steve Block# context. 1387d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1388d0825bca7fe65beaee391d30da42e937db621564Steve Block# This subroutine has unit tests in VCSUtils_unittest.pl. 1389a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch# 1390a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch# Returns $changeLogHashRef: 1391a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch# $changeLogHashRef: a hash reference representing a change log patch. 1392a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch# patch: a ChangeLog patch equivalent to the given one, but with the 1393cad810f21b803229eb11403f9209855525a25d57Steve Block# newest ChangeLog entry inserted at the top of the file, if possible. 1394cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Blocksub fixChangeLogPatch($) 1395cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block{ 1396643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $patch = shift; # $patch will only contain patch fragments for ChangeLog. 1397643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1398643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $patch =~ /(\r?\n)/; 1399643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $lineEnding = $1; 1400d0825bca7fe65beaee391d30da42e937db621564Steve Block my @lines = split(/$lineEnding/, $patch); 1401d0825bca7fe65beaee391d30da42e937db621564Steve Block 1402d0825bca7fe65beaee391d30da42e937db621564Steve Block my $i = 0; # We reuse the same index throughout. 1403d0825bca7fe65beaee391d30da42e937db621564Steve Block 1404d0825bca7fe65beaee391d30da42e937db621564Steve Block # Skip to beginning of first chunk. 1405d0825bca7fe65beaee391d30da42e937db621564Steve Block for (; $i < @lines; ++$i) { 1406d0825bca7fe65beaee391d30da42e937db621564Steve Block if (substr($lines[$i], 0, 1) eq "@") { 1407d0825bca7fe65beaee391d30da42e937db621564Steve Block last; 1408cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } 1409cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block } 1410d0825bca7fe65beaee391d30da42e937db621564Steve Block my $chunkStartIndex = ++$i; 1411a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch my %changeLogHashRef; 1412d0825bca7fe65beaee391d30da42e937db621564Steve Block 1413d0825bca7fe65beaee391d30da42e937db621564Steve Block # Optimization: do not process if new lines already begin the chunk. 1414d0825bca7fe65beaee391d30da42e937db621564Steve Block if (substr($lines[$i], 0, 1) eq "+") { 1415a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch $changeLogHashRef{patch} = $patch; 1416a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return \%changeLogHashRef; 1417d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1418d0825bca7fe65beaee391d30da42e937db621564Steve Block 1419d0825bca7fe65beaee391d30da42e937db621564Steve Block # Skip to first line of newly added ChangeLog entry. 1420d0825bca7fe65beaee391d30da42e937db621564Steve Block # For example, +2009-06-03 Eric Seidel <eric@webkit.org> 1421d0825bca7fe65beaee391d30da42e937db621564Steve Block my $dateStartRegEx = '^\+(\d{4}-\d{2}-\d{2})' # leading "+" and date 1422d0825bca7fe65beaee391d30da42e937db621564Steve Block . '\s+(.+)\s+' # name 1423d0825bca7fe65beaee391d30da42e937db621564Steve Block . '<([^<>]+)>$'; # e-mail address 1424d0825bca7fe65beaee391d30da42e937db621564Steve Block 1425d0825bca7fe65beaee391d30da42e937db621564Steve Block for (; $i < @lines; ++$i) { 1426d0825bca7fe65beaee391d30da42e937db621564Steve Block my $line = $lines[$i]; 1427d0825bca7fe65beaee391d30da42e937db621564Steve Block my $firstChar = substr($line, 0, 1); 1428d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($line =~ /$dateStartRegEx/) { 1429d0825bca7fe65beaee391d30da42e937db621564Steve Block last; 1430d0825bca7fe65beaee391d30da42e937db621564Steve Block } elsif ($firstChar eq " " or $firstChar eq "+") { 1431d0825bca7fe65beaee391d30da42e937db621564Steve Block next; 1432d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1433a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch $changeLogHashRef{patch} = $patch; # Do not change if, for example, "-" or "@" found. 1434a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return \%changeLogHashRef; 1435d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1436d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($i >= @lines) { 1437a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch $changeLogHashRef{patch} = $patch; # Do not change if date not found. 1438a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return \%changeLogHashRef; 1439d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1440d0825bca7fe65beaee391d30da42e937db621564Steve Block my $dateStartIndex = $i; 1441d0825bca7fe65beaee391d30da42e937db621564Steve Block 1442d0825bca7fe65beaee391d30da42e937db621564Steve Block # Rewrite overlapping lines to lead with " ". 1443d0825bca7fe65beaee391d30da42e937db621564Steve Block my @overlappingLines = (); # These will include a leading "+". 1444d0825bca7fe65beaee391d30da42e937db621564Steve Block for (; $i < @lines; ++$i) { 1445d0825bca7fe65beaee391d30da42e937db621564Steve Block my $line = $lines[$i]; 1446d0825bca7fe65beaee391d30da42e937db621564Steve Block if (substr($line, 0, 1) ne "+") { 1447d0825bca7fe65beaee391d30da42e937db621564Steve Block last; 1448d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1449d0825bca7fe65beaee391d30da42e937db621564Steve Block push(@overlappingLines, $line); 1450d0825bca7fe65beaee391d30da42e937db621564Steve Block $lines[$i] = " " . substr($line, 1); 1451d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1452d0825bca7fe65beaee391d30da42e937db621564Steve Block 1453d0825bca7fe65beaee391d30da42e937db621564Steve Block # Remove excess ending context, if necessary. 1454d0825bca7fe65beaee391d30da42e937db621564Steve Block my $shouldTrimContext = 1; 1455d0825bca7fe65beaee391d30da42e937db621564Steve Block for (; $i < @lines; ++$i) { 1456d0825bca7fe65beaee391d30da42e937db621564Steve Block my $firstChar = substr($lines[$i], 0, 1); 1457d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($firstChar eq " ") { 1458d0825bca7fe65beaee391d30da42e937db621564Steve Block next; 1459d0825bca7fe65beaee391d30da42e937db621564Steve Block } elsif ($firstChar eq "@") { 1460d0825bca7fe65beaee391d30da42e937db621564Steve Block last; 1461d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1462d0825bca7fe65beaee391d30da42e937db621564Steve Block $shouldTrimContext = 0; # For example, if "+" or "-" encountered. 1463d0825bca7fe65beaee391d30da42e937db621564Steve Block last; 1464d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1465d0825bca7fe65beaee391d30da42e937db621564Steve Block my $deletedLineCount = 0; 1466d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($shouldTrimContext) { # Also occurs if end of file reached. 1467d0825bca7fe65beaee391d30da42e937db621564Steve Block splice(@lines, $i - @overlappingLines, @overlappingLines); 1468d0825bca7fe65beaee391d30da42e937db621564Steve Block $deletedLineCount = @overlappingLines; 1469d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1470d0825bca7fe65beaee391d30da42e937db621564Steve Block 1471d0825bca7fe65beaee391d30da42e937db621564Steve Block # Work backwards, shifting overlapping lines towards front 1472d0825bca7fe65beaee391d30da42e937db621564Steve Block # while checking that patch stays equivalent. 1473e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke for ($i = $dateStartIndex - 1; @overlappingLines && $i >= $chunkStartIndex; --$i) { 1474d0825bca7fe65beaee391d30da42e937db621564Steve Block my $line = $lines[$i]; 1475d0825bca7fe65beaee391d30da42e937db621564Steve Block if (substr($line, 0, 1) ne " ") { 1476d0825bca7fe65beaee391d30da42e937db621564Steve Block next; 1477d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1478d0825bca7fe65beaee391d30da42e937db621564Steve Block my $text = substr($line, 1); 1479d0825bca7fe65beaee391d30da42e937db621564Steve Block my $newLine = pop(@overlappingLines); 1480d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($text ne substr($newLine, 1)) { 1481a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch $changeLogHashRef{patch} = $patch; # Unexpected difference. 1482a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return \%changeLogHashRef; 1483d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1484d0825bca7fe65beaee391d30da42e937db621564Steve Block $lines[$i] = "+$text"; 1485d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1486d0825bca7fe65beaee391d30da42e937db621564Steve Block 1487cad810f21b803229eb11403f9209855525a25d57Steve Block # If @overlappingLines > 0, this is where we make use of the 1488cad810f21b803229eb11403f9209855525a25d57Steve Block # assumption that the beginning of the source file was not modified. 1489cad810f21b803229eb11403f9209855525a25d57Steve Block splice(@lines, $chunkStartIndex, 0, @overlappingLines); 1490cad810f21b803229eb11403f9209855525a25d57Steve Block 1491cad810f21b803229eb11403f9209855525a25d57Steve Block # Update the date start index as it may have changed after shifting 1492cad810f21b803229eb11403f9209855525a25d57Steve Block # the overlapping lines towards the front. 1493cad810f21b803229eb11403f9209855525a25d57Steve Block for ($i = $chunkStartIndex; $i < $dateStartIndex; ++$i) { 1494cad810f21b803229eb11403f9209855525a25d57Steve Block $dateStartIndex = $i if $lines[$i] =~ /$dateStartRegEx/; 1495cad810f21b803229eb11403f9209855525a25d57Steve Block } 1496cad810f21b803229eb11403f9209855525a25d57Steve Block splice(@lines, $chunkStartIndex, $dateStartIndex - $chunkStartIndex); # Remove context of later entry. 1497cad810f21b803229eb11403f9209855525a25d57Steve Block $deletedLineCount += $dateStartIndex - $chunkStartIndex; 1498cad810f21b803229eb11403f9209855525a25d57Steve Block 1499cad810f21b803229eb11403f9209855525a25d57Steve Block # Update the initial chunk range. 1500d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($lines[$chunkStartIndex - 1] !~ /$chunkRangeRegEx/) { 1501d0825bca7fe65beaee391d30da42e937db621564Steve Block # FIXME: Handle errors differently from ChangeLog files that 1502d0825bca7fe65beaee391d30da42e937db621564Steve Block # are okay but should not be altered. That way we can find out 1503d0825bca7fe65beaee391d30da42e937db621564Steve Block # if improvements to the script ever become necessary. 1504a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch $changeLogHashRef{patch} = $patch; # Error: unexpected patch string format. 1505a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return \%changeLogHashRef; 1506d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1507d0825bca7fe65beaee391d30da42e937db621564Steve Block my $oldSourceLineCount = $2; 1508d0825bca7fe65beaee391d30da42e937db621564Steve Block my $oldTargetLineCount = $3; 1509d0825bca7fe65beaee391d30da42e937db621564Steve Block 1510d0825bca7fe65beaee391d30da42e937db621564Steve Block my $sourceLineCount = $oldSourceLineCount + @overlappingLines - $deletedLineCount; 1511d0825bca7fe65beaee391d30da42e937db621564Steve Block my $targetLineCount = $oldTargetLineCount + @overlappingLines - $deletedLineCount; 1512d0825bca7fe65beaee391d30da42e937db621564Steve Block $lines[$chunkStartIndex - 1] = "@@ -1,$sourceLineCount +1,$targetLineCount @@"; 1513d0825bca7fe65beaee391d30da42e937db621564Steve Block 1514a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch $changeLogHashRef{patch} = join($lineEnding, @lines) . "\n"; # patch(1) expects an extra trailing newline. 1515a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return \%changeLogHashRef; 1516d0825bca7fe65beaee391d30da42e937db621564Steve Block} 1517d0825bca7fe65beaee391d30da42e937db621564Steve Block 1518d0825bca7fe65beaee391d30da42e937db621564Steve Block# This is a supporting method for runPatchCommand. 1519d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1520d0825bca7fe65beaee391d30da42e937db621564Steve Block# Arg: the optional $args parameter passed to runPatchCommand (can be undefined). 1521d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1522d0825bca7fe65beaee391d30da42e937db621564Steve Block# Returns ($patchCommand, $isForcing). 1523d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1524d0825bca7fe65beaee391d30da42e937db621564Steve Block# This subroutine has unit tests in VCSUtils_unittest.pl. 1525d0825bca7fe65beaee391d30da42e937db621564Steve Blocksub generatePatchCommand($) 1526d0825bca7fe65beaee391d30da42e937db621564Steve Block{ 1527d0825bca7fe65beaee391d30da42e937db621564Steve Block my ($passedArgsHashRef) = @_; 1528d0825bca7fe65beaee391d30da42e937db621564Steve Block 1529d0825bca7fe65beaee391d30da42e937db621564Steve Block my $argsHashRef = { # Defaults 1530d0825bca7fe65beaee391d30da42e937db621564Steve Block ensureForce => 0, 1531d0825bca7fe65beaee391d30da42e937db621564Steve Block shouldReverse => 0, 1532d0825bca7fe65beaee391d30da42e937db621564Steve Block options => [] 1533d0825bca7fe65beaee391d30da42e937db621564Steve Block }; 1534d0825bca7fe65beaee391d30da42e937db621564Steve Block 1535d0825bca7fe65beaee391d30da42e937db621564Steve Block # Merges hash references. It's okay here if passed hash reference is undefined. 1536d0825bca7fe65beaee391d30da42e937db621564Steve Block @{$argsHashRef}{keys %{$passedArgsHashRef}} = values %{$passedArgsHashRef}; 1537d0825bca7fe65beaee391d30da42e937db621564Steve Block 1538d0825bca7fe65beaee391d30da42e937db621564Steve Block my $ensureForce = $argsHashRef->{ensureForce}; 1539d0825bca7fe65beaee391d30da42e937db621564Steve Block my $shouldReverse = $argsHashRef->{shouldReverse}; 1540d0825bca7fe65beaee391d30da42e937db621564Steve Block my $options = $argsHashRef->{options}; 1541d0825bca7fe65beaee391d30da42e937db621564Steve Block 1542d0825bca7fe65beaee391d30da42e937db621564Steve Block if (! $options) { 1543d0825bca7fe65beaee391d30da42e937db621564Steve Block $options = []; 1544d0825bca7fe65beaee391d30da42e937db621564Steve Block } else { 1545d0825bca7fe65beaee391d30da42e937db621564Steve Block $options = [@{$options}]; # Copy to avoid side effects. 1546d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1547d0825bca7fe65beaee391d30da42e937db621564Steve Block 1548d0825bca7fe65beaee391d30da42e937db621564Steve Block my $isForcing = 0; 1549d0825bca7fe65beaee391d30da42e937db621564Steve Block if (grep /^--force$/, @{$options}) { 1550d0825bca7fe65beaee391d30da42e937db621564Steve Block $isForcing = 1; 1551d0825bca7fe65beaee391d30da42e937db621564Steve Block } elsif ($ensureForce) { 1552d0825bca7fe65beaee391d30da42e937db621564Steve Block push @{$options}, "--force"; 1553d0825bca7fe65beaee391d30da42e937db621564Steve Block $isForcing = 1; 1554d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1555d0825bca7fe65beaee391d30da42e937db621564Steve Block 1556d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($shouldReverse) { # No check: --reverse should never be passed explicitly. 1557d0825bca7fe65beaee391d30da42e937db621564Steve Block push @{$options}, "--reverse"; 1558d0825bca7fe65beaee391d30da42e937db621564Steve Block } 1559d0825bca7fe65beaee391d30da42e937db621564Steve Block 1560d0825bca7fe65beaee391d30da42e937db621564Steve Block @{$options} = sort(@{$options}); # For easier testing. 1561d0825bca7fe65beaee391d30da42e937db621564Steve Block 1562d0825bca7fe65beaee391d30da42e937db621564Steve Block my $patchCommand = join(" ", "patch -p0", @{$options}); 1563d0825bca7fe65beaee391d30da42e937db621564Steve Block 1564d0825bca7fe65beaee391d30da42e937db621564Steve Block return ($patchCommand, $isForcing); 1565d0825bca7fe65beaee391d30da42e937db621564Steve Block} 1566d0825bca7fe65beaee391d30da42e937db621564Steve Block 1567d0825bca7fe65beaee391d30da42e937db621564Steve Block# Apply the given patch using the patch(1) command. 1568d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1569d0825bca7fe65beaee391d30da42e937db621564Steve Block# On success, return the resulting exit status. Otherwise, exit with the 1570d0825bca7fe65beaee391d30da42e937db621564Steve Block# exit status. If "--force" is passed as an option, however, then never 1571d0825bca7fe65beaee391d30da42e937db621564Steve Block# exit and always return the exit status. 1572d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1573d0825bca7fe65beaee391d30da42e937db621564Steve Block# Args: 1574d0825bca7fe65beaee391d30da42e937db621564Steve Block# $patch: a patch string. 1575d0825bca7fe65beaee391d30da42e937db621564Steve Block# $repositoryRootPath: an absolute path to the repository root. 1576d0825bca7fe65beaee391d30da42e937db621564Steve Block# $pathRelativeToRoot: the path of the file to be patched, relative to the 1577d0825bca7fe65beaee391d30da42e937db621564Steve Block# repository root. This should normally be the path 1578d0825bca7fe65beaee391d30da42e937db621564Steve Block# found in the patch's "Index:" line. It is passed 1579d0825bca7fe65beaee391d30da42e937db621564Steve Block# explicitly rather than reparsed from the patch 1580d0825bca7fe65beaee391d30da42e937db621564Steve Block# string for optimization purposes. 1581d0825bca7fe65beaee391d30da42e937db621564Steve Block# This is used only for error reporting. The 1582d0825bca7fe65beaee391d30da42e937db621564Steve Block# patch command gleans the actual file to patch 1583d0825bca7fe65beaee391d30da42e937db621564Steve Block# from the patch string. 1584d0825bca7fe65beaee391d30da42e937db621564Steve Block# $args: a reference to a hash of optional arguments. The possible 1585d0825bca7fe65beaee391d30da42e937db621564Steve Block# keys are -- 1586d0825bca7fe65beaee391d30da42e937db621564Steve Block# ensureForce: whether to ensure --force is passed (defaults to 0). 1587d0825bca7fe65beaee391d30da42e937db621564Steve Block# shouldReverse: whether to pass --reverse (defaults to 0). 1588d0825bca7fe65beaee391d30da42e937db621564Steve Block# options: a reference to an array of options to pass to the 1589d0825bca7fe65beaee391d30da42e937db621564Steve Block# patch command. The subroutine passes the -p0 option 1590d0825bca7fe65beaee391d30da42e937db621564Steve Block# no matter what. This should not include --reverse. 1591d0825bca7fe65beaee391d30da42e937db621564Steve Block# 1592d0825bca7fe65beaee391d30da42e937db621564Steve Block# This subroutine has unit tests in VCSUtils_unittest.pl. 1593d0825bca7fe65beaee391d30da42e937db621564Steve Blocksub runPatchCommand($$$;$) 1594d0825bca7fe65beaee391d30da42e937db621564Steve Block{ 1595d0825bca7fe65beaee391d30da42e937db621564Steve Block my ($patch, $repositoryRootPath, $pathRelativeToRoot, $args) = @_; 1596d0825bca7fe65beaee391d30da42e937db621564Steve Block 1597d0825bca7fe65beaee391d30da42e937db621564Steve Block my ($patchCommand, $isForcing) = generatePatchCommand($args); 1598d0825bca7fe65beaee391d30da42e937db621564Steve Block 1599d0825bca7fe65beaee391d30da42e937db621564Steve Block # Temporarily change the working directory since the path found 1600d0825bca7fe65beaee391d30da42e937db621564Steve Block # in the patch's "Index:" line is relative to the repository root 1601d0825bca7fe65beaee391d30da42e937db621564Steve Block # (i.e. the same as $pathRelativeToRoot). 1602d0825bca7fe65beaee391d30da42e937db621564Steve Block my $cwd = Cwd::getcwd(); 1603d0825bca7fe65beaee391d30da42e937db621564Steve Block chdir $repositoryRootPath; 1604643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1605d0825bca7fe65beaee391d30da42e937db621564Steve Block open PATCH, "| $patchCommand" or die "Could not call \"$patchCommand\" for file \"$pathRelativeToRoot\": $!"; 1606d0825bca7fe65beaee391d30da42e937db621564Steve Block print PATCH $patch; 1607d0825bca7fe65beaee391d30da42e937db621564Steve Block close PATCH; 1608d0825bca7fe65beaee391d30da42e937db621564Steve Block my $exitStatus = exitStatus($?); 1609643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1610d0825bca7fe65beaee391d30da42e937db621564Steve Block chdir $cwd; 1611643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1612d0825bca7fe65beaee391d30da42e937db621564Steve Block if ($exitStatus && !$isForcing) { 1613d0825bca7fe65beaee391d30da42e937db621564Steve Block print "Calling \"$patchCommand\" for file \"$pathRelativeToRoot\" returned " . 1614d0825bca7fe65beaee391d30da42e937db621564Steve Block "status $exitStatus. Pass --force to ignore patch failures.\n"; 1615d0825bca7fe65beaee391d30da42e937db621564Steve Block exit $exitStatus; 1616643ca7872b450ea4efacab6188849e5aac2ba161Steve Block } 1617643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1618d0825bca7fe65beaee391d30da42e937db621564Steve Block return $exitStatus; 1619643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 1620cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 1621dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# Merge ChangeLog patches using a three-file approach. 1622dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# 1623dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# This is used by resolve-ChangeLogs when it's operated as a merge driver 1624dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# and when it's used to merge conflicts after a patch is applied or after 1625dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# an svn update. 1626dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# 1627dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# It's also used for traditional rejected patches. 1628dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# 1629dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# Args: 1630dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# $fileMine: The merged version of the file. Also known in git as the 1631dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# other branch's version (%B) or "ours". 1632dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# For traditional patch rejects, this is the *.rej file. 1633dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# $fileOlder: The base version of the file. Also known in git as the 1634dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# ancestor version (%O) or "base". 1635dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# For traditional patch rejects, this is the *.orig file. 1636dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# $fileNewer: The current version of the file. Also known in git as the 1637dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# current version (%A) or "theirs". 1638dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# For traditional patch rejects, this is the original-named 1639dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# file. 1640dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# 1641dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# Returns 1 if merge was successful, else 0. 1642dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blocksub mergeChangeLogs($$$) 1643dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block{ 1644dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block my ($fileMine, $fileOlder, $fileNewer) = @_; 1645dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1646dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block my $traditionalReject = $fileMine =~ /\.rej$/ ? 1 : 0; 1647dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1648dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block local $/ = undef; 1649dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1650dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block my $patch; 1651dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if ($traditionalReject) { 1652dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block open(DIFF, "<", $fileMine) or die $!; 1653dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block $patch = <DIFF>; 1654dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block close(DIFF); 1655dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block rename($fileMine, "$fileMine.save"); 1656dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block rename($fileOlder, "$fileOlder.save"); 1657dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block } else { 1658dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block open(DIFF, "-|", qw(diff -u -a --binary), $fileOlder, $fileMine) or die $!; 1659dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block $patch = <DIFF>; 1660dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block close(DIFF); 1661dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block } 1662dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1663dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block unlink("${fileNewer}.orig"); 1664dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block unlink("${fileNewer}.rej"); 1665dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1666dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block open(PATCH, "| patch --force --fuzz=3 --binary $fileNewer > " . File::Spec->devnull()) or die $!; 1667a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch if ($traditionalReject) { 1668a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch print PATCH $patch; 1669a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch } else { 1670a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch my $changeLogHash = fixChangeLogPatch($patch); 1671a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch print PATCH $changeLogHash->{patch}; 1672a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch } 1673dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block close(PATCH); 1674dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1675dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block my $result = !exitStatus($?); 1676dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1677dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # Refuse to merge the patch if it did not apply cleanly 1678dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if (-e "${fileNewer}.rej") { 1679dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block unlink("${fileNewer}.rej"); 1680dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if (-f "${fileNewer}.orig") { 1681dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block unlink($fileNewer); 1682dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block rename("${fileNewer}.orig", $fileNewer); 1683dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block } 1684dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block } else { 1685dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block unlink("${fileNewer}.orig"); 1686dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block } 1687dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1688dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if ($traditionalReject) { 1689dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block rename("$fileMine.save", $fileMine); 1690dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block rename("$fileOlder.save", $fileOlder); 1691dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block } 1692dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1693dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return $result; 1694dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block} 1695dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 1696643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub gitConfig($) 1697643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 1698643ca7872b450ea4efacab6188849e5aac2ba161Steve Block return unless $isGit; 1699643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1700643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my ($config) = @_; 1701643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1702643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $result = `git config $config`; 1703643ca7872b450ea4efacab6188849e5aac2ba161Steve Block if (($? >> 8)) { 1704643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $result = `git repo-config $config`; 1705643ca7872b450ea4efacab6188849e5aac2ba161Steve Block } 1706643ca7872b450ea4efacab6188849e5aac2ba161Steve Block chomp $result; 1707643ca7872b450ea4efacab6188849e5aac2ba161Steve Block return $result; 1708cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block} 1709cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 1710643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub changeLogNameError($) 1711643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 1712643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my ($message) = @_; 1713643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR "$message\nEither:\n"; 1714643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR " set CHANGE_LOG_NAME in your environment\n"; 1715643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR " OR pass --name= on the command line\n"; 1716643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR " OR set REAL_NAME in your environment"; 1717643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR " OR git users can set 'git config user.name'\n"; 1718643ca7872b450ea4efacab6188849e5aac2ba161Steve Block exit(1); 1719643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 1720643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1721643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub changeLogName() 1722643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 1723643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $name = $ENV{CHANGE_LOG_NAME} || $ENV{REAL_NAME} || gitConfig("user.name") || (split /\s*,\s*/, (getpwuid $<)[6])[0]; 1724643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1725643ca7872b450ea4efacab6188849e5aac2ba161Steve Block changeLogNameError("Failed to determine ChangeLog name.") unless $name; 1726643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # getpwuid seems to always succeed on windows, returning the username instead of the full name. This check will catch that case. 1727643ca7872b450ea4efacab6188849e5aac2ba161Steve Block changeLogNameError("'$name' does not contain a space! ChangeLogs should contain your full name.") unless ($name =~ /\w \w/); 1728643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1729643ca7872b450ea4efacab6188849e5aac2ba161Steve Block return $name; 1730643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 1731643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1732643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub changeLogEmailAddressError($) 1733643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 1734643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my ($message) = @_; 1735643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR "$message\nEither:\n"; 1736643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR " set CHANGE_LOG_EMAIL_ADDRESS in your environment\n"; 1737643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR " OR pass --email= on the command line\n"; 1738643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR " OR set EMAIL_ADDRESS in your environment\n"; 1739643ca7872b450ea4efacab6188849e5aac2ba161Steve Block print STDERR " OR git users can set 'git config user.email'\n"; 1740643ca7872b450ea4efacab6188849e5aac2ba161Steve Block exit(1); 1741643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 1742643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1743643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub changeLogEmailAddress() 1744643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 1745643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $emailAddress = $ENV{CHANGE_LOG_EMAIL_ADDRESS} || $ENV{EMAIL_ADDRESS} || gitConfig("user.email"); 1746643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1747643ca7872b450ea4efacab6188849e5aac2ba161Steve Block changeLogEmailAddressError("Failed to determine email address for ChangeLog.") unless $emailAddress; 1748643ca7872b450ea4efacab6188849e5aac2ba161Steve Block changeLogEmailAddressError("Email address '$emailAddress' does not contain '\@' and is likely invalid.") unless ($emailAddress =~ /\@/); 1749643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1750643ca7872b450ea4efacab6188849e5aac2ba161Steve Block return $emailAddress; 1751643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 1752643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1753643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# http://tools.ietf.org/html/rfc1924 1754643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub decodeBase85($) 1755643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 1756643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my ($encoded) = @_; 1757643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my %table; 1758643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my @characters = ('0'..'9', 'A'..'Z', 'a'..'z', '!', '#', '$', '%', '&', '(', ')', '*', '+', '-', ';', '<', '=', '>', '?', '@', '^', '_', '`', '{', '|', '}', '~'); 1759643ca7872b450ea4efacab6188849e5aac2ba161Steve Block for (my $i = 0; $i < 85; $i++) { 1760643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $table{$characters[$i]} = $i; 1761643ca7872b450ea4efacab6188849e5aac2ba161Steve Block } 1762643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1763643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $decoded = ''; 1764643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my @encodedChars = $encoded =~ /./g; 1765643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1766643ca7872b450ea4efacab6188849e5aac2ba161Steve Block for (my $encodedIter = 0; defined($encodedChars[$encodedIter]);) { 1767643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $digit = 0; 1768643ca7872b450ea4efacab6188849e5aac2ba161Steve Block for (my $i = 0; $i < 5; $i++) { 1769643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $digit *= 85; 1770643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $char = $encodedChars[$encodedIter]; 1771643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $digit += $table{$char}; 1772643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $encodedIter++; 1773643ca7872b450ea4efacab6188849e5aac2ba161Steve Block } 1774643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1775643ca7872b450ea4efacab6188849e5aac2ba161Steve Block for (my $i = 0; $i < 4; $i++) { 1776643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $decoded .= chr(($digit >> (3 - $i) * 8) & 255); 1777643ca7872b450ea4efacab6188849e5aac2ba161Steve Block } 1778643ca7872b450ea4efacab6188849e5aac2ba161Steve Block } 1779643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1780643ca7872b450ea4efacab6188849e5aac2ba161Steve Block return $decoded; 1781643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 1782643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1783643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub decodeGitBinaryChunk($$) 1784643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 1785643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my ($contents, $fullPath) = @_; 1786643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1787643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # Load this module lazily in case the user don't have this module 1788643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # and won't handle git binary patches. 1789643ca7872b450ea4efacab6188849e5aac2ba161Steve Block require Compress::Zlib; 1790643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1791643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $encoded = ""; 1792643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $compressedSize = 0; 1793643ca7872b450ea4efacab6188849e5aac2ba161Steve Block while ($contents =~ /^([A-Za-z])(.*)$/gm) { 1794643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $line = $2; 1795643ca7872b450ea4efacab6188849e5aac2ba161Steve Block next if $line eq ""; 1796643ca7872b450ea4efacab6188849e5aac2ba161Steve Block die "$fullPath: unexpected size of a line: $&" if length($2) % 5 != 0; 1797643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $actualSize = length($2) / 5 * 4; 1798643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $encodedExpectedSize = ord($1); 1799643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $expectedSize = $encodedExpectedSize <= ord("Z") ? $encodedExpectedSize - ord("A") + 1 : $encodedExpectedSize - ord("a") + 27; 1800643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1801643ca7872b450ea4efacab6188849e5aac2ba161Steve Block die "$fullPath: unexpected size of a line: $&" if int(($expectedSize + 3) / 4) * 4 != $actualSize; 1802643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $compressedSize += $expectedSize; 1803643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $encoded .= $line; 1804643ca7872b450ea4efacab6188849e5aac2ba161Steve Block } 1805643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1806643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $compressed = decodeBase85($encoded); 1807643ca7872b450ea4efacab6188849e5aac2ba161Steve Block $compressed = substr($compressed, 0, $compressedSize); 1808643ca7872b450ea4efacab6188849e5aac2ba161Steve Block return Compress::Zlib::uncompress($compressed); 1809643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 1810643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1811643ca7872b450ea4efacab6188849e5aac2ba161Steve Blocksub decodeGitBinaryPatch($$) 1812643ca7872b450ea4efacab6188849e5aac2ba161Steve Block{ 1813643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my ($contents, $fullPath) = @_; 1814643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1815643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # Git binary patch has two chunks. One is for the normal patching 1816643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # and another is for the reverse patching. 1817643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # 1818643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # Each chunk a line which starts from either "literal" or "delta", 1819643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # followed by a number which specifies decoded size of the chunk. 1820643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # 1821643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # Then, content of the chunk comes. To decode the content, we 1822643ca7872b450ea4efacab6188849e5aac2ba161Steve Block # need decode it with base85 first, and then zlib. 1823643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $gitPatchRegExp = '(literal|delta) ([0-9]+)\n([A-Za-z0-9!#$%&()*+-;<=>?@^_`{|}~\\n]*?)\n\n'; 1824643ca7872b450ea4efacab6188849e5aac2ba161Steve Block if ($contents !~ m"\nGIT binary patch\n$gitPatchRegExp$gitPatchRegExp\Z") { 1825643ca7872b450ea4efacab6188849e5aac2ba161Steve Block die "$fullPath: unknown git binary patch format" 1826643ca7872b450ea4efacab6188849e5aac2ba161Steve Block } 1827643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1828643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $binaryChunkType = $1; 1829643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $binaryChunkExpectedSize = $2; 1830643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $encodedChunk = $3; 1831643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $reverseBinaryChunkType = $4; 1832643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $reverseBinaryChunkExpectedSize = $5; 1833643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $encodedReverseChunk = $6; 1834643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1835643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $binaryChunk = decodeGitBinaryChunk($encodedChunk, $fullPath); 1836643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $binaryChunkActualSize = length($binaryChunk); 1837643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $reverseBinaryChunk = decodeGitBinaryChunk($encodedReverseChunk, $fullPath); 1838643ca7872b450ea4efacab6188849e5aac2ba161Steve Block my $reverseBinaryChunkActualSize = length($reverseBinaryChunk); 1839643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1840cad810f21b803229eb11403f9209855525a25d57Steve Block die "$fullPath: unexpected size of the first chunk (expected $binaryChunkExpectedSize but was $binaryChunkActualSize" if ($binaryChunkType eq "literal" and $binaryChunkExpectedSize != $binaryChunkActualSize); 1841cad810f21b803229eb11403f9209855525a25d57Steve Block die "$fullPath: unexpected size of the second chunk (expected $reverseBinaryChunkExpectedSize but was $reverseBinaryChunkActualSize" if ($reverseBinaryChunkType eq "literal" and $reverseBinaryChunkExpectedSize != $reverseBinaryChunkActualSize); 1842643ca7872b450ea4efacab6188849e5aac2ba161Steve Block 1843643ca7872b450ea4efacab6188849e5aac2ba161Steve Block return ($binaryChunkType, $binaryChunk, $reverseBinaryChunkType, $reverseBinaryChunk); 1844643ca7872b450ea4efacab6188849e5aac2ba161Steve Block} 1845cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block 1846cad810f21b803229eb11403f9209855525a25d57Steve Blocksub readByte($$) 1847cad810f21b803229eb11403f9209855525a25d57Steve Block{ 1848cad810f21b803229eb11403f9209855525a25d57Steve Block my ($data, $location) = @_; 1849cad810f21b803229eb11403f9209855525a25d57Steve Block 1850cad810f21b803229eb11403f9209855525a25d57Steve Block # Return the byte at $location in $data as a numeric value. 1851cad810f21b803229eb11403f9209855525a25d57Steve Block return ord(substr($data, $location, 1)); 1852cad810f21b803229eb11403f9209855525a25d57Steve Block} 1853cad810f21b803229eb11403f9209855525a25d57Steve Block 1854cad810f21b803229eb11403f9209855525a25d57Steve Block# The git binary delta format is undocumented, except in code: 1855cad810f21b803229eb11403f9209855525a25d57Steve Block# - https://github.com/git/git/blob/master/delta.h:get_delta_hdr_size is the source 1856cad810f21b803229eb11403f9209855525a25d57Steve Block# of the algorithm in decodeGitBinaryPatchDeltaSize. 1857cad810f21b803229eb11403f9209855525a25d57Steve Block# - https://github.com/git/git/blob/master/patch-delta.c:patch_delta is the source 1858cad810f21b803229eb11403f9209855525a25d57Steve Block# of the algorithm in applyGitBinaryPatchDelta. 1859cad810f21b803229eb11403f9209855525a25d57Steve Blocksub decodeGitBinaryPatchDeltaSize($) 1860cad810f21b803229eb11403f9209855525a25d57Steve Block{ 1861cad810f21b803229eb11403f9209855525a25d57Steve Block my ($binaryChunk) = @_; 1862cad810f21b803229eb11403f9209855525a25d57Steve Block 1863cad810f21b803229eb11403f9209855525a25d57Steve Block # Source and destination buffer sizes are stored in 7-bit chunks at the 1864cad810f21b803229eb11403f9209855525a25d57Steve Block # start of the binary delta patch data. The highest bit in each byte 1865cad810f21b803229eb11403f9209855525a25d57Steve Block # except the last is set; the remaining 7 bits provide the next 1866cad810f21b803229eb11403f9209855525a25d57Steve Block # chunk of the size. The chunks are stored in ascending significance 1867cad810f21b803229eb11403f9209855525a25d57Steve Block # order. 1868cad810f21b803229eb11403f9209855525a25d57Steve Block my $cmd; 1869cad810f21b803229eb11403f9209855525a25d57Steve Block my $size = 0; 1870cad810f21b803229eb11403f9209855525a25d57Steve Block my $shift = 0; 1871cad810f21b803229eb11403f9209855525a25d57Steve Block for (my $i = 0; $i < length($binaryChunk);) { 1872cad810f21b803229eb11403f9209855525a25d57Steve Block $cmd = readByte($binaryChunk, $i++); 1873cad810f21b803229eb11403f9209855525a25d57Steve Block $size |= ($cmd & 0x7f) << $shift; 1874cad810f21b803229eb11403f9209855525a25d57Steve Block $shift += 7; 1875cad810f21b803229eb11403f9209855525a25d57Steve Block if (!($cmd & 0x80)) { 1876cad810f21b803229eb11403f9209855525a25d57Steve Block return ($size, $i); 1877cad810f21b803229eb11403f9209855525a25d57Steve Block } 1878cad810f21b803229eb11403f9209855525a25d57Steve Block } 1879cad810f21b803229eb11403f9209855525a25d57Steve Block} 1880cad810f21b803229eb11403f9209855525a25d57Steve Block 1881cad810f21b803229eb11403f9209855525a25d57Steve Blocksub applyGitBinaryPatchDelta($$) 1882cad810f21b803229eb11403f9209855525a25d57Steve Block{ 1883cad810f21b803229eb11403f9209855525a25d57Steve Block my ($binaryChunk, $originalContents) = @_; 1884cad810f21b803229eb11403f9209855525a25d57Steve Block 1885cad810f21b803229eb11403f9209855525a25d57Steve Block # Git delta format consists of two headers indicating source buffer size 1886cad810f21b803229eb11403f9209855525a25d57Steve Block # and result size, then a series of commands. Each command is either 1887cad810f21b803229eb11403f9209855525a25d57Steve Block # a copy-from-old-version (the 0x80 bit is set) or a copy-from-delta 1888cad810f21b803229eb11403f9209855525a25d57Steve Block # command. Commands are applied sequentially to generate the result. 1889cad810f21b803229eb11403f9209855525a25d57Steve Block # 1890cad810f21b803229eb11403f9209855525a25d57Steve Block # A copy-from-old-version command encodes an offset and size to copy 1891cad810f21b803229eb11403f9209855525a25d57Steve Block # from in subsequent bits, while a copy-from-delta command consists only 1892cad810f21b803229eb11403f9209855525a25d57Steve Block # of the number of bytes to copy from the delta. 1893cad810f21b803229eb11403f9209855525a25d57Steve Block 1894cad810f21b803229eb11403f9209855525a25d57Steve Block # We don't use these values, but we need to know how big they are so that 1895cad810f21b803229eb11403f9209855525a25d57Steve Block # we can skip to the diff data. 1896cad810f21b803229eb11403f9209855525a25d57Steve Block my ($size, $bytesUsed) = decodeGitBinaryPatchDeltaSize($binaryChunk); 1897cad810f21b803229eb11403f9209855525a25d57Steve Block $binaryChunk = substr($binaryChunk, $bytesUsed); 1898cad810f21b803229eb11403f9209855525a25d57Steve Block ($size, $bytesUsed) = decodeGitBinaryPatchDeltaSize($binaryChunk); 1899cad810f21b803229eb11403f9209855525a25d57Steve Block $binaryChunk = substr($binaryChunk, $bytesUsed); 1900cad810f21b803229eb11403f9209855525a25d57Steve Block 1901cad810f21b803229eb11403f9209855525a25d57Steve Block my $out = ""; 1902cad810f21b803229eb11403f9209855525a25d57Steve Block for (my $i = 0; $i < length($binaryChunk); ) { 1903cad810f21b803229eb11403f9209855525a25d57Steve Block my $cmd = ord(substr($binaryChunk, $i++, 1)); 1904cad810f21b803229eb11403f9209855525a25d57Steve Block if ($cmd & 0x80) { 1905cad810f21b803229eb11403f9209855525a25d57Steve Block # Extract an offset and size from the delta data, then copy 1906cad810f21b803229eb11403f9209855525a25d57Steve Block # $size bytes from $offset in the original data into the output. 1907cad810f21b803229eb11403f9209855525a25d57Steve Block my $offset = 0; 1908cad810f21b803229eb11403f9209855525a25d57Steve Block my $size = 0; 1909cad810f21b803229eb11403f9209855525a25d57Steve Block if ($cmd & 0x01) { $offset = readByte($binaryChunk, $i++); } 1910cad810f21b803229eb11403f9209855525a25d57Steve Block if ($cmd & 0x02) { $offset |= readByte($binaryChunk, $i++) << 8; } 1911cad810f21b803229eb11403f9209855525a25d57Steve Block if ($cmd & 0x04) { $offset |= readByte($binaryChunk, $i++) << 16; } 1912cad810f21b803229eb11403f9209855525a25d57Steve Block if ($cmd & 0x08) { $offset |= readByte($binaryChunk, $i++) << 24; } 1913cad810f21b803229eb11403f9209855525a25d57Steve Block if ($cmd & 0x10) { $size = readByte($binaryChunk, $i++); } 1914cad810f21b803229eb11403f9209855525a25d57Steve Block if ($cmd & 0x20) { $size |= readByte($binaryChunk, $i++) << 8; } 1915cad810f21b803229eb11403f9209855525a25d57Steve Block if ($cmd & 0x40) { $size |= readByte($binaryChunk, $i++) << 16; } 1916cad810f21b803229eb11403f9209855525a25d57Steve Block if ($size == 0) { $size = 0x10000; } 1917cad810f21b803229eb11403f9209855525a25d57Steve Block $out .= substr($originalContents, $offset, $size); 1918cad810f21b803229eb11403f9209855525a25d57Steve Block } elsif ($cmd) { 1919cad810f21b803229eb11403f9209855525a25d57Steve Block # Copy $cmd bytes from the delta data into the output. 1920cad810f21b803229eb11403f9209855525a25d57Steve Block $out .= substr($binaryChunk, $i, $cmd); 1921cad810f21b803229eb11403f9209855525a25d57Steve Block $i += $cmd; 1922cad810f21b803229eb11403f9209855525a25d57Steve Block } else { 1923cad810f21b803229eb11403f9209855525a25d57Steve Block die "unexpected delta opcode 0"; 1924cad810f21b803229eb11403f9209855525a25d57Steve Block } 1925cad810f21b803229eb11403f9209855525a25d57Steve Block } 1926cad810f21b803229eb11403f9209855525a25d57Steve Block 1927cad810f21b803229eb11403f9209855525a25d57Steve Block return $out; 1928cad810f21b803229eb11403f9209855525a25d57Steve Block} 1929cad810f21b803229eb11403f9209855525a25d57Steve Block 1930563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark1; 1931