diff options
Diffstat (limited to 'Tools/Scripts/VCSUtils.pm')
| -rw-r--r-- | Tools/Scripts/VCSUtils.pm | 187 |
1 files changed, 157 insertions, 30 deletions
diff --git a/Tools/Scripts/VCSUtils.pm b/Tools/Scripts/VCSUtils.pm index 305d65bc0..214150d12 100644 --- a/Tools/Scripts/VCSUtils.pm +++ b/Tools/Scripts/VCSUtils.pm @@ -1,4 +1,4 @@ -# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved. +# Copyright (C) 2007-2013, 2015 Apple Inc. All rights reserved. # Copyright (C) 2009, 2010 Chris Jerdonek (chris.jerdonek@gmail.com) # Copyright (C) 2010, 2011 Research In Motion Limited. All rights reserved. # Copyright (C) 2012 Daniel Bates (dbates@intudata.com) @@ -12,7 +12,7 @@ # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. -# 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of +# 3. Neither the name of Apple Inc. ("Apple") nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # @@ -50,7 +50,6 @@ BEGIN { &callSilently &canonicalizePath &changeLogEmailAddress - &changeLogFileName &changeLogName &chdirReturningRelativePath &decodeGitBinaryChunk @@ -61,11 +60,14 @@ BEGIN { &exitStatus &fixChangeLogPatch &gitBranch + &gitDirectory + &gitTreeDirectory &gitdiff2svndiff &isGit &isGitSVN &isGitBranchBuild &isGitDirectory + &isGitSVNDirectory &isSVN &isSVNDirectory &isSVNVersion16OrNewer @@ -89,6 +91,7 @@ BEGIN { &toWindowsLineEndings &gitCommitForSVNRevision &listOfChangedFilesBetweenRevisions + &unixPath ); %EXPORT_TAGS = ( ); @EXPORT_OK = (); @@ -107,6 +110,7 @@ my $svnVersion; # Project time zone for Cupertino, CA, US my $changeLogTimeZone = "PST8PDT"; +my $unifiedDiffStartRegEx = qr#^--- ([abc]\/)?([^\r\n]+)#; my $gitDiffStartRegEx = qr#^diff --git [^\r\n]+#; my $gitDiffStartWithPrefixRegEx = qr#^diff --git \w/(.+) \w/([^\r\n]+)#; # We suppose that --src-prefix and --dst-prefix don't contain a non-word character (\W) and end with '/'. my $gitDiffStartWithoutPrefixNoSpaceRegEx = qr#^diff --git (\S+) (\S+)$#; @@ -125,7 +129,7 @@ my $svnPropertyValueNoNewlineRegEx = qr#\ No newline at end of property#; sub exitStatus($) { my ($returnvalue) = @_; - if ($^O eq "MSWin32") { + if (isWindows()) { return $returnvalue >> 8; } if (!WIFEXITED($returnvalue)) { @@ -224,15 +228,27 @@ sub isGit() return $isGit; } -sub isGitSVN() +sub isGitSVNDirectory($) { - return $isGitSVN if defined $isGitSVN; + my ($directory) = @_; + + my $savedWorkingDirectory = Cwd::getcwd(); + chdir($directory); # There doesn't seem to be an officially documented way to determine # if you're in a git-svn checkout. The best suggestions seen so far # all use something like the following: my $output = `git config --get svn-remote.svn.fetch 2>& 1`; - $isGitSVN = $output ne ''; + $isGitSVN = exitStatus($?) == 0 && $output ne ""; + chdir($savedWorkingDirectory); + return $isGitSVN; +} + +sub isGitSVN() +{ + return $isGitSVN if defined $isGitSVN; + + $isGitSVN = isGitSVNDirectory("."); return $isGitSVN; } @@ -242,6 +258,12 @@ sub gitDirectory() return $result; } +sub gitTreeDirectory() +{ + chomp(my $result = `git rev-parse --show-toplevel`); + return $result; +} + sub gitBisectStartBranch() { my $bisectStartFile = File::Spec->catfile(gitDirectory(), "BISECT_START"); @@ -314,7 +336,7 @@ sub svnVersion() sub isSVNVersion16OrNewer() { my $version = svnVersion(); - return eval "v$version" ge v1.6; + return "v$version" ge v1.6; } sub chdirReturningRelativePath($) @@ -375,7 +397,9 @@ sub determineSVNRoot() sub determineVCSRoot() { if (isGit()) { - return dirname(gitDirectory()); + # This is the working tree root. If WebKit is a submodule, + # then the relevant metadata directory is somewhere else. + return gitTreeDirectory(); } if (!isSVN()) { @@ -460,6 +484,17 @@ sub makeFilePathRelative($) sub normalizePath($) { my ($path) = @_; + if (isWindows()) { + $path =~ s/\//\\/g; + } else { + $path =~ s/\\/\//g; + } + return $path; +} + +sub unixPath($) +{ + my ($path) = @_; $path =~ s/\\/\//g; return $path; } @@ -908,6 +943,103 @@ sub parseSvnDiffHeader($$) return (\%header, $_); } +# Parse the next Unified diff header from the given file handle, and advance +# the handle so the last line read is the first line after the header. +# +# This subroutine dies if given leading junk. +# +# Args: +# $fileHandle: advanced so the last line read from the handle is the first +# line of the header to parse. This should be a line +# beginning with "Index:". +# $line: the line last read from $fileHandle +# +# Returns ($headerHashRef, $lastReadLine): +# $headerHashRef: a hash reference representing a diff header, as follows-- +# indexPath: the path of the target file, which is the path found in +# the "Index:" line. +# isNew: the value 1 if the diff is for a new file. +# isDeletion: the value 1 if the diff is a file deletion. +# svnConvertedText: the header text converted to a header with the paths +# in some lines corrected. +# $lastReadLine: the line last read from $fileHandle. +sub parseUnifiedDiffHeader($$) +{ + my ($fileHandle, $line) = @_; + + $_ = $line; + + my $currentPosition = tell($fileHandle); + my $indexLine; + my $indexPath; + if (/$unifiedDiffStartRegEx/) { + # Use $POSTMATCH to preserve the end-of-line character. + my $eol = $POSTMATCH; + + $indexPath = $2; + + # In the case of an addition, we look at the next line for the index path + if ($indexPath eq "/dev/null") { + $_ = <$fileHandle>; + if (/^\+\+\+ ([abc]\/)?([^\t\n\r]+)/) { + $indexPath = $2; + } else { + die "Unrecognized unified diff format."; + } + $_ = $line; + } + + $indexLine = "Index: $indexPath$eol"; # Convert to SVN format. + } else { + die("Could not parse leading \"---\" line: \"$line\"."); + } + + seek($fileHandle, $currentPosition, 0); + + my $isDeletion; + my $isHeaderEnding; + my $isNew; + my $svnConvertedText = $indexLine; + while (1) { + # Temporarily strip off any end-of-line characters to simplify + # regex matching below. + s/([\n\r]+)$//; + my $eol = $1; + + if (/^--- \/dev\/null/) { + $isNew = 1; + } elsif (/^\+\+\+ \/dev\/null/) { + $isDeletion = 1; + } + + if (/^(---|\+\+\+) ([abc]\/)?([^\t\n\r]+)/) { + if ($1 eq "---") { + my $prependText = ""; + $prependText = "new file mode 100644\n" if $isNew; + $_ = "${prependText}index 0000000..0000000\n$1 $3"; + } else { + $_ = "$1 $3"; + $isHeaderEnding = 1; + } + } + + $svnConvertedText .= "$_$eol"; # Also restore end-of-line characters. + + $currentPosition = tell($fileHandle); + $_ = <$fileHandle>; # Not defined if end-of-file reached. + last if (!defined($_) || /$unifiedDiffStartRegEx/ || $isHeaderEnding); + } + + my %header; + + $header{indexPath} = $indexPath; + $header{isDeletion} = $isDeletion if $isDeletion; + $header{isNew} = $isNew if $isNew; + $header{svnConvertedText} = $svnConvertedText; + + return (\%header, $_); +} + # Parse the next diff header from the given file handle, and advance # the handle so the last line read is the first line after the header. # @@ -946,6 +1078,7 @@ sub parseDiffHeader($$) my $header; # This is a hash ref. my $isGit; my $isSvn; + my $isUnified; my $lastReadLine; if ($line =~ $svnDiffStartRegEx) { @@ -954,12 +1087,16 @@ sub parseDiffHeader($$) } elsif ($line =~ $gitDiffStartRegEx) { $isGit = 1; ($header, $lastReadLine) = parseGitDiffHeader($fileHandle, $line); + } elsif ($line =~ $unifiedDiffStartRegEx) { + $isUnified = 1; + ($header, $lastReadLine) = parseUnifiedDiffHeader($fileHandle, $line); } else { die("First line of diff does not begin with \"Index:\" or \"diff --git\": \"$line\""); } $header->{isGit} = $isGit if $isGit; $header->{isSvn} = $isSvn if $isSvn; + $header->{isUnified} = $isUnified if $isUnified; return ($header, $lastReadLine); } @@ -1044,6 +1181,10 @@ sub parseDiff($$;$) # all diffs in the patch are formatted the same (SVN or Git). $headerStartRegEx = $gitDiffStartRegEx; } + + if (!$headerHashRef && ($line =~ $unifiedDiffStartRegEx)) { + $headerStartRegEx = $unifiedDiffStartRegEx; + } if ($line =~ $svnPropertiesStartRegEx) { my $propertyPath = $1; @@ -1880,7 +2021,7 @@ sub mergeChangeLogs($$$) sub gitConfig($) { - return unless $isGit; + return unless isGit(); my ($config) = @_; @@ -1889,23 +2030,6 @@ sub gitConfig($) return $result; } -sub changeLogSuffix() -{ - my $rootPath = determineVCSRoot(); - my $changeLogSuffixFile = File::Spec->catfile($rootPath, ".changeLogSuffix"); - return "" if ! -e $changeLogSuffixFile; - open FILE, $changeLogSuffixFile or die "Could not open $changeLogSuffixFile: $!"; - my $changeLogSuffix = <FILE>; - chomp $changeLogSuffix; - close FILE; - return $changeLogSuffix; -} - -sub changeLogFileName() -{ - return "ChangeLog" . changeLogSuffix() -} - sub changeLogNameError($) { my ($message) = @_; @@ -1919,7 +2043,10 @@ sub changeLogNameError($) sub changeLogName() { - my $name = $ENV{CHANGE_LOG_NAME} || $ENV{REAL_NAME} || gitConfig("user.name") || (split /\s*,\s*/, (getpwuid $<)[6])[0]; + my $name = $ENV{CHANGE_LOG_NAME} || $ENV{REAL_NAME} || gitConfig("user.name"); + if (not $name and !isWindows()) { + $name = (split /\s*,\s*/, (getpwuid $<)[6])[0]; + } changeLogNameError("Failed to determine ChangeLog name.") unless $name; # getpwuid seems to always succeed on windows, returning the username instead of the full name. This check will catch that case. @@ -2020,8 +2147,8 @@ sub decodeGitBinaryPatch($$) # Then, content of the chunk comes. To decode the content, we # need decode it with base85 first, and then zlib. my $gitPatchRegExp = '(literal|delta) ([0-9]+)\n([A-Za-z0-9!#$%&()*+-;<=>?@^_`{|}~\\n]*?)\n\n'; - if ($contents !~ m"\nGIT binary patch\n$gitPatchRegExp$gitPatchRegExp\Z") { - die "$fullPath: unknown git binary patch format" + if ($contents !~ m"\nGIT binary patch\n$gitPatchRegExp$gitPatchRegExp(\Z|-- \n)") { + return (); } my $binaryChunkType = $1; |
