diff options
Diffstat (limited to 'WebKitTools/Scripts/resolve-ChangeLogs')
-rwxr-xr-x | WebKitTools/Scripts/resolve-ChangeLogs | 227 |
1 files changed, 195 insertions, 32 deletions
diff --git a/WebKitTools/Scripts/resolve-ChangeLogs b/WebKitTools/Scripts/resolve-ChangeLogs index fa01243..58471ec 100755 --- a/WebKitTools/Scripts/resolve-ChangeLogs +++ b/WebKitTools/Scripts/resolve-ChangeLogs @@ -1,6 +1,6 @@ #!/usr/bin/perl -w -# Copyright (C) 2007 Apple Inc. All rights reserved. +# Copyright (C) 2007, 2008 Apple Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -34,73 +34,82 @@ use FindBin; use lib $FindBin::Bin; use File::Basename; +use File::Path; use File::Spec; use Getopt::Long; +use POSIX; use VCSUtils; sub conflictFiles($); +sub findChangeLog($); sub fixChangeLogPatch($); +sub fixMergedChangeLogs($;@); +sub fixOneMergedChangeLog($); sub mergeChanges($$$); +sub parseFixMerged($$;$); +sub removeChangeLogArguments(); +sub resolveChangeLog($); sub resolveConflict($); sub showStatus($;$); +sub usageAndExit(); my $SVN = "svn"; my $GIT = "git"; +my $fixMerged; my $printWarnings = 1; my $showHelp; my $getOptionsResult = GetOptions( + 'f|fix-merged:s' => \&parseFixMerged, 'h|help' => \$showHelp, 'w|warnings!' => \$printWarnings, ); -sub findChangeLog { - return $_ if basename($_) eq "ChangeLog"; +my @changeLogFiles = removeChangeLogArguments(); - my $file = File::Spec->catfile($_, "ChangeLog"); - return $file if -d $_ and -e $file; - - return undef; -} - -my @changeLogFiles = grep { defined $_ } map { findChangeLog($_) } @ARGV; - -if (scalar(@changeLogFiles) != scalar(@ARGV)) { +if (scalar(@ARGV) > 0) { print STDERR "ERROR: Files listed on command-line that are not ChangeLogs.\n"; undef $getOptionsResult; -} elsif (scalar(@changeLogFiles) == 0) { +} elsif (!defined $fixMerged && scalar(@changeLogFiles) == 0) { print STDERR "ERROR: No ChangeLog files listed on command-line.\n"; undef $getOptionsResult; +} elsif (defined $fixMerged && !isGit()) { + print STDERR "ERROR: --fix-merged may only be used with a git repository\n"; + undef $getOptionsResult; } -if (!$getOptionsResult || $showHelp) { +sub usageAndExit() +{ print STDERR <<__END__; Usage: @{[ basename($0) ]} [options] path/to/ChangeLog [path/to/another/ChangeLog ...] - -h|--help show this help message - -w|--[no-]warnings show or suppress warnings (default: show warnings) + -f|--fix-merged [revision-range] fix git-merged ChangeLog entries; if a revision-range + is specified, run git filter-branch on the range + -h|--help show this help message + -w|--[no-]warnings show or suppress warnings (default: show warnings) __END__ exit 1; } -for my $file (@changeLogFiles) { - my ($fileMine, $fileOlder, $fileNewer) = conflictFiles($file); - if (!$fileMine || !$fileOlder || !$fileNewer) { - next; - } - if (mergeChanges($fileMine, $fileOlder, $fileNewer)) { - if ($file ne $fileNewer) { - unlink($file); - rename($fileNewer, $file) || die; +if (!$getOptionsResult || $showHelp) { + usageAndExit(); +} + +if (defined $fixMerged && length($fixMerged) > 0) { + my $commitRange = $fixMerged; + $commitRange = $commitRange . "..HEAD" if index($commitRange, "..") < 0; + fixMergedChangeLogs($commitRange, @changeLogFiles); +} elsif (@changeLogFiles) { + for my $file (@changeLogFiles) { + if (defined $fixMerged) { + fixOneMergedChangeLog($file); + } else { + resolveChangeLog($file); } - 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(); } +} else { + print STDERR "ERROR: Unknown combination of switches and arguments.\n"; + usageAndExit(); } exit 0; @@ -176,6 +185,15 @@ sub conflictFiles($) 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 fixChangeLogPatch($) { my $patch = shift; @@ -227,6 +245,96 @@ sub fixChangeLogPatch($) return $newPatch; } +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 || die; + push @changedFiles, <GIT>; + close GIT || 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 || die; + $patch = <GIT>; + close GIT || die; + } + + # Always checkout the previous commit's copy of the ChangeLog + system($GIT, "checkout", $ENV{MAPPED_PREVIOUS_COMMIT} || "HEAD^", $file); + + # 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 || die; + for (my $i = 0; $i < 3; $i++) { + $newPatch .= " " . <FILE>; + } + close FILE; + + # Apply the new patch + open(PATCH, "| patch -p1 $file > /dev/null") || die; + print PATCH $newPatch; + close(PATCH) || die; + + # Run "git add" on the fixed ChangeLog file + system($GIT, "add", $file); + + 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); + print STDERR "WARNING: Last change to ${file} could not be fixed and re-merged.\n" if $printWarnings; + } +} + sub mergeChanges($$$) { my ($fileMine, $fileOlder, $fileNewer) = @_; @@ -276,6 +384,61 @@ sub mergeChanges($$$) return $result; } +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 @results = (); + + for (my $i = 0; $i < scalar(@ARGV); ) { + my $file = findChangeLog($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 (mergeChanges($fileMine, $fileOlder, $fileNewer)) { + if ($file ne $fileNewer) { + unlink($file); + rename($fileNewer, $file) || 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) = @_; |