summaryrefslogtreecommitdiffstats
path: root/webkit/tools/merge
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-27 00:20:51 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-27 00:20:51 +0000
commitf5b16fed647e941aa66933178da85db2860d639b (patch)
treef00e9856c04aad3b558a140955e7674add33f051 /webkit/tools/merge
parent920c091ac3ee15079194c82ae8a7a18215f3f23c (diff)
downloadchromium_src-f5b16fed647e941aa66933178da85db2860d639b.zip
chromium_src-f5b16fed647e941aa66933178da85db2860d639b.tar.gz
chromium_src-f5b16fed647e941aa66933178da85db2860d639b.tar.bz2
Add webkit to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/tools/merge')
-rwxr-xr-xwebkit/tools/merge/gen-merge-diff.sh98
-rw-r--r--webkit/tools/merge/update-branch-webkit.py774
2 files changed, 872 insertions, 0 deletions
diff --git a/webkit/tools/merge/gen-merge-diff.sh b/webkit/tools/merge/gen-merge-diff.sh
new file mode 100755
index 0000000..10aa0fb
--- /dev/null
+++ b/webkit/tools/merge/gen-merge-diff.sh
@@ -0,0 +1,98 @@
+#!/bin/sh
+#
+# Copyright 2007 Google Inc.
+# All rights reserved.
+#
+# This script expects to live in chrome/tools/branch/. If it's ever moved,
+# the value of |root| will need to be changed below.
+#
+# Generate a helper script that does a 3-way diff on each forked WebKit file
+# you give it. The diff will be among:
+# base: original third_party file from a Chrome trunk checkout
+# diff1: updated third_party file from the merge branch
+# diff2: forked file in webkit/pending or wherever.
+#
+# Run this script from the merge branch. Give it the path to the root of your
+# trunk checkout (the directory containing src and data directories) as the
+# first argument, and a list of forked files (e.g. from either copy of
+# webkit/pending/) as the second argument.
+#
+# Example usage:
+# ./gen-merge-diff.sh /cygdrive/c/src/chrome/trunk webkit/pending/*.*
+#
+# Once the helper script is generated, you can run it to do the 3-way diff.
+
+wkfilelist=/tmp/webkit-branch-merge/third_party_list.txt
+
+execdir=`dirname $0`
+root=$execdir/../..
+
+origtrunk=$1
+origroot=$origtrunk
+shift
+
+if [[ $# -lt 1 || ! -d "$origroot/third_party" ]]; then
+ echo "Usage: $0 <path-to-the-base-dir-of-chrome-trunk> [list-of-forked-files]"
+ echo "Example: $0 /cygdrive/c/src/chrome/trunk webkit/pending/*.*"
+ exit 1
+fi
+
+if which p4merge.exe >& /dev/null; then
+ merge_exe=p4merge.exe
+else
+ merge_exe="/cygdrive/c/Program Files/Perforce/p4merge.exe"
+ if [ ! -e "$merge_exe" ]; then
+ echo "WARNING: It looks like you don't have p4merge installed."
+ echo "You must edit merge-diff.sh and change the diff3way function to"
+ echo "point at a valid merge tool."
+ fi
+fi
+
+if [ -e $wkfilelist ]; then
+ echo "Using cached file listing of third_party."
+ echo "WARNING: If the cached file is not up to date, you should execute:"
+ echo " rm $wkfilelist"
+ echo "and run this script again."
+else
+ echo "Creating file listing of third_party..."
+ (cd $root && find third_party -type f | grep -v '/\.svn' | grep -v 'ForwardingHeaders' | grep -v 'DerivedSources' > $wkfilelist)
+fi
+
+# Prepare the helper script.
+
+cat > merge-diff.sh <<EOF
+#!/bin/sh
+
+function diff3way {
+ a=\`cygpath -wa \$1\`
+ b=\`cygpath -wa \$2\`
+ c=\`cygpath -wa \$3\`
+ "$merge_exe" "\$a" "\$b" "\$c" "\$c"
+}
+
+EOF
+
+chmod +x merge-diff.sh
+
+# Now add all the diff commands to the helper script.
+
+for each in "$@"; do
+ filename=`echo $each | sed -e "s,.*/,,g"`
+ tpfile=`grep "/\<$filename\>" $wkfilelist`
+ if [ "$tpfile" != "" ]; then
+ # Only run the 3-way diff if the upstream file has changed
+ ignored=`diff "$origroot/$tpfile" "$root/$tpfile"`
+ if [ $? != 0 ]; then
+ echo "diff3way \"$origroot/$tpfile\" \"$root/$tpfile\" \"$each\"" >> merge-diff.sh
+ fi
+ fi
+done
+
+cat << EOF
+
+======================
+Created: merge-diff.sh
+
+Please edit this file to make sure it is correct. Then run it with
+'./merge-diff.sh'.
+EOF
diff --git a/webkit/tools/merge/update-branch-webkit.py b/webkit/tools/merge/update-branch-webkit.py
new file mode 100644
index 0000000..8f61003
--- /dev/null
+++ b/webkit/tools/merge/update-branch-webkit.py
@@ -0,0 +1,774 @@
+#!/bin/env python
+# Copyright 2008, Google 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:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+# OWNER OR 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.
+
+"""
+Update a copy of WebKit to a given version, issuing the necessary gvn commands.
+
+
+This script is intended for internal use only, by knowledgeable users, and
+therefore does not taint-check user-supplied values.
+
+Cygwin is required to run this script, for 'cygpath'.
+
+Only tested on Windows, running in Cygwin. Most of this script should
+be compatible with Linux as well. The paths may not be correct for
+running in a Windows cmd shell.
+
+TODO(pamg): Test in Windows cmd shell and fix problems.
+
+Usage notes:
+ In normal usage, it's expected that the user will first run svn manually
+ to update a local copy of the WebKit trunk to a known (or head) revision.
+ The svn checkout is prone to odd failures (network timeouts, etc.), so
+ it's safer to run it by hand so you can more easily notice and recover
+ from any problems. Note that if you're running svn more than once, to
+ update more than one directory, you'll want to specify a --revision (-r)
+ on any calls after the first, just in case someone's landed something
+ while you were checking out the first directory.
+ For example:
+
+ cd /tmp/webkit-branch-merge/WebKit
+ svn co http://svn.webkit.org/repository/webkit/trunk/WebCore WebCore
+ [...Checked out revision 19776.]
+ svn co -r 19776 \
+ http://svn.webkit.org/repository/webkit/trunk/JavaScriptCore \
+ JavaScriptCore
+ cd /path/to/webkit/tools/merge
+ python ./update-branch-webkit.py --diff --real \
+ --old /path/to/mergebranch/ 19776
+
+ Here the --old parameter points to the root of your merge branch checkout,
+ containing the src/ and data/ directories. You can also append
+ --new [directory name] to point to the directory where you've checked out
+ the latest webkit files (if you checked them out to somewhere other than
+ /tmp/webkit-branch-merge/WebKit as depicted above). The --new parameter
+ should be a "WebKit" directory that corresponds to the webkit svn trunk.
+
+ If you want to see what the script will do before doing it, remove the
+ --real option. When you're satisfied that it's doing the right thing, you
+ can restore --real and run it one more time. In that case, you can also
+ leave off --diff on each run after the first, allowing the script to use a
+ cached file of diffs rather than re-generating it each time. Other options
+ are available as well; see 'update-branch-webkit --help' for a full list.
+
+ Directories may be specified with Windows-style paths, but the slashes
+ must be forward-slashes, not backslashes, or the Cygwin shell will remove
+ them.
+"""
+
+import errno
+import logging
+import optparse
+import os
+import re
+import shutil
+import subprocess
+import sys
+import traceback
+
+# Whether to produce additional debugging information.
+DEBUGGING = False
+
+# The filename holding the new WebKit Subversion revision number.
+BASE_REV_FILE = 'BASE_REVISION'
+
+# The URL from which to checkout the new revision if needed.
+SVN_URL = 'http://svn.webkit.org/repository/webkit/trunk'
+
+# The files in which to save lists of added, deleted, and edited files.
+SAVE_ADDED_FILES = 'merge-add-files.txt'
+SAVE_DELETED_FILES = 'merge-delete-files.txt'
+SAVE_EDITED_FILES = 'merge-edit-files.txt'
+SAVE_OBSOLETE_FILES = 'merge-obsolete-files.txt'
+
+# Executable names, found in the system PATH.
+SVN = 'svn.exe'
+DIFF = 'diff.exe'
+CYGPATH = 'cygpath.exe'
+
+# Directories to be entirely ignored when updating the merge branch.
+IGNORE_DIRS = ['.svn', 'DerivedSources']
+
+# Global cygpath process.
+_cygpath_proc = None
+
+########################################
+# Error classes
+
+class LocalError(Exception):
+ """Base class for local errors."""
+ stack_trace = True
+ code = 1
+
+class ArgumentError(LocalError):
+ """Exception raised for errors in parsed script arguments."""
+ stack_trace = False
+ code = 2
+
+class CannotFindError(LocalError):
+ """Exception raised for an expected file or directory that does not exist."""
+ def __init__(self, description, path):
+ LocalError.__init__(self, "Can't find %s '%s'" % (description, path))
+ stack_trace = False
+ code = 3
+
+class CannotWriteError(LocalError):
+ """Exception raised when writing to a necessary file fails."""
+ def __init__(self, path):
+ LocalError.__init__(self, "Can't write to '%s'" % path)
+ stack_trace = False
+ code = 4
+
+class CommandError(LocalError):
+ """Exception raised when an external command fails."""
+ def __init__(self, prefix, value):
+ """
+ Build a descriptive error message using available information.
+
+ Args:
+ prefix: Descriptive text prepended to the error message.
+ value: Error value as provided in the exception, if any. May be None.
+ """
+ if value is not None:
+ (errnum, message) = value
+ LocalError.__init__(self, "%s: %s (%s)" % (prefix, message, errnum))
+ else:
+ LocalError.__init__(self, prefix)
+ stack_trace = False
+ code = 5
+
+
+########################################
+# Utility functions
+
+def GetAbsolutePath(path):
+ """Convert an unknown-style path to an absolute, mixed-style Windows path.
+
+ We use an external cygpath binary to do the conversion. For performance
+ reasons, we use a single cygpath process.
+ """
+ global _cygpath_proc
+ if not _cygpath_proc:
+ _cygpath_proc = subprocess.Popen([CYGPATH, '-a', '-m', '-f', '-'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ _cygpath_proc.stdin.write(path + '\n')
+ return _cygpath_proc.stdout.readline().rstrip()
+
+def PathFromChromeRoot(subdir):
+ """Return the path of the given WebKit path relative to the Chrome merge dir.
+
+ We could get fancy and search for these, but it's so much simpler to just
+ set them, it's arguably even easier to maintain that way.
+
+ Args:
+ subdir: The relative path to be converted. It may be a directory or a
+ filename, and it should be specified relative to the WebKit checkout
+ (e.g., 'WebCore').
+
+ Returns:
+ The path to the given subdirectory dir in the Chrome webkit tree, starting
+ from the src directory.
+ """
+ return os.path.join('third_party', subdir)
+
+def NewDir(subdir=''):
+ """Return an absolute path to the subdirectory within the WebKit checkout.
+
+ The subdir is specified relative to the WebKit checkout (e.g., 'WebCore').
+ """
+ return GetAbsolutePath(os.path.join(options.new, subdir))
+
+def OldDir(subdir=''):
+ """Return an absolute path to the given subdirectory within the merge branch.
+
+ The subdir is specified relative to the WebKit checkout (e.g., 'WebCore').
+ """
+ reldir = PathFromChromeRoot(subdir)
+ return GetAbsolutePath(os.path.join(options.old, reldir))
+
+def TempDir(relative_path=''):
+ """Return an absolute path to the given location in the temp directory."""
+ return GetAbsolutePath(os.path.join(options.temp_dir, relative_path))
+
+def CollectFileList(search_dir, old):
+ """Recursively collect a set of files within the given directory.
+
+ FIXME(pamg): This function assumes that there are no instances of the string
+ given by the search_dir argument in either of the source paths (old and
+ new). That is, if dir is 'WebCore', the --old and --new options must not
+ contain the string 'WebCore'.
+
+ Args:
+ search_dir: The directory within which to search. It should be specified
+ relative to the WebKit checkout (e.g., 'WebCore').
+ old: Boolean value indicating whether to look for files in the old
+ directory (i.e., the Chrome merge branch) or the new one (the WebKit
+ checkout).
+
+ Returns:
+ A set of file paths relative to (and including) the given dir, with no
+ leading slash.
+ """
+ # FIXME(pamg): This replacement is why --old and --new can't contain
+ # search_dir.
+ if old:
+ path = OldDir(search_dir)
+ basedir = path.replace(search_dir, '')
+ else:
+ path = NewDir(search_dir)
+ basedir = path.replace(search_dir, '')
+
+ result = set()
+ for root, dirs, files in os.walk(path):
+ # We want the directory from search_dir onward, and we need '/' separators
+ # to match what diff returns.
+ relative_dir = root.replace(basedir, '', 1).replace('\\', '/')
+ for filename in files:
+ if not IgnoreFile(root):
+ result.add('/'.join([relative_dir, filename]))
+ return result
+
+def MakeDirectory(path):
+ """Create a path and its parents, returning a set of the paths created."""
+
+ logging.debug('Making directory %s' % path)
+ add = set()
+ (basepath, subdir) = os.path.split(path)
+ if not os.path.exists(basepath):
+ add = add.union(MakeDirectory(basepath))
+ if options.real:
+ os.mkdir(path)
+ add.add(path)
+ return add
+
+def IsAvailable(executable):
+ """Return True if the given executable can be found.
+
+ An executable is deemed available if it exists (for an absolute or relative
+ path) or is in the system path (for a simple filename). This is a naive
+ implementation that assumes that if a file with the given name exists, it is
+ accessible and executable.
+ """
+
+ if os.path.dirname(executable) != '':
+ # This is a pathname, either relative or absolute.
+ return os.path.exists(executable)
+
+ paths = os.environ['PATH'].split(os.pathsep)
+ for path_dir in paths:
+ if os.path.exists(os.path.join(path_dir, executable)):
+ return True
+ return False
+
+def IgnoreFile(path):
+ """Return True if the path is in the list of directories to be ignored."""
+ for ignore_dir in IGNORE_DIRS:
+ path = path.replace('\\', '/')
+ path = '/' + path + '/'
+ if '/' + ignore_dir + '/' in path:
+ return True
+ return False
+
+def WriteFile(path, data):
+ """Write the given data to the path.
+
+ Args:
+ path: The absolute filename to be written.
+ data: The string data to be written to the file.
+
+ Raises:
+ CannotWriteError if the write fails.
+ """
+ logging.debug('Writing file to %s' % path)
+ try:
+ f = open(path, 'w')
+ f.write(data)
+ f.close()
+ except IOError, e:
+ raise CannotWriteError(path)
+
+def WriteAbsoluteFileList(filename, file_list):
+ """Write a file containing a sorted list of absolute paths in the branch.
+
+ Args:
+ filename: The file to write.
+ file_list: An unsorted list of file paths, given relative to the WebKit
+ checkout (e.g., 'WebCore/ChangeLog').
+ """
+ logging.info('Saving %s' % filename)
+ file_paths = [OldDir(x) for x in file_list]
+ # Sort after converting to absolute paths, in case some of the incoming paths
+ # were already absolute.
+ file_paths.sort()
+ WriteFile(filename, '\n'.join(file_paths))
+
+def RunShellCommand(command, description, run=True):
+ """Run a command in the shell, waiting for completion.
+
+ Args:
+ command: The shell command to be run, as a string.
+ description: A brief description of the command, to be used in an error
+ message if needed.
+ run: If false, the command will be logged, but it won't actually be
+ executed.
+
+ Raises:
+ CommandError if the command runs but exits with a nonzero status.
+ """
+ logging.debug(command)
+ if run:
+ result = subprocess.call(command, shell=True)
+ if result:
+ raise CommandError(description, None)
+
+
+########################################
+# Task functions.
+
+def ValidateArguments():
+ """Validate incoming options.
+
+ Canonicalize paths and make sure that they exist, make sure a diff file
+ exists if one is being used, and check for all required executables.
+ """
+ if options.revision is None:
+ raise ArgumentError('Please specify a --revision.')
+ try:
+ # We need the revision as a string, but we want to make sure it's numeric.
+ options.revision = str(int(options.revision))
+ except ValueError:
+ raise ArgumentError('Revision must be a number.')
+ if options.revision < 1:
+ raise ArgumentError('Revision must be at least 1.')
+
+ options.dir_list = options.directories.split(',')
+ if not len(options.dir_list):
+ raise ArgumentError('Please list at least one directory.')
+
+ options.old = GetAbsolutePath(options.old)
+ options.new = GetAbsolutePath(options.new)
+ options.temp_dir = GetAbsolutePath(options.temp_dir)
+
+ if not os.path.exists(options.old):
+ raise CannotFindError('old dir', options.old)
+ if not os.path.exists(options.new):
+ raise CannotFindError('new dir', options.new)
+ for dir_name in options.dir_list:
+ if not os.path.exists(os.path.join(options.old, 'third_party', dir_name)):
+ raise CannotFindError('old %s' % dir_name, '')
+ if not os.path.exists(os.path.join(options.new, dir_name)):
+ raise CannotFindError('new %s' % dir_name, '')
+ if not os.path.exists(options.temp_dir):
+ logging.info('Creating temp directory %s' % options.temp_dir)
+ os.makedirs(options.temp_dir)
+
+ saved_diff_path = TempDir(SAVE_EDITED_FILES)
+ if not options.diff and not os.path.exists(saved_diff_path):
+ logging.warning("No saved diff file at '%s'. Running diff again." %
+ saved_diff_path)
+ options.diff = True
+
+ if options.svn_checkout:
+ if not IsAvailable(SVN):
+ raise CannotFindError('executable', SVN)
+ else:
+ checkout_dir = NewDir()
+ if not IsAvailable(checkout_dir):
+ raise CannotFindError('new WebKit checkout', checkout_dir)
+ if options.diff:
+ if not IsAvailable(DIFF):
+ raise CannotFindError('executable', DIFF)
+ if not IsAvailable('gvn.bat'):
+ raise CannotFindError('batch file', 'gvn.bat')
+
+def RunSvnCheckout(revision):
+ """Use svn to checkout a new WebKit from SVN_URL into the temp dir."""
+ logging.info('Checking out revision %s', revision)
+ for checkout_dir in options.dir_list:
+ new_dir = NewDir(checkout_dir)
+ url = SVN_URL + '/' + checkout_dir
+ command = '%s checkout -r %s %s %s' % (SVN, revision, url, new_dir)
+ RunShellCommand(command, 'Running svn')
+
+def UpdateBaseRevision(revision):
+ """Update the merge branch BASE_REVISION file."""
+ new_baserev_file = NewDir(BASE_REV_FILE)
+ try:
+ existing_revision = open(new_baserev_file, 'r').read().rstrip()
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ existing_revision = -1
+ else:
+ raise
+ if revision != existing_revision:
+ logging.info('Updating %s to %s' % (new_baserev_file, revision))
+ WriteFile(new_baserev_file, revision)
+
+def RunDiff(saved_file):
+ """Collect a set of files that differ between the WebKit and Chrome trees.
+
+ Run a recursive diff for each of the relevant directories, collecting a set
+ of files that differ between them. Save the resulting list in a file, and
+ also return the set.
+
+ Args:
+ saved_file: The file in which to save the list of differing files.
+
+ Returns:
+ A set of paths to files that differ between the two trees. Paths are
+ given relative to the WebKit checkout (e.g., 'WebCore/ChangeLog').
+
+ Raises:
+ CommandError if the diff command returns an error or produces unrecognized
+ output.
+ """
+ # Regular expressions for parsing the output of the diff.
+ add_delete_re = re.compile("^Only in [^ ]+")
+ edit_re = re.compile("^Files ([^ ]+) and [^ ]+ differ")
+
+ # Generate a set of the possible leading directory strings, to be removed
+ # from the file paths reported by diff.
+ dir_replacements = set()
+ for checkout_dir in options.dir_list:
+ # FIXME(pamg): This assumes that the name of the dir being processed is
+ # not a substring of options.new. See the FIXME for CollectFileList.
+ base_dir = NewDir(checkout_dir).replace(checkout_dir, '')
+ dir_replacements.add(base_dir)
+
+ # Collect the set of edited files.
+ edited = set()
+ for diff_dir in options.dir_list:
+ logging.info('Generating diffs for %s' % diff_dir)
+ command = [DIFF, '--recursive', '--ignore-space-change', '--brief',
+ NewDir(diff_dir), OldDir(diff_dir)]
+ logging.debug(command)
+ try:
+ p = subprocess.Popen(command, stdout=subprocess.PIPE)
+ diff_text = p.stdout.read().replace('\r', '')
+ rc = p.wait()
+ except OSError, e:
+ raise CommandError('Running diff ' + command, e)
+ # Diff exit status is 0 if the directories compared are the same, 1 if
+ # they differ, or 2 if an error was encountered.
+ if rc < 0 or rc > 1:
+ raise CommandError('Error running diff command', None)
+
+ # Examine this directory's diff result and build a list of changed files.
+ # Although we're not interested in added or deleted files, we parse the
+ # "Only in..." lines to catch any errors.
+ # Files C:/new/directory/file.h and C:/old/directory/file.h differ
+ # (ignored) Only in C:/new/directory: new_filename.h
+ # (ignored) Only in C:/old/directory: old_filename.h
+ logging.info('Analyzing %s diff results' % diff_dir)
+ for diff_line in diff_text.split('\n'):
+ if IgnoreFile(diff_line):
+ continue
+ if edit_re.search(diff_line) is not None:
+ filename = re.sub(edit_re, r'\1', diff_line)
+ for replace in dir_replacements:
+ filename = filename.replace(replace, '', 1)
+ edited.add(filename)
+ elif add_delete_re.search(diff_line) is not None:
+ # Do nothing, but don't complain about an error.
+ pass
+ elif diff_line != '':
+ raise CommandError("Unknown diff result '%s'" % diff_line, None)
+
+ WriteFile(saved_file, '\n'.join(sorted(edited)))
+ return edited
+
+def CopyFiles(files):
+ """Copy files from WebKit to the merge branch, collecting added directories.
+
+ If options.real is False, collect the directories that would be added, but
+ don't actually make the directories or perform the copies.
+
+ Args:
+ files: The set of files to be copied, with paths given relative to the
+ WebKit checkout (e.g., 'WebCore/ChangeLog').
+
+ Returns:
+ A set of absolute directory paths that were created in the merge branch
+ in order to copy the files.
+
+ Raises:
+ CommandError if the file copy fails.
+ """
+ logging.info('Copying files from %s to %s' % (options.new, options.old))
+ added = set()
+ for file in files:
+ # Create the directory if it doesn't exist.
+ # TODO(pamg): This has a lot of redundancy and could probably be made
+ # faster.
+ dirname = os.path.dirname(file)
+ old_dir = OldDir(dirname)
+ if not os.path.exists(old_dir):
+ # We need to handle each new directory level individually, for gvn add.
+ added = added.union(MakeDirectory(old_dir))
+ new_path = NewDir(file)
+ old_path = OldDir(file)
+ if options.real:
+ try:
+ shutil.copyfile(new_path, old_path)
+ except IOError, e:
+ raise CommandError('Copying file', e)
+ return added
+
+def CopyObsoleteFiles(files):
+ """Copy new versions of obsolete header files into .obsolete names.
+
+ If options.real is False, log the copy but do not actually perform it.
+
+ Args:
+ files: The set of header files to be copied, with paths given relative to
+ the WebKit checkout and not including '.obsolete' (e.g.,
+ 'WebCore/html/HTMLElement.h'). Their directories should already exist.
+
+ Raises:
+ CommandError if the file copy fails.
+ """
+ for file in files:
+ # The directory should already exist.
+ new_path = NewDir(file)
+ old_path = OldDir(file + '.obsolete')
+ logging.debug('Copying %s to %s' % (new_path, old_path))
+ if options.real:
+ try:
+ shutil.copyfile(new_path, old_path)
+ except IOError, e:
+ raise CommandError('Copying obsolete file', e)
+
+def CopyBaseRevisionFile():
+ """Copy the BASE_REV_FILE from the WebKit checkout to the merge branch.
+
+ If options.real is False, log the copy but do not actually perform it.
+
+ Raises:
+ CommandError if the file copy fails.
+ """
+ logging.info('Copying %s to %s' % (BASE_REV_FILE, OldDir()))
+ if options.real:
+ try:
+ shutil.copyfile(NewDir(BASE_REV_FILE), OldDir(BASE_REV_FILE))
+ except IOError, e:
+ raise CommandError('Copying %s' % BASE_REV_FILE, e)
+
+
+def main(options, args):
+ """Perform the merge."""
+
+ ValidateArguments()
+
+ # Report configuration.
+ logging.info('Updating to revision %s' % options.revision)
+ logging.info('Updating into old directory %s' % options.old)
+ logging.info('Using WebKit from new directory %s' % options.new)
+ logging.info('Using temp directory %s' % options.temp_dir)
+
+ if not options.real:
+ logging.warning('DEBUGGING: Not issuing mkdir, cp, or gvn commands')
+
+ if options.svn_checkout:
+ RunSvnCheckout(options.revision)
+
+ UpdateBaseRevision(options.revision)
+
+ # Make sure desired directories are in the checkout.
+ for checkout_dir in options.dir_list:
+ if not os.path.exists(NewDir(checkout_dir)):
+ raise CannotFindError('webkit checkout dir', NewDir(checkout_dir))
+
+ # Generate or load diffs between relevant directories in the two trees.
+ saved_diff_path = TempDir(SAVE_EDITED_FILES)
+ if options.diff:
+ edit_files = RunDiff(saved_diff_path)
+ else:
+ logging.info('Reading diffs from %s' % saved_diff_path)
+ diff_text = open(saved_diff_path, 'r').read()
+ edit_files = set(diff_text.split('\n'))
+
+ # Collect filenames in each directory.
+ old_files = set()
+ new_files = set()
+ logging.info('Collecting lists of old and new files')
+ for collect_dir in options.dir_list:
+ logging.debug('Collecting old files in %s' % collect_dir)
+ old_files = old_files.union(CollectFileList(collect_dir, old=True))
+ logging.debug('Collecting new files in %s' % collect_dir)
+ new_files = new_files.union(CollectFileList(collect_dir, old=False))
+
+ add_files = new_files - old_files
+ delete_files = old_files - new_files
+
+ if DEBUGGING:
+ WriteFile(TempDir('old.txt'), '\n'.join(sorted(old_files)))
+ WriteFile(TempDir('new.txt'), '\n'.join(sorted(new_files)))
+
+ # Find any obsolete files ostensibly to be deleted, and rename (rather than
+ # re-adding) their corresponding new .h files. We trust that no filename
+ # that ends with '.obsolete' will also contain another '.obsolete' in its
+ # name.
+ obsolete_files = set()
+ # Iterate through a copy of the deleted set since we'll be changing it.
+ delete_files_copy = delete_files.copy()
+ for deleted in delete_files_copy:
+ if deleted.endswith('.obsolete'):
+ try:
+ # Only the add_files.remove() should be able to raise a KeyError.
+ # If we have no original file to rename, don't delete the .obsolete
+ # one either.
+ delete_files.remove(deleted)
+ deleted = deleted.replace('.obsolete', '')
+ add_files.remove(deleted)
+ obsolete_files.add(deleted)
+ except KeyError, e:
+ logging.warning('No new file found for old %s.obsolete.' % deleted)
+
+ # Save the list of obsolete files for future reference.
+ WriteFile(TempDir(SAVE_OBSOLETE_FILES), '\n'.join(sorted(obsolete_files)))
+
+ # Save the list of deleted files, converted to absolute paths, for use by
+ # gvn.
+ WriteAbsoluteFileList(TempDir(SAVE_DELETED_FILES), delete_files)
+
+ # Issue gvn deletes. We do this before copying over the added files so that
+ # an apparently new file that only differs from an old one in the case of
+ # its filename will be copied with the right name even though Windows
+ # filenames are case-insensitive. (Otherwise, if the file cppparser.h
+ # already existed in the Chrome branch but was renamed to CPPParser.h in the
+ # WebKit checkout, the "added" file CPPParser.h would be copied into
+ # cppparser.h rather than using its new name.)
+ if len(delete_files):
+ logging.info('Issuing gvn delete')
+ command = 'gvn --targets %s delete' % TempDir(SAVE_DELETED_FILES)
+ RunShellCommand(command, 'Running gvn delete', options.real)
+
+ # Copy added and edited files from the svn checkout, collecting added
+ # directories in the process.
+ if not options.deletes_only:
+ add_dirs = CopyFiles(add_files | edit_files)
+ for dir_name in add_dirs:
+ # We want relative paths, so truncate the path.
+ add_files.add(dir_name[len(OldDir()) + 1:])
+ CopyObsoleteFiles(obsolete_files)
+ CopyBaseRevisionFile()
+
+ # Save the sorted list of added files, converted to absolute paths, for use
+ # by gvn. Sorting ensures that new directories are added before their
+ # contents are.
+ WriteAbsoluteFileList(TempDir(SAVE_ADDED_FILES), add_files)
+
+ # Issue gvn adds.
+ os.chdir(OldDir())
+ if len(add_files) and not options.deletes_only:
+ logging.info('Issuing gvn add')
+ command = 'gvn --targets %s add' % TempDir(SAVE_ADDED_FILES)
+ RunShellCommand(command, 'Running gvn add', options.real)
+
+ # Print statistics.
+ logging.info('Edited: %s' % len(edit_files))
+ logging.info('Added: %s' % len(add_files))
+ logging.info('Deleted: %s' % len(delete_files))
+ logging.info('Renamed (obsoletes): %s' % len(obsolete_files))
+
+ return 0
+
+if '__main__' == __name__:
+ # Set up logging.
+ if DEBUGGING:
+ loglevel = logging.DEBUG
+ else:
+ loglevel = logging.INFO
+ logging.basicConfig(level=loglevel,
+ format='%(asctime)s %(levelname)-7s: %(message)s',
+ datefmt='%H:%M:%S')
+
+ # We need cygpath to convert our default paths.
+ try:
+ if not IsAvailable(CYGPATH):
+ raise CannotFindError('cygpath', CYGPATH)
+ except LocalError, e:
+ if e.stack_trace or DEBUGGING:
+ traceback.print_exc()
+ logging.error(e)
+ sys.exit(e.code)
+
+ # Set up option parsing.
+ opt = optparse.OptionParser()
+
+ opt.add_option('-r', '--revision', default=None,
+ help='(REQUIRED): New WebKit revision (already checked out, or to be)')
+
+ opt.add_option('', '--directories',
+ default='JavaScriptCore,WebCore',
+ help='Comma-separated list of directories to update')
+
+ opt.add_option('', '--old',
+ default=GetAbsolutePath('/cygdrive/c/src/merge/'),
+ help='Path to top of branch checkout, holding src/ and data/')
+
+ opt.add_option('', '--new',
+ default=GetAbsolutePath('/tmp/webkit-branch-merge/WebKit'),
+ help='Directory containing new WebKit checkout')
+
+ opt.add_option('', '--temp-dir',
+ default=GetAbsolutePath('/tmp/webkit-branch-merge'),
+ help='Temp directory available for use (will be created)')
+
+ opt.add_option('', '--svn-checkout', action='store_true',
+ default=False,
+ help='Run a new svn checkout into the new dir')
+
+ opt.add_option('', '--diff', action='store_true',
+ default=False,
+ help='Run a fresh diff rather than using a saved diff file')
+
+ opt.add_option('', '--deletes-only', action='store_true',
+ default=False,
+ help='Only issue gvn commands for deletes (this is useful'
+ 'when the case of a filename has changed)')
+
+ opt.add_option('', '--real', action='store_true',
+ default=False,
+ help="I'm ready: do the mkdir, cp, and gvn commands for real")
+
+ (options, args) = opt.parse_args()
+
+ # Run.
+ try:
+ exit_value = main(options, args)
+ except LocalError, e:
+ if e.stack_trace or DEBUGGING:
+ traceback.print_exc()
+ logging.error(e)
+ exit_value = e.code
+
+ sys.exit(exit_value)