summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy/common/checkout
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@qt.io>2017-05-30 12:48:17 +0200
committerOswald Buddenhagen <oswald.buddenhagen@qt.io>2017-05-30 12:48:17 +0200
commit881da28418d380042aa95a97f0cbd42560a64f7c (patch)
treea794dff3274695e99c651902dde93d934ea7a5af /Tools/Scripts/webkitpy/common/checkout
parent7e104c57a70fdf551bb3d22a5d637cdcbc69dbea (diff)
parent0fcedcd17cc00d3dd44c718b3cb36c1033319671 (diff)
downloadqtwebkit-881da28418d380042aa95a97f0cbd42560a64f7c.tar.gz
Merge 'wip/next' into dev
Change-Id: Iff9ee5e23bb326c4371ec8ed81d56f2f05d680e9
Diffstat (limited to 'Tools/Scripts/webkitpy/common/checkout')
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/__init__.py3
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/baselineoptimizer.py274
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py162
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/changelog.py459
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py667
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/checkout.py173
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/checkout_mock.py113
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/checkout_unittest.py260
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/commitinfo.py100
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py61
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/diff_parser.py193
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py175
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/diff_test_data.py80
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/__init__.py8
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/commitmessage.py62
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/detection.py83
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/detection_unittest.py51
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/git.py514
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/scm.py249
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py135
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/scm_unittest.py1600
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/svn.py380
22 files changed, 0 insertions, 5802 deletions
diff --git a/Tools/Scripts/webkitpy/common/checkout/__init__.py b/Tools/Scripts/webkitpy/common/checkout/__init__.py
deleted file mode 100644
index f385ae4f1..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Required for Python to search this directory for module files
-
-from .checkout import Checkout
diff --git a/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer.py b/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer.py
deleted file mode 100644
index d2d53a568..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer.py
+++ /dev/null
@@ -1,274 +0,0 @@
-# Copyright (C) 2011, 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.
-
-import copy
-import logging
-
-
-_log = logging.getLogger(__name__)
-
-
-# Yes, it's a hypergraph.
-# FIXME: Should this function live with the ports somewhere?
-# Perhaps this should move onto PortFactory?
-def _baseline_search_hypergraph(host, port_names):
- hypergraph = {}
-
- # These edges in the hypergraph aren't visible on build.webkit.org,
- # but they impose constraints on how we optimize baselines.
- hypergraph.update(_VIRTUAL_PORTS)
-
- # FIXME: Should we get this constant from somewhere?
- fallback_path = ['LayoutTests']
-
- port_factory = host.port_factory
- for port_name in port_names:
- port = port_factory.get(port_name)
- webkit_base = port.webkit_base()
- search_path = port.baseline_search_path()
- if search_path:
- hypergraph[port_name] = [host.filesystem.relpath(path, webkit_base) for path in search_path] + fallback_path
- return hypergraph
-
-
-_VIRTUAL_PORTS = {
- 'mac-future': ['LayoutTests/platform/mac-future', 'LayoutTests/platform/mac', 'LayoutTests'],
- 'win-future': ['LayoutTests/platform/win-future', 'LayoutTests/platform/win', 'LayoutTests'],
- 'qt-unknown': ['LayoutTests/platform/qt-unknown', 'LayoutTests/platform/qt', 'LayoutTests'],
-}
-
-
-# FIXME: Should this function be somewhere more general?
-def _invert_dictionary(dictionary):
- inverted_dictionary = {}
- for key, value in dictionary.items():
- if inverted_dictionary.get(value):
- inverted_dictionary[value].append(key)
- else:
- inverted_dictionary[value] = [key]
- return inverted_dictionary
-
-
-class BaselineOptimizer(object):
- def __init__(self, host, port_names):
- self._host = host
- self._filesystem = self._host.filesystem
- self._scm = self._host.scm()
- self._hypergraph = _baseline_search_hypergraph(host, port_names)
- self._directories = reduce(set.union, map(set, self._hypergraph.values()))
-
- def read_results_by_directory(self, baseline_name):
- results_by_directory = {}
- for directory in self._directories:
- path = self._filesystem.join(self._scm.checkout_root, directory, baseline_name)
- if self._filesystem.exists(path):
- results_by_directory[directory] = self._filesystem.sha1(path)
- return results_by_directory
-
- def _results_by_port_name(self, results_by_directory):
- results_by_port_name = {}
- for port_name, search_path in self._hypergraph.items():
- for directory in search_path:
- if directory in results_by_directory:
- results_by_port_name[port_name] = results_by_directory[directory]
- break
- return results_by_port_name
-
- def _most_specific_common_directory(self, port_names):
- paths = [self._hypergraph[port_name] for port_name in port_names]
- common_directories = reduce(set.intersection, map(set, paths))
-
- def score(directory):
- return sum([path.index(directory) for path in paths])
-
- _, directory = sorted([(score(directory), directory) for directory in common_directories])[0]
- return directory
-
- def _filter_port_names_by_result(self, predicate, port_names_by_result):
- filtered_port_names_by_result = {}
- for result, port_names in port_names_by_result.items():
- filtered_port_names = filter(predicate, port_names)
- if filtered_port_names:
- filtered_port_names_by_result[result] = filtered_port_names
- return filtered_port_names_by_result
-
- def _place_results_in_most_specific_common_directory(self, port_names_by_result, results_by_directory):
- for result, port_names in port_names_by_result.items():
- directory = self._most_specific_common_directory(port_names)
- results_by_directory[directory] = result
-
- def _find_optimal_result_placement(self, baseline_name):
- results_by_directory = self.read_results_by_directory(baseline_name)
- results_by_port_name = self._results_by_port_name(results_by_directory)
- port_names_by_result = _invert_dictionary(results_by_port_name)
-
- new_results_by_directory = self._optimize_by_most_specific_common_directory(results_by_directory, results_by_port_name, port_names_by_result)
- if not new_results_by_directory:
- new_results_by_directory = self._optimize_by_pushing_results_up(results_by_directory, results_by_port_name, port_names_by_result)
-
- return results_by_directory, new_results_by_directory
-
- def _optimize_by_most_specific_common_directory(self, results_by_directory, results_by_port_name, port_names_by_result):
- new_results_by_directory = {}
- unsatisfied_port_names_by_result = port_names_by_result
- while unsatisfied_port_names_by_result:
- self._place_results_in_most_specific_common_directory(unsatisfied_port_names_by_result, new_results_by_directory)
- new_results_by_port_name = self._results_by_port_name(new_results_by_directory)
-
- def is_unsatisfied(port_name):
- return results_by_port_name[port_name] != new_results_by_port_name[port_name]
-
- new_unsatisfied_port_names_by_result = self._filter_port_names_by_result(is_unsatisfied, port_names_by_result)
-
- if len(new_unsatisfied_port_names_by_result.values()) >= len(unsatisfied_port_names_by_result.values()):
- return {} # Frowns. We do not appear to be converging.
- unsatisfied_port_names_by_result = new_unsatisfied_port_names_by_result
-
- return new_results_by_directory
-
- def _optimize_by_pushing_results_up(self, results_by_directory, results_by_port_name, port_names_by_result):
- try:
- results_by_directory = results_by_directory
- best_so_far = results_by_directory
- while True:
- new_results_by_directory = copy.copy(best_so_far)
- for port_name in self._hypergraph.keys():
- fallback_path = self._hypergraph[port_name]
- current_index, current_directory = self._find_in_fallbackpath(fallback_path, results_by_port_name[port_name], best_so_far)
- current_result = results_by_port_name[port_name]
- for index in range(current_index + 1, len(fallback_path)):
- new_directory = fallback_path[index]
- if not new_directory in new_results_by_directory:
- new_results_by_directory[new_directory] = current_result
- if current_directory in new_results_by_directory:
- del new_results_by_directory[current_directory]
- elif new_results_by_directory[new_directory] == current_result:
- if current_directory in new_results_by_directory:
- del new_results_by_directory[current_directory]
- else:
- # The new_directory contains a different result, so stop trying to push results up.
- break
-
- if len(new_results_by_directory) >= len(best_so_far):
- # We've failed to improve, so give up.
- break
- best_so_far = new_results_by_directory
-
- return best_so_far
- except KeyError as e:
- # FIXME: KeyErrors get raised if we're missing baselines. We should handle this better.
- return {}
-
- def _find_in_fallbackpath(self, fallback_path, current_result, results_by_directory):
- for index, directory in enumerate(fallback_path):
- if directory in results_by_directory and (results_by_directory[directory] == current_result):
- return index, directory
- assert False, "result %s not found in fallback_path %s, %s" % (current_result, fallback_path, results_by_directory)
-
- def _filtered_results_by_port_name(self, results_by_directory):
- results_by_port_name = self._results_by_port_name(results_by_directory)
- for port_name in _VIRTUAL_PORTS.keys():
- if port_name in results_by_port_name:
- del results_by_port_name[port_name]
- return results_by_port_name
-
- def _platform(self, filename):
- platform_dir = 'LayoutTests' + self._filesystem.sep + 'platform' + self._filesystem.sep
- if filename.startswith(platform_dir):
- return filename.replace(platform_dir, '').split(self._filesystem.sep)[0]
- platform_dir = self._filesystem.join(self._scm.checkout_root, platform_dir)
- if filename.startswith(platform_dir):
- return filename.replace(platform_dir, '').split(self._filesystem.sep)[0]
- return '(generic)'
-
- def _move_baselines(self, baseline_name, results_by_directory, new_results_by_directory):
- data_for_result = {}
- for directory, result in results_by_directory.items():
- if not result in data_for_result:
- source = self._filesystem.join(self._scm.checkout_root, directory, baseline_name)
- data_for_result[result] = self._filesystem.read_binary_file(source)
-
- file_names = []
- for directory, result in results_by_directory.items():
- if new_results_by_directory.get(directory) != result:
- file_names.append(self._filesystem.join(self._scm.checkout_root, directory, baseline_name))
- if file_names:
- _log.debug(" Deleting:")
- for platform_dir in sorted(self._platform(filename) for filename in file_names):
- _log.debug(" " + platform_dir)
- self._scm.delete_list(file_names)
- else:
- _log.debug(" (Nothing to delete)")
-
- file_names = []
- for directory, result in new_results_by_directory.items():
- if results_by_directory.get(directory) != result:
- destination = self._filesystem.join(self._scm.checkout_root, directory, baseline_name)
- self._filesystem.maybe_make_directory(self._filesystem.split(destination)[0])
- self._filesystem.write_binary_file(destination, data_for_result[result])
- file_names.append(destination)
- if file_names:
- _log.debug(" Adding:")
- for platform_dir in sorted(self._platform(filename) for filename in file_names):
- _log.debug(" " + platform_dir)
- self._scm.add_list(file_names)
- else:
- _log.debug(" (Nothing to add)")
-
- def directories_by_result(self, baseline_name):
- results_by_directory = self.read_results_by_directory(baseline_name)
- return _invert_dictionary(results_by_directory)
-
- def write_by_directory(self, results_by_directory, writer, indent):
- for path in sorted(results_by_directory):
- writer("%s%s: %s" % (indent, self._platform(path), results_by_directory[path][0:6]))
-
- def optimize(self, baseline_name):
- basename = self._filesystem.basename(baseline_name)
- results_by_directory, new_results_by_directory = self._find_optimal_result_placement(baseline_name)
- self.new_results_by_directory = new_results_by_directory
- if new_results_by_directory == results_by_directory:
- if new_results_by_directory:
- _log.debug(" %s: (already optimal)" % basename)
- self.write_by_directory(results_by_directory, _log.debug, " ")
- else:
- _log.debug(" %s: (no baselines found)" % basename)
- return True
- if self._filtered_results_by_port_name(results_by_directory) != self._filtered_results_by_port_name(new_results_by_directory):
- _log.warning(" %s: optimization failed" % basename)
- self.write_by_directory(results_by_directory, _log.warning, " ")
- return False
-
- _log.debug(" %s:" % basename)
- _log.debug(" Before: ")
- self.write_by_directory(results_by_directory, _log.debug, " ")
- _log.debug(" After: ")
- self.write_by_directory(new_results_by_directory, _log.debug, " ")
-
- self._move_baselines(baseline_name, results_by_directory, new_results_by_directory)
- return True
diff --git a/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py b/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py
deleted file mode 100644
index dcd649a5a..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# Copyright (C) 2011 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.
-
-import sys
-import unittest2 as unittest
-
-from webkitpy.common.checkout.baselineoptimizer import BaselineOptimizer
-from webkitpy.common.system.filesystem_mock import MockFileSystem
-from webkitpy.common.host_mock import MockHost
-
-
-class TestBaselineOptimizer(BaselineOptimizer):
- def __init__(self, mock_results_by_directory):
- host = MockHost()
- BaselineOptimizer.__init__(self, host, host.port_factory.all_port_names())
- self._mock_results_by_directory = mock_results_by_directory
-
- # We override this method for testing so we don't have to construct an
- # elaborate mock file system.
- def read_results_by_directory(self, baseline_name):
- return self._mock_results_by_directory
-
- def _move_baselines(self, baseline_name, results_by_directory, new_results_by_directory):
- self.new_results_by_directory = new_results_by_directory
-
-
-class BaselineOptimizerTest(unittest.TestCase):
- def _assertOptimization(self, results_by_directory, expected_new_results_by_directory):
- baseline_optimizer = TestBaselineOptimizer(results_by_directory)
- self.assertTrue(baseline_optimizer.optimize('mock-baseline.png'))
- self.assertEqual(baseline_optimizer.new_results_by_directory, expected_new_results_by_directory)
-
- def _assertOptimizationFailed(self, results_by_directory):
- baseline_optimizer = TestBaselineOptimizer(results_by_directory)
- self.assertFalse(baseline_optimizer.optimize('mock-baseline.png'))
-
- def test_move_baselines(self):
- host = MockHost()
- host.filesystem.write_binary_file('/mock-checkout/LayoutTests/platform/mac-lion/another/test-expected.txt', 'result A')
- host.filesystem.write_binary_file('/mock-checkout/LayoutTests/platform/mac-lion-wk2/another/test-expected.txt', 'result A')
- host.filesystem.write_binary_file('/mock-checkout/LayoutTests/platform/mac/another/test-expected.txt', 'result B')
- baseline_optimizer = BaselineOptimizer(host, host.port_factory.all_port_names())
- baseline_optimizer._move_baselines('another/test-expected.txt', {
- 'LayoutTests/platform/mac-lion': 'aaa',
- 'LayoutTests/platform/mac-lion-wk2': 'aaa',
- 'LayoutTests/platform/mac': 'bbb',
- }, {
- 'LayoutTests/platform/mac': 'aaa',
- })
- self.assertEqual(host.filesystem.read_binary_file('/mock-checkout/LayoutTests/platform/mac/another/test-expected.txt'), 'result A')
-
- def test_efl(self):
- self._assertOptimization({
- 'LayoutTests/platform/efl': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
- }, {
- 'LayoutTests/platform/efl': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
- })
-
- def test_no_add_mac_future(self):
- self._assertOptimization({
- 'LayoutTests/platform/mac': '29a1715a6470d5dd9486a142f609708de84cdac8',
- 'LayoutTests/platform/win-xp': '453e67177a75b2e79905154ece0efba6e5bfb65d',
- 'LayoutTests/platform/mac-lion': 'c43eaeb358f49d5e835236ae23b7e49d7f2b089f',
- }, {
- 'LayoutTests/platform/mac': '29a1715a6470d5dd9486a142f609708de84cdac8',
- 'LayoutTests/platform/win-xp': '453e67177a75b2e79905154ece0efba6e5bfb65d',
- 'LayoutTests/platform/mac-lion': 'c43eaeb358f49d5e835236ae23b7e49d7f2b089f',
- })
-
- def test_mac_future(self):
- self._assertOptimization({
- 'LayoutTests/platform/mac-lion': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
- }, {
- 'LayoutTests/platform/mac-lion': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
- })
-
- def test_qt_unknown(self):
- self._assertOptimization({
- 'LayoutTests/platform/qt': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
- }, {
- 'LayoutTests/platform/qt': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
- })
-
- def test_win_does_not_drop_to_win_7sp0(self):
- self._assertOptimization({
- 'LayoutTests/platform/win': '1',
- 'LayoutTests/platform/mac': '2',
- 'LayoutTests/platform/gtk': '3',
- 'LayoutTests/platform/qt': '4',
- }, {
- 'LayoutTests/platform/win': '1',
- 'LayoutTests/platform/mac': '2',
- 'LayoutTests/platform/gtk': '3',
- 'LayoutTests/platform/qt': '4',
- })
-
- def test_common_directory_includes_root(self):
- # This test case checks that we don't throw an exception when we fail
- # to optimize.
- self._assertOptimizationFailed({
- 'LayoutTests/platform/gtk': 'e8608763f6241ddacdd5c1ef1973ba27177d0846',
- 'LayoutTests/platform/qt': 'bcbd457d545986b7abf1221655d722363079ac87',
- 'LayoutTests/platform/mac': 'e8608763f6241ddacdd5c1ef1973ba27177d0846',
- })
-
- self._assertOptimization({
- 'LayoutTests': '9c876f8c3e4cc2aef9519a6c1174eb3432591127',
- }, {
- 'LayoutTests': '9c876f8c3e4cc2aef9519a6c1174eb3432591127',
- })
-
- def test_complex_shadowing(self):
- # This test relies on OS specific functionality, so it doesn't work on Windows.
- # FIXME: What functionality does this rely on? When can we remove this if?
- if sys.platform == 'win32':
- return
- self._assertOptimization({
- 'LayoutTests/platform/mac': '5daa78e55f05d9f0d1bb1f32b0cd1bc3a01e9364',
- 'LayoutTests/platform/mac-lion': '7ad045ece7c030e2283c5d21d9587be22bcba56e',
- 'LayoutTests/platform/win-xp': '5b1253ef4d5094530d5f1bc6cdb95c90b446bec7',
- }, {
- 'LayoutTests/platform/mac': '5daa78e55f05d9f0d1bb1f32b0cd1bc3a01e9364',
- 'LayoutTests/platform/mac-lion': '7ad045ece7c030e2283c5d21d9587be22bcba56e',
- 'LayoutTests/platform/win-xp': '5b1253ef4d5094530d5f1bc6cdb95c90b446bec7',
- })
-
- def test_virtual_ports_filtered(self):
- self._assertOptimization({
- 'LayoutTests/platform/gtk': '3',
- 'LayoutTests/platform/efl': '3',
- 'LayoutTests/platform/qt': '4',
- 'LayoutTests/platform/mac': '5',
- }, {
- 'LayoutTests': '3',
- 'LayoutTests/platform/qt': '4',
- 'LayoutTests/platform/mac': '5',
- })
diff --git a/Tools/Scripts/webkitpy/common/checkout/changelog.py b/Tools/Scripts/webkitpy/common/checkout/changelog.py
deleted file mode 100644
index 47c6b64c5..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/changelog.py
+++ /dev/null
@@ -1,459 +0,0 @@
-# Copyright (C) 2009, 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.
-#
-# WebKit's Python module for parsing and modifying ChangeLog files
-
-import logging
-import re
-from StringIO import StringIO
-import textwrap
-
-from webkitpy.common.config.committers import CommitterList
-from webkitpy.common.system.filesystem import FileSystem
-import webkitpy.common.config.urls as config_urls
-
-_log = logging.getLogger(__name__)
-
-
-# FIXME: parse_bug_id_from_changelog should not be a free function.
-# Parse the bug ID out of a Changelog message based on the format that is
-# used by prepare-ChangeLog
-def parse_bug_id_from_changelog(message):
- if not message:
- return None
- match = re.search("^\s*" + config_urls.bug_url_short + "$", message, re.MULTILINE)
- if match:
- return int(match.group('bug_id'))
- match = re.search("^\s*" + config_urls.bug_url_long + "$", message, re.MULTILINE)
- if match:
- return int(match.group('bug_id'))
- # We weren't able to find a bug URL in the format used by prepare-ChangeLog. Fall back to the
- # first bug URL found anywhere in the message.
- return config_urls.parse_bug_id(message)
-
-
-class ChangeLogEntry(object):
- # e.g. 2009-06-03 Eric Seidel <eric@webkit.org>
- date_line_regexp = r'^(?P<date>\d{4}-\d{2}-\d{2})\s+(?P<authors>(?P<name>[^<]+?)\s+<(?P<email>[^<>]+)>.*?)$'
-
- # e.g. * Source/WebCore/page/EventHandler.cpp: Implement FooBarQuux.
- touched_files_regexp = r'^\s*\*\s*(?P<file>[A-Za-z0-9_\-\./\\]+)\s*\:'
- # e.g. (ChangeLogEntry.touched_functions): Added.
- touched_functions_regexp = r'^\s*\((?P<function>[^)]*)\):'
-
- # e.g. Reviewed by Darin Adler.
- # (Discard everything after the first period to match more invalid lines.)
- reviewed_by_regexp = r'^\s*((\w+\s+)+and\s+)?(Review|Rubber(\s*|-)stamp)(s|ed)?\s+([a-z]+\s+)*?by\s+(?P<reviewer>.*?)[\.,]?\s*$'
-
- reviewed_byless_regexp = r'^\s*((Review|Rubber(\s*|-)stamp)(s|ed)?|RS)(\s+|\s*=\s*)(?P<reviewer>([A-Z]\w+\s*)+)[\.,]?\s*$'
-
- reviewer_name_noise_regexp = re.compile(r"""
- (\s+((tweaked\s+)?and\s+)?(landed|committed|okayed)\s+by.+) # "landed by", "commented by", etc...
- |(^(Reviewed\s+)?by\s+) # extra "Reviewed by" or "by"
- |([(<]\s*[\w_\-\.]+@[\w_\-\.]+[>)]) # email addresses
- |([(<](https?://?bugs.)webkit.org[^>)]+[>)]) # bug url
- |("[^"]+") # wresler names like 'Sean/Shawn/Shaun' in 'Geoffrey "Sean/Shawn/Shaun" Garen'
- |('[^']+') # wresler names like "The Belly" in "Sam 'The Belly' Weinig"
- |((Mr|Ms|Dr|Mrs|Prof)\.(\s+|$))
- """, re.IGNORECASE | re.VERBOSE)
-
- reviewer_name_casesensitive_noise_regexp = re.compile(r"""
- ((\s+|^)(and\s+)?([a-z-]+\s+){5,}by\s+) # e.g. "and given a good once-over by"
- |(\(\s*(?!(and|[A-Z])).+\)) # any parenthesis that doesn't start with "and" or a capital letter
- |(with(\s+[a-z-]+)+) # phrases with "with no hesitation" in "Sam Weinig with no hesitation"
- """, re.VERBOSE)
-
- reviewer_name_noise_needing_a_backreference_regexp = re.compile(r"""
- (\S\S)\.(?:(\s.+|$)) # Text after the two word characters (don't match initials) and a period followed by a space.
- """, re.IGNORECASE | re.VERBOSE)
-
- nobody_regexp = re.compile(r"""(\s+|^)nobody(
- ((,|\s+-)?\s+(\w+\s+)+fix.*) # e.g. nobody, build fix...
- |(\s*\([^)]+\).*) # NOBODY (..)...
- |$)""", re.IGNORECASE | re.VERBOSE)
-
- # e.g. == Rolled over to ChangeLog-2011-02-16 ==
- rolled_over_regexp = r'^== Rolled over to ChangeLog-\d{4}-\d{2}-\d{2} ==$'
-
- # e.g. git-svn-id: http://svn.webkit.org/repository/webkit/trunk@96161 268f45cc-cd09-0410-ab3c-d52691b4dbfc
- svn_id_regexp = r'git-svn-id: http://svn.webkit.org/repository/webkit/trunk@(?P<svnid>\d+) '
-
- split_names_regexp = r'\s*(?:,(?:\s+and\s+|&)?|(?:^|\s+)and\s+|&&|[/+&])\s*'
-
- def __init__(self, contents, committer_list=CommitterList(), revision=None):
- self._contents = contents
- self._committer_list = committer_list
- self._revision = revision
- self._parse_entry()
-
- @classmethod
- def _parse_reviewer_text(cls, text):
- match = re.search(ChangeLogEntry.reviewed_by_regexp, text, re.MULTILINE | re.IGNORECASE)
- if not match:
- # There are cases where people omit "by". We match it only if reviewer part looked nice
- # in order to avoid matching random lines that start with Reviewed
- match = re.search(ChangeLogEntry.reviewed_byless_regexp, text, re.MULTILINE | re.IGNORECASE)
- if not match:
- return None, None
-
- reviewer_text = match.group("reviewer")
-
- reviewer_text = ChangeLogEntry.nobody_regexp.sub('', reviewer_text)
- reviewer_text = ChangeLogEntry.reviewer_name_noise_regexp.sub('', reviewer_text)
- reviewer_text = ChangeLogEntry.reviewer_name_casesensitive_noise_regexp.sub('', reviewer_text)
- reviewer_text = ChangeLogEntry.reviewer_name_noise_needing_a_backreference_regexp.sub(r'\1', reviewer_text)
- reviewer_text = reviewer_text.replace('(', '').replace(')', '')
- reviewer_text = re.sub(r'\s\s+|[,.]\s*$', ' ', reviewer_text).strip()
- if not len(reviewer_text):
- return None, None
-
- reviewer_list = ChangeLogEntry._split_reviewer_names(reviewer_text)
-
- # Get rid of "reviewers" like "even though this is just a..." in "Reviewed by Sam Weinig, even though this is just a..."
- # and "who wrote the original code" in "Noam Rosenthal, who wrote the original code"
- reviewer_list = [reviewer for reviewer in reviewer_list if not re.match('^who\s|^([a-z]+(\s+|\.|$)){6,}$', reviewer)]
-
- return reviewer_text, reviewer_list
-
- @classmethod
- def _split_reviewer_names(cls, text):
- return re.split(ChangeLogEntry.split_names_regexp, text)
-
- @classmethod
- def _split_author_names_with_emails(cls, text):
- regex = '>' + ChangeLogEntry.split_names_regexp
- names = re.split(regex, text)
- if len(names) > 1:
- names = [name + ">" for name in names[:-1]] + [names[-1]]
- return names
-
- def _fuzz_match_reviewers(self, reviewers_text_list):
- if not reviewers_text_list:
- return []
- list_of_reviewers = [self._committer_list.contributors_by_fuzzy_match(reviewer)[0] for reviewer in reviewers_text_list]
- # Flatten lists and get rid of any reviewers with more than one candidate.
- return [reviewers[0] for reviewers in list_of_reviewers if len(reviewers) == 1]
-
- @classmethod
- def _parse_author_name_and_email(cls, author_name_and_email):
- match = re.match(r'(?P<name>.+?)\s+<(?P<email>[^>]+)>', author_name_and_email)
- return {'name': match.group("name"), 'email': match.group("email")}
-
- @classmethod
- def _parse_author_text(cls, text):
- if not text:
- return []
- authors = cls._split_author_names_with_emails(text)
- assert(authors and len(authors) >= 1)
- return [cls._parse_author_name_and_email(author) for author in authors]
-
- @classmethod
- def _parse_touched_functions(cls, text):
- result = {}
- cur_file = None
- for line in text.splitlines():
- file_match = re.match(cls.touched_files_regexp, line)
- if file_match:
- cur_file = file_match.group("file")
- result[cur_file] = []
- func_match = re.match(cls.touched_functions_regexp, line)
- if func_match and cur_file:
- result[cur_file].append(func_match.group("function"))
- return result
-
- @classmethod
- def _parse_bug_description(cls, text):
- # If line 4 is a bug url, line 3 is the bug description.
- # It's too hard to guess in other cases, so we return None.
- lines = text.splitlines()
- if len(lines) < 4:
- return None
- for bug_url in (config_urls.bug_url_short, config_urls.bug_url_long):
- if re.match("^\s*" + bug_url + "$", lines[3]):
- return lines[2].strip()
- return None
-
- def _parse_entry(self):
- match = re.match(self.date_line_regexp, self._contents, re.MULTILINE)
- if not match:
- _log.warning("Creating invalid ChangeLogEntry:\n%s" % self._contents)
-
- self._date_line = match.group()
- self._date = match.group("date")
- self._bug_description = self._parse_bug_description(self._contents)
-
- # FIXME: group("name") does not seem to be Unicode? Probably due to self._contents not being unicode.
- self._author_text = match.group("authors") if match else None
- self._authors = ChangeLogEntry._parse_author_text(self._author_text)
-
- self._reviewer_text, self._reviewers_text_list = ChangeLogEntry._parse_reviewer_text(self._contents)
- self._reviewers = self._fuzz_match_reviewers(self._reviewers_text_list)
- self._author = self._committer_list.contributor_by_email(self.author_email()) or self._committer_list.contributor_by_name(self.author_name())
-
- self._touched_files = re.findall(self.touched_files_regexp, self._contents, re.MULTILINE)
- self._touched_functions = self._parse_touched_functions(self._contents)
-
- def date_line(self):
- return self._date_line
-
- def date(self):
- return self._date
-
- def author_text(self):
- return self._author_text
-
- def revision(self):
- return self._revision
-
- def author_name(self):
- return self._authors[0]['name']
-
- def author_email(self):
- return self._authors[0]['email']
-
- def author(self):
- return self._author # Might be None
-
- def authors(self):
- return self._authors
-
- # FIXME: Eventually we would like to map reviwer names to reviewer objects.
- # See https://bugs.webkit.org/show_bug.cgi?id=26533
- def reviewer_text(self):
- return self._reviewer_text
-
- # Might be None, might also not be a Reviewer!
- def reviewer(self):
- return self._reviewers[0] if len(self._reviewers) > 0 else None
-
- def reviewers(self):
- return self._reviewers
-
- def has_valid_reviewer(self):
- if self._reviewers_text_list:
- for reviewer in self._reviewers_text_list:
- reviewer = self._committer_list.committer_by_name(reviewer)
- if reviewer:
- return True
- return bool(re.search("unreviewed", self._contents, re.IGNORECASE))
-
- def contents(self):
- return self._contents
-
- def bug_id(self):
- return parse_bug_id_from_changelog(self._contents)
-
- def bug_description(self):
- return self._bug_description
-
- def touched_files(self):
- return self._touched_files
-
- # Returns a dict from file name to lists of function names.
- def touched_functions(self):
- return self._touched_functions
-
- def touched_files_text(self):
- match = re.search(self.touched_files_regexp, self._contents, re.MULTILINE)
- return self._contents[match.start():].lstrip("\n\r") if match else ""
-
- # Determine if any text has been added to the section on touched files
- def is_touched_files_text_clean(self):
- file_line_end = r"( (Added|Removed|(Copied|Renamed) from [A-Za-z0-9_\-./\\]+).)?$"
- for line in self.touched_files_text().splitlines():
- if re.match(self.touched_files_regexp + file_line_end, line):
- continue
- if re.match(self.touched_functions_regexp + "$", line):
- continue
- return False
- return True
-
-# FIXME: Various methods on ChangeLog should move into ChangeLogEntry instead.
-class ChangeLog(object):
-
- def __init__(self, path, filesystem=None):
- self.path = path
- self._filesystem = filesystem or FileSystem()
-
- _changelog_indent = " " * 8
-
- @classmethod
- def parse_latest_entry_from_file(cls, changelog_file):
- try:
- return next(cls.parse_entries_from_file(changelog_file))
- except StopIteration, e:
- return None
-
- svn_blame_regexp = re.compile(r'^(\s*(?P<revision>\d+) [^ ]+)\s*(?P<line>.*?\n)')
-
- @classmethod
- def _separate_revision_and_line(cls, line):
- match = cls.svn_blame_regexp.match(line)
- if not match:
- return None, line
- return int(match.group('revision')), match.group('line')
-
- @classmethod
- def parse_entries_from_file(cls, changelog_file):
- """changelog_file must be a file-like object which returns
- unicode strings, e.g. from StringIO(unicode()) or
- fs.open_text_file_for_reading()"""
- date_line_regexp = re.compile(ChangeLogEntry.date_line_regexp)
- rolled_over_regexp = re.compile(ChangeLogEntry.rolled_over_regexp)
-
- # The first line should be a date line.
- revision, first_line = cls._separate_revision_and_line(changelog_file.readline())
- assert(isinstance(first_line, unicode))
- if not date_line_regexp.match(cls.svn_blame_regexp.sub('', first_line)):
- raise StopIteration
-
- entry_lines = [first_line]
- revisions_in_entry = {revision: 1} if revision != None else None
- for line in changelog_file:
- if revisions_in_entry:
- revision, line = cls._separate_revision_and_line(line)
-
- if rolled_over_regexp.match(line):
- break
-
- if date_line_regexp.match(line):
- most_probable_revision = max(revisions_in_entry, key=revisions_in_entry.__getitem__) if revisions_in_entry else None
- # Remove the extra newline at the end
- yield ChangeLogEntry(''.join(entry_lines[:-1]), revision=most_probable_revision)
- entry_lines = []
- revisions_in_entry = {revision: 0}
-
- entry_lines.append(line)
- if revisions_in_entry:
- revisions_in_entry[revision] = revisions_in_entry.get(revision, 0) + 1
-
- most_probable_revision = max(revisions_in_entry, key=revisions_in_entry.__getitem__) if revisions_in_entry else None
- yield ChangeLogEntry(''.join(entry_lines[:-1]), revision=most_probable_revision)
-
- def latest_entry(self):
- # ChangeLog files are always UTF-8, we read them in as such to support Reviewers with unicode in their names.
- changelog_file = self._filesystem.open_text_file_for_reading(self.path)
- try:
- return self.parse_latest_entry_from_file(changelog_file)
- finally:
- changelog_file.close()
-
- # _wrap_line and _wrap_lines exist to work around
- # http://bugs.python.org/issue1859
-
- def _wrap_line(self, line):
- return textwrap.fill(line,
- width=70,
- initial_indent=self._changelog_indent,
- # Don't break urls which may be longer than width.
- break_long_words=False,
- subsequent_indent=self._changelog_indent)
-
- # Workaround as suggested by guido in
- # http://bugs.python.org/issue1859#msg60040
-
- def _wrap_lines(self, message):
- lines = [self._wrap_line(line) for line in message.splitlines()]
- return "\n".join(lines)
-
- def update_with_unreviewed_message(self, message):
- first_boilerplate_line_regexp = re.compile(
- "%sNeed a short description \(OOPS!\)\." % self._changelog_indent)
- removing_boilerplate = False
- result = StringIO()
- with self._filesystem.open_text_file_for_reading(self.path) as file:
- for line in file:
- if first_boilerplate_line_regexp.search(line):
- message_lines = self._wrap_lines(message)
- result.write(first_boilerplate_line_regexp.sub(message_lines, line))
- # Remove all the ChangeLog boilerplate before the first changed
- # file.
- removing_boilerplate = True
- elif removing_boilerplate:
- if line.find('*') >= 0: # each changed file is preceded by a *
- removing_boilerplate = False
-
- if not removing_boilerplate:
- result.write(line)
- self._filesystem.write_text_file(self.path, result.getvalue())
-
- def set_reviewer(self, reviewer):
- latest_entry = self.latest_entry()
- latest_entry_contents = latest_entry.contents()
- reviewer_text = latest_entry.reviewer()
- found_nobody = re.search("NOBODY\s*\(OOPS!\)", latest_entry_contents, re.MULTILINE)
-
- if not found_nobody and not reviewer_text:
- bug_url_number_of_items = len(re.findall(config_urls.bug_url_long, latest_entry_contents, re.MULTILINE))
- bug_url_number_of_items += len(re.findall(config_urls.bug_url_short, latest_entry_contents, re.MULTILINE))
- result = StringIO()
- with self._filesystem.open_text_file_for_reading(self.path) as file:
- for line in file:
- found_bug_url = re.search(config_urls.bug_url_long, line)
- if not found_bug_url:
- found_bug_url = re.search(config_urls.bug_url_short, line)
- result.write(line)
- if found_bug_url:
- if bug_url_number_of_items == 1:
- result.write("\n Reviewed by %s.\n" % reviewer)
- bug_url_number_of_items -= 1
- self._filesystem.write_text_file(self.path, result.getvalue())
- else:
- data = self._filesystem.read_text_file(self.path)
- newdata = data.replace("NOBODY (OOPS!)", reviewer)
- self._filesystem.write_text_file(self.path, newdata)
-
- def set_short_description_and_bug_url(self, short_description, bug_url):
- message = "%s\n%s%s" % (short_description, self._changelog_indent, bug_url)
- bug_boilerplate = "%sNeed the bug URL (OOPS!).\n" % self._changelog_indent
- result = StringIO()
- with self._filesystem.open_text_file_for_reading(self.path) as file:
- for line in file:
- line = line.replace("Need a short description (OOPS!).", message)
- if line != bug_boilerplate:
- result.write(line)
- self._filesystem.write_text_file(self.path, result.getvalue())
-
- def delete_entries(self, num_entries):
- date_line_regexp = re.compile(ChangeLogEntry.date_line_regexp)
- rolled_over_regexp = re.compile(ChangeLogEntry.rolled_over_regexp)
- entries = 0
- result = StringIO()
- with self._filesystem.open_text_file_for_reading(self.path) as file:
- for line in file:
- if date_line_regexp.match(line):
- entries += 1
- elif rolled_over_regexp.match(line):
- entries = num_entries + 1
- if entries > num_entries:
- result.write(line)
- self._filesystem.write_text_file(self.path, result.getvalue())
-
- def prepend_text(self, text):
- data = self._filesystem.read_text_file(self.path)
- self._filesystem.write_text_file(self.path, text + data)
diff --git a/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py b/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py
deleted file mode 100644
index 05b21e0d3..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py
+++ /dev/null
@@ -1,667 +0,0 @@
-# Copyright (C) 2009 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.
-
-import unittest2 as unittest
-
-from StringIO import StringIO
-
-from webkitpy.common.system.filesystem_mock import MockFileSystem
-from webkitpy.common.checkout.changelog import *
-
-
-class ChangeLogTest(unittest.TestCase):
-
- _changelog_path = 'Tools/ChangeLog'
-
- _example_entry = u'''2009-08-17 Peter Kasting <pkasting@google.com>
-
- Reviewed by Tor Arne Vestb\xf8.
-
- https://bugs.webkit.org/show_bug.cgi?id=27323
- Only add Cygwin to the path when it isn't already there. This avoids
- causing problems for people who purposefully have non-Cygwin versions of
- executables like svn in front of the Cygwin ones in their paths.
-
- * DumpRenderTree/win/DumpRenderTree.vcproj:
- * DumpRenderTree/win/ImageDiff.vcproj:
- * DumpRenderTree/win/TestNetscapePlugin/TestNetscapePlugin.vcproj:
-'''
-
- _rolled_over_footer = '== Rolled over to ChangeLog-2009-06-16 =='
-
- # More example text than we need. Eventually we need to support parsing this all and write tests for the parsing.
- _example_changelog = u"""2009-08-17 Tor Arne Vestb\xf8 <vestbo@webkit.org>
-
- <http://webkit.org/b/28393> check-webkit-style: add check for use of std::max()/std::min() instead of MAX()/MIN()
-
- Reviewed by David Levin.
-
- * Scripts/modules/cpp_style.py:
- (_ERROR_CATEGORIES): Added 'runtime/max_min_macros'.
- (check_max_min_macros): Added. Returns level 4 error when MAX()
- and MIN() macros are used in header files and C++ source files.
- (check_style): Added call to check_max_min_macros().
- * Scripts/modules/cpp_style_unittest.py: Added unit tests.
- (test_max_macro): Added.
- (test_min_macro): Added.
-
-2009-08-16 David Kilzer <ddkilzer@apple.com>
-
- Backed out r47343 which was mistakenly committed
-
- * Scripts/bugzilla-tool:
- * Scripts/modules/scm.py:
-
-2009-06-18 Darin Adler <darin@apple.com>
-
- Rubber stamped by Mark Rowe.
-
- * DumpRenderTree/mac/DumpRenderTreeWindow.mm:
- (-[DumpRenderTreeWindow close]): Resolved crashes seen during regression
- tests. The close method can be called on a window that's already closed
- so we can't assert here.
-
-2011-11-04 Benjamin Poulain <bpoulain@apple.com>
-
- [Mac] ResourceRequest's nsURLRequest() does not differentiate null and empty URLs with CFNetwork
- https://bugs.webkit.org/show_bug.cgi?id=71539
-
- Reviewed by David Kilzer.
-
- In order to have CFURL and NSURL to be consistent when both are used on Mac,
- KURL::createCFURL() is changed to support empty URL values.
-
- * This change log entry is made up to test _parse_entry:
- * a list of things
-
- * platform/cf/KURLCFNet.cpp:
- (WebCore::createCFURLFromBuffer):
- (WebCore::KURL::createCFURL):
- * platform/mac/KURLMac.mm :
- (WebCore::KURL::operator NSURL *):
- (WebCore::KURL::createCFURL):
- * WebCoreSupport/ChromeClientEfl.cpp:
- (WebCore::ChromeClientEfl::closeWindowSoon): call new function and moves its
- previous functionality there.
- * ewk/ewk_private.h:
- * ewk/ewk_view.cpp:
-
-2011-03-02 Carol Szabo <carol.szabo@nokia.com>
-
- Reviewed by David Hyatt <hyatt@apple.com>
-
- content property doesn't support quotes
- https://bugs.webkit.org/show_bug.cgi?id=6503
-
- Added full support for quotes as defined by CSS 2.1.
-
- Tests: fast/css/content/content-quotes-01.html
- fast/css/content/content-quotes-02.html
- fast/css/content/content-quotes-03.html
- fast/css/content/content-quotes-04.html
- fast/css/content/content-quotes-05.html
- fast/css/content/content-quotes-06.html
-
-2011-03-31 Brent Fulgham <bfulgham@webkit.org>
-
- Reviewed Adam Roben.
-
- [WinCairo] Implement Missing drawWindowsBitmap method.
- https://bugs.webkit.org/show_bug.cgi?id=57409
-
-2011-03-28 Dirk Pranke <dpranke@chromium.org>
-
- RS=Tony Chang.
-
- r81977 moved FontPlatformData.h from
- WebCore/platform/graphics/cocoa to platform/graphics. This
- change updates the chromium build accordingly.
-
- https://bugs.webkit.org/show_bug.cgi?id=57281
-
- * platform/graphics/chromium/CrossProcessFontLoading.mm:
-
-2011-05-04 Alexis Menard <alexis.menard@openbossa.org>
-
- Unreviewed warning fix.
-
- The variable is just used in the ASSERT macro. Let's use ASSERT_UNUSED to avoid
- a warning in Release build.
-
- * accessibility/AccessibilityRenderObject.cpp:
- (WebCore::lastChildConsideringContinuation):
-
-2011-10-11 Antti Koivisto <antti@apple.com>
-
- Resolve regular and visited link style in a single pass
- https://bugs.webkit.org/show_bug.cgi?id=69838
-
- Reviewed by Darin Adler
-
- We can simplify and speed up selector matching by removing the recursive matching done
- to generate the style for the :visited pseudo selector. Both regular and visited link style
- can be generated in a single pass through the style selector.
-
-== Rolled over to ChangeLog-2009-06-16 ==
-"""
-
- def test_parse_bug_id_from_changelog(self):
- commit_text = '''
-2011-03-23 Ojan Vafai <ojan@chromium.org>
-
- Add failing result for WebKit2. All tests that require
- focus fail on WebKit2. See https://bugs.webkit.org/show_bug.cgi?id=56988.
-
- * platform/mac-wk2/fast/css/pseudo-any-expected.txt: Added.
-
- '''
-
- self.assertEqual(56988, parse_bug_id_from_changelog(commit_text))
-
- commit_text = '''
-2011-03-23 Ojan Vafai <ojan@chromium.org>
-
- Add failing result for WebKit2. All tests that require
- focus fail on WebKit2. See https://bugs.webkit.org/show_bug.cgi?id=56988.
- https://bugs.webkit.org/show_bug.cgi?id=12345
-
- * platform/mac-wk2/fast/css/pseudo-any-expected.txt: Added.
-
- '''
-
- self.assertEqual(12345, parse_bug_id_from_changelog(commit_text))
-
- commit_text = '''
-2011-03-31 Adam Roben <aroben@apple.com>
-
- Quote the executable path we pass to ::CreateProcessW
-
- This will ensure that spaces in the path will be interpreted correctly.
-
- Fixes <http://webkit.org/b/57569> Web process sometimes fails to launch when there are
- spaces in its path
-
- Reviewed by Steve Falkenburg.
-
- * UIProcess/Launcher/win/ProcessLauncherWin.cpp:
- (WebKit::ProcessLauncher::launchProcess): Surround the executable path in quotes.
-
- '''
-
- self.assertEqual(57569, parse_bug_id_from_changelog(commit_text))
-
- commit_text = '''
-2011-03-29 Timothy Hatcher <timothy@apple.com>
-
- Update WebCore Localizable.strings to contain WebCore, WebKit/mac and WebKit2 strings.
-
- https://webkit.org/b/57354
-
- Reviewed by Sam Weinig.
-
- * English.lproj/Localizable.strings: Updated.
- * StringsNotToBeLocalized.txt: Removed. To hard to maintain in WebCore.
- * platform/network/cf/LoaderRunLoopCF.h: Remove a single quote in an #error so
- extract-localizable-strings does not complain about unbalanced single quotes.
- '''
-
- self.assertEqual(57354, parse_bug_id_from_changelog(commit_text))
-
- def test_parse_log_entries_from_changelog(self):
- changelog_file = StringIO(self._example_changelog)
- parsed_entries = list(ChangeLog.parse_entries_from_file(changelog_file))
- self.assertEqual(len(parsed_entries), 9)
- self.assertEqual(parsed_entries[0].date_line(), u"2009-08-17 Tor Arne Vestb\xf8 <vestbo@webkit.org>")
- self.assertEqual(parsed_entries[0].date(), "2009-08-17")
- self.assertEqual(parsed_entries[0].reviewer_text(), "David Levin")
- self.assertEqual(parsed_entries[0].is_touched_files_text_clean(), False)
- self.assertEqual(parsed_entries[1].date_line(), "2009-08-16 David Kilzer <ddkilzer@apple.com>")
- self.assertEqual(parsed_entries[1].date(), "2009-08-16")
- self.assertEqual(parsed_entries[1].author_email(), "ddkilzer@apple.com")
- self.assertEqual(parsed_entries[1].touched_files_text(), " * Scripts/bugzilla-tool:\n * Scripts/modules/scm.py:\n")
- self.assertEqual(parsed_entries[1].is_touched_files_text_clean(), True)
- self.assertEqual(parsed_entries[2].reviewer_text(), "Mark Rowe")
- self.assertEqual(parsed_entries[2].touched_files(), ["DumpRenderTree/mac/DumpRenderTreeWindow.mm"])
- self.assertEqual(parsed_entries[2].touched_functions(), {"DumpRenderTree/mac/DumpRenderTreeWindow.mm": ["-[DumpRenderTreeWindow close]"]})
- self.assertEqual(parsed_entries[2].is_touched_files_text_clean(), False)
- self.assertEqual(parsed_entries[3].author_name(), "Benjamin Poulain")
- self.assertEqual(parsed_entries[3].touched_files(), ["platform/cf/KURLCFNet.cpp", "platform/mac/KURLMac.mm",
- "WebCoreSupport/ChromeClientEfl.cpp", "ewk/ewk_private.h", "ewk/ewk_view.cpp"])
- self.assertEqual(parsed_entries[3].touched_functions(), {"platform/cf/KURLCFNet.cpp": ["WebCore::createCFURLFromBuffer", "WebCore::KURL::createCFURL"],
- "platform/mac/KURLMac.mm": ["WebCore::KURL::operator NSURL *", "WebCore::KURL::createCFURL"],
- "WebCoreSupport/ChromeClientEfl.cpp": ["WebCore::ChromeClientEfl::closeWindowSoon"], "ewk/ewk_private.h": [], "ewk/ewk_view.cpp": []})
- self.assertEqual(parsed_entries[3].bug_description(), "[Mac] ResourceRequest's nsURLRequest() does not differentiate null and empty URLs with CFNetwork")
- self.assertEqual(parsed_entries[4].reviewer_text(), "David Hyatt")
- self.assertIsNone(parsed_entries[4].bug_description())
- self.assertEqual(parsed_entries[5].reviewer_text(), "Adam Roben")
- self.assertEqual(parsed_entries[6].reviewer_text(), "Tony Chang")
- self.assertIsNone(parsed_entries[7].reviewer_text())
- self.assertEqual(parsed_entries[8].reviewer_text(), 'Darin Adler')
-
- def test_parse_log_entries_from_annotated_file(self):
- # Note that there are trailing spaces on some of the lines intentionally.
- changelog_file = StringIO(u"100000 ossy@webkit.org 2011-11-11 Csaba Osztrogon\u00e1c <ossy@webkit.org>\n"
- u"100000 ossy@webkit.org\n"
- u"100000 ossy@webkit.org 100,000 !!!\n"
- u"100000 ossy@webkit.org \n"
- u"100000 ossy@webkit.org Reviewed by Zoltan Herczeg.\n"
- u"100000 ossy@webkit.org \n"
- u"100000 ossy@webkit.org * ChangeLog: Point out revision 100,000.\n"
- u"100000 ossy@webkit.org \n"
- u"93798 ap@apple.com 2011-08-25 Alexey Proskuryakov <ap@apple.com>\n"
- u"93798 ap@apple.com \n"
- u"93798 ap@apple.com Fix build when GCC 4.2 is not installed.\n"
- u"93798 ap@apple.com \n"
- u"93798 ap@apple.com * gtest/xcode/Config/CompilerVersion.xcconfig: Copied from Source/WebCore/Configurations/CompilerVersion.xcconfig.\n"
- u"93798 ap@apple.com * gtest/xcode/Config/General.xcconfig:\n"
- u"93798 ap@apple.com Use the same compiler version as other projects do.\n"
- u"93798 ap@apple.com\n"
- u"99491 andreas.kling@nokia.com 2011-11-03 Andreas Kling <kling@webkit.org>\n"
- u"99491 andreas.kling@nokia.com \n"
- u"99190 andreas.kling@nokia.com Unreviewed build fix, sigh.\n"
- u"99190 andreas.kling@nokia.com \n"
- u"99190 andreas.kling@nokia.com * css/CSSFontFaceRule.h:\n"
- u"99190 andreas.kling@nokia.com * css/CSSMutableStyleDeclaration.h:\n"
- u"99190 andreas.kling@nokia.com\n"
- u"99190 andreas.kling@nokia.com 2011-11-03 Andreas Kling <kling@webkit.org>\n"
- u"99190 andreas.kling@nokia.com \n"
- u"99187 andreas.kling@nokia.com Unreviewed build fix, out-of-line StyleSheet::parentStyleSheet()\n"
- u"99187 andreas.kling@nokia.com again since there's a cycle in the includes between CSSRule/StyleSheet.\n"
- u"99187 andreas.kling@nokia.com \n"
- u"99187 andreas.kling@nokia.com * css/StyleSheet.cpp:\n"
- u"99187 andreas.kling@nokia.com (WebCore::StyleSheet::parentStyleSheet):\n"
- u"99187 andreas.kling@nokia.com * css/StyleSheet.h:\n"
- u"99187 andreas.kling@nokia.com \n")
-
- parsed_entries = list(ChangeLog.parse_entries_from_file(changelog_file))
- self.assertEqual(parsed_entries[0].revision(), 100000)
- self.assertEqual(parsed_entries[0].reviewer_text(), "Zoltan Herczeg")
- self.assertEqual(parsed_entries[0].author_name(), u"Csaba Osztrogon\u00e1c")
- self.assertEqual(parsed_entries[0].author_email(), "ossy@webkit.org")
- self.assertEqual(parsed_entries[1].revision(), 93798)
- self.assertEqual(parsed_entries[1].author_name(), "Alexey Proskuryakov")
- self.assertEqual(parsed_entries[2].revision(), 99190)
- self.assertEqual(parsed_entries[2].author_name(), "Andreas Kling")
- self.assertEqual(parsed_entries[3].revision(), 99187)
- self.assertEqual(parsed_entries[3].author_name(), "Andreas Kling")
-
- def _assert_parse_reviewer_text_and_list(self, text, expected_reviewer_text, expected_reviewer_text_list=None):
- reviewer_text, reviewer_text_list = ChangeLogEntry._parse_reviewer_text(text)
- self.assertEqual(reviewer_text, expected_reviewer_text)
- if expected_reviewer_text_list:
- self.assertEqual(reviewer_text_list, expected_reviewer_text_list)
- else:
- self.assertEqual(reviewer_text_list, [expected_reviewer_text])
-
- def _assert_parse_reviewer_text_list(self, text, expected_reviewer_text_list):
- reviewer_text, reviewer_text_list = ChangeLogEntry._parse_reviewer_text(text)
- self.assertEqual(reviewer_text_list, expected_reviewer_text_list)
-
- def test_parse_reviewer_text(self):
- self._assert_parse_reviewer_text_and_list(' reviewed by Ryosuke Niwa, Oliver Hunt, and Dimitri Glazkov',
- 'Ryosuke Niwa, Oliver Hunt, and Dimitri Glazkov', ['Ryosuke Niwa', 'Oliver Hunt', 'Dimitri Glazkov'])
- self._assert_parse_reviewer_text_and_list('Reviewed by Brady Eidson and David Levin, landed by Brady Eidson',
- 'Brady Eidson and David Levin', ['Brady Eidson', 'David Levin'])
-
- self._assert_parse_reviewer_text_and_list('Reviewed by Simon Fraser. Committed by Beth Dakin.', 'Simon Fraser')
- self._assert_parse_reviewer_text_and_list('Reviewed by Geoff Garen. V8 fixes courtesy of Dmitry Titov.', 'Geoff Garen')
- self._assert_parse_reviewer_text_and_list('Reviewed by Adam Roben&Dirk Schulze', 'Adam Roben&Dirk Schulze', ['Adam Roben', 'Dirk Schulze'])
- self._assert_parse_reviewer_text_and_list('Rubber stamps by Darin Adler & Sam Weinig.', 'Darin Adler & Sam Weinig', ['Darin Adler', 'Sam Weinig'])
-
- self._assert_parse_reviewer_text_and_list('Reviewed by adam,andy and andy adam, andy smith',
- 'adam,andy and andy adam, andy smith', ['adam', 'andy', 'andy adam', 'andy smith'])
-
- self._assert_parse_reviewer_text_and_list('rubber stamped by Oliver Hunt (oliver@apple.com) and Darin Adler (darin@apple.com)',
- 'Oliver Hunt and Darin Adler', ['Oliver Hunt', 'Darin Adler'])
-
- self._assert_parse_reviewer_text_and_list('rubber Stamped by David Hyatt <hyatt@apple.com>', 'David Hyatt')
- self._assert_parse_reviewer_text_and_list('Rubber-stamped by Antti Koivisto.', 'Antti Koivisto')
- self._assert_parse_reviewer_text_and_list('Rubberstamped by Dan Bernstein.', 'Dan Bernstein')
- self._assert_parse_reviewer_text_and_list('Reviews by Ryosuke Niwa', 'Ryosuke Niwa')
- self._assert_parse_reviewer_text_and_list('Reviews Ryosuke Niwa', 'Ryosuke Niwa')
- self._assert_parse_reviewer_text_and_list('Rubberstamp Ryosuke Niwa', 'Ryosuke Niwa')
- self._assert_parse_reviewer_text_and_list('Typed and reviewed by Alexey Proskuryakov.', 'Alexey Proskuryakov')
- self._assert_parse_reviewer_text_and_list('Reviewed and landed by Brady Eidson', 'Brady Eidson')
- self._assert_parse_reviewer_text_and_list('Reviewed by rniwa@webkit.org.', 'rniwa@webkit.org')
- self._assert_parse_reviewer_text_and_list('Reviewed by Dirk Schulze / Darin Adler.', 'Dirk Schulze / Darin Adler', ['Dirk Schulze', 'Darin Adler'])
- self._assert_parse_reviewer_text_and_list('Reviewed by Sam Weinig + Oliver Hunt.', 'Sam Weinig + Oliver Hunt', ['Sam Weinig', 'Oliver Hunt'])
-
- self._assert_parse_reviewer_text_list('Reviewed by Sam Weinig, and given a good once-over by Jeff Miller.', ['Sam Weinig', 'Jeff Miller'])
- self._assert_parse_reviewer_text_list(' Reviewed by Sam Weinig, even though this is just a...', ['Sam Weinig'])
- self._assert_parse_reviewer_text_list('Rubber stamped by by Gustavo Noronha Silva', ['Gustavo Noronha Silva'])
- self._assert_parse_reviewer_text_list('Rubberstamped by Noam Rosenthal, who wrote the original code.', ['Noam Rosenthal'])
- self._assert_parse_reviewer_text_list('Reviewed by Dan Bernstein (relanding of r47157)', ['Dan Bernstein'])
- self._assert_parse_reviewer_text_list('Reviewed by Geoffrey "Sean/Shawn/Shaun" Garen', ['Geoffrey Garen'])
- self._assert_parse_reviewer_text_list('Reviewed by Dave "Messy" Hyatt.', ['Dave Hyatt'])
- self._assert_parse_reviewer_text_list('Reviewed by Sam \'The Belly\' Weinig', ['Sam Weinig'])
- self._assert_parse_reviewer_text_list('Rubber-stamped by David "I\'d prefer not" Hyatt.', ['David Hyatt'])
- self._assert_parse_reviewer_text_list('Reviewed by Mr. Geoffrey Garen.', ['Geoffrey Garen'])
- self._assert_parse_reviewer_text_list('Reviewed by Darin (ages ago)', ['Darin'])
- self._assert_parse_reviewer_text_list('Reviewed by Sam Weinig (except for a few comment and header tweaks).', ['Sam Weinig'])
- self._assert_parse_reviewer_text_list('Reviewed by Sam Weinig (all but the FormDataListItem rename)', ['Sam Weinig'])
- self._assert_parse_reviewer_text_list('Reviewed by Darin Adler, tweaked and landed by Beth.', ['Darin Adler'])
- self._assert_parse_reviewer_text_list('Reviewed by Sam Weinig with no hesitation', ['Sam Weinig'])
- self._assert_parse_reviewer_text_list('Reviewed by Oliver Hunt, okayed by Darin Adler.', ['Oliver Hunt'])
- self._assert_parse_reviewer_text_list('Reviewed by Darin Adler).', ['Darin Adler'])
-
- # For now, we let unofficial reviewers recognized as reviewers
- self._assert_parse_reviewer_text_list('Reviewed by Sam Weinig, Anders Carlsson, and (unofficially) Adam Barth.',
- ['Sam Weinig', 'Anders Carlsson', 'Adam Barth'])
-
- self._assert_parse_reviewer_text_list('Reviewed by NOBODY.', None)
- self._assert_parse_reviewer_text_list('Reviewed by NOBODY - Build Fix.', None)
- self._assert_parse_reviewer_text_list('Reviewed by NOBODY, layout tests fix.', None)
- self._assert_parse_reviewer_text_list('Reviewed by NOBODY (Qt build fix pt 2).', None)
- self._assert_parse_reviewer_text_list('Reviewed by NOBODY(rollout)', None)
- self._assert_parse_reviewer_text_list('Reviewed by NOBODY (Build fix, forgot to svn add this file)', None)
- self._assert_parse_reviewer_text_list('Reviewed by nobody (trivial follow up fix), Joseph Pecoraro LGTM-ed.', None)
-
- def _entry_with_author(self, author_text):
- return ChangeLogEntry('''2009-08-19 AUTHOR_TEXT
-
- Reviewed by Ryosuke Niwa
-
- * Scripts/bugzilla-tool:
-'''.replace("AUTHOR_TEXT", author_text))
-
- def _entry_with_reviewer(self, reviewer_line):
- return ChangeLogEntry('''2009-08-19 Eric Seidel <eric@webkit.org>
-
- REVIEW_LINE
-
- * Scripts/bugzilla-tool:
-'''.replace("REVIEW_LINE", reviewer_line))
-
- def _contributors(self, names):
- return [CommitterList().contributor_by_name(name) for name in names]
-
- def _assert_fuzzy_reviewer_match(self, reviewer_text, expected_text_list, expected_contributors):
- unused, reviewer_text_list = ChangeLogEntry._parse_reviewer_text(reviewer_text)
- self.assertEqual(reviewer_text_list, expected_text_list)
- self.assertEqual(self._entry_with_reviewer(reviewer_text).reviewers(), self._contributors(expected_contributors))
-
- def test_fuzzy_reviewer_match__none(self):
- self._assert_fuzzy_reviewer_match('Reviewed by BUILD FIX', ['BUILD FIX'], [])
- self._assert_fuzzy_reviewer_match('Reviewed by Mac build fix', ['Mac build fix'], [])
-
- def test_fuzzy_reviewer_match_adam_barth(self):
- self._assert_fuzzy_reviewer_match('Reviewed by Adam Barth.:w', ['Adam Barth.:w'], ['Adam Barth'])
-
- def test_fuzzy_reviewer_match_darin_adler_et_al(self):
- self._assert_fuzzy_reviewer_match('Reviewed by Darin Adler in <https://bugs.webkit.org/show_bug.cgi?id=47736>.', ['Darin Adler in'], ['Darin Adler'])
- self._assert_fuzzy_reviewer_match('Reviewed by Darin Adler, Dan Bernstein, Adele Peterson, and others.',
- ['Darin Adler', 'Dan Bernstein', 'Adele Peterson', 'others'], ['Darin Adler', 'Dan Bernstein', 'Adele Peterson'])
-
- def test_fuzzy_reviewer_match_dimitri_glazkov(self):
- self._assert_fuzzy_reviewer_match('Reviewed by Dimitri Glazkov, build fix', ['Dimitri Glazkov', 'build fix'], ['Dimitri Glazkov'])
-
- def test_fuzzy_reviewer_match_george_staikos(self):
- self._assert_fuzzy_reviewer_match('Reviewed by George Staikos (and others)', ['George Staikos', 'others'], ['George Staikos'])
-
- def test_fuzzy_reviewer_match_mark_rowe(self):
- self._assert_fuzzy_reviewer_match('Reviewed by Mark Rowe, but Dan Bernstein also reviewed and asked thoughtful questions.',
- ['Mark Rowe', 'but Dan Bernstein also reviewed', 'asked thoughtful questions'], ['Mark Rowe'])
-
- def test_fuzzy_reviewer_match_initial(self):
- self._assert_fuzzy_reviewer_match('Reviewed by Alejandro G. Castro.',
- ['Alejandro G. Castro'], ['Alejandro G. Castro'])
- self._assert_fuzzy_reviewer_match('Reviewed by G. Alejandro G. Castro and others.',
- ['G. Alejandro G. Castro', 'others'], ['Alejandro G. Castro'])
-
- # If a reviewer has a name that ended with an initial, the regular expression
- # will incorrectly trim the last period, but it will still match fuzzily to
- # the full reviewer name.
- self._assert_fuzzy_reviewer_match('Reviewed by G. Alejandro G. G. Castro G.',
- ['G. Alejandro G. G. Castro G'], ['Alejandro G. Castro'])
-
- def _assert_parse_authors(self, author_text, expected_contributors):
- parsed_authors = [(author['name'], author['email']) for author in self._entry_with_author(author_text).authors()]
- self.assertEqual(parsed_authors, expected_contributors)
-
- def test_parse_authors(self):
- self._assert_parse_authors(u'Aaron Colwell <acolwell@chromium.org>', [(u'Aaron Colwell', u'acolwell@chromium.org')])
- self._assert_parse_authors('Eric Seidel <eric@webkit.org>, Ryosuke Niwa <rniwa@webkit.org>',
- [('Eric Seidel', 'eric@webkit.org'), ('Ryosuke Niwa', 'rniwa@webkit.org')])
- self._assert_parse_authors('Zan Dobersek <zandobersek@gmail.com> and Philippe Normand <pnormand@igalia.com>',
- [('Zan Dobersek', 'zandobersek@gmail.com'), ('Philippe Normand', 'pnormand@igalia.com')])
- self._assert_parse_authors('New Contributor <new@webkit.org> and Noob <noob@webkit.org>',
- [('New Contributor', 'new@webkit.org'), ('Noob', 'noob@webkit.org')])
- self._assert_parse_authors('Adam Barth <abarth@webkit.org> && Benjamin Poulain <bpoulain@apple.com>',
- [('Adam Barth', 'abarth@webkit.org'), ('Benjamin Poulain', 'bpoulain@apple.com')])
- self._assert_parse_authors(u'Pawe\u0142 Hajdan, Jr. <phajdan.jr@chromium.org>',
- [(u'Pawe\u0142 Hajdan, Jr.', u'phajdan.jr@chromium.org')])
- self._assert_parse_authors(u'Pawe\u0142 Hajdan, Jr. <phajdan.jr@chromium.org>, Adam Barth <abarth@webkit.org>',
- [(u'Pawe\u0142 Hajdan, Jr.', u'phajdan.jr@chromium.org'), (u'Adam Barth', u'abarth@webkit.org')])
-
- def _assert_has_valid_reviewer(self, reviewer_line, expected):
- self.assertEqual(self._entry_with_reviewer(reviewer_line).has_valid_reviewer(), expected)
-
- def test_has_valid_reviewer(self):
- self._assert_has_valid_reviewer("Reviewed by Eric Seidel.", True)
- self._assert_has_valid_reviewer("Reviewed by Eric Seidel", True) # Not picky about the '.'
- self._assert_has_valid_reviewer("Reviewed by Eric.", False)
- self._assert_has_valid_reviewer("Reviewed by Eric C Seidel.", False)
- self._assert_has_valid_reviewer("Rubber-stamped by Eric.", False)
- self._assert_has_valid_reviewer("Rubber-stamped by Eric Seidel.", True)
- self._assert_has_valid_reviewer("Rubber stamped by Eric.", False)
- self._assert_has_valid_reviewer("Rubber stamped by Eric Seidel.", True)
- self._assert_has_valid_reviewer("Unreviewed build fix.", True)
-
- def test_is_touched_files_text_clean(self):
- tests = [
- ('''2013-01-30 Timothy Loh <timloh@chromium.com>
-
- Make ChangeLogEntry detect annotations by prepare-ChangeLog (Added/Removed/Copied from/Renamed from) as clean.
- https://bugs.webkit.org/show_bug.cgi?id=108433
-
- * Scripts/webkitpy/common/checkout/changelog.py:
- (ChangeLogEntry.is_touched_files_text_clean):
- * Scripts/webkitpy/common/checkout/changelog_unittest.py:
- (test_is_touched_files_text_clean):
-''', True),
- ('''2013-01-10 Alan Cutter <alancutter@chromium.org>
-
- Perform some file operations (automatically added comments).
-
- * QueueStatusServer/config/charts.py: Copied from Tools/QueueStatusServer/model/queuelog.py.
- (get_time_unit):
- * QueueStatusServer/handlers/queuecharts.py: Added.
- (QueueCharts):
- * Scripts/webkitpy/tool/bot/testdata/webkit_sheriff_0.js: Removed.
- * EWSTools/build-vm.sh: Renamed from Tools/EWSTools/cold-boot.sh.
-''', True),
- ('''2013-01-30 Timothy Loh <timloh@chromium.com>
-
- Add unit test (manually added comment).
-
- * Scripts/webkitpy/common/checkout/changelog_unittest.py:
- (test_is_touched_files_text_clean): Added.
-''', False),
- ('''2013-01-30 Timothy Loh <timloh@chromium.com>
-
- Add file (manually added comment).
-
- * Scripts/webkitpy/common/checkout/super_changelog.py: Copied from the internet.
-''', False),
- ]
-
- for contents, expected_result in tests:
- entry = ChangeLogEntry(contents)
- self.assertEqual(entry.is_touched_files_text_clean(), expected_result)
-
- def test_latest_entry_parse(self):
- changelog_contents = u"%s\n%s" % (self._example_entry, self._example_changelog)
- changelog_file = StringIO(changelog_contents)
- latest_entry = ChangeLog.parse_latest_entry_from_file(changelog_file)
- self.assertEqual(latest_entry.contents(), self._example_entry)
- self.assertEqual(latest_entry.author_name(), "Peter Kasting")
- self.assertEqual(latest_entry.author_email(), "pkasting@google.com")
- self.assertEqual(latest_entry.reviewer_text(), u"Tor Arne Vestb\xf8")
- touched_files = ["DumpRenderTree/win/DumpRenderTree.vcproj", "DumpRenderTree/win/ImageDiff.vcproj", "DumpRenderTree/win/TestNetscapePlugin/TestNetscapePlugin.vcproj"]
- self.assertEqual(latest_entry.touched_files(), touched_files)
- self.assertEqual(latest_entry.touched_functions(), dict((f, []) for f in touched_files))
-
- self.assertTrue(latest_entry.reviewer()) # Make sure that our UTF8-based lookup of Tor works.
-
- def test_latest_entry_parse_single_entry(self):
- changelog_contents = u"%s\n%s" % (self._example_entry, self._rolled_over_footer)
- changelog_file = StringIO(changelog_contents)
- latest_entry = ChangeLog.parse_latest_entry_from_file(changelog_file)
- self.assertEqual(latest_entry.contents(), self._example_entry)
- self.assertEqual(latest_entry.author_name(), "Peter Kasting")
-
- # FIXME: We really should be getting this from prepare-ChangeLog itself.
- _new_entry_boilerplate = '''2009-08-19 Eric Seidel <eric@webkit.org>
-
- Need a short description (OOPS!).
- Need the bug URL (OOPS!).
-
- Reviewed by NOBODY (OOPS!).
-
- * Scripts/bugzilla-tool:
-'''
-
- _new_entry_boilerplate_with_bugurl = '''2009-08-19 Eric Seidel <eric@webkit.org>
-
- Need a short description (OOPS!).
- https://bugs.webkit.org/show_bug.cgi?id=12345
-
- Reviewed by NOBODY (OOPS!).
-
- * Scripts/bugzilla-tool:
-'''
-
- _new_entry_boilerplate_with_multiple_bugurl = '''2009-08-19 Eric Seidel <eric@webkit.org>
-
- Need a short description (OOPS!).
- https://bugs.webkit.org/show_bug.cgi?id=12345
- http://webkit.org/b/12345
-
- Reviewed by NOBODY (OOPS!).
-
- * Scripts/bugzilla-tool:
-'''
-
- _new_entry_boilerplate_without_reviewer_line = '''2009-08-19 Eric Seidel <eric@webkit.org>
-
- Need a short description (OOPS!).
- https://bugs.webkit.org/show_bug.cgi?id=12345
-
- * Scripts/bugzilla-tool:
-'''
-
- _new_entry_boilerplate_without_reviewer_multiple_bugurl = '''2009-08-19 Eric Seidel <eric@webkit.org>
-
- Need a short description (OOPS!).
- https://bugs.webkit.org/show_bug.cgi?id=12345
- http://webkit.org/b/12345
-
- * Scripts/bugzilla-tool:
-'''
-
- def test_set_reviewer(self):
- fs = MockFileSystem()
-
- changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate_with_bugurl, self._example_changelog)
- reviewer_name = 'Test Reviewer'
- fs.write_text_file(self._changelog_path, changelog_contents)
- ChangeLog(self._changelog_path, fs).set_reviewer(reviewer_name)
- actual_contents = fs.read_text_file(self._changelog_path)
- expected_contents = changelog_contents.replace('NOBODY (OOPS!)', reviewer_name)
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
-
- changelog_contents_without_reviewer_line = u"%s\n%s" % (self._new_entry_boilerplate_without_reviewer_line, self._example_changelog)
- fs.write_text_file(self._changelog_path, changelog_contents_without_reviewer_line)
- ChangeLog(self._changelog_path, fs).set_reviewer(reviewer_name)
- actual_contents = fs.read_text_file(self._changelog_path)
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
-
- changelog_contents_without_reviewer_line = u"%s\n%s" % (self._new_entry_boilerplate_without_reviewer_multiple_bugurl, self._example_changelog)
- fs.write_text_file(self._changelog_path, changelog_contents_without_reviewer_line)
- ChangeLog(self._changelog_path, fs).set_reviewer(reviewer_name)
- actual_contents = fs.read_text_file(self._changelog_path)
- changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate_with_multiple_bugurl, self._example_changelog)
- expected_contents = changelog_contents.replace('NOBODY (OOPS!)', reviewer_name)
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
-
- def test_set_short_description_and_bug_url(self):
- fs = MockFileSystem()
-
- changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate_with_bugurl, self._example_changelog)
- fs.write_text_file(self._changelog_path, changelog_contents)
- short_description = "A short description"
- bug_url = "http://example.com/b/2344"
- ChangeLog(self._changelog_path, fs).set_short_description_and_bug_url(short_description, bug_url)
- actual_contents = fs.read_text_file(self._changelog_path)
- expected_message = "%s\n %s" % (short_description, bug_url)
- expected_contents = changelog_contents.replace("Need a short description (OOPS!).", expected_message)
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
-
- changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog)
- fs.write_text_file(self._changelog_path, changelog_contents)
- short_description = "A short description 2"
- bug_url = "http://example.com/b/2345"
- ChangeLog(self._changelog_path, fs).set_short_description_and_bug_url(short_description, bug_url)
- actual_contents = fs.read_text_file(self._changelog_path)
- expected_message = "%s\n %s" % (short_description, bug_url)
- expected_contents = changelog_contents.replace("Need a short description (OOPS!).\n Need the bug URL (OOPS!).", expected_message)
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
-
- def test_delete_entries(self):
- fs = MockFileSystem()
- fs.write_text_file(self._changelog_path, self._example_changelog)
- ChangeLog(self._changelog_path, fs).delete_entries(8)
- actual_contents = fs.read_text_file(self._changelog_path)
- expected_contents = """2011-10-11 Antti Koivisto <antti@apple.com>
-
- Resolve regular and visited link style in a single pass
- https://bugs.webkit.org/show_bug.cgi?id=69838
-
- Reviewed by Darin Adler
-
- We can simplify and speed up selector matching by removing the recursive matching done
- to generate the style for the :visited pseudo selector. Both regular and visited link style
- can be generated in a single pass through the style selector.
-
-== Rolled over to ChangeLog-2009-06-16 ==
-"""
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
-
- ChangeLog(self._changelog_path, fs).delete_entries(2)
- actual_contents = fs.read_text_file(self._changelog_path)
- expected_contents = "== Rolled over to ChangeLog-2009-06-16 ==\n"
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
-
-
- def test_prepend_text(self):
- fs = MockFileSystem()
- fs.write_text_file(self._changelog_path, self._example_changelog)
- ChangeLog(self._changelog_path, fs).prepend_text(self._example_entry + "\n")
- actual_contents = fs.read_text_file(self._changelog_path)
- expected_contents = self._example_entry + "\n" + self._example_changelog
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
diff --git a/Tools/Scripts/webkitpy/common/checkout/checkout.py b/Tools/Scripts/webkitpy/common/checkout/checkout.py
deleted file mode 100644
index 60e15b29c..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/checkout.py
+++ /dev/null
@@ -1,173 +0,0 @@
-# Copyright (c) 2010 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.
-
-import StringIO
-
-from webkitpy.common.config import urls
-from webkitpy.common.checkout.changelog import ChangeLog, parse_bug_id_from_changelog
-from webkitpy.common.checkout.commitinfo import CommitInfo
-from webkitpy.common.checkout.scm import CommitMessage
-from webkitpy.common.memoized import memoized
-from webkitpy.common.system.executive import ScriptError
-
-
-# This class represents the WebKit-specific parts of the checkout (like ChangeLogs).
-# FIXME: Move a bunch of ChangeLog-specific processing from SCM to this object.
-# NOTE: All paths returned from this class should be absolute.
-class Checkout(object):
- def __init__(self, scm, executive=None, filesystem=None):
- self._scm = scm
- # FIXME: We shouldn't be grabbing at private members on scm.
- self._executive = executive or self._scm._executive
- self._filesystem = filesystem or self._scm._filesystem
-
- def is_path_to_changelog(self, path):
- return self._filesystem.basename(path) == "ChangeLog"
-
- def _latest_entry_for_changelog_at_revision(self, changelog_path, revision):
- changelog_contents = self._scm.contents_at_revision(changelog_path, revision)
- # contents_at_revision returns a byte array (str()), but we know
- # that ChangeLog files are utf-8. parse_latest_entry_from_file
- # expects a file-like object which vends unicode(), so we decode here.
- # Old revisions of Sources/WebKit/wx/ChangeLog have some invalid utf8 characters.
- changelog_file = StringIO.StringIO(changelog_contents.decode("utf-8", "ignore"))
- return ChangeLog.parse_latest_entry_from_file(changelog_file)
-
- def changelog_entries_for_revision(self, revision, changed_files=None):
- if not changed_files:
- changed_files = self._scm.changed_files_for_revision(revision)
- # FIXME: This gets confused if ChangeLog files are moved, as
- # deletes are still "changed files" per changed_files_for_revision.
- # FIXME: For now we hack around this by caching any exceptions
- # which result from having deleted files included the changed_files list.
- changelog_entries = []
- for path in changed_files:
- if not self.is_path_to_changelog(path):
- continue
- try:
- changelog_entries.append(self._latest_entry_for_changelog_at_revision(path, revision))
- except ScriptError:
- pass
- return changelog_entries
-
- def _changelog_data_for_revision(self, revision):
- changed_files = self._scm.changed_files_for_revision(revision)
- changelog_entries = self.changelog_entries_for_revision(revision, changed_files=changed_files)
- # Assume for now that the first entry has everything we need:
- # FIXME: This will throw an exception if there were no ChangeLogs.
- if not len(changelog_entries):
- return None
- changelog_entry = changelog_entries[0]
- return {
- "bug_id": parse_bug_id_from_changelog(changelog_entry.contents()),
- "author_name": changelog_entry.author_name(),
- "author_email": changelog_entry.author_email(),
- "author": changelog_entry.author(),
- "reviewer_text": changelog_entry.reviewer_text(),
- "reviewer": changelog_entry.reviewer(),
- "contents": changelog_entry.contents(),
- "changed_files": changed_files,
- }
-
- @memoized
- def commit_info_for_revision(self, revision):
- committer_email = self._scm.committer_email_for_revision(revision)
- changelog_data = self._changelog_data_for_revision(revision)
- if not changelog_data:
- return None
- return CommitInfo(revision, committer_email, changelog_data)
-
- def bug_id_for_revision(self, revision):
- return self.commit_info_for_revision(revision).bug_id()
-
- def _modified_files_matching_predicate(self, git_commit, predicate, changed_files=None):
- # SCM returns paths relative to scm.checkout_root
- # Callers (especially those using the ChangeLog class) may
- # expect absolute paths, so this method returns absolute paths.
- if not changed_files:
- changed_files = self._scm.changed_files(git_commit)
- return filter(predicate, map(self._scm.absolute_path, changed_files))
-
- def modified_changelogs(self, git_commit, changed_files=None):
- return self._modified_files_matching_predicate(git_commit, self.is_path_to_changelog, changed_files=changed_files)
-
- def modified_non_changelogs(self, git_commit, changed_files=None):
- return self._modified_files_matching_predicate(git_commit, lambda path: not self.is_path_to_changelog(path), changed_files=changed_files)
-
- def commit_message_for_this_commit(self, git_commit, changed_files=None, return_stderr=False):
- changelog_paths = self.modified_changelogs(git_commit, changed_files)
- if not len(changelog_paths):
- raise ScriptError(message="Found no modified ChangeLogs, cannot create a commit message.\n"
- "All changes require a ChangeLog. See:\n %s" % urls.contribution_guidelines)
-
- message_text = self._scm.run([self._scm.script_path('commit-log-editor'), '--print-log'] + changelog_paths, return_stderr=return_stderr)
- return CommitMessage(message_text.splitlines())
-
- def recent_commit_infos_for_files(self, paths):
- revisions = set(sum(map(self._scm.revisions_changing_file, paths), []))
- return set(map(self.commit_info_for_revision, revisions))
-
- def suggested_reviewers(self, git_commit, changed_files=None):
- changed_files = self.modified_non_changelogs(git_commit, changed_files)
- commit_infos = sorted(self.recent_commit_infos_for_files(changed_files), key=lambda info: info.revision(), reverse=True)
- reviewers = filter(lambda person: person and person.can_review, sum(map(lambda info: [info.reviewer(), info.author()], commit_infos), []))
- unique_reviewers = reduce(lambda suggestions, reviewer: suggestions + [reviewer if reviewer not in suggestions else None], reviewers, [])
- return filter(lambda reviewer: reviewer, unique_reviewers)
-
- def bug_id_for_this_commit(self, git_commit, changed_files=None):
- try:
- return parse_bug_id_from_changelog(self.commit_message_for_this_commit(git_commit, changed_files).message())
- except ScriptError, e:
- pass # We might not have ChangeLogs.
-
- def apply_patch(self, patch):
- # It's possible that the patch was not made from the root directory.
- # We should detect and handle that case.
- # FIXME: Move _scm.script_path here once we get rid of all the dependencies.
- # --force (continue after errors) is the common case, so we always use it.
- args = [self._scm.script_path('svn-apply'), "--force"]
- if patch.reviewer():
- args += ['--reviewer', patch.reviewer().full_name]
- self._executive.run_command(args, input=patch.contents(), cwd=self._scm.checkout_root)
-
- def apply_reverse_diff(self, revision):
- self._scm.apply_reverse_diff(revision)
-
- # We revert the ChangeLogs because removing lines from a ChangeLog
- # doesn't make sense. ChangeLogs are append only.
- changelog_paths = self.modified_changelogs(git_commit=None)
- if len(changelog_paths):
- self._scm.revert_files(changelog_paths)
-
- conflicts = self._scm.conflicted_files()
- if len(conflicts):
- raise ScriptError(message="Failed to apply reverse diff for revision %s because of the following conflicts:\n%s" % (revision, "\n".join(conflicts)))
-
- def apply_reverse_diffs(self, revision_list):
- for revision in sorted(revision_list, reverse=True):
- self.apply_reverse_diff(revision)
diff --git a/Tools/Scripts/webkitpy/common/checkout/checkout_mock.py b/Tools/Scripts/webkitpy/common/checkout/checkout_mock.py
deleted file mode 100644
index 8a17145ca..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/checkout_mock.py
+++ /dev/null
@@ -1,113 +0,0 @@
-# Copyright (C) 2011 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.
-
-from .commitinfo import CommitInfo
-
-# FIXME: These imports are wrong, we should use a shared MockCommittersList.
-from webkitpy.common.config.committers import CommitterList
-from webkitpy.common.net.bugzilla.bugzilla_mock import _mock_reviewers
-from webkitpy.common.system.filesystem_mock import MockFileSystem
-
-
-class MockCommitMessage(object):
- def message(self):
- return "This is a fake commit message that is at least 50 characters."
-
-
-committer_list = CommitterList()
-
-mock_revisions = {
- 1: CommitInfo(852, "eric@webkit.org", {
- "bug_id": 50000,
- "author_name": "Adam Barth",
- "author_email": "abarth@webkit.org",
- "author": committer_list.contributor_by_email("abarth@webkit.org"),
- "reviewer_text": "Darin Adler",
- "reviewer": committer_list.committer_by_name("Darin Adler"),
- "changed_files": [
- "path/to/file",
- "another/file",
- ],
- }),
- 3001: CommitInfo(3001, "tomz@codeaurora.org", {
- "bug_id": 50004,
- "author_name": "Tom Zakrajsek",
- "author_email": "tomz@codeaurora.org",
- "author": committer_list.contributor_by_email("tomz@codeaurora.org"),
- "reviewer_text": "Darin Adler",
- "reviewer": committer_list.committer_by_name("Darin Adler"),
- "changed_files": [
- "path/to/file",
- "another/file",
- ],
- })
-}
-
-class MockCheckout(object):
- def __init__(self):
- # FIXME: It's unclear if a MockCheckout is very useful. A normal Checkout
- # with a MockSCM/MockFileSystem/MockExecutive is probably better.
- self._filesystem = MockFileSystem()
-
- def commit_info_for_revision(self, svn_revision):
- # There are legacy tests that all expected these revision numbers to map
- # to the same commit description (now mock_revisions[1])
- if svn_revision in [32, 123, 852, 853, 854, 1234, 21654, 21655, 21656]:
- return mock_revisions[1]
-
- if svn_revision in mock_revisions:
- return mock_revisions[svn_revision]
-
- # any "unrecognized" svn_revision will return None.
-
- def is_path_to_changelog(self, path):
- return self._filesystem.basename(path) == "ChangeLog"
-
- def bug_id_for_revision(self, svn_revision):
- return 12345
-
- def recent_commit_infos_for_files(self, paths):
- return [self.commit_info_for_revision(32)]
-
- def modified_changelogs(self, git_commit, changed_files=None):
- # Ideally we'd return something more interesting here. The problem is
- # that LandDiff will try to actually read the patch from disk!
- return []
-
- def commit_message_for_this_commit(self, git_commit, changed_files=None):
- return MockCommitMessage()
-
- def apply_patch(self, patch):
- pass
-
- def apply_reverse_diffs(self, revision):
- pass
-
- def suggested_reviewers(self, git_commit, changed_files=None):
- # FIXME: We should use a shared mock commiter list.
- return [_mock_reviewers[0]]
diff --git a/Tools/Scripts/webkitpy/common/checkout/checkout_unittest.py b/Tools/Scripts/webkitpy/common/checkout/checkout_unittest.py
deleted file mode 100644
index 587798e77..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/checkout_unittest.py
+++ /dev/null
@@ -1,260 +0,0 @@
-# Copyright (C) 2010 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.
-
-import codecs
-import os
-import shutil
-import tempfile
-import unittest2 as unittest
-
-from .checkout import Checkout
-from .changelog import ChangeLogEntry
-from .scm import CommitMessage, SCMDetector
-from .scm.scm_mock import MockSCM
-from webkitpy.common.webkit_finder import WebKitFinder
-from webkitpy.common.system.executive import Executive, ScriptError
-from webkitpy.common.system.filesystem import FileSystem # FIXME: This should not be needed.
-from webkitpy.common.system.filesystem_mock import MockFileSystem
-from webkitpy.common.system.executive_mock import MockExecutive
-from webkitpy.common.system.outputcapture import OutputCapture
-from webkitpy.thirdparty.mock import Mock
-
-
-_changelog1entry1 = u"""2010-03-25 Tor Arne Vestb\u00f8 <vestbo@webkit.org>
-
- Unreviewed build fix to un-break webkit-patch land.
-
- Move commit_message_for_this_commit from scm to checkout
- https://bugs.webkit.org/show_bug.cgi?id=36629
-
- * Scripts/webkitpy/common/checkout/api.py: import scm.CommitMessage
-"""
-_changelog1entry2 = u"""2010-03-25 Adam Barth <abarth@webkit.org>
-
- Reviewed by Eric Seidel.
-
- Move commit_message_for_this_commit from scm to checkout
- https://bugs.webkit.org/show_bug.cgi?id=36629
-
- * Scripts/webkitpy/common/checkout/api.py:
-"""
-_changelog1 = u"\n".join([_changelog1entry1, _changelog1entry2])
-_changelog2 = u"""2010-03-25 Tor Arne Vestb\u00f8 <vestbo@webkit.org>
-
- Unreviewed build fix to un-break webkit-patch land.
-
- Second part of this complicated change by me, Tor Arne Vestb\u00f8!
-
- * Path/To/Complicated/File: Added.
-
-2010-03-25 Adam Barth <abarth@webkit.org>
-
- Reviewed by Eric Seidel.
-
- Filler change.
-"""
-
-class CommitMessageForThisCommitTest(unittest.TestCase):
- expected_commit_message = u"""Unreviewed build fix to un-break webkit-patch land.
-
-Tools:
-
-Move commit_message_for_this_commit from scm to checkout
-https://bugs.webkit.org/show_bug.cgi?id=36629
-
-* Scripts/webkitpy/common/checkout/api.py: import scm.CommitMessage
-
-LayoutTests:
-
-Second part of this complicated change by me, Tor Arne Vestb\u00f8!
-
-* Path/To/Complicated/File: Added.
-"""
-
- def setUp(self):
- # FIXME: This should not need to touch the filesystem, however
- # ChangeLog is difficult to mock at current.
- self.filesystem = FileSystem()
- self.temp_dir = str(self.filesystem.mkdtemp(suffix="changelogs"))
- self.old_cwd = self.filesystem.getcwd()
- self.filesystem.chdir(self.temp_dir)
- self.webkit_base = WebKitFinder(self.filesystem).webkit_base()
-
- # Trick commit-log-editor into thinking we're in a Subversion working copy so it won't
- # complain about not being able to figure out what SCM is in use.
- # FIXME: VCSTools.pm is no longer so easily fooled. It logs because "svn info" doesn't
- # treat a bare .svn directory being part of an svn checkout.
- self.filesystem.maybe_make_directory(".svn")
-
- self.changelogs = map(self.filesystem.abspath, (self.filesystem.join("Tools", "ChangeLog"), self.filesystem.join("LayoutTests", "ChangeLog")))
- for path, contents in zip(self.changelogs, (_changelog1, _changelog2)):
- self.filesystem.maybe_make_directory(self.filesystem.dirname(path))
- self.filesystem.write_text_file(path, contents)
-
- def tearDown(self):
- self.filesystem.rmtree(self.temp_dir)
- self.filesystem.chdir(self.old_cwd)
-
- def test_commit_message_for_this_commit(self):
- executive = Executive()
-
- def mock_run(*args, **kwargs):
- # Note that we use a real Executive here, not a MockExecutive, so we can test that we're
- # invoking commit-log-editor correctly.
- env = os.environ.copy()
- env['CHANGE_LOG_EMAIL_ADDRESS'] = 'vestbo@webkit.org'
- kwargs['env'] = env
- return executive.run_command(*args, **kwargs)
-
- detector = SCMDetector(self.filesystem, executive)
- real_scm = detector.detect_scm_system(self.webkit_base)
-
- mock_scm = MockSCM()
- mock_scm.run = mock_run
- mock_scm.script_path = real_scm.script_path
-
- checkout = Checkout(mock_scm)
- checkout.modified_changelogs = lambda git_commit, changed_files=None: self.changelogs
- commit_message = checkout.commit_message_for_this_commit(git_commit=None, return_stderr=True)
- # Throw away the first line - a warning about unknown VCS root.
- commit_message.message_lines = commit_message.message_lines[1:]
- self.assertMultiLineEqual(commit_message.message(), self.expected_commit_message)
-
-
-class CheckoutTest(unittest.TestCase):
- def _make_checkout(self):
- return Checkout(scm=MockSCM(), filesystem=MockFileSystem(), executive=MockExecutive())
-
- def test_latest_entry_for_changelog_at_revision(self):
- def mock_contents_at_revision(changelog_path, revision):
- self.assertEqual(changelog_path, "foo")
- self.assertEqual(revision, "bar")
- # contents_at_revision is expected to return a byte array (str)
- # so we encode our unicode ChangeLog down to a utf-8 stream.
- # The ChangeLog utf-8 decoding should ignore invalid codepoints.
- invalid_utf8 = "\255"
- return _changelog1.encode("utf-8") + invalid_utf8
- checkout = self._make_checkout()
- checkout._scm.contents_at_revision = mock_contents_at_revision
- entry = checkout._latest_entry_for_changelog_at_revision("foo", "bar")
- self.assertMultiLineEqual(entry.contents(), _changelog1entry1) # Pylint is confused about this line, pylint: disable=E1101
-
- # FIXME: This tests a hack around our current changed_files handling.
- # Right now changelog_entries_for_revision tries to fetch deleted files
- # from revisions, resulting in a ScriptError exception. Test that we
- # recover from those and still return the other ChangeLog entries.
- def test_changelog_entries_for_revision(self):
- checkout = self._make_checkout()
- checkout._scm.changed_files_for_revision = lambda revision: ['foo/ChangeLog', 'bar/ChangeLog']
-
- def mock_latest_entry_for_changelog_at_revision(path, revision):
- if path == "foo/ChangeLog":
- return 'foo'
- raise ScriptError()
-
- checkout._latest_entry_for_changelog_at_revision = mock_latest_entry_for_changelog_at_revision
-
- # Even though fetching one of the entries failed, the other should succeed.
- entries = checkout.changelog_entries_for_revision(1)
- self.assertEqual(len(entries), 1)
- self.assertEqual(entries[0], 'foo')
-
- def test_commit_info_for_revision(self):
- checkout = self._make_checkout()
- checkout._scm.changed_files_for_revision = lambda revision: ['path/to/file', 'another/file']
- checkout._scm.committer_email_for_revision = lambda revision, changed_files=None: "committer@example.com"
- checkout.changelog_entries_for_revision = lambda revision, changed_files=None: [ChangeLogEntry(_changelog1entry1)]
- commitinfo = checkout.commit_info_for_revision(4)
- self.assertEqual(commitinfo.bug_id(), 36629)
- self.assertEqual(commitinfo.author_name(), u"Tor Arne Vestb\u00f8")
- self.assertEqual(commitinfo.author_email(), "vestbo@webkit.org")
- self.assertIsNone(commitinfo.reviewer_text())
- self.assertIsNone(commitinfo.reviewer())
- self.assertEqual(commitinfo.committer_email(), "committer@example.com")
- self.assertIsNone(commitinfo.committer())
- self.assertEqual(commitinfo.to_json(), {
- 'bug_id': 36629,
- 'author_email': 'vestbo@webkit.org',
- 'changed_files': [
- 'path/to/file',
- 'another/file',
- ],
- 'reviewer_text': None,
- 'author_name': u'Tor Arne Vestb\xf8',
- })
-
- checkout.changelog_entries_for_revision = lambda revision, changed_files=None: []
- self.assertIsNone(checkout.commit_info_for_revision(1))
-
- def test_bug_id_for_revision(self):
- checkout = self._make_checkout()
- checkout._scm.committer_email_for_revision = lambda revision: "committer@example.com"
- checkout.changelog_entries_for_revision = lambda revision, changed_files=None: [ChangeLogEntry(_changelog1entry1)]
- self.assertEqual(checkout.bug_id_for_revision(4), 36629)
-
- def test_bug_id_for_this_commit(self):
- checkout = self._make_checkout()
- checkout.commit_message_for_this_commit = lambda git_commit, changed_files=None: CommitMessage(ChangeLogEntry(_changelog1entry1).contents().splitlines())
- self.assertEqual(checkout.bug_id_for_this_commit(git_commit=None), 36629)
-
- def test_modified_changelogs(self):
- checkout = self._make_checkout()
- checkout._scm.checkout_root = "/foo/bar"
- checkout._scm.changed_files = lambda git_commit: ["file1", "ChangeLog", "relative/path/ChangeLog"]
- expected_changlogs = ["/foo/bar/ChangeLog", "/foo/bar/relative/path/ChangeLog"]
- self.assertEqual(checkout.modified_changelogs(git_commit=None), expected_changlogs)
-
- def test_suggested_reviewers(self):
- def mock_changelog_entries_for_revision(revision, changed_files=None):
- if revision % 2 == 0:
- return [ChangeLogEntry(_changelog1entry1)]
- return [ChangeLogEntry(_changelog1entry2)]
-
- def mock_revisions_changing_file(path, limit=5):
- if path.endswith("ChangeLog"):
- return [3]
- return [4, 8]
-
- checkout = self._make_checkout()
- checkout._scm.checkout_root = "/foo/bar"
- checkout._scm.changed_files = lambda git_commit: ["file1", "file2", "relative/path/ChangeLog"]
- checkout._scm.revisions_changing_file = mock_revisions_changing_file
- checkout.changelog_entries_for_revision = mock_changelog_entries_for_revision
- reviewers = checkout.suggested_reviewers(git_commit=None)
- reviewer_names = [reviewer.full_name for reviewer in reviewers]
- self.assertEqual(reviewer_names, [u'Tor Arne Vestb\xf8'])
-
- def test_apply_patch(self):
- checkout = self._make_checkout()
- checkout._executive = MockExecutive(should_log=True)
- checkout._scm.script_path = lambda script: script
- mock_patch = Mock()
- mock_patch.contents = lambda: "foo"
- mock_patch.reviewer = lambda: None
- expected_logs = "MOCK run_command: ['svn-apply', '--force'], cwd=/mock-checkout, input=foo\n"
- OutputCapture().assert_outputs(self, checkout.apply_patch, [mock_patch], expected_logs=expected_logs)
diff --git a/Tools/Scripts/webkitpy/common/checkout/commitinfo.py b/Tools/Scripts/webkitpy/common/checkout/commitinfo.py
deleted file mode 100644
index 79cb79f7c..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/commitinfo.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright (c) 2010 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.
-#
-# WebKit's python module for holding information on a commit
-
-from webkitpy.common.config import urls
-from webkitpy.common.config.committers import CommitterList
-
-
-class CommitInfo(object):
- def __init__(self, revision, committer_email, changelog_data, committer_list=CommitterList()):
- self._revision = revision
- self._committer_email = committer_email
- self._changelog_data = changelog_data
-
- # Derived values:
- self._committer = committer_list.committer_by_email(committer_email)
-
- def revision(self):
- return self._revision
-
- def committer(self):
- return self._committer # None if committer isn't in contributors.json
-
- def committer_email(self):
- return self._committer_email
-
- def bug_id(self):
- return self._changelog_data["bug_id"] # May be None
-
- def author(self):
- return self._changelog_data["author"] # May be None
-
- def author_name(self):
- return self._changelog_data["author_name"]
-
- def author_email(self):
- return self._changelog_data["author_email"]
-
- def reviewer(self):
- return self._changelog_data["reviewer"] # May be None
-
- def reviewer_text(self):
- return self._changelog_data["reviewer_text"] # May be None
-
- def changed_files(self):
- return self._changelog_data["changed_files"]
-
- def to_json(self):
- return {
- "bug_id": self.bug_id(),
- "author_name": self.author_name(),
- "author_email": self.author_email(),
- "reviewer_text": self.reviewer_text(),
- "changed_files": self.changed_files(),
- }
-
- def responsible_parties(self):
- responsible_parties = [
- self.committer(),
- self.author(),
- self.reviewer(),
- ]
- return set([party for party in responsible_parties if party]) # Filter out None
-
- # FIXME: It is slightly lame that this "view" method is on this "model" class (in MVC terms)
- def blame_string(self, bugs):
- string = "r%s:\n" % self.revision()
- string += " %s\n" % urls.view_revision_url(self.revision())
- string += " Bug: %s (%s)\n" % (self.bug_id(), bugs.bug_url_for_bug_id(self.bug_id()))
- author_line = "\"%s\" <%s>" % (self.author_name(), self.author_email())
- string += " Author: %s\n" % (self.author() or author_line)
- string += " Reviewer: %s\n" % (self.reviewer() or self.reviewer_text())
- string += " Committer: %s" % self.committer()
- return string
diff --git a/Tools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py b/Tools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py
deleted file mode 100644
index 826673de6..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright (C) 2010 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.
-
-import unittest2 as unittest
-
-from webkitpy.common.checkout.commitinfo import CommitInfo
-from webkitpy.common.config.committers import CommitterList, Committer, Reviewer
-
-class CommitInfoTest(unittest.TestCase):
-
- def test_commit_info_creation(self):
- author = Committer("Author", "author@example.com")
- committer = Committer("Committer", "committer@example.com")
- reviewer = Reviewer("Reviewer", "reviewer@example.com")
- committer_list = CommitterList(committers=[author, committer], reviewers=[reviewer])
-
- changelog_data = {
- "bug_id": 1234,
- "author_name": "Committer",
- "author_email": "author@example.com",
- "author": author,
- "reviewer_text": "Reviewer",
- "reviewer": reviewer,
- }
- commit = CommitInfo(123, "committer@example.com", changelog_data, committer_list)
-
- self.assertEqual(commit.revision(), 123)
- self.assertEqual(commit.bug_id(), 1234)
- self.assertEqual(commit.author_name(), "Committer")
- self.assertEqual(commit.author_email(), "author@example.com")
- self.assertEqual(commit.author(), author)
- self.assertEqual(commit.reviewer_text(), "Reviewer")
- self.assertEqual(commit.reviewer(), reviewer)
- self.assertEqual(commit.committer(), committer)
- self.assertEqual(commit.committer_email(), "committer@example.com")
- self.assertEqual(commit.responsible_parties(), set([author, committer, reviewer]))
diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
deleted file mode 100644
index 3a9ea9224..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# Copyright (C) 2009 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.
-
-"""WebKit's Python module for interacting with patches."""
-
-import logging
-import re
-
-_log = logging.getLogger(__name__)
-
-
-# FIXME: This is broken. We should compile our regexps up-front
-# instead of using a custom cache.
-_regexp_compile_cache = {}
-
-
-# FIXME: This function should be removed.
-def match(pattern, string):
- """Matches the string with the pattern, caching the compiled regexp."""
- if not pattern in _regexp_compile_cache:
- _regexp_compile_cache[pattern] = re.compile(pattern)
- return _regexp_compile_cache[pattern].match(string)
-
-
-# FIXME: This belongs on DiffParser (e.g. as to_svn_diff()).
-def git_diff_to_svn_diff(line):
- """Converts a git formatted diff line to a svn formatted line.
-
- Args:
- line: A string representing a line of the diff.
- """
- # FIXME: This list should be a class member on DiffParser.
- # These regexp patterns should be compiled once instead of every time.
- conversion_patterns = (("^diff --git \w/(.+) \w/(?P<FilePath>.+)", lambda matched: "Index: " + matched.group('FilePath') + "\n"),
- ("^new file.*", lambda matched: "\n"),
- ("^index (([0-9a-f]{7}\.\.[0-9a-f]{7})|([0-9a-f]{40}\.\.[0-9a-f]{40})) [0-9]{6}", lambda matched: "===================================================================\n"),
- ("^--- \w/(?P<FilePath>.+)", lambda matched: "--- " + matched.group('FilePath') + "\n"),
- ("^\+\+\+ \w/(?P<FilePath>.+)", lambda matched: "+++ " + matched.group('FilePath') + "\n"))
-
- for pattern, conversion in conversion_patterns:
- matched = match(pattern, line)
- if matched:
- return conversion(matched)
- return line
-
-
-# This function exists so we can unittest get_diff_converter function
-def svn_diff_to_svn_diff(line):
- return line
-
-
-# FIXME: This method belongs on DiffParser
-def get_diff_converter(lines):
- """Gets a converter function of diff lines.
-
- Args:
- lines: The lines of a diff file.
- If this line is git formatted, we'll return a
- converter from git to SVN.
- """
- for i, line in enumerate(lines[:-1]):
- # Stop when we find the first patch
- if line[:3] == "+++" and lines[i + 1] == "---":
- break
- if match(r"^diff --git \w/", line):
- return git_diff_to_svn_diff
- return svn_diff_to_svn_diff
-
-_INITIAL_STATE = 1
-_DECLARED_FILE_PATH = 2
-_PROCESSING_CHUNK = 3
-
-
-class DiffFile(object):
- """Contains the information for one file in a patch.
-
- The field "lines" is a list which contains tuples in this format:
- (deleted_line_number, new_line_number, line_string)
- If deleted_line_number is zero, it means this line is newly added.
- If new_line_number is zero, it means this line is deleted.
- """
- # FIXME: Tuples generally grow into classes. We should consider
- # adding a DiffLine object.
-
- def added_or_modified_line_numbers(self):
- # This logic was moved from patchreader.py, but may not be
- # the right API for this object long-term.
- return [line[1] for line in self.lines if not line[0]]
-
- def __init__(self, filename):
- self.filename = filename
- self.lines = []
-
- def add_new_line(self, line_number, line):
- self.lines.append((0, line_number, line))
-
- def add_deleted_line(self, line_number, line):
- self.lines.append((line_number, 0, line))
-
- def add_unchanged_line(self, deleted_line_number, new_line_number, line):
- self.lines.append((deleted_line_number, new_line_number, line))
-
-
-# If this is going to be called DiffParser, it should be a re-useable parser.
-# Otherwise we should rename it to ParsedDiff or just Diff.
-class DiffParser(object):
- """A parser for a patch file.
-
- The field "files" is a dict whose key is the filename and value is
- a DiffFile object.
- """
-
- def __init__(self, diff_input):
- """Parses a diff.
-
- Args:
- diff_input: An iterable object.
- """
- self.files = self._parse_into_diff_files(diff_input)
-
- # FIXME: This function is way too long and needs to be broken up.
- def _parse_into_diff_files(self, diff_input):
- files = {}
- state = _INITIAL_STATE
- current_file = None
- old_diff_line = None
- new_diff_line = None
- transform_line = get_diff_converter(diff_input)
- for line in diff_input:
- line = line.rstrip("\n")
- line = transform_line(line)
-
- file_declaration = match(r"^Index: (?P<FilePath>.+)", line)
- if file_declaration:
- filename = file_declaration.group('FilePath')
- current_file = DiffFile(filename)
- files[filename] = current_file
- state = _DECLARED_FILE_PATH
- continue
-
- lines_changed = match(r"^@@ -(?P<OldStartLine>\d+)(,\d+)? \+(?P<NewStartLine>\d+)(,\d+)? @@", line)
- if lines_changed:
- if state != _DECLARED_FILE_PATH and state != _PROCESSING_CHUNK:
- _log.error('Unexpected line change without file path '
- 'declaration: %r' % line)
- old_diff_line = int(lines_changed.group('OldStartLine'))
- new_diff_line = int(lines_changed.group('NewStartLine'))
- state = _PROCESSING_CHUNK
- continue
-
- if state == _PROCESSING_CHUNK:
- if line.startswith('+'):
- current_file.add_new_line(new_diff_line, line[1:])
- new_diff_line += 1
- elif line.startswith('-'):
- current_file.add_deleted_line(old_diff_line, line[1:])
- old_diff_line += 1
- elif line.startswith(' '):
- current_file.add_unchanged_line(old_diff_line, new_diff_line, line[1:])
- old_diff_line += 1
- new_diff_line += 1
- elif line == '\\ No newline at end of file':
- # Nothing to do. We may still have some added lines.
- pass
- else:
- _log.error('Unexpected diff format when parsing a '
- 'chunk: %r' % line)
- return files
diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py b/Tools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py
deleted file mode 100644
index 78dab26bc..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# Copyright (C) 2009 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.
-
-import cStringIO as StringIO
-import unittest2 as unittest
-import diff_parser
-import re
-
-from webkitpy.common.checkout.diff_test_data import DIFF_TEST_DATA
-
-class DiffParserTest(unittest.TestCase):
- maxDiff = None
-
- def test_diff_parser(self, parser = None):
- if not parser:
- parser = diff_parser.DiffParser(DIFF_TEST_DATA.splitlines())
- self.assertEqual(3, len(parser.files))
-
- self.assertTrue('WebCore/rendering/style/StyleFlexibleBoxData.h' in parser.files)
- diff = parser.files['WebCore/rendering/style/StyleFlexibleBoxData.h']
- self.assertEqual(7, len(diff.lines))
- # The first two unchaged lines.
- self.assertEqual((47, 47), diff.lines[0][0:2])
- self.assertEqual('', diff.lines[0][2])
- self.assertEqual((48, 48), diff.lines[1][0:2])
- self.assertEqual(' unsigned align : 3; // EBoxAlignment', diff.lines[1][2])
- # The deleted line
- self.assertEqual((50, 0), diff.lines[3][0:2])
- self.assertEqual(' unsigned orient: 1; // EBoxOrient', diff.lines[3][2])
-
- # The first file looks OK. Let's check the next, more complicated file.
- self.assertTrue('WebCore/rendering/style/StyleRareInheritedData.cpp' in parser.files)
- diff = parser.files['WebCore/rendering/style/StyleRareInheritedData.cpp']
- # There are 3 chunks.
- self.assertEqual(7 + 7 + 9, len(diff.lines))
- # Around an added line.
- self.assertEqual((60, 61), diff.lines[9][0:2])
- self.assertEqual((0, 62), diff.lines[10][0:2])
- self.assertEqual((61, 63), diff.lines[11][0:2])
- # Look through the last chunk, which contains both add's and delete's.
- self.assertEqual((81, 83), diff.lines[14][0:2])
- self.assertEqual((82, 84), diff.lines[15][0:2])
- self.assertEqual((83, 85), diff.lines[16][0:2])
- self.assertEqual((84, 0), diff.lines[17][0:2])
- self.assertEqual((0, 86), diff.lines[18][0:2])
- self.assertEqual((0, 87), diff.lines[19][0:2])
- self.assertEqual((85, 88), diff.lines[20][0:2])
- self.assertEqual((86, 89), diff.lines[21][0:2])
- self.assertEqual((87, 90), diff.lines[22][0:2])
-
- # Check if a newly added file is correctly handled.
- diff = parser.files['LayoutTests/platform/mac/fast/flexbox/box-orient-button-expected.checksum']
- self.assertEqual(1, len(diff.lines))
- self.assertEqual((0, 1), diff.lines[0][0:2])
-
- def test_diff_converter(self):
- comment_lines = [
- "Hey guys,\n",
- "\n",
- "See my awesome patch below!\n",
- "\n",
- " - Cool Hacker\n",
- "\n",
- ]
-
- revision_lines = [
- "Subversion Revision 289799\n",
- ]
-
- svn_diff_lines = [
- "Index: Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
- "===================================================================\n",
- "--- Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
- "+++ Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
- "@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):\n",
- ]
- self.assertEqual(diff_parser.get_diff_converter(svn_diff_lines), diff_parser.svn_diff_to_svn_diff)
- self.assertEqual(diff_parser.get_diff_converter(comment_lines + svn_diff_lines), diff_parser.svn_diff_to_svn_diff)
- self.assertEqual(diff_parser.get_diff_converter(revision_lines + svn_diff_lines), diff_parser.svn_diff_to_svn_diff)
-
- git_diff_lines = [
- "diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
- "index 3c5b45b..0197ead 100644\n",
- "--- a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
- "+++ b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
- "@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):\n",
- ]
- self.assertEqual(diff_parser.get_diff_converter(git_diff_lines), diff_parser.git_diff_to_svn_diff)
- self.assertEqual(diff_parser.get_diff_converter(comment_lines + git_diff_lines), diff_parser.git_diff_to_svn_diff)
- self.assertEqual(diff_parser.get_diff_converter(revision_lines + git_diff_lines), diff_parser.git_diff_to_svn_diff)
-
- def test_git_mnemonicprefix(self):
- p = re.compile(r' ([a|b])/')
-
- prefixes = [
- { 'a' : 'i', 'b' : 'w' }, # git-diff (compares the (i)ndex and the (w)ork tree)
- { 'a' : 'c', 'b' : 'w' }, # git-diff HEAD (compares a (c)ommit and the (w)ork tree)
- { 'a' : 'c', 'b' : 'i' }, # git diff --cached (compares a (c)ommit and the (i)ndex)
- { 'a' : 'o', 'b' : 'w' }, # git-diff HEAD:file1 file2 (compares an (o)bject and a (w)ork tree entity)
- { 'a' : '1', 'b' : '2' }, # git diff --no-index a b (compares two non-git things (1) and (2))
- ]
-
- for prefix in prefixes:
- patch = p.sub(lambda x: " %s/" % prefix[x.group(1)], DIFF_TEST_DATA)
- self.test_diff_parser(diff_parser.DiffParser(patch.splitlines()))
-
- def test_git_diff_to_svn_diff(self):
- output = """\
-Index: Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-===================================================================
---- Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-+++ Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):
- A
- B
- C
-+D
- E
- F
-"""
-
- inputfmt = StringIO.StringIO("""\
-diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-index 2ed552c4555db72df16b212547f2c125ae301a04..72870482000c0dba64ce4300ed782c03ee79b74f 100644
---- a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-+++ b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):
- A
- B
- C
-+D
- E
- F
-""")
- shortfmt = StringIO.StringIO("""\
-diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-index b48b162..f300960 100644
---- a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-+++ b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):
- A
- B
- C
-+D
- E
- F
-""")
-
- self.assertMultiLineEqual(output, ''.join(diff_parser.git_diff_to_svn_diff(x) for x in shortfmt.readlines()))
- self.assertMultiLineEqual(output, ''.join(diff_parser.git_diff_to_svn_diff(x) for x in inputfmt.readlines()))
diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_test_data.py b/Tools/Scripts/webkitpy/common/checkout/diff_test_data.py
deleted file mode 100644
index 5f1719da8..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/diff_test_data.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright (C) 2011 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.
-
-# FIXME: Store this as a .patch file in some new fixtures directory or similar.
-DIFF_TEST_DATA = '''diff --git a/WebCore/rendering/style/StyleFlexibleBoxData.h b/WebCore/rendering/style/StyleFlexibleBoxData.h
-index f5d5e74..3b6aa92 100644
---- a/WebCore/rendering/style/StyleFlexibleBoxData.h
-+++ b/WebCore/rendering/style/StyleFlexibleBoxData.h
-@@ -47,7 +47,6 @@ public:
-
- unsigned align : 3; // EBoxAlignment
- unsigned pack: 3; // EBoxAlignment
-- unsigned orient: 1; // EBoxOrient
- unsigned lines : 1; // EBoxLines
-
- private:
-diff --git a/WebCore/rendering/style/StyleRareInheritedData.cpp b/WebCore/rendering/style/StyleRareInheritedData.cpp
-index ce21720..324929e 100644
---- a/WebCore/rendering/style/StyleRareInheritedData.cpp
-+++ b/WebCore/rendering/style/StyleRareInheritedData.cpp
-@@ -39,6 +39,7 @@ StyleRareInheritedData::StyleRareInheritedData()
- , textSizeAdjust(RenderStyle::initialTextSizeAdjust())
- , resize(RenderStyle::initialResize())
- , userSelect(RenderStyle::initialUserSelect())
-+ , boxOrient(RenderStyle::initialBoxOrient())
- {
- }
-
-@@ -58,6 +59,7 @@ StyleRareInheritedData::StyleRareInheritedData(const StyleRareInheritedData& o)
- , textSizeAdjust(o.textSizeAdjust)
- , resize(o.resize)
- , userSelect(o.userSelect)
-+ , boxOrient(o.boxOrient)
- {
- }
-
-@@ -81,7 +83,8 @@ bool StyleRareInheritedData::operator==(const StyleRareInheritedData& o) const
- && khtmlLineBreak == o.khtmlLineBreak
- && textSizeAdjust == o.textSizeAdjust
- && resize == o.resize
-- && userSelect == o.userSelect;
-+ && userSelect == o.userSelect
-+ && boxOrient == o.boxOrient;
- }
-
- bool StyleRareInheritedData::shadowDataEquivalent(const StyleRareInheritedData& o) const
-diff --git a/LayoutTests/platform/mac/fast/flexbox/box-orient-button-expected.checksum b/LayoutTests/platform/mac/fast/flexbox/box-orient-button-expected.checksum
-new file mode 100644
-index 0000000..6db26bd
---- /dev/null
-+++ b/LayoutTests/platform/mac/fast/flexbox/box-orient-button-expected.checksum
-@@ -0,0 +1 @@
-+61a373ee739673a9dcd7bac62b9f182e
-\ No newline at end of file
-'''
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/__init__.py b/Tools/Scripts/webkitpy/common/checkout/scm/__init__.py
deleted file mode 100644
index f691f58e1..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Required for Python to search this directory for module files
-
-# We only export public API here.
-from .commitmessage import CommitMessage
-from .detection import SCMDetector
-from .git import Git, AmbiguousCommitError
-from .scm import SCM, AuthenticationError, CheckoutNeedsUpdate
-from .svn import SVN
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/commitmessage.py b/Tools/Scripts/webkitpy/common/checkout/scm/commitmessage.py
deleted file mode 100644
index be0d431f9..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/commitmessage.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright (c) 2009, 2010, 2011 Google Inc. All rights reserved.
-# Copyright (c) 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:
-#
-# * 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.
-
-import re
-
-
-def _first_non_empty_line_after_index(lines, index=0):
- first_non_empty_line = index
- for line in lines[index:]:
- if re.match("^\s*$", line):
- first_non_empty_line += 1
- else:
- break
- return first_non_empty_line
-
-
-class CommitMessage:
- def __init__(self, message):
- self.message_lines = message[_first_non_empty_line_after_index(message, 0):]
-
- def body(self, lstrip=False):
- lines = self.message_lines[_first_non_empty_line_after_index(self.message_lines, 1):]
- if lstrip:
- lines = [line.lstrip() for line in lines]
- return "\n".join(lines) + "\n"
-
- def description(self, lstrip=False, strip_url=False):
- line = self.message_lines[0]
- if lstrip:
- line = line.lstrip()
- if strip_url:
- line = re.sub("^(\s*)<.+> ", "\1", line)
- return line
-
- def message(self):
- return "\n".join(self.message_lines) + "\n"
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/detection.py b/Tools/Scripts/webkitpy/common/checkout/scm/detection.py
deleted file mode 100644
index e635b4075..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/detection.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright (c) 2009, 2010, 2011 Google Inc. All rights reserved.
-# Copyright (c) 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:
-#
-# * 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.
-
-import logging
-
-from webkitpy.common.system.filesystem import FileSystem
-from webkitpy.common.system.executive import Executive
-
-from .svn import SVN
-from .git import Git
-
-_log = logging.getLogger(__name__)
-
-
-class SCMDetector(object):
- def __init__(self, filesystem, executive):
- self._filesystem = filesystem
- self._executive = executive
-
- def default_scm(self, patch_directories=None):
- """Return the default SCM object as determined by the CWD and running code.
-
- Returns the default SCM object for the current working directory; if the
- CWD is not in a checkout, then we attempt to figure out if the SCM module
- itself is part of a checkout, and return that one. If neither is part of
- a checkout, None is returned.
- """
- cwd = self._filesystem.getcwd()
- scm_system = self.detect_scm_system(cwd, patch_directories)
- if not scm_system:
- script_directory = self._filesystem.dirname(self._filesystem.path_to_module(self.__module__))
- scm_system = self.detect_scm_system(script_directory, patch_directories)
- if scm_system:
- _log.info("The current directory (%s) is not a WebKit checkout, using %s" % (cwd, scm_system.checkout_root))
- else:
- raise Exception("FATAL: Failed to determine the SCM system for either %s or %s" % (cwd, script_directory))
- return scm_system
-
- def detect_scm_system(self, path, patch_directories=None):
- absolute_path = self._filesystem.abspath(path)
-
- if patch_directories == []:
- patch_directories = None
-
- if SVN.in_working_directory(absolute_path, executive=self._executive):
- return SVN(cwd=absolute_path, patch_directories=patch_directories, filesystem=self._filesystem, executive=self._executive)
-
- if Git.in_working_directory(absolute_path, executive=self._executive):
- return Git(cwd=absolute_path, filesystem=self._filesystem, executive=self._executive)
-
- return None
-
-
-# FIXME: These free functions are all deprecated:
-
-def detect_scm_system(path, patch_directories=None):
- return SCMDetector(FileSystem(), Executive()).detect_scm_system(path, patch_directories)
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/detection_unittest.py b/Tools/Scripts/webkitpy/common/checkout/scm/detection_unittest.py
deleted file mode 100644
index 593f093c4..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/detection_unittest.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (C) 2009 Google Inc. All rights reserved.
-# Copyright (C) 2009 Apple Inc. All rights reserved.
-# Copyright (C) 2011 Daniel Bates (dbates@intudata.com). 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.
-
-import unittest2 as unittest
-
-from .detection import SCMDetector
-from webkitpy.common.system.filesystem_mock import MockFileSystem
-from webkitpy.common.system.executive_mock import MockExecutive
-from webkitpy.common.system.outputcapture import OutputCapture
-
-
-class SCMDetectorTest(unittest.TestCase):
- def test_detect_scm_system(self):
- filesystem = MockFileSystem()
- executive = MockExecutive(should_log=True)
- detector = SCMDetector(filesystem, executive)
-
- expected_logs = """\
-MOCK run_command: ['svn', 'info'], cwd=/
-MOCK run_command: ['git', 'rev-parse', '--is-inside-work-tree'], cwd=/
-"""
- scm = OutputCapture().assert_outputs(self, detector.detect_scm_system, ["/"], expected_logs=expected_logs)
- self.assertIsNone(scm)
- # FIXME: This should make a synthetic tree and test SVN and Git detection in that tree.
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/git.py b/Tools/Scripts/webkitpy/common/checkout/scm/git.py
deleted file mode 100644
index 58eda7032..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/git.py
+++ /dev/null
@@ -1,514 +0,0 @@
-# Copyright (c) 2009, 2010, 2011 Google Inc. All rights reserved.
-# Copyright (c) 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:
-#
-# * 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.
-
-import datetime
-import logging
-import os
-import re
-
-from webkitpy.common.memoized import memoized
-from webkitpy.common.system.executive import Executive, ScriptError
-
-from .commitmessage import CommitMessage
-from .scm import AuthenticationError, SCM, commit_error_handler
-from .svn import SVN, SVNRepository
-
-_log = logging.getLogger(__name__)
-
-
-class AmbiguousCommitError(Exception):
- def __init__(self, num_local_commits, has_working_directory_changes):
- Exception.__init__(self, "Found %s local commits and the working directory is %s" % (
- num_local_commits, ["clean", "not clean"][has_working_directory_changes]))
- self.num_local_commits = num_local_commits
- self.has_working_directory_changes = has_working_directory_changes
-
-
-class Git(SCM, SVNRepository):
-
- # Git doesn't appear to document error codes, but seems to return
- # 1 or 128, mostly.
- ERROR_FILE_IS_MISSING = 128
-
- executable_name = 'git'
-
- def __init__(self, cwd, **kwargs):
- SCM.__init__(self, cwd, **kwargs)
- self._check_git_architecture()
-
- def _machine_is_64bit(self):
- import platform
- # This only is tested on Mac.
- if not platform.mac_ver()[0]:
- return False
-
- # platform.architecture()[0] can be '64bit' even if the machine is 32bit:
- # http://mail.python.org/pipermail/pythonmac-sig/2009-September/021648.html
- # Use the sysctl command to find out what the processor actually supports.
- return self.run(['sysctl', '-n', 'hw.cpu64bit_capable']).rstrip() == '1'
-
- def _executable_is_64bit(self, path):
- # Again, platform.architecture() fails us. On my machine
- # git_bits = platform.architecture(executable=git_path, bits='default')[0]
- # git_bits is just 'default', meaning the call failed.
- file_output = self.run(['file', path])
- return re.search('x86_64', file_output)
-
- def _check_git_architecture(self):
- if not self._machine_is_64bit():
- return
-
- # We could path-search entirely in python or with
- # which.py (http://code.google.com/p/which), but this is easier:
- git_path = self.run(['which', self.executable_name]).rstrip()
- if self._executable_is_64bit(git_path):
- return
-
- webkit_dev_thread_url = "https://lists.webkit.org/pipermail/webkit-dev/2010-December/015287.html"
- _log.warning("This machine is 64-bit, but the git binary (%s) does not support 64-bit.\nInstall a 64-bit git for better performance, see:\n%s\n" % (git_path, webkit_dev_thread_url))
-
- def _run_git(self, command_args, **kwargs):
- full_command_args = [self.executable_name] + command_args
- full_kwargs = kwargs
- if not 'cwd' in full_kwargs:
- full_kwargs['cwd'] = self.checkout_root
- return self.run(full_command_args, **full_kwargs)
-
- @classmethod
- def in_working_directory(cls, path, executive=None):
- try:
- executive = executive or Executive()
- return executive.run_command([cls.executable_name, 'rev-parse', '--is-inside-work-tree'], cwd=path, error_handler=Executive.ignore_error).rstrip() == "true"
- except OSError, e:
- # The Windows bots seem to through a WindowsError when git isn't installed.
- return False
-
- def find_checkout_root(self, path):
- # "git rev-parse --show-cdup" would be another way to get to the root
- checkout_root = self._run_git(['rev-parse', '--show-toplevel'], cwd=(path or "./")).strip()
- if not self._filesystem.isabs(checkout_root): # Sometimes git returns relative paths
- checkout_root = self._filesystem.join(path, checkout_root)
- return checkout_root
-
- def to_object_name(self, filepath):
- # FIXME: This can't be the right way to append a slash.
- root_end_with_slash = self._filesystem.join(self.find_checkout_root(self._filesystem.dirname(filepath)), '')
- # FIXME: This seems to want some sort of rel_path instead?
- return filepath.replace(root_end_with_slash, '')
-
- @classmethod
- def read_git_config(cls, key, cwd=None, executive=None):
- # FIXME: This should probably use cwd=self.checkout_root.
- # Pass --get-all for cases where the config has multiple values
- # Pass the cwd if provided so that we can handle the case of running webkit-patch outside of the working directory.
- # FIXME: This should use an Executive.
- executive = executive or Executive()
- return executive.run_command([cls.executable_name, "config", "--get-all", key], error_handler=Executive.ignore_error, cwd=cwd).rstrip('\n')
-
- @staticmethod
- def commit_success_regexp():
- return "^Committed r(?P<svn_revision>\d+)$"
-
- def discard_local_commits(self):
- self._run_git(['reset', '--hard', self.remote_branch_ref()])
-
- def local_commits(self):
- return self._run_git(['log', '--pretty=oneline', 'HEAD...' + self.remote_branch_ref()]).splitlines()
-
- def rebase_in_progress(self):
- return self._filesystem.exists(self.absolute_path(self._filesystem.join('.git', 'rebase-apply')))
-
- def has_working_directory_changes(self):
- return self._run_git(['diff', 'HEAD', '--no-renames', '--name-only']) != ""
-
- def discard_working_directory_changes(self):
- # Could run git clean here too, but that wouldn't match subversion
- self._run_git(['reset', 'HEAD', '--hard'])
- # Aborting rebase even though this does not match subversion
- if self.rebase_in_progress():
- self._run_git(['rebase', '--abort'])
-
- def status_command(self):
- # git status returns non-zero when there are changes, so we use git diff name --name-status HEAD instead.
- # No file contents printed, thus utf-8 autodecoding in self.run is fine.
- return [self.executable_name, "diff", "--name-status", "--no-renames", "HEAD"]
-
- def _status_regexp(self, expected_types):
- return '^(?P<status>[%s])\t(?P<filename>.+)$' % expected_types
-
- def add_list(self, paths):
- self._run_git(["add"] + paths)
-
- def delete_list(self, paths):
- return self._run_git(["rm", "-f"] + paths)
-
- def exists(self, path):
- return_code = self._run_git(["show", "HEAD:%s" % path], return_exit_code=True, decode_output=False)
- return return_code != self.ERROR_FILE_IS_MISSING
-
- def _branch_from_ref(self, ref):
- return ref.replace('refs/heads/', '')
-
- def _current_branch(self):
- return self._branch_from_ref(self._run_git(['symbolic-ref', '-q', 'HEAD']).strip())
-
- def _upstream_branch(self):
- current_branch = self._current_branch()
- return self._branch_from_ref(self.read_git_config('branch.%s.merge' % current_branch, cwd=self.checkout_root, executive=self._executive).strip())
-
- def merge_base(self, git_commit):
- if git_commit:
- # Rewrite UPSTREAM to the upstream branch
- if 'UPSTREAM' in git_commit:
- upstream = self._upstream_branch()
- if not upstream:
- raise ScriptError(message='No upstream/tracking branch set.')
- git_commit = git_commit.replace('UPSTREAM', upstream)
-
- # Special-case <refname>.. to include working copy changes, e.g., 'HEAD....' shows only the diffs from HEAD.
- if git_commit.endswith('....'):
- return git_commit[:-4]
-
- if '..' not in git_commit:
- git_commit = git_commit + "^.." + git_commit
- return git_commit
-
- return self.remote_merge_base()
-
- def changed_files(self, git_commit=None):
- # FIXME: --diff-filter could be used to avoid the "extract_filenames" step.
- status_command = [self.executable_name, 'diff', '-r', '--name-status', "--no-renames", "--no-ext-diff", "--full-index", self.merge_base(git_commit)]
- # FIXME: I'm not sure we're returning the same set of files that SVN.changed_files is.
- # Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R)
- return self.run_status_and_extract_filenames(status_command, self._status_regexp("ADM"))
-
- def _changes_files_for_commit(self, git_commit):
- # --pretty="format:" makes git show not print the commit log header,
- changed_files = self._run_git(["show", "--pretty=format:", "--name-only", git_commit]).splitlines()
- # instead it just prints a blank line at the top, so we skip the blank line:
- return changed_files[1:]
-
- def changed_files_for_revision(self, revision):
- commit_id = self.git_commit_from_svn_revision(revision)
- return self._changes_files_for_commit(commit_id)
-
- def revisions_changing_file(self, path, limit=5):
- # raise a script error if path does not exists to match the behavior of the svn implementation.
- if not self._filesystem.exists(path):
- raise ScriptError(message="Path %s does not exist." % path)
-
- # git rev-list head --remove-empty --limit=5 -- path would be equivalent.
- commit_ids = self._run_git(["log", "--remove-empty", "--pretty=format:%H", "-%s" % limit, "--", path]).splitlines()
- return filter(lambda revision: revision, map(self.svn_revision_from_git_commit, commit_ids))
-
- def conflicted_files(self):
- # We do not need to pass decode_output for this diff command
- # as we're passing --name-status which does not output any data.
- status_command = [self.executable_name, 'diff', '--name-status', '--no-renames', '--diff-filter=U']
- return self.run_status_and_extract_filenames(status_command, self._status_regexp("U"))
-
- def added_files(self):
- return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("A"))
-
- def deleted_files(self):
- return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("D"))
-
- @staticmethod
- def supports_local_commits():
- return True
-
- def display_name(self):
- return "git"
-
- def _most_recent_log_matching(self, grep_str, path):
- # We use '--grep=' + foo rather than '--grep', foo because
- # git 1.7.0.4 (and earlier) didn't support the separate arg.
- return self._run_git(['log', '-1', '--grep=' + grep_str, '--date=iso', self.find_checkout_root(path)])
-
- def svn_revision(self, path):
- git_log = self._most_recent_log_matching('git-svn-id:', path)
- match = re.search("^\s*git-svn-id:.*@(?P<svn_revision>\d+)\ ", git_log, re.MULTILINE)
- if not match:
- return ""
- return str(match.group('svn_revision'))
-
- def timestamp_of_revision(self, path, revision):
- git_log = self._most_recent_log_matching('git-svn-id:.*@%s' % revision, path)
- match = re.search("^Date:\s*(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}) ([+-])(\d{2})(\d{2})$", git_log, re.MULTILINE)
- if not match:
- return ""
-
- # Manually modify the timezone since Git doesn't have an option to show it in UTC.
- # Git also truncates milliseconds but we're going to ignore that for now.
- time_with_timezone = datetime.datetime(int(match.group(1)), int(match.group(2)), int(match.group(3)),
- int(match.group(4)), int(match.group(5)), int(match.group(6)), 0)
-
- sign = 1 if match.group(7) == '+' else -1
- time_without_timezone = time_with_timezone - datetime.timedelta(hours=sign * int(match.group(8)), minutes=int(match.group(9)))
- return time_without_timezone.strftime('%Y-%m-%dT%H:%M:%SZ')
-
- def prepend_svn_revision(self, diff):
- revision = self.head_svn_revision()
- if not revision:
- return diff
-
- return "Subversion Revision: " + revision + '\n' + diff
-
- def create_patch(self, git_commit=None, changed_files=None):
- """Returns a byte array (str()) representing the patch file.
- Patch files are effectively binary since they may contain
- files of multiple different encodings."""
-
- # Put code changes at the top of the patch and layout tests
- # at the bottom, this makes for easier reviewing.
- config_path = self._filesystem.dirname(self._filesystem.path_to_module('webkitpy.common.config'))
- order_file = self._filesystem.join(config_path, 'orderfile')
- order = ""
- if self._filesystem.exists(order_file):
- order = "-O%s" % order_file
-
- command = [self.executable_name, 'diff', '--binary', '--no-color', "--no-ext-diff", "--full-index", "--no-renames", order, self.merge_base(git_commit), "--"]
- if changed_files:
- command += changed_files
- return self.prepend_svn_revision(self.run(command, decode_output=False, cwd=self.checkout_root))
-
- def _run_git_svn_find_rev(self, arg):
- # git svn find-rev always exits 0, even when the revision or commit is not found.
- return self._run_git(['svn', 'find-rev', arg]).rstrip()
-
- def _string_to_int_or_none(self, string):
- try:
- return int(string)
- except ValueError, e:
- return None
-
- @memoized
- def git_commit_from_svn_revision(self, svn_revision):
- # FIXME: https://bugs.webkit.org/show_bug.cgi?id=111668
- # We should change this to run git log --grep 'git-svn-id' instead
- # so that we don't require git+svn to be set up.
- git_commit = self._run_git_svn_find_rev('r%s' % svn_revision)
- if not git_commit:
- # FIXME: Alternatively we could offer to update the checkout? Or return None?
- raise ScriptError(message='Failed to find git commit for revision %s, your checkout likely needs an update.' % svn_revision)
- return git_commit
-
- @memoized
- def svn_revision_from_git_commit(self, git_commit):
- svn_revision = self._run_git_svn_find_rev(git_commit)
- return self._string_to_int_or_none(svn_revision)
-
- def contents_at_revision(self, path, revision):
- """Returns a byte array (str()) containing the contents
- of path @ revision in the repository."""
- return self._run_git(["show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)], decode_output=False)
-
- def diff_for_revision(self, revision):
- git_commit = self.git_commit_from_svn_revision(revision)
- return self.create_patch(git_commit)
-
- def diff_for_file(self, path, log=None):
- return self._run_git(['diff', 'HEAD', '--no-renames', '--', path])
-
- def show_head(self, path):
- return self._run_git(['show', 'HEAD:' + self.to_object_name(path)], decode_output=False)
-
- def committer_email_for_revision(self, revision):
- git_commit = self.git_commit_from_svn_revision(revision)
- committer_email = self._run_git(["log", "-1", "--pretty=format:%ce", git_commit])
- # Git adds an extra @repository_hash to the end of every committer email, remove it:
- return committer_email.rsplit("@", 1)[0]
-
- def apply_reverse_diff(self, revision):
- # Assume the revision is an svn revision.
- git_commit = self.git_commit_from_svn_revision(revision)
- # I think this will always fail due to ChangeLogs.
- self._run_git(['revert', '--no-commit', git_commit], error_handler=Executive.ignore_error)
-
- def revert_files(self, file_paths):
- self._run_git(['checkout', 'HEAD'] + file_paths)
-
- def _assert_can_squash(self, has_working_directory_changes):
- squash = self.read_git_config('webkit-patch.commit-should-always-squash', cwd=self.checkout_root, executive=self._executive)
- should_squash = squash and squash.lower() == "true"
-
- if not should_squash:
- # Only warn if there are actually multiple commits to squash.
- num_local_commits = len(self.local_commits())
- if num_local_commits > 1 or (num_local_commits > 0 and has_working_directory_changes):
- raise AmbiguousCommitError(num_local_commits, has_working_directory_changes)
-
- def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None):
- # Username is ignored during Git commits.
- has_working_directory_changes = self.has_working_directory_changes()
-
- if git_commit:
- # Special-case HEAD.. to mean working-copy changes only.
- if git_commit.upper() == 'HEAD..':
- if not has_working_directory_changes:
- raise ScriptError(message="The working copy is not modified. --git-commit=HEAD.. only commits working copy changes.")
- self.commit_locally_with_message(message)
- return self._commit_on_branch(message, 'HEAD', username=username, password=password)
-
- # Need working directory changes to be committed so we can checkout the merge branch.
- if has_working_directory_changes:
- # FIXME: webkit-patch land will modify the ChangeLogs to correct the reviewer.
- # That will modify the working-copy and cause us to hit this error.
- # The ChangeLog modification could be made to modify the existing local commit.
- raise ScriptError(message="Working copy is modified. Cannot commit individual git_commits.")
- return self._commit_on_branch(message, git_commit, username=username, password=password)
-
- if not force_squash:
- self._assert_can_squash(has_working_directory_changes)
- self._run_git(['reset', '--soft', self.remote_merge_base()])
- self.commit_locally_with_message(message)
- return self.push_local_commits_to_server(username=username, password=password)
-
- def _commit_on_branch(self, message, git_commit, username=None, password=None):
- branch_name = self._current_branch()
- commit_ids = self.commit_ids_from_commitish_arguments([git_commit])
-
- # We want to squash all this branch's commits into one commit with the proper description.
- # We do this by doing a "merge --squash" into a new commit branch, then dcommitting that.
- MERGE_BRANCH_NAME = 'webkit-patch-land'
- self.delete_branch(MERGE_BRANCH_NAME)
-
- # We might be in a directory that's present in this branch but not in the
- # trunk. Move up to the top of the tree so that git commands that expect a
- # valid CWD won't fail after we check out the merge branch.
- # FIXME: We should never be using chdir! We can instead pass cwd= to run_command/self.run!
- self._filesystem.chdir(self.checkout_root)
-
- # Stuff our change into the merge branch.
- # We wrap in a try...finally block so if anything goes wrong, we clean up the branches.
- commit_succeeded = True
- try:
- self._run_git(['checkout', '-q', '-b', MERGE_BRANCH_NAME, self.remote_branch_ref()])
-
- for commit in commit_ids:
- # We're on a different branch now, so convert "head" to the branch name.
- commit = re.sub(r'(?i)head', branch_name, commit)
- # FIXME: Once changed_files and create_patch are modified to separately handle each
- # commit in a commit range, commit each cherry pick so they'll get dcommitted separately.
- self._run_git(['cherry-pick', '--no-commit', commit])
-
- self._run_git(['commit', '-m', message])
- output = self.push_local_commits_to_server(username=username, password=password)
- except Exception, e:
- _log.warning("COMMIT FAILED: " + str(e))
- output = "Commit failed."
- commit_succeeded = False
- finally:
- # And then swap back to the original branch and clean up.
- self.discard_working_directory_changes()
- self._run_git(['checkout', '-q', branch_name])
- self.delete_branch(MERGE_BRANCH_NAME)
-
- return output
-
- def svn_commit_log(self, svn_revision):
- svn_revision = self.strip_r_from_svn_revision(svn_revision)
- return self._run_git(['svn', 'log', '-r', svn_revision])
-
- def last_svn_commit_log(self):
- return self._run_git(['svn', 'log', '--limit=1'])
-
- def svn_blame(self, path):
- return self._run_git(['svn', 'blame', path])
-
- # Git-specific methods:
- def _branch_ref_exists(self, branch_ref):
- return self._run_git(['show-ref', '--quiet', '--verify', branch_ref], return_exit_code=True) == 0
-
- def delete_branch(self, branch_name):
- if self._branch_ref_exists('refs/heads/' + branch_name):
- self._run_git(['branch', '-D', branch_name])
-
- def remote_merge_base(self):
- return self._run_git(['merge-base', self.remote_branch_ref(), 'HEAD']).strip()
-
- def remote_branch_ref(self):
- # Use references so that we can avoid collisions, e.g. we don't want to operate on refs/heads/trunk if it exists.
- remote_branch_refs = self.read_git_config('svn-remote.svn.fetch', cwd=self.checkout_root, executive=self._executive)
- if not remote_branch_refs:
- remote_master_ref = 'refs/remotes/origin/master'
- if not self._branch_ref_exists(remote_master_ref):
- raise ScriptError(message="Can't find a branch to diff against. svn-remote.svn.fetch is not in the git config and %s does not exist" % remote_master_ref)
- return remote_master_ref
-
- # FIXME: What's the right behavior when there are multiple svn-remotes listed?
- # For now, just use the first one.
- first_remote_branch_ref = remote_branch_refs.split('\n')[0]
- return first_remote_branch_ref.split(':')[1]
-
- def commit_locally_with_message(self, message):
- self._run_git(['commit', '--all', '-F', '-'], input=message)
-
- def push_local_commits_to_server(self, username=None, password=None):
- dcommit_command = ['svn', 'dcommit']
- if (not username or not password) and not self.has_authorization_for_realm(self.svn_server_realm):
- raise AuthenticationError(self.svn_server_host, prompt_for_password=True)
- if username:
- dcommit_command.extend(["--username", username])
- output = self._run_git(dcommit_command, error_handler=commit_error_handler, input=password)
- return output
-
- # This function supports the following argument formats:
- # no args : rev-list trunk..HEAD
- # A..B : rev-list A..B
- # A...B : error!
- # A B : [A, B] (different from git diff, which would use "rev-list A..B")
- def commit_ids_from_commitish_arguments(self, args):
- if not len(args):
- args.append('%s..HEAD' % self.remote_branch_ref())
-
- commit_ids = []
- for commitish in args:
- if '...' in commitish:
- raise ScriptError(message="'...' is not supported (found in '%s'). Did you mean '..'?" % commitish)
- elif '..' in commitish:
- commit_ids += reversed(self._run_git(['rev-list', commitish]).splitlines())
- else:
- # Turn single commits or branch or tag names into commit ids.
- commit_ids += self._run_git(['rev-parse', '--revs-only', commitish]).splitlines()
- return commit_ids
-
- def commit_message_for_local_commit(self, commit_id):
- commit_lines = self._run_git(['cat-file', 'commit', commit_id]).splitlines()
-
- # Skip the git headers.
- first_line_after_headers = 0
- for line in commit_lines:
- first_line_after_headers += 1
- if line == "":
- break
- return CommitMessage(commit_lines[first_line_after_headers:])
-
- def files_changed_summary_for_commit(self, commit_id):
- return self._run_git(['diff-tree', '--shortstat', '--no-renames', '--no-commit-id', commit_id])
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/scm.py b/Tools/Scripts/webkitpy/common/checkout/scm/scm.py
deleted file mode 100644
index b005ea239..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/scm.py
+++ /dev/null
@@ -1,249 +0,0 @@
-# Copyright (c) 2009, Google Inc. All rights reserved.
-# Copyright (c) 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:
-#
-# * 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.
-#
-# Python module for interacting with an SCM system (like SVN or Git)
-
-import logging
-import re
-import sys
-
-from webkitpy.common.system.executive import Executive, ScriptError
-from webkitpy.common.system.filesystem import FileSystem
-
-_log = logging.getLogger(__name__)
-
-
-class CheckoutNeedsUpdate(ScriptError):
- def __init__(self, script_args, exit_code, output, cwd):
- ScriptError.__init__(self, script_args=script_args, exit_code=exit_code, output=output, cwd=cwd)
-
-
-# FIXME: Should be moved onto SCM
-def commit_error_handler(error):
- if re.search("resource out of date", error.output):
- raise CheckoutNeedsUpdate(script_args=error.script_args, exit_code=error.exit_code, output=error.output, cwd=error.cwd)
- Executive.default_error_handler(error)
-
-
-class AuthenticationError(Exception):
- def __init__(self, server_host, prompt_for_password=False):
- self.server_host = server_host
- self.prompt_for_password = prompt_for_password
-
-
-
-# SCM methods are expected to return paths relative to self.checkout_root.
-class SCM:
- def __init__(self, cwd, executive=None, filesystem=None):
- self.cwd = cwd
- self._executive = executive or Executive()
- self._filesystem = filesystem or FileSystem()
- self.checkout_root = self.find_checkout_root(self.cwd)
-
- # A wrapper used by subclasses to create processes.
- def run(self, args, cwd=None, input=None, error_handler=None, return_exit_code=False, return_stderr=True, decode_output=True):
- # FIXME: We should set cwd appropriately.
- return self._executive.run_command(args,
- cwd=cwd,
- input=input,
- error_handler=error_handler,
- return_exit_code=return_exit_code,
- return_stderr=return_stderr,
- decode_output=decode_output)
-
- # SCM always returns repository relative path, but sometimes we need
- # absolute paths to pass to rm, etc.
- def absolute_path(self, repository_relative_path):
- return self._filesystem.join(self.checkout_root, repository_relative_path)
-
- # FIXME: This belongs in Checkout, not SCM.
- def scripts_directory(self):
- return self._filesystem.join(self.checkout_root, "Tools", "Scripts")
-
- # FIXME: This belongs in Checkout, not SCM.
- def script_path(self, script_name):
- return self._filesystem.join(self.scripts_directory(), script_name)
-
- def run_status_and_extract_filenames(self, status_command, status_regexp):
- filenames = []
- # We run with cwd=self.checkout_root so that returned-paths are root-relative.
- for line in self.run(status_command, cwd=self.checkout_root).splitlines():
- match = re.search(status_regexp, line)
- if not match:
- continue
- # status = match.group('status')
- filename = match.group('filename')
- filenames.append(filename)
- return filenames
-
- def strip_r_from_svn_revision(self, svn_revision):
- match = re.match("^r(?P<svn_revision>\d+)", unicode(svn_revision))
- if (match):
- return match.group('svn_revision')
- return svn_revision
-
- def svn_revision_from_commit_text(self, commit_text):
- match = re.search(self.commit_success_regexp(), commit_text, re.MULTILINE)
- return match.group('svn_revision')
-
- @staticmethod
- def _subclass_must_implement():
- raise NotImplementedError("subclasses must implement")
-
- @classmethod
- def in_working_directory(cls, path, executive=None):
- SCM._subclass_must_implement()
-
- def find_checkout_root(self, path):
- SCM._subclass_must_implement()
-
- @staticmethod
- def commit_success_regexp():
- SCM._subclass_must_implement()
-
- def status_command(self):
- self._subclass_must_implement()
-
- def add(self, path):
- self.add_list([path])
-
- def add_list(self, paths):
- self._subclass_must_implement()
-
- def delete(self, path):
- self.delete_list([path])
-
- def delete_list(self, paths):
- self._subclass_must_implement()
-
- def exists(self, path):
- self._subclass_must_implement()
-
- def changed_files(self, git_commit=None):
- self._subclass_must_implement()
-
- def changed_files_for_revision(self, revision):
- self._subclass_must_implement()
-
- def revisions_changing_file(self, path, limit=5):
- self._subclass_must_implement()
-
- def added_files(self):
- self._subclass_must_implement()
-
- def conflicted_files(self):
- self._subclass_must_implement()
-
- def display_name(self):
- self._subclass_must_implement()
-
- def head_svn_revision(self):
- return self.svn_revision(self.checkout_root)
-
- def svn_revision(self, path):
- """Returns the latest svn revision found in the checkout."""
- self._subclass_must_implement()
-
- def timestamp_of_revision(self, path, revision):
- self._subclass_must_implement()
-
- def create_patch(self, git_commit=None, changed_files=None):
- self._subclass_must_implement()
-
- def committer_email_for_revision(self, revision):
- self._subclass_must_implement()
-
- def contents_at_revision(self, path, revision):
- self._subclass_must_implement()
-
- def diff_for_revision(self, revision):
- self._subclass_must_implement()
-
- def diff_for_file(self, path, log=None):
- self._subclass_must_implement()
-
- def show_head(self, path):
- self._subclass_must_implement()
-
- def apply_reverse_diff(self, revision):
- self._subclass_must_implement()
-
- def revert_files(self, file_paths):
- self._subclass_must_implement()
-
- def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None):
- self._subclass_must_implement()
-
- def svn_commit_log(self, svn_revision):
- self._subclass_must_implement()
-
- def last_svn_commit_log(self):
- self._subclass_must_implement()
-
- def svn_blame(self, path):
- self._subclass_must_implement()
-
- def has_working_directory_changes(self):
- self._subclass_must_implement()
-
- def discard_working_directory_changes(self):
- self._subclass_must_implement()
-
- #--------------------------------------------------------------------------
- # Subclasses must indicate if they support local commits,
- # but the SCM baseclass will only call local_commits methods when this is true.
- @staticmethod
- def supports_local_commits():
- SCM._subclass_must_implement()
-
- def local_commits(self):
- return []
-
- def has_local_commits(self):
- return len(self.local_commits()) > 0
-
- def discard_local_commits(self):
- return
-
- def remote_merge_base(self):
- SCM._subclass_must_implement()
-
- def commit_locally_with_message(self, message):
- _log.error("Your source control manager does not support local commits.")
- sys.exit(1)
-
- def local_changes_exist(self):
- return (self.supports_local_commits() and self.has_local_commits()) or self.has_working_directory_changes()
-
- def discard_local_changes(self):
- if self.has_working_directory_changes():
- self.discard_working_directory_changes()
-
- if self.has_local_commits():
- self.discard_local_commits()
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py b/Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py
deleted file mode 100644
index c5d10fcb1..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# Copyright (C) 2011 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.
-
-from webkitpy.common.checkout.scm import CommitMessage
-from webkitpy.common.system.filesystem_mock import MockFileSystem
-from webkitpy.common.system.executive_mock import MockExecutive
-
-
-class MockSCM(object):
- def __init__(self, filesystem=None, executive=None):
- self.checkout_root = "/mock-checkout"
- self.added_paths = set()
- self._filesystem = filesystem or MockFileSystem()
- self._executive = executive or MockExecutive()
-
- def add(self, destination_path):
- self.add_list([destination_path])
-
- def add_list(self, destination_paths):
- self.added_paths.update(set(destination_paths))
-
- def has_working_directory_changes(self):
- return False
-
- def discard_working_directory_changes(self):
- pass
-
- def supports_local_commits(self):
- return True
-
- def has_local_commits(self):
- return False
-
- def discard_local_commits(self):
- pass
-
- def discard_local_changes(self):
- pass
-
- def exists(self, path):
- # TestRealMain.test_real_main (and several other rebaseline tests) are sensitive to this return value.
- # We should make those tests more robust, but for now we just return True always (since no test needs otherwise).
- return True
-
- def absolute_path(self, *comps):
- return self._filesystem.join(self.checkout_root, *comps)
-
- def changed_files(self, git_commit=None):
- return ["MockFile1"]
-
- def changed_files_for_revision(self, revision):
- return ["MockFile1"]
-
- def head_svn_revision(self):
- return '1234'
-
- def svn_revision(self, path):
- return '5678'
-
- def timestamp_of_revision(self, path, revision):
- return '2013-02-01 08:48:05 +0000'
-
- def create_patch(self, git_commit, changed_files=None):
- return "Patch1"
-
- def commit_ids_from_commitish_arguments(self, args):
- return ["Commitish1", "Commitish2"]
-
- def committer_email_for_revision(self, revision):
- return "mock@webkit.org"
-
- def commit_locally_with_message(self, message):
- pass
-
- def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None):
- pass
-
- def merge_base(self, git_commit):
- return None
-
- def commit_message_for_local_commit(self, commit_id):
- if commit_id == "Commitish1":
- return CommitMessage("CommitMessage1\n" \
- "https://bugs.example.org/show_bug.cgi?id=50000\n")
- if commit_id == "Commitish2":
- return CommitMessage("CommitMessage2\n" \
- "https://bugs.example.org/show_bug.cgi?id=50001\n")
- raise Exception("Bogus commit_id in commit_message_for_local_commit.")
-
- def diff_for_file(self, path, log=None):
- return path + '-diff'
-
- def diff_for_revision(self, revision):
- return "DiffForRevision%s\nhttp://bugs.webkit.org/show_bug.cgi?id=12345" % revision
-
- def show_head(self, path):
- return path
-
- def svn_revision_from_commit_text(self, commit_text):
- return "49824"
-
- def delete(self, path):
- return self.delete_list([path])
-
- def delete_list(self, paths):
- if not self._filesystem:
- return
- for path in paths:
- if self._filesystem.exists(path):
- self._filesystem.remove(path)
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/scm_unittest.py b/Tools/Scripts/webkitpy/common/checkout/scm/scm_unittest.py
deleted file mode 100644
index 15432f0e8..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/scm_unittest.py
+++ /dev/null
@@ -1,1600 +0,0 @@
-# Copyright (C) 2009 Google Inc. All rights reserved.
-# Copyright (C) 2009 Apple Inc. All rights reserved.
-# Copyright (C) 2011 Daniel Bates (dbates@intudata.com). 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.
-
-import atexit
-import base64
-import codecs
-import getpass
-import os
-import os.path
-import re
-import stat
-import sys
-import subprocess
-import tempfile
-import time
-import unittest2 as unittest
-import urllib
-import shutil
-
-from datetime import date
-from webkitpy.common.checkout.checkout import Checkout
-from webkitpy.common.config.committers import Committer # FIXME: This should not be needed
-from webkitpy.common.net.bugzilla import Attachment # FIXME: This should not be needed
-from webkitpy.common.system.executive import Executive, ScriptError
-from webkitpy.common.system.filesystem_mock import MockFileSystem
-from webkitpy.common.system.outputcapture import OutputCapture
-from webkitpy.common.system.executive_mock import MockExecutive
-from .git import Git, AmbiguousCommitError
-from .detection import detect_scm_system
-from .scm import SCM, CheckoutNeedsUpdate, commit_error_handler, AuthenticationError
-from .svn import SVN
-
-
-# We cache the mock SVN repo so that we don't create it again for each call to an SVNTest or GitTest test_ method.
-# We store it in a global variable so that we can delete this cached repo on exit(3).
-# FIXME: Remove this once we migrate to Python 2.7. Unittest in Python 2.7 supports module-specific setup and teardown functions.
-cached_svn_repo_path = None
-
-
-def remove_dir(path):
- # Change directory to / to ensure that we aren't in the directory we want to delete.
- os.chdir('/')
- shutil.rmtree(path)
-
-
-# FIXME: Remove this once we migrate to Python 2.7. Unittest in Python 2.7 supports module-specific setup and teardown functions.
-@atexit.register
-def delete_cached_mock_repo_at_exit():
- if cached_svn_repo_path:
- remove_dir(cached_svn_repo_path)
-
-# Eventually we will want to write tests which work for both scms. (like update_webkit, changed_files, etc.)
-# Perhaps through some SCMTest base-class which both SVNTest and GitTest inherit from.
-
-def run_command(*args, **kwargs):
- # FIXME: This should not be a global static.
- # New code should use Executive.run_command directly instead
- return Executive().run_command(*args, **kwargs)
-
-
-# FIXME: This should be unified into one of the executive.py commands!
-# Callers could use run_and_throw_if_fail(args, cwd=cwd, quiet=True)
-def run_silent(args, cwd=None):
- # Note: Not thread safe: http://bugs.python.org/issue2320
- process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
- process.communicate() # ignore output
- exit_code = process.wait()
- if exit_code:
- raise ScriptError('Failed to run "%s" exit_code: %d cwd: %s' % (args, exit_code, cwd))
-
-
-def write_into_file_at_path(file_path, contents, encoding="utf-8"):
- if encoding:
- with codecs.open(file_path, "w", encoding) as file:
- file.write(contents)
- else:
- with open(file_path, "w") as file:
- file.write(contents)
-
-
-def read_from_path(file_path, encoding="utf-8"):
- with codecs.open(file_path, "r", encoding) as file:
- return file.read()
-
-
-def _make_diff(command, *args):
- # We use this wrapper to disable output decoding. diffs should be treated as
- # binary files since they may include text files of multiple differnet encodings.
- # FIXME: This should use an Executive.
- return run_command([command, "diff"] + list(args), decode_output=False)
-
-
-def _svn_diff(*args):
- return _make_diff("svn", *args)
-
-
-def _git_diff(*args):
- return _make_diff("git", *args)
-
-
-# Exists to share svn repository creation code between the git and svn tests
-class SVNTestRepository(object):
- @classmethod
- def _svn_add(cls, path):
- run_command(["svn", "add", path])
-
- @classmethod
- def _svn_commit(cls, message):
- run_command(["svn", "commit", "--quiet", "--message", message])
-
- @classmethod
- def _setup_test_commits(cls, svn_repo_url):
-
- svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout")
- run_command(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path])
-
- # Add some test commits
- os.chdir(svn_checkout_path)
-
- write_into_file_at_path("test_file", "test1")
- cls._svn_add("test_file")
- cls._svn_commit("initial commit")
-
- write_into_file_at_path("test_file", "test1test2")
- # This used to be the last commit, but doing so broke
- # GitTest.test_apply_git_patch which use the inverse diff of the last commit.
- # svn-apply fails to remove directories in Git, see:
- # https://bugs.webkit.org/show_bug.cgi?id=34871
- os.mkdir("test_dir")
- # Slash should always be the right path separator since we use cygwin on Windows.
- test_file3_path = "test_dir/test_file3"
- write_into_file_at_path(test_file3_path, "third file")
- cls._svn_add("test_dir")
- cls._svn_commit("second commit")
-
- write_into_file_at_path("test_file", "test1test2test3\n")
- write_into_file_at_path("test_file2", "second file")
- cls._svn_add("test_file2")
- cls._svn_commit("third commit")
-
- # This 4th commit is used to make sure that our patch file handling
- # code correctly treats patches as binary and does not attempt to
- # decode them assuming they're utf-8.
- write_into_file_at_path("test_file", u"latin1 test: \u00A0\n", "latin1")
- write_into_file_at_path("test_file2", u"utf-8 test: \u00A0\n", "utf-8")
- cls._svn_commit("fourth commit")
-
- # svn does not seem to update after commit as I would expect.
- run_command(['svn', 'update'])
- remove_dir(svn_checkout_path)
-
- # This is a hot function since it's invoked by unittest before calling each test_ method in SVNTest and
- # GitTest. We create a mock SVN repo once and then perform an SVN checkout from a filesystem copy of
- # it since it's expensive to create the mock repo.
- @classmethod
- def setup(cls, test_object):
- global cached_svn_repo_path
- if not cached_svn_repo_path:
- cached_svn_repo_path = cls._setup_mock_repo()
-
- test_object.temp_directory = tempfile.mkdtemp(suffix="svn_test")
- test_object.svn_repo_path = os.path.join(test_object.temp_directory, "repo")
- test_object.svn_repo_url = "file://%s" % test_object.svn_repo_path
- test_object.svn_checkout_path = os.path.join(test_object.temp_directory, "checkout")
- shutil.copytree(cached_svn_repo_path, test_object.svn_repo_path)
- run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url + "/trunk", test_object.svn_checkout_path])
-
- @classmethod
- def _setup_mock_repo(cls):
- # Create an test SVN repository
- svn_repo_path = tempfile.mkdtemp(suffix="svn_test_repo")
- svn_repo_url = "file://%s" % svn_repo_path # Not sure this will work on windows
- # git svn complains if we don't pass --pre-1.5-compatible, not sure why:
- # Expected FS format '2'; found format '3' at /usr/local/libexec/git-core//git-svn line 1477
- run_command(['svnadmin', 'create', '--pre-1.5-compatible', svn_repo_path])
-
- # Create a test svn checkout
- svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout")
- run_command(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path])
-
- # Create and checkout a trunk dir to match the standard svn configuration to match git-svn's expectations
- os.chdir(svn_checkout_path)
- os.mkdir('trunk')
- cls._svn_add('trunk')
- # We can add tags and branches as well if we ever need to test those.
- cls._svn_commit('add trunk')
-
- # Change directory out of the svn checkout so we can delete the checkout directory.
- remove_dir(svn_checkout_path)
-
- cls._setup_test_commits(svn_repo_url + "/trunk")
- return svn_repo_path
-
- @classmethod
- def tear_down(cls, test_object):
- remove_dir(test_object.temp_directory)
-
- # Now that we've deleted the checkout paths, cwddir may be invalid
- # Change back to a valid directory so that later calls to os.getcwd() do not fail.
- if os.path.isabs(__file__):
- path = os.path.dirname(__file__)
- else:
- path = sys.path[0]
- os.chdir(detect_scm_system(path).checkout_root)
-
-
-# For testing the SCM baseclass directly.
-class SCMClassTests(unittest.TestCase):
- def setUp(self):
- self.dev_null = open(os.devnull, "w") # Used to make our Popen calls quiet.
-
- def tearDown(self):
- self.dev_null.close()
-
- def test_run_command_with_pipe(self):
- input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null)
- self.assertEqual(run_command(['grep', 'bar'], input=input_process.stdout), "bar\n")
-
- # Test the non-pipe case too:
- self.assertEqual(run_command(['grep', 'bar'], input="foo\nbar"), "bar\n")
-
- command_returns_non_zero = ['/bin/sh', '--invalid-option']
- # Test when the input pipe process fails.
- input_process = subprocess.Popen(command_returns_non_zero, stdout=subprocess.PIPE, stderr=self.dev_null)
- self.assertNotEqual(input_process.poll(), 0)
- self.assertRaises(ScriptError, run_command, ['grep', 'bar'], input=input_process.stdout)
-
- # Test when the run_command process fails.
- input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) # grep shows usage and calls exit(2) when called w/o arguments.
- self.assertRaises(ScriptError, run_command, command_returns_non_zero, input=input_process.stdout)
-
- def test_error_handlers(self):
- git_failure_message="Merge conflict during commit: Your file or directory 'WebCore/ChangeLog' is probably out-of-date: resource out of date; try updating at /usr/local/libexec/git-core//git-svn line 469"
- svn_failure_message="""svn: Commit failed (details follow):
-svn: File or directory 'ChangeLog' is out of date; try updating
-svn: resource out of date; try updating
-"""
- command_does_not_exist = ['does_not_exist', 'invalid_option']
- self.assertRaises(OSError, run_command, command_does_not_exist)
- self.assertRaises(OSError, run_command, command_does_not_exist, error_handler=Executive.ignore_error)
-
- command_returns_non_zero = ['/bin/sh', '--invalid-option']
- self.assertRaises(ScriptError, run_command, command_returns_non_zero)
- # Check if returns error text:
- self.assertTrue(run_command(command_returns_non_zero, error_handler=Executive.ignore_error))
-
- self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=git_failure_message))
- self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=svn_failure_message))
- self.assertRaises(ScriptError, commit_error_handler, ScriptError(output='blah blah blah'))
-
-
-# GitTest and SVNTest inherit from this so any test_ methods here will be run once for this class and then once for each subclass.
-class SCMTest(unittest.TestCase):
- def _create_patch(self, patch_contents):
- # FIXME: This code is brittle if the Attachment API changes.
- attachment = Attachment({"bug_id": 12345}, None)
- attachment.contents = lambda: patch_contents
-
- joe_cool = Committer("Joe Cool", "joe@cool.com")
- attachment.reviewer = lambda: joe_cool
-
- return attachment
-
- def _setup_webkittools_scripts_symlink(self, local_scm):
- webkit_scm = detect_scm_system(os.path.dirname(os.path.abspath(__file__)))
- webkit_scripts_directory = webkit_scm.scripts_directory()
- local_scripts_directory = local_scm.scripts_directory()
- os.mkdir(os.path.dirname(local_scripts_directory))
- os.symlink(webkit_scripts_directory, local_scripts_directory)
-
- # Tests which both GitTest and SVNTest should run.
- # FIXME: There must be a simpler way to add these w/o adding a wrapper method to both subclasses
-
- def _shared_test_changed_files(self):
- write_into_file_at_path("test_file", "changed content")
- self.assertItemsEqual(self.scm.changed_files(), ["test_file"])
- write_into_file_at_path("test_dir/test_file3", "new stuff")
- self.assertItemsEqual(self.scm.changed_files(), ["test_dir/test_file3", "test_file"])
- old_cwd = os.getcwd()
- os.chdir("test_dir")
- # Validate that changed_files does not change with our cwd, see bug 37015.
- self.assertItemsEqual(self.scm.changed_files(), ["test_dir/test_file3", "test_file"])
- os.chdir(old_cwd)
-
- def _shared_test_added_files(self):
- write_into_file_at_path("test_file", "changed content")
- self.assertItemsEqual(self.scm.added_files(), [])
-
- write_into_file_at_path("added_file", "new stuff")
- self.scm.add("added_file")
-
- write_into_file_at_path("added_file3", "more new stuff")
- write_into_file_at_path("added_file4", "more new stuff")
- self.scm.add_list(["added_file3", "added_file4"])
-
- os.mkdir("added_dir")
- write_into_file_at_path("added_dir/added_file2", "new stuff")
- self.scm.add("added_dir")
-
- # SVN reports directory changes, Git does not.
- added_files = self.scm.added_files()
- if "added_dir" in added_files:
- added_files.remove("added_dir")
- self.assertItemsEqual(added_files, ["added_dir/added_file2", "added_file", "added_file3", "added_file4"])
-
- # Test also to make sure discard_working_directory_changes removes added files
- self.scm.discard_working_directory_changes()
- self.assertItemsEqual(self.scm.added_files(), [])
- self.assertFalse(os.path.exists("added_file"))
- self.assertFalse(os.path.exists("added_file3"))
- self.assertFalse(os.path.exists("added_file4"))
- self.assertFalse(os.path.exists("added_dir"))
-
- def _shared_test_changed_files_for_revision(self):
- # SVN reports directory changes, Git does not.
- changed_files = self.scm.changed_files_for_revision(3)
- if "test_dir" in changed_files:
- changed_files.remove("test_dir")
- self.assertItemsEqual(changed_files, ["test_dir/test_file3", "test_file"])
- self.assertItemsEqual(self.scm.changed_files_for_revision(4), ["test_file", "test_file2"]) # Git and SVN return different orders.
- self.assertItemsEqual(self.scm.changed_files_for_revision(2), ["test_file"])
-
- def _shared_test_contents_at_revision(self):
- self.assertEqual(self.scm.contents_at_revision("test_file", 3), "test1test2")
- self.assertEqual(self.scm.contents_at_revision("test_file", 4), "test1test2test3\n")
-
- # Verify that contents_at_revision returns a byte array, aka str():
- self.assertEqual(self.scm.contents_at_revision("test_file", 5), u"latin1 test: \u00A0\n".encode("latin1"))
- self.assertEqual(self.scm.contents_at_revision("test_file2", 5), u"utf-8 test: \u00A0\n".encode("utf-8"))
-
- self.assertEqual(self.scm.contents_at_revision("test_file2", 4), "second file")
- # Files which don't exist:
- # Currently we raise instead of returning None because detecting the difference between
- # "file not found" and any other error seems impossible with svn (git seems to expose such through the return code).
- self.assertRaises(ScriptError, self.scm.contents_at_revision, "test_file2", 2)
- self.assertRaises(ScriptError, self.scm.contents_at_revision, "does_not_exist", 2)
-
- def _shared_test_revisions_changing_file(self):
- self.assertItemsEqual(self.scm.revisions_changing_file("test_file"), [5, 4, 3, 2])
- self.assertRaises(ScriptError, self.scm.revisions_changing_file, "non_existent_file")
-
- def _shared_test_committer_email_for_revision(self):
- self.assertEqual(self.scm.committer_email_for_revision(3), getpass.getuser()) # Committer "email" will be the current user
-
- def _shared_test_reverse_diff(self):
- self._setup_webkittools_scripts_symlink(self.scm) # Git's apply_reverse_diff uses resolve-ChangeLogs
- # Only test the simple case, as any other will end up with conflict markers.
- self.scm.apply_reverse_diff('5')
- self.assertEqual(read_from_path('test_file'), "test1test2test3\n")
-
- def _shared_test_diff_for_revision(self):
- # Patch formats are slightly different between svn and git, so just regexp for things we know should be there.
- r3_patch = self.scm.diff_for_revision(4)
- self.assertRegexpMatches(r3_patch, 'test3')
- self.assertNotRegexpMatches(r3_patch, 'test4')
- self.assertRegexpMatches(r3_patch, 'test2')
- self.assertRegexpMatches(self.scm.diff_for_revision(3), 'test2')
-
- def _shared_test_svn_apply_git_patch(self):
- self._setup_webkittools_scripts_symlink(self.scm)
- git_binary_addition = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif
-new file mode 100644
-index 0000000000000000000000000000000000000000..64a9532e7794fcd791f6f12157406d90
-60151690
-GIT binary patch
-literal 512
-zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c?
-zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap
-zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ
-zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A
-zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&)
-zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b
-zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB
-z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X
-z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4
-ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H
-
-literal 0
-HcmV?d00001
-
-"""
- self.checkout.apply_patch(self._create_patch(git_binary_addition))
- added = read_from_path('fizzbuzz7.gif', encoding=None)
- self.assertEqual(512, len(added))
- self.assertTrue(added.startswith('GIF89a'))
- self.assertIn('fizzbuzz7.gif', self.scm.changed_files())
-
- # The file already exists.
- self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_addition))
-
- git_binary_modification = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif
-index 64a9532e7794fcd791f6f12157406d9060151690..323fae03f4606ea9991df8befbb2fca7
-GIT binary patch
-literal 7
-OcmYex&reD$;sO8*F9L)B
-
-literal 512
-zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c?
-zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap
-zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ
-zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A
-zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&)
-zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b
-zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB
-z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X
-z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4
-ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H
-
-"""
- self.checkout.apply_patch(self._create_patch(git_binary_modification))
- modified = read_from_path('fizzbuzz7.gif', encoding=None)
- self.assertEqual('foobar\n', modified)
- self.assertIn('fizzbuzz7.gif', self.scm.changed_files())
-
- # Applying the same modification should fail.
- self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_modification))
-
- git_binary_deletion = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif
-deleted file mode 100644
-index 323fae0..0000000
-GIT binary patch
-literal 0
-HcmV?d00001
-
-literal 7
-OcmYex&reD$;sO8*F9L)B
-
-"""
- self.checkout.apply_patch(self._create_patch(git_binary_deletion))
- self.assertFalse(os.path.exists('fizzbuzz7.gif'))
- self.assertNotIn('fizzbuzz7.gif', self.scm.changed_files())
-
- # Cannot delete again.
- self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_deletion))
-
- def _shared_test_add_recursively(self):
- os.mkdir("added_dir")
- write_into_file_at_path("added_dir/added_file", "new stuff")
- self.scm.add("added_dir/added_file")
- self.assertIn("added_dir/added_file", self.scm.added_files())
-
- def _shared_test_delete_recursively(self):
- os.mkdir("added_dir")
- write_into_file_at_path("added_dir/added_file", "new stuff")
- self.scm.add("added_dir/added_file")
- self.assertIn("added_dir/added_file", self.scm.added_files())
- self.scm.delete("added_dir/added_file")
- self.assertNotIn("added_dir", self.scm.added_files())
-
- def _shared_test_delete_recursively_or_not(self):
- os.mkdir("added_dir")
- write_into_file_at_path("added_dir/added_file", "new stuff")
- write_into_file_at_path("added_dir/another_added_file", "more new stuff")
- self.scm.add("added_dir/added_file")
- self.scm.add("added_dir/another_added_file")
- self.assertIn("added_dir/added_file", self.scm.added_files())
- self.assertIn("added_dir/another_added_file", self.scm.added_files())
- self.scm.delete("added_dir/added_file")
- self.assertIn("added_dir/another_added_file", self.scm.added_files())
-
- def _shared_test_exists(self, scm, commit_function):
- os.chdir(scm.checkout_root)
- self.assertFalse(scm.exists('foo.txt'))
- write_into_file_at_path('foo.txt', 'some stuff')
- self.assertFalse(scm.exists('foo.txt'))
- scm.add('foo.txt')
- commit_function('adding foo')
- self.assertTrue(scm.exists('foo.txt'))
- scm.delete('foo.txt')
- commit_function('deleting foo')
- self.assertFalse(scm.exists('foo.txt'))
-
- def _shared_test_head_svn_revision(self):
- self.assertEqual(self.scm.head_svn_revision(), '5')
-
-
-# Context manager that overrides the current timezone.
-class TimezoneOverride(object):
- def __init__(self, timezone_string):
- self._timezone_string = timezone_string
-
- def __enter__(self):
- if hasattr(time, 'tzset'):
- self._saved_timezone = os.environ.get('TZ', None)
- os.environ['TZ'] = self._timezone_string
- time.tzset()
-
- def __exit__(self, type, value, traceback):
- if hasattr(time, 'tzset'):
- if self._saved_timezone:
- os.environ['TZ'] = self._saved_timezone
- else:
- del os.environ['TZ']
- time.tzset()
-
-
-class SVNTest(SCMTest):
-
- @staticmethod
- def _set_date_and_reviewer(changelog_entry):
- # Joe Cool matches the reviewer set in SCMTest._create_patch
- changelog_entry = changelog_entry.replace('REVIEWER_HERE', 'Joe Cool')
- # svn-apply will update ChangeLog entries with today's date (as in Cupertino, CA, US)
- with TimezoneOverride('PST8PDT'):
- return changelog_entry.replace('DATE_HERE', date.today().isoformat())
-
- def test_svn_apply(self):
- first_entry = """2009-10-26 Eric Seidel <eric@webkit.org>
-
- Reviewed by Foo Bar.
-
- Most awesome change ever.
-
- * scm_unittest.py:
-"""
- intermediate_entry = """2009-10-27 Eric Seidel <eric@webkit.org>
-
- Reviewed by Baz Bar.
-
- A more awesomer change yet!
-
- * scm_unittest.py:
-"""
- one_line_overlap_patch = """Index: ChangeLog
-===================================================================
---- ChangeLog (revision 5)
-+++ ChangeLog (working copy)
-@@ -1,5 +1,13 @@
- 2009-10-26 Eric Seidel <eric@webkit.org>
-%(whitespace)s
-+ Reviewed by NOBODY (OOPS!).
-+
-+ Second most awesome change ever.
-+
-+ * scm_unittest.py:
-+
-+2009-10-26 Eric Seidel <eric@webkit.org>
-+
- Reviewed by Foo Bar.
-%(whitespace)s
- Most awesome change ever.
-""" % {'whitespace': ' '}
- one_line_overlap_entry = """DATE_HERE Eric Seidel <eric@webkit.org>
-
- Reviewed by REVIEWER_HERE.
-
- Second most awesome change ever.
-
- * scm_unittest.py:
-"""
- two_line_overlap_patch = """Index: ChangeLog
-===================================================================
---- ChangeLog (revision 5)
-+++ ChangeLog (working copy)
-@@ -2,6 +2,14 @@
-%(whitespace)s
- Reviewed by Foo Bar.
-%(whitespace)s
-+ Second most awesome change ever.
-+
-+ * scm_unittest.py:
-+
-+2009-10-26 Eric Seidel <eric@webkit.org>
-+
-+ Reviewed by Foo Bar.
-+
- Most awesome change ever.
-%(whitespace)s
- * scm_unittest.py:
-""" % {'whitespace': ' '}
- two_line_overlap_entry = """DATE_HERE Eric Seidel <eric@webkit.org>
-
- Reviewed by Foo Bar.
-
- Second most awesome change ever.
-
- * scm_unittest.py:
-"""
- write_into_file_at_path('ChangeLog', first_entry)
- run_command(['svn', 'add', 'ChangeLog'])
- run_command(['svn', 'commit', '--quiet', '--message', 'ChangeLog commit'])
-
- # Patch files were created against just 'first_entry'.
- # Add a second commit to make svn-apply have to apply the patches with fuzz.
- changelog_contents = "%s\n%s" % (intermediate_entry, first_entry)
- write_into_file_at_path('ChangeLog', changelog_contents)
- run_command(['svn', 'commit', '--quiet', '--message', 'Intermediate commit'])
-
- self._setup_webkittools_scripts_symlink(self.scm)
- self.checkout.apply_patch(self._create_patch(one_line_overlap_patch))
- expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(one_line_overlap_entry), changelog_contents)
- self.assertEqual(read_from_path('ChangeLog'), expected_changelog_contents)
-
- self.scm.revert_files(['ChangeLog'])
- self.checkout.apply_patch(self._create_patch(two_line_overlap_patch))
- expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(two_line_overlap_entry), changelog_contents)
- self.assertEqual(read_from_path('ChangeLog'), expected_changelog_contents)
-
- def setUp(self):
- SVNTestRepository.setup(self)
- os.chdir(self.svn_checkout_path)
- self.scm = detect_scm_system(self.svn_checkout_path)
- self.scm.svn_server_realm = None
- # For historical reasons, we test some checkout code here too.
- self.checkout = Checkout(self.scm)
-
- def tearDown(self):
- SVNTestRepository.tear_down(self)
-
- def test_detect_scm_system_relative_url(self):
- scm = detect_scm_system(".")
- # I wanted to assert that we got the right path, but there was some
- # crazy magic with temp folder names that I couldn't figure out.
- self.assertTrue(scm.checkout_root)
-
- def test_create_patch_is_full_patch(self):
- test_dir_path = os.path.join(self.svn_checkout_path, "test_dir2")
- os.mkdir(test_dir_path)
- test_file_path = os.path.join(test_dir_path, 'test_file2')
- write_into_file_at_path(test_file_path, 'test content')
- run_command(['svn', 'add', 'test_dir2'])
-
- # create_patch depends on 'svn-create-patch', so make a dummy version.
- scripts_path = os.path.join(self.svn_checkout_path, 'Tools', 'Scripts')
- os.makedirs(scripts_path)
- create_patch_path = os.path.join(scripts_path, 'svn-create-patch')
- write_into_file_at_path(create_patch_path, '#!/bin/sh\necho $PWD') # We could pass -n to prevent the \n, but not all echo accept -n.
- os.chmod(create_patch_path, stat.S_IXUSR | stat.S_IRUSR)
-
- # Change into our test directory and run the create_patch command.
- os.chdir(test_dir_path)
- scm = detect_scm_system(test_dir_path)
- self.assertEqual(scm.checkout_root, self.svn_checkout_path) # Sanity check that detection worked right.
- patch_contents = scm.create_patch()
- # Our fake 'svn-create-patch' returns $PWD instead of a patch, check that it was executed from the root of the repo.
- self.assertEqual("%s\n" % os.path.realpath(scm.checkout_root), patch_contents) # Add a \n because echo adds a \n.
-
- def test_detection(self):
- self.assertEqual(self.scm.display_name(), "svn")
- self.assertEqual(self.scm.supports_local_commits(), False)
-
- def test_apply_small_binary_patch(self):
- patch_contents = """Index: test_file.swf
-===================================================================
-Cannot display: file marked as a binary type.
-svn:mime-type = application/octet-stream
-
-Property changes on: test_file.swf
-___________________________________________________________________
-Name: svn:mime-type
- + application/octet-stream
-
-
-Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
-"""
- expected_contents = base64.b64decode("Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==")
- self._setup_webkittools_scripts_symlink(self.scm)
- patch_file = self._create_patch(patch_contents)
- self.checkout.apply_patch(patch_file)
- actual_contents = read_from_path("test_file.swf", encoding=None)
- self.assertEqual(actual_contents, expected_contents)
-
- def test_apply_svn_patch(self):
- patch = self._create_patch(_svn_diff("-r5:4"))
- self._setup_webkittools_scripts_symlink(self.scm)
- Checkout(self.scm).apply_patch(patch)
-
- def test_commit_logs(self):
- # Commits have dates and usernames in them, so we can't just direct compare.
- self.assertRegexpMatches(self.scm.last_svn_commit_log(), 'fourth commit')
- self.assertRegexpMatches(self.scm.svn_commit_log(3), 'second commit')
-
- def _shared_test_commit_with_message(self, username=None):
- write_into_file_at_path('test_file', 'more test content')
- commit_text = self.scm.commit_with_message("another test commit", username)
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
-
- def test_commit_in_subdir(self, username=None):
- write_into_file_at_path('test_dir/test_file3', 'more test content')
- os.chdir("test_dir")
- commit_text = self.scm.commit_with_message("another test commit", username)
- os.chdir("..")
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
-
- def test_commit_text_parsing(self):
- self._shared_test_commit_with_message()
-
- def test_commit_with_username(self):
- self._shared_test_commit_with_message("dbates@webkit.org")
-
- def test_commit_without_authorization(self):
- # FIXME: https://bugs.webkit.org/show_bug.cgi?id=111669
- # This test ends up looking in the actal $HOME/.subversion for authorization,
- # which makes it fragile. For now, set it to use a realm that won't be authorized,
- # but we should really plumb through a fake_home_dir here like we do in
- # test_has_authorization_for_realm.
- self.scm.svn_server_realm = '<http://svn.example.com:80> Example'
- self.assertRaises(AuthenticationError, self._shared_test_commit_with_message)
-
- def test_has_authorization_for_realm_using_credentials_with_passtype(self):
- credentials = """
-K 8
-passtype
-V 8
-keychain
-K 15
-svn:realmstring
-V 39
-<http://svn.webkit.org:80> Mac OS Forge
-K 8
-username
-V 17
-dbates@webkit.org
-END
-"""
- self.assertTrue(self._test_has_authorization_for_realm_using_credentials(SVN.svn_server_realm, credentials))
-
- def test_has_authorization_for_realm_using_credentials_with_password(self):
- credentials = """
-K 15
-svn:realmstring
-V 39
-<http://svn.webkit.org:80> Mac OS Forge
-K 8
-username
-V 17
-dbates@webkit.org
-K 8
-password
-V 4
-blah
-END
-"""
- self.assertTrue(self._test_has_authorization_for_realm_using_credentials(SVN.svn_server_realm, credentials))
-
- def _test_has_authorization_for_realm_using_credentials(self, realm, credentials):
- fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir")
- svn_config_dir_path = os.path.join(fake_home_dir, ".subversion")
- os.mkdir(svn_config_dir_path)
- fake_webkit_auth_file = os.path.join(svn_config_dir_path, "fake_webkit_auth_file")
- write_into_file_at_path(fake_webkit_auth_file, credentials)
- result = self.scm.has_authorization_for_realm(realm, home_directory=fake_home_dir)
- os.remove(fake_webkit_auth_file)
- os.rmdir(svn_config_dir_path)
- os.rmdir(fake_home_dir)
- return result
-
- def test_not_have_authorization_for_realm_with_credentials_missing_password_and_passtype(self):
- credentials = """
-K 15
-svn:realmstring
-V 39
-<http://svn.webkit.org:80> Mac OS Forge
-K 8
-username
-V 17
-dbates@webkit.org
-END
-"""
- self.assertFalse(self._test_has_authorization_for_realm_using_credentials(SVN.svn_server_realm, credentials))
-
- def test_not_have_authorization_for_realm_when_missing_credentials_file(self):
- fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir")
- svn_config_dir_path = os.path.join(fake_home_dir, ".subversion")
- os.mkdir(svn_config_dir_path)
- self.assertFalse(self.scm.has_authorization_for_realm(SVN.svn_server_realm, home_directory=fake_home_dir))
- os.rmdir(svn_config_dir_path)
- os.rmdir(fake_home_dir)
-
- def test_reverse_diff(self):
- self._shared_test_reverse_diff()
-
- def test_diff_for_revision(self):
- self._shared_test_diff_for_revision()
-
- def test_svn_apply_git_patch(self):
- self._shared_test_svn_apply_git_patch()
-
- def test_changed_files(self):
- self._shared_test_changed_files()
-
- def test_changed_files_for_revision(self):
- self._shared_test_changed_files_for_revision()
-
- def test_added_files(self):
- self._shared_test_added_files()
-
- def test_contents_at_revision(self):
- self._shared_test_contents_at_revision()
-
- def test_revisions_changing_file(self):
- self._shared_test_revisions_changing_file()
-
- def test_committer_email_for_revision(self):
- self._shared_test_committer_email_for_revision()
-
- def test_add_recursively(self):
- self._shared_test_add_recursively()
-
- def test_delete(self):
- os.chdir(self.svn_checkout_path)
- self.scm.delete("test_file")
- self.assertIn("test_file", self.scm.deleted_files())
-
- def test_delete_list(self):
- os.chdir(self.svn_checkout_path)
- self.scm.delete_list(["test_file", "test_file2"])
- self.assertIn("test_file", self.scm.deleted_files())
- self.assertIn("test_file2", self.scm.deleted_files())
-
- def test_delete_recursively(self):
- self._shared_test_delete_recursively()
-
- def test_delete_recursively_or_not(self):
- self._shared_test_delete_recursively_or_not()
-
- def test_head_svn_revision(self):
- self._shared_test_head_svn_revision()
-
- def test_propset_propget(self):
- filepath = os.path.join(self.svn_checkout_path, "test_file")
- expected_mime_type = "x-application/foo-bar"
- self.scm.propset("svn:mime-type", expected_mime_type, filepath)
- self.assertEqual(expected_mime_type, self.scm.propget("svn:mime-type", filepath))
-
- def test_show_head(self):
- write_into_file_at_path("test_file", u"Hello!", "utf-8")
- SVNTestRepository._svn_commit("fourth commit")
- self.assertEqual("Hello!", self.scm.show_head('test_file'))
-
- def test_show_head_binary(self):
- data = "\244"
- write_into_file_at_path("binary_file", data, encoding=None)
- self.scm.add("binary_file")
- self.scm.commit_with_message("a test commit")
- self.assertEqual(data, self.scm.show_head('binary_file'))
-
- def do_test_diff_for_file(self):
- write_into_file_at_path('test_file', 'some content')
- self.scm.commit_with_message("a test commit")
- diff = self.scm.diff_for_file('test_file')
- self.assertEqual(diff, "")
-
- write_into_file_at_path("test_file", "changed content")
- diff = self.scm.diff_for_file('test_file')
- self.assertIn("-some content", diff)
- self.assertIn("+changed content", diff)
-
- def clean_bogus_dir(self):
- self.bogus_dir = self.scm._bogus_dir_name()
- if os.path.exists(self.bogus_dir):
- shutil.rmtree(self.bogus_dir)
-
- def test_diff_for_file_with_existing_bogus_dir(self):
- self.clean_bogus_dir()
- os.mkdir(self.bogus_dir)
- self.do_test_diff_for_file()
- self.assertTrue(os.path.exists(self.bogus_dir))
- shutil.rmtree(self.bogus_dir)
-
- def test_diff_for_file_with_missing_bogus_dir(self):
- self.clean_bogus_dir()
- self.do_test_diff_for_file()
- self.assertFalse(os.path.exists(self.bogus_dir))
-
- def test_svn_lock(self):
- if self.scm.svn_version() >= "1.7":
- # the following technique with .svn/lock then svn update doesn't work with subversion client 1.7 or later
- pass
- else:
- svn_root_lock_path = ".svn/lock"
- write_into_file_at_path(svn_root_lock_path, "", "utf-8")
- # webkit-patch uses a Checkout object and runs update-webkit, just use svn update here.
- self.assertRaises(ScriptError, run_command, ['svn', 'update'])
- self.scm.discard_working_directory_changes()
- self.assertFalse(os.path.exists(svn_root_lock_path))
- run_command(['svn', 'update']) # Should succeed and not raise.
-
- def test_exists(self):
- self._shared_test_exists(self.scm, self.scm.commit_with_message)
-
-class GitTest(SCMTest):
-
- def setUp(self):
- """Sets up fresh git repository with one commit. Then setups a second git
- repo that tracks the first one."""
- # FIXME: We should instead clone a git repo that is tracking an SVN repo.
- # That better matches what we do with WebKit.
- self.original_dir = os.getcwd()
-
- self.untracking_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout2")
- run_command(['git', 'init', self.untracking_checkout_path])
-
- os.chdir(self.untracking_checkout_path)
- write_into_file_at_path('foo_file', 'foo')
- run_command(['git', 'add', 'foo_file'])
- run_command(['git', 'commit', '-am', 'dummy commit'])
- self.untracking_scm = detect_scm_system(self.untracking_checkout_path)
-
- self.tracking_git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout")
- run_command(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path])
- os.chdir(self.tracking_git_checkout_path)
- self.tracking_scm = detect_scm_system(self.tracking_git_checkout_path)
-
- def tearDown(self):
- # Change back to a valid directory so that later calls to os.getcwd() do not fail.
- os.chdir(self.original_dir)
- run_command(['rm', '-rf', self.tracking_git_checkout_path])
- run_command(['rm', '-rf', self.untracking_checkout_path])
-
- def test_remote_branch_ref(self):
- self.assertEqual(self.tracking_scm.remote_branch_ref(), 'refs/remotes/origin/master')
-
- os.chdir(self.untracking_checkout_path)
- self.assertRaises(ScriptError, self.untracking_scm.remote_branch_ref)
-
- def test_multiple_remotes(self):
- run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote1'])
- run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2'])
- self.assertEqual(self.tracking_scm.remote_branch_ref(), 'remote1')
-
- def test_create_patch(self):
- write_into_file_at_path('test_file_commit1', 'contents')
- run_command(['git', 'add', 'test_file_commit1'])
- scm = self.tracking_scm
- scm.commit_locally_with_message('message')
-
- patch = scm.create_patch()
- self.assertNotRegexpMatches(patch, r'Subversion Revision:')
-
- def test_orderfile(self):
- os.mkdir("Tools")
- os.mkdir("Source")
- os.mkdir("LayoutTests")
- os.mkdir("Websites")
-
- # Slash should always be the right path separator since we use cygwin on Windows.
- Tools_ChangeLog = "Tools/ChangeLog"
- write_into_file_at_path(Tools_ChangeLog, "contents")
- Source_ChangeLog = "Source/ChangeLog"
- write_into_file_at_path(Source_ChangeLog, "contents")
- LayoutTests_ChangeLog = "LayoutTests/ChangeLog"
- write_into_file_at_path(LayoutTests_ChangeLog, "contents")
- Websites_ChangeLog = "Websites/ChangeLog"
- write_into_file_at_path(Websites_ChangeLog, "contents")
-
- Tools_ChangeFile = "Tools/ChangeFile"
- write_into_file_at_path(Tools_ChangeFile, "contents")
- Source_ChangeFile = "Source/ChangeFile"
- write_into_file_at_path(Source_ChangeFile, "contents")
- LayoutTests_ChangeFile = "LayoutTests/ChangeFile"
- write_into_file_at_path(LayoutTests_ChangeFile, "contents")
- Websites_ChangeFile = "Websites/ChangeFile"
- write_into_file_at_path(Websites_ChangeFile, "contents")
-
- run_command(['git', 'add', 'Tools/ChangeLog'])
- run_command(['git', 'add', 'LayoutTests/ChangeLog'])
- run_command(['git', 'add', 'Source/ChangeLog'])
- run_command(['git', 'add', 'Websites/ChangeLog'])
- run_command(['git', 'add', 'Tools/ChangeFile'])
- run_command(['git', 'add', 'LayoutTests/ChangeFile'])
- run_command(['git', 'add', 'Source/ChangeFile'])
- run_command(['git', 'add', 'Websites/ChangeFile'])
- scm = self.tracking_scm
- scm.commit_locally_with_message('message')
-
- patch = scm.create_patch()
- self.assertTrue(re.search(r'Tools/ChangeLog', patch).start() < re.search(r'Tools/ChangeFile', patch).start())
- self.assertTrue(re.search(r'Websites/ChangeLog', patch).start() < re.search(r'Websites/ChangeFile', patch).start())
- self.assertTrue(re.search(r'Source/ChangeLog', patch).start() < re.search(r'Source/ChangeFile', patch).start())
- self.assertTrue(re.search(r'LayoutTests/ChangeLog', patch).start() < re.search(r'LayoutTests/ChangeFile', patch).start())
-
- self.assertTrue(re.search(r'Source/ChangeLog', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start())
- self.assertTrue(re.search(r'Tools/ChangeLog', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start())
- self.assertTrue(re.search(r'Websites/ChangeLog', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start())
-
- self.assertTrue(re.search(r'Source/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start())
- self.assertTrue(re.search(r'Tools/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start())
- self.assertTrue(re.search(r'Websites/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start())
-
- self.assertTrue(re.search(r'Source/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeFile', patch).start())
- self.assertTrue(re.search(r'Tools/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeFile', patch).start())
- self.assertTrue(re.search(r'Websites/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeFile', patch).start())
-
- def test_exists(self):
- scm = self.untracking_scm
- self._shared_test_exists(scm, scm.commit_locally_with_message)
-
- def test_head_svn_revision(self):
- scm = detect_scm_system(self.untracking_checkout_path)
- # If we cloned a git repo tracking an SVN repo, this would give the same result as
- # self._shared_test_head_svn_revision().
- self.assertEqual(scm.head_svn_revision(), '')
-
- def test_rename_files(self):
- scm = self.tracking_scm
-
- run_command(['git', 'mv', 'foo_file', 'bar_file'])
- scm.commit_locally_with_message('message')
-
- patch = scm.create_patch()
- self.assertNotRegexpMatches(patch, r'rename from ')
- self.assertNotRegexpMatches(patch, r'rename to ')
-
-
-class GitSVNTest(SCMTest):
-
- def _setup_git_checkout(self):
- self.git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout")
- # --quiet doesn't make git svn silent, so we use run_silent to redirect output
- run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path])
- os.chdir(self.git_checkout_path)
-
- def _tear_down_git_checkout(self):
- # Change back to a valid directory so that later calls to os.getcwd() do not fail.
- os.chdir(self.original_dir)
- run_command(['rm', '-rf', self.git_checkout_path])
-
- def setUp(self):
- self.original_dir = os.getcwd()
-
- SVNTestRepository.setup(self)
- self._setup_git_checkout()
- self.scm = detect_scm_system(self.git_checkout_path)
- self.scm.svn_server_realm = None
- # For historical reasons, we test some checkout code here too.
- self.checkout = Checkout(self.scm)
-
- def tearDown(self):
- SVNTestRepository.tear_down(self)
- self._tear_down_git_checkout()
-
- def test_detection(self):
- self.assertEqual(self.scm.display_name(), "git")
- self.assertEqual(self.scm.supports_local_commits(), True)
-
- def test_read_git_config(self):
- key = 'test.git-config'
- value = 'git-config value'
- run_command(['git', 'config', key, value])
- self.assertEqual(self.scm.read_git_config(key), value)
-
- def test_local_commits(self):
- test_file = os.path.join(self.git_checkout_path, 'test_file')
- write_into_file_at_path(test_file, 'foo')
- run_command(['git', 'commit', '-a', '-m', 'local commit'])
-
- self.assertEqual(len(self.scm.local_commits()), 1)
-
- def test_discard_local_commits(self):
- test_file = os.path.join(self.git_checkout_path, 'test_file')
- write_into_file_at_path(test_file, 'foo')
- run_command(['git', 'commit', '-a', '-m', 'local commit'])
-
- self.assertEqual(len(self.scm.local_commits()), 1)
- self.scm.discard_local_commits()
- self.assertEqual(len(self.scm.local_commits()), 0)
-
- def test_delete_branch(self):
- new_branch = 'foo'
-
- run_command(['git', 'checkout', '-b', new_branch])
- self.assertEqual(run_command(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch)
-
- run_command(['git', 'checkout', '-b', 'bar'])
- self.scm.delete_branch(new_branch)
-
- self.assertNotRegexpMatches(run_command(['git', 'branch']), r'foo')
-
- def test_remote_merge_base(self):
- # Diff to merge-base should include working-copy changes,
- # which the diff to svn_branch.. doesn't.
- test_file = os.path.join(self.git_checkout_path, 'test_file')
- write_into_file_at_path(test_file, 'foo')
-
- diff_to_common_base = _git_diff(self.scm.remote_branch_ref() + '..')
- diff_to_merge_base = _git_diff(self.scm.remote_merge_base())
-
- self.assertNotRegexpMatches(diff_to_common_base, r'foo')
- self.assertRegexpMatches(diff_to_merge_base, r'foo')
-
- def test_rebase_in_progress(self):
- svn_test_file = os.path.join(self.svn_checkout_path, 'test_file')
- write_into_file_at_path(svn_test_file, "svn_checkout")
- run_command(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path)
-
- git_test_file = os.path.join(self.git_checkout_path, 'test_file')
- write_into_file_at_path(git_test_file, "git_checkout")
- run_command(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort'])
-
- # --quiet doesn't make git svn silent, so use run_silent to redirect output
- self.assertRaises(ScriptError, run_silent, ['git', 'svn', '--quiet', 'rebase']) # Will fail due to a conflict leaving us mid-rebase.
-
- self.assertTrue(self.scm.rebase_in_progress())
-
- # Make sure our cleanup works.
- self.scm.discard_working_directory_changes()
- self.assertFalse(self.scm.rebase_in_progress())
-
- # Make sure cleanup doesn't throw when no rebase is in progress.
- self.scm.discard_working_directory_changes()
-
- def test_commitish_parsing(self):
- # Multiple revisions are cherry-picked.
- self.assertEqual(len(self.scm.commit_ids_from_commitish_arguments(['HEAD~2'])), 1)
- self.assertEqual(len(self.scm.commit_ids_from_commitish_arguments(['HEAD', 'HEAD~2'])), 2)
-
- # ... is an invalid range specifier
- self.assertRaises(ScriptError, self.scm.commit_ids_from_commitish_arguments, ['trunk...HEAD'])
-
- def test_commitish_order(self):
- commit_range = 'HEAD~3..HEAD'
-
- actual_commits = self.scm.commit_ids_from_commitish_arguments([commit_range])
- expected_commits = []
- expected_commits += reversed(run_command(['git', 'rev-list', commit_range]).splitlines())
-
- self.assertEqual(actual_commits, expected_commits)
-
- def test_apply_git_patch(self):
- # We carefullly pick a diff which does not have a directory addition
- # as currently svn-apply will error out when trying to remove directories
- # in Git: https://bugs.webkit.org/show_bug.cgi?id=34871
- patch = self._create_patch(_git_diff('HEAD..HEAD^'))
- self._setup_webkittools_scripts_symlink(self.scm)
- Checkout(self.scm).apply_patch(patch)
-
- def test_commit_text_parsing(self):
- write_into_file_at_path('test_file', 'more test content')
- commit_text = self.scm.commit_with_message("another test commit")
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
-
- def test_commit_with_message_working_copy_only(self):
- write_into_file_at_path('test_file_commit1', 'more test content')
- run_command(['git', 'add', 'test_file_commit1'])
- commit_text = self.scm.commit_with_message("yet another test commit")
-
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
-
- def _local_commit(self, filename, contents, message):
- write_into_file_at_path(filename, contents)
- run_command(['git', 'add', filename])
- self.scm.commit_locally_with_message(message)
-
- def _one_local_commit(self):
- self._local_commit('test_file_commit1', 'more test content', 'another test commit')
-
- def _one_local_commit_plus_working_copy_changes(self):
- self._one_local_commit()
- write_into_file_at_path('test_file_commit2', 'still more test content')
- run_command(['git', 'add', 'test_file_commit2'])
-
- def _second_local_commit(self):
- self._local_commit('test_file_commit2', 'still more test content', 'yet another test commit')
-
- def _two_local_commits(self):
- self._one_local_commit()
- self._second_local_commit()
-
- def _three_local_commits(self):
- self._local_commit('test_file_commit0', 'more test content', 'another test commit')
- self._two_local_commits()
-
- def test_revisions_changing_files_with_local_commit(self):
- self._one_local_commit()
- self.assertItemsEqual(self.scm.revisions_changing_file('test_file_commit1'), [])
-
- def test_commit_with_message(self):
- self._one_local_commit_plus_working_copy_changes()
- self.assertRaises(AmbiguousCommitError, self.scm.commit_with_message, "yet another test commit")
- commit_text = self.scm.commit_with_message("yet another test commit", force_squash=True)
-
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertRegexpMatches(svn_log, r'test_file_commit2')
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
-
- def test_commit_with_message_git_commit(self):
- self._two_local_commits()
-
- commit_text = self.scm.commit_with_message("another test commit", git_commit="HEAD^")
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
- self.assertNotRegexpMatches(svn_log, r'test_file_commit2')
-
- def test_commit_with_message_git_commit_range(self):
- self._three_local_commits()
-
- commit_text = self.scm.commit_with_message("another test commit", git_commit="HEAD~2..HEAD")
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertNotRegexpMatches(svn_log, r'test_file_commit0')
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
- self.assertRegexpMatches(svn_log, r'test_file_commit2')
-
- def test_commit_with_message_only_local_commit(self):
- self._one_local_commit()
- commit_text = self.scm.commit_with_message("another test commit")
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
-
- def test_commit_with_message_multiple_local_commits_and_working_copy(self):
- self._two_local_commits()
- write_into_file_at_path('test_file_commit1', 'working copy change')
-
- self.assertRaises(AmbiguousCommitError, self.scm.commit_with_message, "another test commit")
- commit_text = self.scm.commit_with_message("another test commit", force_squash=True)
-
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertRegexpMatches(svn_log, r'test_file_commit2')
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
-
- def test_commit_with_message_git_commit_and_working_copy(self):
- self._two_local_commits()
- write_into_file_at_path('test_file_commit1', 'working copy change')
- self.assertRaises(ScriptError, self.scm.commit_with_message, "another test commit", git_commit="HEAD^")
-
- def test_commit_with_message_multiple_local_commits_always_squash(self):
- run_command(['git', 'config', 'webkit-patch.commit-should-always-squash', 'true'])
- self._two_local_commits()
- commit_text = self.scm.commit_with_message("yet another test commit")
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertRegexpMatches(svn_log, r'test_file_commit2')
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
-
- def test_commit_with_message_multiple_local_commits(self):
- self._two_local_commits()
- self.assertRaises(AmbiguousCommitError, self.scm.commit_with_message, "yet another test commit")
- commit_text = self.scm.commit_with_message("yet another test commit", force_squash=True)
-
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertRegexpMatches(svn_log, r'test_file_commit2')
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
-
- def test_commit_with_message_not_synced(self):
- run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3'])
- self._two_local_commits()
- self.assertRaises(AmbiguousCommitError, self.scm.commit_with_message, "another test commit")
- commit_text = self.scm.commit_with_message("another test commit", force_squash=True)
-
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertNotRegexpMatches(svn_log, r'test_file2')
- self.assertRegexpMatches(svn_log, r'test_file_commit2')
- self.assertRegexpMatches(svn_log, r'test_file_commit1')
-
- def test_commit_with_message_not_synced_with_conflict(self):
- run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3'])
- self._local_commit('test_file2', 'asdf', 'asdf commit')
-
- # There's a conflict between trunk and the test_file2 modification.
- self.assertRaises(ScriptError, self.scm.commit_with_message, "another test commit", force_squash=True)
-
- def test_upstream_branch(self):
- run_command(['git', 'checkout', '-t', '-b', 'my-branch'])
- run_command(['git', 'checkout', '-t', '-b', 'my-second-branch'])
- self.assertEqual(self.scm._upstream_branch(), 'my-branch')
-
- def test_remote_branch_ref(self):
- self.assertEqual(self.scm.remote_branch_ref(), 'refs/remotes/trunk')
-
- def test_reverse_diff(self):
- self._shared_test_reverse_diff()
-
- def test_diff_for_revision(self):
- self._shared_test_diff_for_revision()
-
- def test_svn_apply_git_patch(self):
- self._shared_test_svn_apply_git_patch()
-
- def test_create_patch_local_plus_working_copy(self):
- self._one_local_commit_plus_working_copy_changes()
- patch = self.scm.create_patch()
- self.assertRegexpMatches(patch, r'test_file_commit1')
- self.assertRegexpMatches(patch, r'test_file_commit2')
-
- def test_create_patch(self):
- self._one_local_commit_plus_working_copy_changes()
- patch = self.scm.create_patch()
- self.assertRegexpMatches(patch, r'test_file_commit2')
- self.assertRegexpMatches(patch, r'test_file_commit1')
- self.assertRegexpMatches(patch, r'Subversion Revision: 5')
-
- def test_create_patch_after_merge(self):
- run_command(['git', 'checkout', '-b', 'dummy-branch', 'trunk~3'])
- self._one_local_commit()
- run_command(['git', 'merge', 'trunk'])
-
- patch = self.scm.create_patch()
- self.assertRegexpMatches(patch, r'test_file_commit1')
- self.assertRegexpMatches(patch, r'Subversion Revision: 5')
-
- def test_create_patch_with_changed_files(self):
- self._one_local_commit_plus_working_copy_changes()
- patch = self.scm.create_patch(changed_files=['test_file_commit2'])
- self.assertRegexpMatches(patch, r'test_file_commit2')
-
- def test_create_patch_with_rm_and_changed_files(self):
- self._one_local_commit_plus_working_copy_changes()
- os.remove('test_file_commit1')
- patch = self.scm.create_patch()
- patch_with_changed_files = self.scm.create_patch(changed_files=['test_file_commit1', 'test_file_commit2'])
- self.assertEqual(patch, patch_with_changed_files)
-
- def test_create_patch_git_commit(self):
- self._two_local_commits()
- patch = self.scm.create_patch(git_commit="HEAD^")
- self.assertRegexpMatches(patch, r'test_file_commit1')
- self.assertNotRegexpMatches(patch, r'test_file_commit2')
-
- def test_create_patch_git_commit_range(self):
- self._three_local_commits()
- patch = self.scm.create_patch(git_commit="HEAD~2..HEAD")
- self.assertNotRegexpMatches(patch, r'test_file_commit0')
- self.assertRegexpMatches(patch, r'test_file_commit2')
- self.assertRegexpMatches(patch, r'test_file_commit1')
-
- def test_create_patch_working_copy_only(self):
- self._one_local_commit_plus_working_copy_changes()
- patch = self.scm.create_patch(git_commit="HEAD....")
- self.assertNotRegexpMatches(patch, r'test_file_commit1')
- self.assertRegexpMatches(patch, r'test_file_commit2')
-
- def test_create_patch_multiple_local_commits(self):
- self._two_local_commits()
- patch = self.scm.create_patch()
- self.assertRegexpMatches(patch, r'test_file_commit2')
- self.assertRegexpMatches(patch, r'test_file_commit1')
-
- def test_create_patch_not_synced(self):
- run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3'])
- self._two_local_commits()
- patch = self.scm.create_patch()
- self.assertNotRegexpMatches(patch, r'test_file2')
- self.assertRegexpMatches(patch, r'test_file_commit2')
- self.assertRegexpMatches(patch, r'test_file_commit1')
-
- def test_create_binary_patch(self):
- # Create a git binary patch and check the contents.
- test_file_name = 'binary_file'
- test_file_path = os.path.join(self.git_checkout_path, test_file_name)
- file_contents = ''.join(map(chr, range(256)))
- write_into_file_at_path(test_file_path, file_contents, encoding=None)
- run_command(['git', 'add', test_file_name])
- patch = self.scm.create_patch()
- self.assertRegexpMatches(patch, r'\nliteral 0\n')
- self.assertRegexpMatches(patch, r'\nliteral 256\n')
-
- # Check if we can apply the created patch.
- run_command(['git', 'rm', '-f', test_file_name])
- self._setup_webkittools_scripts_symlink(self.scm)
- self.checkout.apply_patch(self._create_patch(patch))
- self.assertEqual(file_contents, read_from_path(test_file_path, encoding=None))
-
- # Check if we can create a patch from a local commit.
- write_into_file_at_path(test_file_path, file_contents, encoding=None)
- run_command(['git', 'add', test_file_name])
- run_command(['git', 'commit', '-m', 'binary diff'])
-
- patch_from_local_commit = self.scm.create_patch('HEAD')
- self.assertRegexpMatches(patch_from_local_commit, r'\nliteral 0\n')
- self.assertRegexpMatches(patch_from_local_commit, r'\nliteral 256\n')
-
- def test_changed_files_local_plus_working_copy(self):
- self._one_local_commit_plus_working_copy_changes()
- files = self.scm.changed_files()
- self.assertIn('test_file_commit1', files)
- self.assertIn('test_file_commit2', files)
-
- # working copy should *not* be in the list.
- files = self.scm.changed_files('trunk..')
- self.assertIn('test_file_commit1', files)
- self.assertNotIn('test_file_commit2', files)
-
- # working copy *should* be in the list.
- files = self.scm.changed_files('trunk....')
- self.assertIn('test_file_commit1', files)
- self.assertIn('test_file_commit2', files)
-
- def test_changed_files_git_commit(self):
- self._two_local_commits()
- files = self.scm.changed_files(git_commit="HEAD^")
- self.assertIn('test_file_commit1', files)
- self.assertNotIn('test_file_commit2', files)
-
- def test_changed_files_git_commit_range(self):
- self._three_local_commits()
- files = self.scm.changed_files(git_commit="HEAD~2..HEAD")
- self.assertNotIn('test_file_commit0', files)
- self.assertIn('test_file_commit1', files)
- self.assertIn('test_file_commit2', files)
-
- def test_changed_files_working_copy_only(self):
- self._one_local_commit_plus_working_copy_changes()
- files = self.scm.changed_files(git_commit="HEAD....")
- self.assertNotIn('test_file_commit1', files)
- self.assertIn('test_file_commit2', files)
-
- def test_changed_files_multiple_local_commits(self):
- self._two_local_commits()
- files = self.scm.changed_files()
- self.assertIn('test_file_commit2', files)
- self.assertIn('test_file_commit1', files)
-
- def test_changed_files_not_synced(self):
- run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3'])
- self._two_local_commits()
- files = self.scm.changed_files()
- self.assertNotIn('test_file2', files)
- self.assertIn('test_file_commit2', files)
- self.assertIn('test_file_commit1', files)
-
- def test_changed_files_not_synced(self):
- run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3'])
- self._two_local_commits()
- files = self.scm.changed_files()
- self.assertNotIn('test_file2', files)
- self.assertIn('test_file_commit2', files)
- self.assertIn('test_file_commit1', files)
-
- def test_changed_files(self):
- self._shared_test_changed_files()
-
- def test_changed_files_for_revision(self):
- self._shared_test_changed_files_for_revision()
-
- def test_changed_files_upstream(self):
- run_command(['git', 'checkout', '-t', '-b', 'my-branch'])
- self._one_local_commit()
- run_command(['git', 'checkout', '-t', '-b', 'my-second-branch'])
- self._second_local_commit()
- write_into_file_at_path('test_file_commit0', 'more test content')
- run_command(['git', 'add', 'test_file_commit0'])
-
- # equivalent to 'git diff my-branch..HEAD, should not include working changes
- files = self.scm.changed_files(git_commit='UPSTREAM..')
- self.assertNotIn('test_file_commit1', files)
- self.assertIn('test_file_commit2', files)
- self.assertNotIn('test_file_commit0', files)
-
- # equivalent to 'git diff my-branch', *should* include working changes
- files = self.scm.changed_files(git_commit='UPSTREAM....')
- self.assertNotIn('test_file_commit1', files)
- self.assertIn('test_file_commit2', files)
- self.assertIn('test_file_commit0', files)
-
- def test_contents_at_revision(self):
- self._shared_test_contents_at_revision()
-
- def test_revisions_changing_file(self):
- self._shared_test_revisions_changing_file()
-
- def test_added_files(self):
- self._shared_test_added_files()
-
- def test_committer_email_for_revision(self):
- self._shared_test_committer_email_for_revision()
-
- def test_add_recursively(self):
- self._shared_test_add_recursively()
-
- def test_delete(self):
- self._two_local_commits()
- self.scm.delete('test_file_commit1')
- self.assertIn("test_file_commit1", self.scm.deleted_files())
-
- def test_delete_list(self):
- self._two_local_commits()
- self.scm.delete_list(["test_file_commit1", "test_file_commit2"])
- self.assertIn("test_file_commit1", self.scm.deleted_files())
- self.assertIn("test_file_commit2", self.scm.deleted_files())
-
- def test_delete_recursively(self):
- self._shared_test_delete_recursively()
-
- def test_delete_recursively_or_not(self):
- self._shared_test_delete_recursively_or_not()
-
- def test_head_svn_revision(self):
- self._shared_test_head_svn_revision()
-
- def test_to_object_name(self):
- relpath = 'test_file_commit1'
- fullpath = os.path.realpath(os.path.join(self.git_checkout_path, relpath))
- self.assertEqual(relpath, self.scm.to_object_name(fullpath))
-
- def test_show_head(self):
- self._two_local_commits()
- self.assertEqual("more test content", self.scm.show_head('test_file_commit1'))
-
- def test_show_head_binary(self):
- self._two_local_commits()
- data = "\244"
- write_into_file_at_path("binary_file", data, encoding=None)
- self.scm.add("binary_file")
- self.scm.commit_locally_with_message("a test commit")
- self.assertEqual(data, self.scm.show_head('binary_file'))
-
- def test_diff_for_file(self):
- self._two_local_commits()
- write_into_file_at_path('test_file_commit1', "Updated", encoding=None)
-
- diff = self.scm.diff_for_file('test_file_commit1')
- cached_diff = self.scm.diff_for_file('test_file_commit1')
- self.assertIn("+Updated", diff)
- self.assertIn("-more test content", diff)
-
- self.scm.add('test_file_commit1')
-
- cached_diff = self.scm.diff_for_file('test_file_commit1')
- self.assertIn("+Updated", cached_diff)
- self.assertIn("-more test content", cached_diff)
-
- def test_exists(self):
- self._shared_test_exists(self.scm, self.scm.commit_locally_with_message)
-
-
-# We need to split off more of these SCM tests to use mocks instead of the filesystem.
-# This class is the first part of that.
-class GitTestWithMock(unittest.TestCase):
- maxDiff = None
-
- def make_scm(self, logging_executive=False):
- # We do this should_log dance to avoid logging when Git.__init__ runs sysctl on mac to check for 64-bit support.
- scm = Git(cwd=".", executive=MockExecutive(), filesystem=MockFileSystem())
- scm.read_git_config = lambda *args, **kw: "MOCKKEY:MOCKVALUE"
- scm._executive._should_log = logging_executive
- return scm
-
- def test_create_patch(self):
- scm = self.make_scm(logging_executive=True)
- expected_stderr = """\
-MOCK run_command: ['git', 'merge-base', 'MOCKVALUE', 'HEAD'], cwd=%(checkout)s
-MOCK run_command: ['git', 'diff', '--binary', '--no-color', '--no-ext-diff', '--full-index', '--no-renames', '', 'MOCK output of child process', '--'], cwd=%(checkout)s
-MOCK run_command: ['git', 'rev-parse', '--show-toplevel'], cwd=%(checkout)s
-MOCK run_command: ['git', 'log', '-1', '--grep=git-svn-id:', '--date=iso', './MOCK output of child process/MOCK output of child process'], cwd=%(checkout)s
-""" % {'checkout': scm.checkout_root}
- OutputCapture().assert_outputs(self, scm.create_patch, expected_logs=expected_stderr)
-
- def test_push_local_commits_to_server_with_username_and_password(self):
- self.assertEqual(self.make_scm().push_local_commits_to_server(username='dbates@webkit.org', password='blah'), "MOCK output of child process")
-
- def test_push_local_commits_to_server_without_username_and_password(self):
- self.assertRaises(AuthenticationError, self.make_scm().push_local_commits_to_server)
-
- def test_push_local_commits_to_server_with_username_and_without_password(self):
- self.assertRaises(AuthenticationError, self.make_scm().push_local_commits_to_server, {'username': 'dbates@webkit.org'})
-
- def test_push_local_commits_to_server_without_username_and_with_password(self):
- self.assertRaises(AuthenticationError, self.make_scm().push_local_commits_to_server, {'password': 'blah'})
-
- def test_timestamp_of_revision(self):
- scm = self.make_scm()
- scm.find_checkout_root = lambda path: ''
- scm._run_git = lambda args: 'Date: 2013-02-08 08:05:49 +0000'
- self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-08T08:05:49Z')
-
- scm._run_git = lambda args: 'Date: 2013-02-08 01:02:03 +0130'
- self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-07T23:32:03Z')
-
- scm._run_git = lambda args: 'Date: 2013-02-08 01:55:21 -0800'
- self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-08T09:55:21Z')
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/svn.py b/Tools/Scripts/webkitpy/common/checkout/scm/svn.py
deleted file mode 100644
index c146f5d32..000000000
--- a/Tools/Scripts/webkitpy/common/checkout/scm/svn.py
+++ /dev/null
@@ -1,380 +0,0 @@
-# Copyright (c) 2009, 2010, 2011 Google Inc. All rights reserved.
-# Copyright (c) 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:
-#
-# * 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.
-
-import logging
-import os
-import random
-import re
-import shutil
-import string
-import sys
-import tempfile
-
-from webkitpy.common.memoized import memoized
-from webkitpy.common.system.executive import Executive, ScriptError
-
-from .scm import AuthenticationError, SCM, commit_error_handler
-
-_log = logging.getLogger(__name__)
-
-
-# A mixin class that represents common functionality for SVN and Git-SVN.
-class SVNRepository(object):
- # FIXME: These belong in common.config.urls
- svn_server_host = "svn.webkit.org"
- svn_server_realm = "<http://svn.webkit.org:80> Mac OS Forge"
-
- def has_authorization_for_realm(self, realm, home_directory=os.getenv("HOME")):
- # If we are working on a file:// repository realm will be None
- if realm is None:
- return True
- # ignore false positives for methods implemented in the mixee class. pylint: disable=E1101
- # Assumes find and grep are installed.
- if not os.path.isdir(os.path.join(home_directory, ".subversion")):
- return False
- find_args = ["find", ".subversion", "-type", "f", "-exec", "grep", "-q", realm, "{}", ";", "-print"]
- find_output = self.run(find_args, cwd=home_directory, error_handler=Executive.ignore_error).rstrip()
- if not find_output or not os.path.isfile(os.path.join(home_directory, find_output)):
- return False
- # Subversion either stores the password in the credential file, indicated by the presence of the key "password",
- # or uses the system password store (e.g. Keychain on Mac OS X) as indicated by the presence of the key "passtype".
- # We assume that these keys will not coincide with the actual credential data (e.g. that a person's username
- # isn't "password") so that we can use grep.
- if self.run(["grep", "password", find_output], cwd=home_directory, return_exit_code=True) == 0:
- return True
- return self.run(["grep", "passtype", find_output], cwd=home_directory, return_exit_code=True) == 0
-
-
-class SVN(SCM, SVNRepository):
-
- executable_name = "svn"
-
- _svn_metadata_files = frozenset(['.svn', '_svn'])
-
- def __init__(self, cwd, patch_directories, **kwargs):
- SCM.__init__(self, cwd, **kwargs)
- self._bogus_dir = None
- if patch_directories == []:
- raise Exception(message='Empty list of patch directories passed to SCM.__init__')
- elif patch_directories == None:
- self._patch_directories = [self._filesystem.relpath(cwd, self.checkout_root)]
- else:
- self._patch_directories = patch_directories
-
- @classmethod
- def in_working_directory(cls, path, executive=None):
- if os.path.isdir(os.path.join(path, '.svn')):
- # This is a fast shortcut for svn info that is usually correct for SVN < 1.7,
- # but doesn't work for SVN >= 1.7.
- return True
-
- executive = executive or Executive()
- svn_info_args = [cls.executable_name, 'info']
- exit_code = executive.run_command(svn_info_args, cwd=path, return_exit_code=True)
- return (exit_code == 0)
-
- def find_uuid(self, path):
- if not self.in_working_directory(path):
- return None
- return self.value_from_svn_info(path, 'Repository UUID')
-
- @classmethod
- def value_from_svn_info(cls, path, field_name):
- svn_info_args = [cls.executable_name, 'info']
- # FIXME: This method should use a passed in executive or be made an instance method and use self._executive.
- info_output = Executive().run_command(svn_info_args, cwd=path).rstrip()
- match = re.search("^%s: (?P<value>.+)$" % field_name, info_output, re.MULTILINE)
- if not match:
- raise ScriptError(script_args=svn_info_args, message='svn info did not contain a %s.' % field_name)
- return match.group('value').rstrip('\r')
-
- def find_checkout_root(self, path):
- uuid = self.find_uuid(path)
- # If |path| is not in a working directory, we're supposed to return |path|.
- if not uuid:
- return path
- # Search up the directory hierarchy until we find a different UUID.
- last_path = None
- while True:
- if uuid != self.find_uuid(path):
- return last_path
- last_path = path
- (path, last_component) = self._filesystem.split(path)
- if last_path == path:
- return None
-
- @staticmethod
- def commit_success_regexp():
- return "^Committed revision (?P<svn_revision>\d+)\.$"
-
- def _run_svn(self, args, **kwargs):
- return self.run([self.executable_name] + args, **kwargs)
-
- @memoized
- def svn_version(self):
- return self._run_svn(['--version', '--quiet'])
-
- def has_working_directory_changes(self):
- # FIXME: What about files which are not committed yet?
- return self._run_svn(["diff"], cwd=self.checkout_root, decode_output=False) != ""
-
- def discard_working_directory_changes(self):
- # Make sure there are no locks lying around from a previously aborted svn invocation.
- # This is slightly dangerous, as it's possible the user is running another svn process
- # on this checkout at the same time. However, it's much more likely that we're running
- # under windows and svn just sucks (or the user interrupted svn and it failed to clean up).
- self._run_svn(["cleanup"], cwd=self.checkout_root)
-
- # svn revert -R is not as awesome as git reset --hard.
- # It will leave added files around, causing later svn update
- # calls to fail on the bots. We make this mirror git reset --hard
- # by deleting any added files as well.
- added_files = reversed(sorted(self.added_files()))
- # added_files() returns directories for SVN, we walk the files in reverse path
- # length order so that we remove files before we try to remove the directories.
- self._run_svn(["revert", "-R", "."], cwd=self.checkout_root)
- for path in added_files:
- # This is robust against cwd != self.checkout_root
- absolute_path = self.absolute_path(path)
- # Completely lame that there is no easy way to remove both types with one call.
- if os.path.isdir(path):
- os.rmdir(absolute_path)
- else:
- os.remove(absolute_path)
-
- def status_command(self):
- return [self.executable_name, 'status']
-
- def _status_regexp(self, expected_types):
- field_count = 6 if self.svn_version() > "1.6" else 5
- return "^(?P<status>[%s]).{%s} (?P<filename>.+)$" % (expected_types, field_count)
-
- def _add_parent_directories(self, path):
- """Does 'svn add' to the path and its parents."""
- if self.in_working_directory(path):
- return
- self.add(path)
-
- def add_list(self, paths):
- for path in paths:
- self._add_parent_directories(os.path.dirname(os.path.abspath(path)))
- if self.svn_version() >= "1.7":
- # For subversion client 1.7 and later, need to add '--parents' option to ensure intermediate directories
- # are added; in addition, 1.7 returns an exit code of 1 from svn add if one or more of the requested
- # adds are already under version control, including intermediate directories subject to addition
- # due to --parents
- svn_add_args = ['svn', 'add', '--parents'] + paths
- exit_code = self.run(svn_add_args, return_exit_code=True)
- if exit_code and exit_code != 1:
- raise ScriptError(script_args=svn_add_args, exit_code=exit_code)
- else:
- self._run_svn(["add"] + paths)
-
- def _delete_parent_directories(self, path):
- if not self.in_working_directory(path):
- return
- if set(os.listdir(path)) - self._svn_metadata_files:
- return # Directory has non-trivial files in it.
- self.delete(path)
-
- def delete_list(self, paths):
- for path in paths:
- abs_path = os.path.abspath(path)
- parent, base = os.path.split(abs_path)
- result = self._run_svn(["delete", "--force", base], cwd=parent)
- self._delete_parent_directories(os.path.dirname(abs_path))
- return result
-
- def exists(self, path):
- return not self._run_svn(["info", path], return_exit_code=True, decode_output=False)
-
- def changed_files(self, git_commit=None):
- status_command = [self.executable_name, "status"]
- status_command.extend(self._patch_directories)
- # ACDMR: Addded, Conflicted, Deleted, Modified or Replaced
- return self.run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR"))
-
- def changed_files_for_revision(self, revision):
- # As far as I can tell svn diff --summarize output looks just like svn status output.
- # No file contents printed, thus utf-8 auto-decoding in self.run is fine.
- status_command = [self.executable_name, "diff", "--summarize", "-c", revision]
- return self.run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR"))
-
- def revisions_changing_file(self, path, limit=5):
- revisions = []
- # svn log will exit(1) (and thus self.run will raise) if the path does not exist.
- log_command = ['log', '--quiet', '--limit=%s' % limit, path]
- for line in self._run_svn(log_command, cwd=self.checkout_root).splitlines():
- match = re.search('^r(?P<revision>\d+) ', line)
- if not match:
- continue
- revisions.append(int(match.group('revision')))
- return revisions
-
- def conflicted_files(self):
- return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("C"))
-
- def added_files(self):
- return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("A"))
-
- def deleted_files(self):
- return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("D"))
-
- @staticmethod
- def supports_local_commits():
- return False
-
- def display_name(self):
- return "svn"
-
- def svn_revision(self, path):
- return self.value_from_svn_info(path, 'Revision')
-
- def timestamp_of_revision(self, path, revision):
- # We use --xml to get timestamps like 2013-02-08T08:18:04.964409Z
- repository_root = self.value_from_svn_info(self.checkout_root, 'Repository Root')
- info_output = Executive().run_command([self.executable_name, 'log', '-r', revision, '--xml', repository_root], cwd=path).rstrip()
- match = re.search(r"^<date>(?P<value>.+)</date>\r?$", info_output, re.MULTILINE)
- return match.group('value')
-
- # FIXME: This method should be on Checkout.
- def create_patch(self, git_commit=None, changed_files=None):
- """Returns a byte array (str()) representing the patch file.
- Patch files are effectively binary since they may contain
- files of multiple different encodings."""
- if changed_files == []:
- return ""
- elif changed_files == None:
- changed_files = []
- return self.run([self.script_path("svn-create-patch")] + changed_files,
- cwd=self.checkout_root, return_stderr=False,
- decode_output=False)
-
- def committer_email_for_revision(self, revision):
- return self._run_svn(["propget", "svn:author", "--revprop", "-r", revision]).rstrip()
-
- def contents_at_revision(self, path, revision):
- """Returns a byte array (str()) containing the contents
- of path @ revision in the repository."""
- remote_path = "%s/%s" % (self._repository_url(), path)
- return self._run_svn(["cat", "-r", revision, remote_path], decode_output=False)
-
- def diff_for_revision(self, revision):
- # FIXME: This should probably use cwd=self.checkout_root
- return self._run_svn(['diff', '-c', revision])
-
- def _bogus_dir_name(self):
- rnd = ''.join(random.sample(string.ascii_letters, 5))
- if sys.platform.startswith("win"):
- parent_dir = tempfile.gettempdir()
- else:
- parent_dir = sys.path[0] # tempdir is not secure.
- return os.path.join(parent_dir, "temp_svn_config_" + rnd)
-
- def _setup_bogus_dir(self, log):
- self._bogus_dir = self._bogus_dir_name()
- if not os.path.exists(self._bogus_dir):
- os.mkdir(self._bogus_dir)
- self._delete_bogus_dir = True
- else:
- self._delete_bogus_dir = False
- if log:
- log.debug(' Html: temp config dir: "%s".', self._bogus_dir)
-
- def _teardown_bogus_dir(self, log):
- if self._delete_bogus_dir:
- shutil.rmtree(self._bogus_dir, True)
- if log:
- log.debug(' Html: removed temp config dir: "%s".', self._bogus_dir)
- self._bogus_dir = None
-
- def diff_for_file(self, path, log=None):
- self._setup_bogus_dir(log)
- try:
- args = ['diff']
- if self._bogus_dir:
- args += ['--config-dir', self._bogus_dir]
- args.append(path)
- return self._run_svn(args, cwd=self.checkout_root)
- finally:
- self._teardown_bogus_dir(log)
-
- def show_head(self, path):
- return self._run_svn(['cat', '-r', 'BASE', path], decode_output=False)
-
- def _repository_url(self):
- return self.value_from_svn_info(self.checkout_root, 'URL')
-
- def apply_reverse_diff(self, revision):
- # '-c -revision' applies the inverse diff of 'revision'
- svn_merge_args = ['merge', '--non-interactive', '-c', '-%s' % revision, self._repository_url()]
- _log.warning("svn merge has been known to take more than 10 minutes to complete. It is recommended you use git for rollouts.")
- _log.debug("Running 'svn %s'" % " ".join(svn_merge_args))
- # FIXME: Should this use cwd=self.checkout_root?
- self._run_svn(svn_merge_args)
-
- def revert_files(self, file_paths):
- # FIXME: This should probably use cwd=self.checkout_root.
- self._run_svn(['revert'] + file_paths)
-
- def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None):
- # git-commit and force are not used by SVN.
- svn_commit_args = ["commit"]
-
- if not username and not self.has_authorization_for_realm(self.svn_server_realm):
- raise AuthenticationError(self.svn_server_host)
- if username:
- svn_commit_args.extend(["--username", username])
-
- svn_commit_args.extend(["-m", message])
-
- if changed_files:
- svn_commit_args.extend(changed_files)
-
- return self._run_svn(svn_commit_args, cwd=self.checkout_root, error_handler=commit_error_handler)
-
- def svn_commit_log(self, svn_revision):
- svn_revision = self.strip_r_from_svn_revision(svn_revision)
- return self._run_svn(['log', '--non-interactive', '--revision', svn_revision])
-
- def last_svn_commit_log(self):
- # BASE is the checkout revision, HEAD is the remote repository revision
- # http://svnbook.red-bean.com/en/1.0/ch03s03.html
- return self.svn_commit_log('BASE')
-
- def svn_blame(self, path):
- return self._run_svn(['blame', path])
-
- def propset(self, pname, pvalue, path):
- dir, base = os.path.split(path)
- return self._run_svn(['pset', pname, pvalue, base], cwd=dir)
-
- def propget(self, pname, path):
- dir, base = os.path.split(path)
- return self._run_svn(['pget', pname, base], cwd=dir).encode('utf-8').rstrip("\n")