diff options
Diffstat (limited to 'WebKitTools/Scripts/resolve-ChangeLogs')
-rwxr-xr-x | WebKitTools/Scripts/resolve-ChangeLogs | 488 |
1 files changed, 0 insertions, 488 deletions
diff --git a/WebKitTools/Scripts/resolve-ChangeLogs b/WebKitTools/Scripts/resolve-ChangeLogs deleted file mode 100755 index 6635711..0000000 --- a/WebKitTools/Scripts/resolve-ChangeLogs +++ /dev/null @@ -1,488 +0,0 @@ -#!/usr/bin/perl -w - -# Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the name of Apple Computer, 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. -# -# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# Merge and resolve ChangeLog conflicts for svn and git repositories - -use strict; - -use FindBin; -use lib $FindBin::Bin; - -use File::Basename; -use File::Copy; -use File::Path; -use File::Spec; -use Getopt::Long; -use POSIX; -use VCSUtils; - -sub canonicalRelativePath($); -sub conflictFiles($); -sub findChangeLog($); -sub findUnmergedChangeLogs(); -sub fixMergedChangeLogs($;@); -sub fixOneMergedChangeLog($); -sub hasGitUnmergedFiles(); -sub isInGitFilterBranch(); -sub parseFixMerged($$;$); -sub removeChangeLogArguments($); -sub resolveChangeLog($); -sub resolveConflict($); -sub showStatus($;$); -sub usageAndExit(); - -my $isGit = isGit(); -my $isSVN = isSVN(); - -my $SVN = "svn"; -my $GIT = "git"; - -my $fixMerged; -my $gitRebaseContinue = 0; -my $mergeDriver = 0; -my $printWarnings = 1; -my $showHelp; - -my $getOptionsResult = GetOptions( - 'c|continue!' => \$gitRebaseContinue, - 'f|fix-merged:s' => \&parseFixMerged, - 'm|merge-driver!' => \$mergeDriver, - 'h|help' => \$showHelp, - 'w|warnings!' => \$printWarnings, -); - -my $relativePath = isInGitFilterBranch() ? '.' : chdirReturningRelativePath(determineVCSRoot()); - -my @changeLogFiles = removeChangeLogArguments($relativePath); - -if (!defined $fixMerged && !$mergeDriver && scalar(@changeLogFiles) == 0) { - @changeLogFiles = findUnmergedChangeLogs(); -} - -if (!$mergeDriver && scalar(@ARGV) > 0) { - print STDERR "ERROR: Files listed on command-line that are not ChangeLogs.\n"; - undef $getOptionsResult; -} elsif (!defined $fixMerged && !$mergeDriver && scalar(@changeLogFiles) == 0) { - print STDERR "ERROR: No ChangeLog files listed on command-line or found unmerged.\n"; - undef $getOptionsResult; -} elsif ($gitRebaseContinue && !$isGit) { - print STDERR "ERROR: --continue may only be used with a git repository\n"; - undef $getOptionsResult; -} elsif (defined $fixMerged && !$isGit) { - print STDERR "ERROR: --fix-merged may only be used with a git repository\n"; - undef $getOptionsResult; -} elsif ($mergeDriver && !$isGit) { - print STDERR "ERROR: --merge-driver may only be used with a git repository\n"; - undef $getOptionsResult; -} elsif ($mergeDriver && scalar(@ARGV) < 3) { - print STDERR "ERROR: --merge-driver expects %O %A %B as arguments\n"; - undef $getOptionsResult; -} - -sub usageAndExit() -{ - print STDERR <<__END__; -Usage: @{[ basename($0) ]} [options] [path/to/ChangeLog] [path/to/another/ChangeLog ...] - -c|--[no-]continue run "git rebase --continue" after fixing ChangeLog - entries (default: --no-continue) - -f|--fix-merged [revision-range] fix git-merged ChangeLog entries; if a revision-range - is specified, run git filter-branch on the range - -m|--merge-driver %O %A %B act as a git merge-driver on files %O %A %B - -h|--help show this help message - -w|--[no-]warnings show or suppress warnings (default: show warnings) -__END__ - exit 1; -} - -if (!$getOptionsResult || $showHelp) { - usageAndExit(); -} - -if (defined $fixMerged && length($fixMerged) > 0) { - my $commitRange = $fixMerged; - $commitRange = $commitRange . "..HEAD" if index($commitRange, "..") < 0; - fixMergedChangeLogs($commitRange, @changeLogFiles); -} elsif ($mergeDriver) { - my ($base, $theirs, $ours) = @ARGV; - if (mergeChangeLogs($ours, $base, $theirs)) { - unlink($ours); - copy($theirs, $ours) or die $!; - } else { - exec qw(git merge-file -L THEIRS -L BASE -L OURS), $theirs, $base, $ours; - } -} elsif (@changeLogFiles) { - for my $file (@changeLogFiles) { - if (defined $fixMerged) { - fixOneMergedChangeLog($file); - } else { - resolveChangeLog($file); - } - } -} else { - print STDERR "ERROR: Unknown combination of switches and arguments.\n"; - usageAndExit(); -} - -if ($gitRebaseContinue) { - if (hasGitUnmergedFiles()) { - print "Unmerged files; skipping '$GIT rebase --continue'.\n"; - } else { - print "Running '$GIT rebase --continue'...\n"; - print `$GIT rebase --continue`; - } -} - -exit 0; - -sub canonicalRelativePath($) -{ - my ($originalPath) = @_; - my $absolutePath = Cwd::abs_path($originalPath); - return File::Spec->abs2rel($absolutePath, Cwd::getcwd()); -} - -sub conflictFiles($) -{ - my ($file) = @_; - my $fileMine; - my $fileOlder; - my $fileNewer; - - if (-e $file && -e "$file.orig" && -e "$file.rej") { - return ("$file.rej", "$file.orig", $file); - } - - if ($isSVN) { - open STAT, "-|", $SVN, "status", $file or die $!; - my $status = <STAT>; - close STAT; - if (!$status || $status !~ m/^C\s+/) { - print STDERR "WARNING: ${file} is not in a conflicted state.\n" if $printWarnings; - return (); - } - - $fileMine = "${file}.mine" if -e "${file}.mine"; - - my $currentRevision; - open INFO, "-|", $SVN, "info", $file or die $!; - while (my $line = <INFO>) { - if ($line =~ m/^Revision: ([0-9]+)/) { - $currentRevision = $1; - { local $/ = undef; <INFO>; } # Consume rest of input. - } - } - close INFO; - $fileNewer = "${file}.r${currentRevision}" if -e "${file}.r${currentRevision}"; - - my @matchingFiles = grep { $_ ne $fileNewer } glob("${file}.r[0-9][0-9]*"); - if (scalar(@matchingFiles) > 1) { - print STDERR "WARNING: Too many conflict files exist for ${file}!\n" if $printWarnings; - } else { - $fileOlder = shift @matchingFiles; - } - } elsif ($isGit) { - my $gitPrefix = `$GIT rev-parse --show-prefix`; - chomp $gitPrefix; - open GIT, "-|", $GIT, "ls-files", "--unmerged", $file or die $!; - while (my $line = <GIT>) { - my ($mode, $hash, $stage, $fileName) = split(' ', $line); - my $outputFile; - if ($stage == 1) { - $fileOlder = "${file}.BASE.$$"; - $outputFile = $fileOlder; - } elsif ($stage == 2) { - $fileNewer = "${file}.LOCAL.$$"; - $outputFile = $fileNewer; - } elsif ($stage == 3) { - $fileMine = "${file}.REMOTE.$$"; - $outputFile = $fileMine; - } else { - die "Unknown file stage: $stage"; - } - system("$GIT cat-file blob :${stage}:${gitPrefix}${file} > $outputFile"); - die $! if WEXITSTATUS($?); - } - close GIT or die $!; - } else { - die "Unknown version control system"; - } - - if (!$fileMine && !$fileOlder && !$fileNewer) { - print STDERR "WARNING: ${file} does not need merging.\n" if $printWarnings; - } elsif (!$fileMine || !$fileOlder || !$fileNewer) { - print STDERR "WARNING: ${file} is missing some conflict files.\n" if $printWarnings; - } - - return ($fileMine, $fileOlder, $fileNewer); -} - -sub findChangeLog($) -{ - return $_[0] if basename($_[0]) eq "ChangeLog"; - - my $file = File::Spec->catfile($_[0], "ChangeLog"); - return $file if -d $_[0] and -e $file; - - return undef; -} - -sub findUnmergedChangeLogs() -{ - my $statCommand = ""; - - if ($isSVN) { - $statCommand = "$SVN stat | grep '^C'"; - } elsif ($isGit) { - $statCommand = "$GIT diff -r --name-status --diff-filter=U -C -C -M"; - } else { - return (); - } - - my @results = (); - open STAT, "-|", $statCommand or die "The status failed: $!.\n"; - while (<STAT>) { - if ($isSVN) { - my $matches; - my $file; - if (isSVNVersion16OrNewer()) { - $matches = /^([C]).{6} (.+?)[\r\n]*$/; - $file = $2; - } else { - $matches = /^([C]).{5} (.+?)[\r\n]*$/; - $file = $2; - } - if ($matches) { - $file = findChangeLog(normalizePath($file)); - push @results, $file if $file; - } else { - print; # error output from svn stat - } - } elsif ($isGit) { - if (/^([U])\t(.+)$/) { - my $file = findChangeLog(normalizePath($2)); - push @results, $file if $file; - } else { - print; # error output from git diff - } - } - } - close STAT; - - return @results; -} - -sub fixMergedChangeLogs($;@) -{ - my $revisionRange = shift; - my @changedFiles = @_; - - if (scalar(@changedFiles) < 1) { - # Read in list of files changed in $revisionRange - open GIT, "-|", $GIT, "diff", "--name-only", $revisionRange or die $!; - push @changedFiles, <GIT>; - close GIT or die $!; - die "No changed files in $revisionRange" if scalar(@changedFiles) < 1; - chomp @changedFiles; - } - - my @changeLogs = grep { defined $_ } map { findChangeLog($_) } @changedFiles; - die "No changed ChangeLog files in $revisionRange" if scalar(@changeLogs) < 1; - - system("$GIT filter-branch --tree-filter 'PREVIOUS_COMMIT=\`$GIT rev-parse \$GIT_COMMIT^\` && MAPPED_PREVIOUS_COMMIT=\`map \$PREVIOUS_COMMIT\` \"$0\" -f \"" . join('" "', @changeLogs) . "\"' $revisionRange"); - - # On success, remove the backup refs directory - if (WEXITSTATUS($?) == 0) { - rmtree(qw(.git/refs/original)); - } -} - -sub fixOneMergedChangeLog($) -{ - my $file = shift; - my $patch; - - # Read in patch for incorrectly merged ChangeLog entry - { - local $/ = undef; - open GIT, "-|", $GIT, "diff", ($ENV{GIT_COMMIT} || "HEAD") . "^", $file or die $!; - $patch = <GIT>; - close GIT or die $!; - } - - # Always checkout the previous commit's copy of the ChangeLog - system($GIT, "checkout", $ENV{MAPPED_PREVIOUS_COMMIT} || "HEAD^", $file); - die $! if WEXITSTATUS($?); - - # The patch must have 0 or more lines of context, then 1 or more lines - # of additions, and then 1 or more lines of context. If not, we skip it. - if ($patch =~ /\n@@ -(\d+),(\d+) \+(\d+),(\d+) @@\n( .*\n)*((\+.*\n)+)( .*\n)+$/m) { - # Copy the header from the original patch. - my $newPatch = substr($patch, 0, index($patch, "@@ -${1},${2} +${3},${4} @@")); - - # Generate a new set of line numbers and patch lengths. Our new - # patch will start with the lines for the fixed ChangeLog entry, - # then have 3 lines of context from the top of the current file to - # make the patch apply cleanly. - $newPatch .= "@@ -1,3 +1," . ($4 - $2 + 3) . " @@\n"; - - # We assume that top few lines of the ChangeLog entry are actually - # at the bottom of the list of added lines (due to the way the patch - # algorithm works), so we simply search through the lines until we - # find the date line, then move the rest of the lines to the top. - my @patchLines = map { $_ . "\n" } split(/\n/, $6); - foreach my $i (0 .. $#patchLines) { - if ($patchLines[$i] =~ /^\+\d{4}-\d{2}-\d{2} /) { - unshift(@patchLines, splice(@patchLines, $i, scalar(@patchLines) - $i)); - last; - } - } - - $newPatch .= join("", @patchLines); - - # Add 3 lines of context to the end - open FILE, "<", $file or die $!; - for (my $i = 0; $i < 3; $i++) { - $newPatch .= " " . <FILE>; - } - close FILE; - - # Apply the new patch - open(PATCH, "| patch -p1 $file > " . File::Spec->devnull()) or die $!; - print PATCH $newPatch; - close(PATCH) or die $!; - - # Run "git add" on the fixed ChangeLog file - system($GIT, "add", $file); - die $! if WEXITSTATUS($?); - - showStatus($file, 1); - } elsif ($patch) { - # Restore the current copy of the ChangeLog file since we can't repatch it - system($GIT, "checkout", $ENV{GIT_COMMIT} || "HEAD", $file); - die $! if WEXITSTATUS($?); - print STDERR "WARNING: Last change to ${file} could not be fixed and re-merged.\n" if $printWarnings; - } -} - -sub hasGitUnmergedFiles() -{ - my $output = `$GIT ls-files --unmerged`; - return $output ne ""; -} - -sub isInGitFilterBranch() -{ - return exists $ENV{MAPPED_PREVIOUS_COMMIT} && $ENV{MAPPED_PREVIOUS_COMMIT}; -} - -sub parseFixMerged($$;$) -{ - my ($switchName, $key, $value) = @_; - if (defined $key) { - if (defined findChangeLog($key)) { - unshift(@ARGV, $key); - $fixMerged = ""; - } else { - $fixMerged = $key; - } - } else { - $fixMerged = ""; - } -} - -sub removeChangeLogArguments($) -{ - my ($baseDir) = @_; - my @results = (); - - for (my $i = 0; $i < scalar(@ARGV); ) { - my $file = findChangeLog(canonicalRelativePath(File::Spec->catfile($baseDir, $ARGV[$i]))); - if (defined $file) { - splice(@ARGV, $i, 1); - push @results, $file; - } else { - $i++; - } - } - - return @results; -} - -sub resolveChangeLog($) -{ - my ($file) = @_; - - my ($fileMine, $fileOlder, $fileNewer) = conflictFiles($file); - - return unless $fileMine && $fileOlder && $fileNewer; - - if (mergeChangeLogs($fileMine, $fileOlder, $fileNewer)) { - if ($file ne $fileNewer) { - unlink($file); - rename($fileNewer, $file) or die $!; - } - unlink($fileMine, $fileOlder); - resolveConflict($file); - showStatus($file, 1); - } else { - showStatus($file); - print STDERR "WARNING: ${file} could not be merged using fuzz level 3.\n" if $printWarnings; - unlink($fileMine, $fileOlder, $fileNewer) if $isGit; - } -} - -sub resolveConflict($) -{ - my ($file) = @_; - - if ($isSVN) { - system($SVN, "resolved", $file); - die $! if WEXITSTATUS($?); - } elsif ($isGit) { - system($GIT, "add", $file); - die $! if WEXITSTATUS($?); - } else { - die "Unknown version control system"; - } -} - -sub showStatus($;$) -{ - my ($file, $isConflictResolved) = @_; - - if ($isSVN) { - system($SVN, "status", $file); - } elsif ($isGit) { - my @args = qw(--name-status); - unshift @args, qw(--cached) if $isConflictResolved; - system($GIT, "diff", @args, $file); - } else { - die "Unknown version control system"; - } -} - |