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