summaryrefslogtreecommitdiffstats
path: root/WebKitTools/Scripts/webkitpy/common/checkout
diff options
context:
space:
mode:
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/common/checkout')
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/__init__.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/api.py160
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py196
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/changelog.py195
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py203
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/commitinfo.py93
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py61
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/diff_parser.py181
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py146
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/scm.py915
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py1291
11 files changed, 0 insertions, 3444 deletions
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py b/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py
deleted file mode 100644
index 597dcbd..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Required for Python to search this directory for module files
-
-from api import Checkout
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/api.py b/WebKitTools/Scripts/webkitpy/common/checkout/api.py
deleted file mode 100644
index dbe1e84..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/api.py
+++ /dev/null
@@ -1,160 +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 os
-import StringIO
-
-from webkitpy.common.checkout.changelog import ChangeLog
-from webkitpy.common.checkout.commitinfo import CommitInfo
-from webkitpy.common.checkout.scm import CommitMessage
-from webkitpy.common.memoized import memoized
-from webkitpy.common.net.bugzilla import parse_bug_id
-from webkitpy.common.system.executive import Executive, run_command, ScriptError
-from webkitpy.common.system.deprecated_logging import log
-
-
-# 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.
-class Checkout(object):
- def __init__(self, scm):
- self._scm = scm
-
- def _is_path_to_changelog(self, path):
- return os.path.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.
- changelog_file = StringIO.StringIO(changelog_contents.decode("utf-8"))
- return ChangeLog.parse_latest_entry_from_file(changelog_file)
-
- def changelog_entries_for_revision(self, revision):
- changed_files = self._scm.changed_files_for_revision(revision)
- return [self._latest_entry_for_changelog_at_revision(path, revision) for path in changed_files if self._is_path_to_changelog(path)]
-
- @memoized
- def commit_info_for_revision(self, revision):
- committer_email = self._scm.committer_email_for_revision(revision)
- changelog_entries = self.changelog_entries_for_revision(revision)
- # 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]
- changelog_data = {
- "bug_id": parse_bug_id(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(),
- }
- # We could pass the changelog_entry instead of a dictionary here, but that makes
- # mocking slightly more involved, and would make aggregating data from multiple
- # entries more difficult to wire in if we need to do that in the future.
- 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)
- absolute_paths = [os.path.join(self._scm.checkout_root, path) for path in changed_files]
- return [path for path in absolute_paths if predicate(path)]
-
- 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):
- 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"
- "http://webkit.org/coding/contributing.html")
-
- changelog_messages = []
- for changelog_path in changelog_paths:
- log("Parsing ChangeLog: %s" % changelog_path)
- changelog_entry = ChangeLog(changelog_path).latest_entry()
- if not changelog_entry:
- raise ScriptError(message="Failed to parse ChangeLog: %s" % os.path.abspath(changelog_path))
- changelog_messages.append(changelog_entry.contents())
-
- # FIXME: We should sort and label the ChangeLog messages like commit-log-editor does.
- return CommitMessage("".join(changelog_messages).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 = self.recent_commit_infos_for_files(changed_files)
- reviewers = [commit_info.reviewer() for commit_info in commit_infos if commit_info.reviewer()]
- reviewers.extend([commit_info.author() for commit_info in commit_infos if commit_info.author() and commit_info.author().can_review])
- return sorted(set(reviewers))
-
- def bug_id_for_this_commit(self, git_commit, changed_files=None):
- try:
- return parse_bug_id(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, force=False):
- # 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.
- args = [self._scm.script_path('svn-apply')]
- if patch.reviewer():
- args += ['--reviewer', patch.reviewer().full_name]
- if force:
- args.append('--force')
- run_command(args, input=patch.contents())
-
- 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)))
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py
deleted file mode 100644
index 1f97abd..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py
+++ /dev/null
@@ -1,196 +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.
-
-from __future__ import with_statement
-
-import codecs
-import os
-import shutil
-import tempfile
-import unittest
-
-from webkitpy.common.checkout.api import Checkout
-from webkitpy.common.checkout.changelog import ChangeLogEntry
-from webkitpy.common.checkout.scm import detect_scm_system, CommitMessage
-from webkitpy.common.system.outputcapture import OutputCapture
-from webkitpy.thirdparty.mock import Mock
-
-
-# FIXME: Copied from scm_unittest.py
-def write_into_file_at_path(file_path, contents, encoding="utf-8"):
- with codecs.open(file_path, "w", encoding) as file:
- file.write(contents)
-
-
-_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.
-
- * 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"""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
-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.
-
- * Path/To/Complicated/File: Added.
-"""
-
- def setUp(self):
- self.temp_dir = tempfile.mkdtemp(suffix="changelogs")
- self.old_cwd = os.getcwd()
- os.chdir(self.temp_dir)
- write_into_file_at_path("ChangeLog1", _changelog1)
- write_into_file_at_path("ChangeLog2", _changelog2)
-
- def tearDown(self):
- shutil.rmtree(self.temp_dir, ignore_errors=True)
- os.chdir(self.old_cwd)
-
- # FIXME: This should not need to touch the file system, however
- # ChangeLog is difficult to mock at current.
- def test_commit_message_for_this_commit(self):
- checkout = Checkout(None)
- checkout.modified_changelogs = lambda git_commit, changed_files=None: ["ChangeLog1", "ChangeLog2"]
- output = OutputCapture()
- expected_stderr = "Parsing ChangeLog: ChangeLog1\nParsing ChangeLog: ChangeLog2\n"
- commit_message = output.assert_outputs(self, checkout.commit_message_for_this_commit,
- kwargs={"git_commit": None}, expected_stderr=expected_stderr)
- self.assertEqual(commit_message.message(), self.expected_commit_message)
-
-
-class CheckoutTest(unittest.TestCase):
- def test_latest_entry_for_changelog_at_revision(self):
- scm = Mock()
- 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.
- return _changelog1.encode("utf-8")
- scm.contents_at_revision = mock_contents_at_revision
- checkout = Checkout(scm)
- entry = checkout._latest_entry_for_changelog_at_revision("foo", "bar")
- self.assertEqual(entry.contents(), _changelog1entry1)
-
- def test_commit_info_for_revision(self):
- scm = Mock()
- scm.committer_email_for_revision = lambda revision: "committer@example.com"
- checkout = Checkout(scm)
- checkout.changelog_entries_for_revision = lambda revision: [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.assertEqual(commitinfo.reviewer_text(), None)
- self.assertEqual(commitinfo.reviewer(), None)
- self.assertEqual(commitinfo.committer_email(), "committer@example.com")
- self.assertEqual(commitinfo.committer(), None)
-
- checkout.changelog_entries_for_revision = lambda revision: []
- self.assertEqual(checkout.commit_info_for_revision(1), None)
-
- def test_bug_id_for_revision(self):
- scm = Mock()
- scm.committer_email_for_revision = lambda revision: "committer@example.com"
- checkout = Checkout(scm)
- checkout.changelog_entries_for_revision = lambda revision: [ChangeLogEntry(_changelog1entry1)]
- self.assertEqual(checkout.bug_id_for_revision(4), 36629)
-
- def test_bug_id_for_this_commit(self):
- scm = Mock()
- checkout = Checkout(scm)
- 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):
- scm = Mock()
- scm.checkout_root = "/foo/bar"
- scm.changed_files = lambda git_commit: ["file1", "ChangeLog", "relative/path/ChangeLog"]
- checkout = Checkout(scm)
- 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):
- 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]
-
- scm = Mock()
- scm.checkout_root = "/foo/bar"
- scm.changed_files = lambda git_commit: ["file1", "file2", "relative/path/ChangeLog"]
- scm.revisions_changing_file = mock_revisions_changing_file
- checkout = Checkout(scm)
- 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'])
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py b/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py
deleted file mode 100644
index 40657eb..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py
+++ /dev/null
@@ -1,195 +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 codecs
-import fileinput # inplace file editing for set_reviewer_in_changelog
-import os.path
-import re
-import textwrap
-
-from webkitpy.common.system.deprecated_logging import log
-from webkitpy.common.config.committers import CommitterList
-from webkitpy.common.net.bugzilla import parse_bug_id
-
-
-def view_source_url(revision_number):
- # FIMXE: This doesn't really belong in this file, but we don't have a
- # better home for it yet.
- # Maybe eventually a webkit_config.py?
- return "http://trac.webkit.org/changeset/%s" % revision_number
-
-
-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<name>.+?)\s+<(?P<email>[^<>]+)>$'
-
- def __init__(self, contents, committer_list=CommitterList()):
- self._contents = contents
- self._committer_list = committer_list
- self._parse_entry()
-
- 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)
-
- # FIXME: group("name") does not seem to be Unicode? Probably due to self._contents not being unicode.
- self._author_name = match.group("name") if match else None
- self._author_email = match.group("email") if match else None
-
- match = re.search("^\s+Reviewed by (?P<reviewer>.*?)[\.,]?\s*$", self._contents, re.MULTILINE) # Discard everything after the first period
- self._reviewer_text = match.group("reviewer") if match else None
-
- self._reviewer = self._committer_list.committer_by_name(self._reviewer_text)
- self._author = self._committer_list.committer_by_email(self._author_email) or self._committer_list.committer_by_name(self._author_name)
-
- def author_name(self):
- return self._author_name
-
- def author_email(self):
- return self._author_email
-
- def author(self):
- return self._author # Might be None
-
- # 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
-
- def reviewer(self):
- return self._reviewer # Might be None
-
- def contents(self):
- return self._contents
-
- def bug_id(self):
- return parse_bug_id(self._contents)
-
-
-# FIXME: Various methods on ChangeLog should move into ChangeLogEntry instead.
-class ChangeLog(object):
-
- def __init__(self, path):
- self.path = path
-
- _changelog_indent = " " * 8
-
- @staticmethod
- def parse_latest_entry_from_file(changelog_file):
- """changelog_file must be a file-like object which returns
- unicode strings. Use codecs.open or StringIO(unicode())
- to pass file objects to this class."""
- date_line_regexp = re.compile(ChangeLogEntry.date_line_regexp)
- entry_lines = []
- # The first line should be a date line.
- first_line = changelog_file.readline()
- assert(isinstance(first_line, unicode))
- if not date_line_regexp.match(first_line):
- return None
- entry_lines.append(first_line)
-
- for line in changelog_file:
- # If we've hit the next entry, return.
- if date_line_regexp.match(line):
- # Remove the extra newline at the end
- return ChangeLogEntry(''.join(entry_lines[:-1]))
- entry_lines.append(line)
- return None # We never found a date line!
-
- 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 = codecs.open(self.path, "r", "utf-8")
- 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)
-
- # This probably does not belong in changelogs.py
- def _message_for_revert(self, revision, reason, bug_url):
- message = "Unreviewed, rolling out r%s.\n" % revision
- message += "%s\n" % view_source_url(revision)
- if bug_url:
- message += "%s\n" % bug_url
- # Add an extra new line after the rollout links, before any reason.
- message += "\n"
- if reason:
- message += "%s\n\n" % reason
- return self._wrap_lines(message)
-
- def update_for_revert(self, revision, reason, bug_url=None):
- reviewed_by_regexp = re.compile(
- "%sReviewed by NOBODY \(OOPS!\)\." % self._changelog_indent)
- removing_boilerplate = False
- # inplace=1 creates a backup file and re-directs stdout to the file
- for line in fileinput.FileInput(self.path, inplace=1):
- if reviewed_by_regexp.search(line):
- message_lines = self._message_for_revert(revision,
- reason,
- bug_url)
- print reviewed_by_regexp.sub(message_lines, line),
- # Remove all the ChangeLog boilerplate between the Reviewed by
- # line and 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:
- print line,
-
- def set_reviewer(self, reviewer):
- # inplace=1 creates a backup file and re-directs stdout to the file
- for line in fileinput.FileInput(self.path, inplace=1):
- # Trailing comma suppresses printing newline
- print line.replace("NOBODY (OOPS!)", reviewer.encode("utf-8")),
-
- def set_short_description_and_bug_url(self, short_description, bug_url):
- message = "%s\n %s" % (short_description, bug_url)
- for line in fileinput.FileInput(self.path, inplace=1):
- print line.replace("Need a short description and bug URL (OOPS!)", message.encode("utf-8")),
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py
deleted file mode 100644
index 6aeb1f8..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py
+++ /dev/null
@@ -1,203 +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.
-
-from __future__ import with_statement
-
-import codecs
-import os
-import tempfile
-import unittest
-
-from StringIO import StringIO
-
-from webkitpy.common.checkout.changelog import *
-
-
-class ChangeLogTest(unittest.TestCase):
-
- _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:
-'''
-
- # 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.
-
-== Rolled over to ChangeLog-2009-06-16 ==
-"""
-
- 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.assertEquals(latest_entry.contents(), self._example_entry)
- self.assertEquals(latest_entry.author_name(), "Peter Kasting")
- self.assertEquals(latest_entry.author_email(), "pkasting@google.com")
- self.assertEquals(latest_entry.reviewer_text(), u"Tor Arne Vestb\xf8")
- self.assertTrue(latest_entry.reviewer()) # Make sure that our UTF8-based lookup of Tor works.
-
- @staticmethod
- def _write_tmp_file_with_contents(byte_array):
- assert(isinstance(byte_array, str))
- (file_descriptor, file_path) = tempfile.mkstemp() # NamedTemporaryFile always deletes the file on close in python < 2.6
- with os.fdopen(file_descriptor, "w") as file:
- file.write(byte_array)
- return file_path
-
- @staticmethod
- def _read_file_contents(file_path, encoding):
- with codecs.open(file_path, "r", encoding) as file:
- return file.read()
-
- _new_entry_boilerplate = '''2009-08-19 Eric Seidel <eric@webkit.org>
-
- Reviewed by NOBODY (OOPS!).
-
- Need a short description and bug URL (OOPS!)
-
- * Scripts/bugzilla-tool:
-'''
-
- def test_set_reviewer(self):
- changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog)
- changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
- reviewer_name = 'Test Reviewer'
- ChangeLog(changelog_path).set_reviewer(reviewer_name)
- actual_contents = self._read_file_contents(changelog_path, "utf-8")
- expected_contents = changelog_contents.replace('NOBODY (OOPS!)', reviewer_name)
- os.remove(changelog_path)
- self.assertEquals(actual_contents, expected_contents)
-
- def test_set_short_description_and_bug_url(self):
- changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog)
- changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
- short_description = "A short description"
- bug_url = "http://example.com/b/2344"
- ChangeLog(changelog_path).set_short_description_and_bug_url(short_description, bug_url)
- actual_contents = self._read_file_contents(changelog_path, "utf-8")
- expected_message = "%s\n %s" % (short_description, bug_url)
- expected_contents = changelog_contents.replace("Need a short description and bug URL (OOPS!)", expected_message)
- os.remove(changelog_path)
- self.assertEquals(actual_contents, expected_contents)
-
- _revert_message = """ Unreviewed, rolling out r12345.
- http://trac.webkit.org/changeset/12345
- http://example.com/123
-
- This is a very long reason which should be long enough so that
- _message_for_revert will need to wrap it. We'll also include
- a
- https://veryveryveryveryverylongbugurl.com/reallylongbugthingy.cgi?bug_id=12354
- link so that we can make sure we wrap that right too.
-"""
-
- def test_message_for_revert(self):
- changelog = ChangeLog("/fake/path")
- long_reason = "This is a very long reason which should be long enough so that _message_for_revert will need to wrap it. We'll also include a https://veryveryveryveryverylongbugurl.com/reallylongbugthingy.cgi?bug_id=12354 link so that we can make sure we wrap that right too."
- message = changelog._message_for_revert(12345, long_reason, "http://example.com/123")
- self.assertEquals(message, self._revert_message)
-
- _revert_entry_with_bug_url = '''2009-08-19 Eric Seidel <eric@webkit.org>
-
- Unreviewed, rolling out r12345.
- http://trac.webkit.org/changeset/12345
- http://example.com/123
-
- Reason
-
- * Scripts/bugzilla-tool:
-'''
-
- _revert_entry_without_bug_url = '''2009-08-19 Eric Seidel <eric@webkit.org>
-
- Unreviewed, rolling out r12345.
- http://trac.webkit.org/changeset/12345
-
- Reason
-
- * Scripts/bugzilla-tool:
-'''
-
- def _assert_update_for_revert_output(self, args, expected_entry):
- changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog)
- changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
- changelog = ChangeLog(changelog_path)
- changelog.update_for_revert(*args)
- actual_entry = changelog.latest_entry()
- os.remove(changelog_path)
- self.assertEquals(actual_entry.contents(), expected_entry)
- self.assertEquals(actual_entry.reviewer_text(), None)
- # These checks could be removed to allow this to work on other entries:
- self.assertEquals(actual_entry.author_name(), "Eric Seidel")
- self.assertEquals(actual_entry.author_email(), "eric@webkit.org")
-
- def test_update_for_revert(self):
- self._assert_update_for_revert_output([12345, "Reason"], self._revert_entry_without_bug_url)
- self._assert_update_for_revert_output([12345, "Reason", "http://example.com/123"], self._revert_entry_with_bug_url)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo.py b/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo.py
deleted file mode 100644
index 448d530..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo.py
+++ /dev/null
@@ -1,93 +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.checkout.changelog import view_source_url
-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._bug_id = changelog_data["bug_id"]
- self._author_name = changelog_data["author_name"]
- self._author_email = changelog_data["author_email"]
- self._author = changelog_data["author"]
- self._reviewer_text = changelog_data["reviewer_text"]
- self._reviewer = changelog_data["reviewer"]
-
- # 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 committers.py
-
- def committer_email(self):
- return self._committer_email
-
- def bug_id(self):
- return self._bug_id # May be None
-
- def author(self):
- return self._author # May be None
-
- def author_name(self):
- return self._author_name
-
- def author_email(self):
- return self._author_email
-
- def reviewer(self):
- return self._reviewer # May be None
-
- def reviewer_text(self):
- return self._reviewer_text # May be None
-
- 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" % view_source_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/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py
deleted file mode 100644
index f58e6f1..0000000
--- a/WebKitTools/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 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/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser.py b/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser.py
deleted file mode 100644
index a6ea756..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser.py
+++ /dev/null
@@ -1,181 +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("webkitpy.common.checkout.diff_parser")
-
-
-# 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-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
-
-
-# FIXME: This method belongs on DiffParser
-def get_diff_converter(first_diff_line):
- """Gets a converter function of diff lines.
-
- Args:
- first_diff_line: The first filename line of a diff file.
- If this line is git formatted, we'll return a
- converter from git to SVN.
- """
- if match(r"^diff --git \w/", first_diff_line):
- return git_diff_to_svn_diff
- return lambda input: input
-
-
-_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))
-
-
-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.
- """
-
- # FIXME: This function is way too long and needs to be broken up.
- def __init__(self, diff_input):
- """Parses a diff.
-
- Args:
- diff_input: An iterable object.
- """
- state = _INITIAL_STATE
-
- self.files = {}
- current_file = None
- old_diff_line = None
- new_diff_line = None
- for line in diff_input:
- line = line.rstrip("\n")
- if state == _INITIAL_STATE:
- transform_line = get_diff_converter(line)
- line = transform_line(line)
-
- file_declaration = match(r"^Index: (?P<FilePath>.+)", line)
- if file_declaration:
- filename = file_declaration.group('FilePath')
- current_file = DiffFile(filename)
- self.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)
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py
deleted file mode 100644
index 7eb0eab..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py
+++ /dev/null
@@ -1,146 +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 unittest
-import diff_parser
-import re
-
-
-class DiffParserTest(unittest.TestCase):
-
- _PATCH = '''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
-'''
-
- def test_diff_parser(self, parser = None):
- if not parser:
- parser = diff_parser.DiffParser(self._PATCH.splitlines())
- self.assertEquals(3, len(parser.files))
-
- self.assertTrue('WebCore/rendering/style/StyleFlexibleBoxData.h' in parser.files)
- diff = parser.files['WebCore/rendering/style/StyleFlexibleBoxData.h']
- self.assertEquals(7, len(diff.lines))
- # The first two unchaged lines.
- self.assertEquals((47, 47), diff.lines[0][0:2])
- self.assertEquals('', diff.lines[0][2])
- self.assertEquals((48, 48), diff.lines[1][0:2])
- self.assertEquals(' unsigned align : 3; // EBoxAlignment', diff.lines[1][2])
- # The deleted line
- self.assertEquals((50, 0), diff.lines[3][0:2])
- self.assertEquals(' 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.assertEquals(7 + 7 + 9, len(diff.lines))
- # Around an added line.
- self.assertEquals((60, 61), diff.lines[9][0:2])
- self.assertEquals((0, 62), diff.lines[10][0:2])
- self.assertEquals((61, 63), diff.lines[11][0:2])
- # Look through the last chunk, which contains both add's and delete's.
- self.assertEquals((81, 83), diff.lines[14][0:2])
- self.assertEquals((82, 84), diff.lines[15][0:2])
- self.assertEquals((83, 85), diff.lines[16][0:2])
- self.assertEquals((84, 0), diff.lines[17][0:2])
- self.assertEquals((0, 86), diff.lines[18][0:2])
- self.assertEquals((0, 87), diff.lines[19][0:2])
- self.assertEquals((85, 88), diff.lines[20][0:2])
- self.assertEquals((86, 89), diff.lines[21][0:2])
- self.assertEquals((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.assertEquals(1, len(diff.lines))
- self.assertEquals((0, 1), diff.lines[0][0:2])
-
- 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)], self._PATCH)
- self.test_diff_parser(diff_parser.DiffParser(patch.splitlines()))
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
deleted file mode 100644
index d39b8b4..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
+++ /dev/null
@@ -1,915 +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 os
-import re
-import sys
-import shutil
-
-from webkitpy.common.system.executive import Executive, run_command, ScriptError
-from webkitpy.common.system.deprecated_logging import error, log
-from webkitpy.common.memoized import memoized
-
-
-def find_checkout_root():
- """Returns the current checkout root (as determined by default_scm().
-
- Returns the absolute path to the top of the WebKit checkout, or None
- if it cannot be determined.
-
- """
- scm_system = default_scm()
- if scm_system:
- return scm_system.checkout_root
- return None
-
-
-def default_scm():
- """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 = os.getcwd()
- scm_system = detect_scm_system(cwd)
- if not scm_system:
- script_directory = os.path.dirname(os.path.abspath(__file__))
- scm_system = detect_scm_system(script_directory)
- if scm_system:
- log("The current directory (%s) is not a WebKit checkout, using %s" % (cwd, scm_system.checkout_root))
- else:
- error("FATAL: Failed to determine the SCM system for either %s or %s" % (cwd, script_directory))
- return scm_system
-
-
-def detect_scm_system(path):
- absolute_path = os.path.abspath(path)
-
- if SVN.in_working_directory(absolute_path):
- return SVN(cwd=absolute_path)
-
- if Git.in_working_directory(absolute_path):
- return Git(cwd=absolute_path)
-
- return None
-
-
-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"
-
-
-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)
-
-
-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):
- self.server_host = server_host
-
-
-class AmbiguousCommitError(Exception):
- def __init__(self, num_local_commits, working_directory_is_clean):
- self.num_local_commits = num_local_commits
- self.working_directory_is_clean = working_directory_is_clean
-
-
-# SCM methods are expected to return paths relative to self.checkout_root.
-class SCM:
- def __init__(self, cwd):
- self.cwd = cwd
- self.checkout_root = self.find_checkout_root(self.cwd)
- self.dryrun = False
-
- # 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.
- # FIXME: We should use Executive.
- return 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 os.path.join(self.checkout_root, repository_relative_path)
-
- # FIXME: This belongs in Checkout, not SCM.
- def scripts_directory(self):
- return os.path.join(self.checkout_root, "WebKitTools", "Scripts")
-
- # FIXME: This belongs in Checkout, not SCM.
- def script_path(self, script_name):
- return os.path.join(self.scripts_directory(), script_name)
-
- def ensure_clean_working_directory(self, force_clean):
- if not force_clean and not self.working_directory_is_clean():
- # FIXME: Shouldn't this use cwd=self.checkout_root?
- print self.run(self.status_command(), error_handler=Executive.ignore_error)
- raise ScriptError(message="Working directory has modifications, pass --force-clean or --no-clean to continue.")
-
- log("Cleaning working directory")
- self.clean_working_directory()
-
- def ensure_no_local_commits(self, force):
- if not self.supports_local_commits():
- return
- commits = self.local_commits()
- if not len(commits):
- return
- if not force:
- error("Working directory has local commits, pass --force-clean to continue.")
- self.discard_local_commits()
-
- 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")
-
- @staticmethod
- def in_working_directory(path):
- SCM._subclass_must_implement()
-
- @staticmethod
- def find_checkout_root(path):
- SCM._subclass_must_implement()
-
- @staticmethod
- def commit_success_regexp():
- SCM._subclass_must_implement()
-
- def working_directory_is_clean(self):
- self._subclass_must_implement()
-
- def clean_working_directory(self):
- self._subclass_must_implement()
-
- def status_command(self):
- self._subclass_must_implement()
-
- def add(self, path, return_exit_code=False):
- self._subclass_must_implement()
-
- def delete(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 create_patch(self, git_commit=None, changed_files=[]):
- 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, git_commit=None, force_squash=False):
- 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()
-
- # 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 remote_merge_base():
- SCM._subclass_must_implement()
-
- def commit_locally_with_message(self, message):
- error("Your source control manager does not support local commits.")
-
- def discard_local_commits(self):
- pass
-
- def local_commits(self):
- return []
-
-
-class SVN(SCM):
- # FIXME: We should move these values to a WebKit-specific config. file.
- svn_server_host = "svn.webkit.org"
- svn_server_realm = "<http://svn.webkit.org:80> Mac OS Forge"
-
- def __init__(self, cwd):
- SCM.__init__(self, cwd)
- self._bogus_dir = None
-
- @staticmethod
- def in_working_directory(path):
- return os.path.isdir(os.path.join(path, '.svn'))
-
- @classmethod
- def find_uuid(cls, path):
- if not cls.in_working_directory(path):
- return None
- return cls.value_from_svn_info(path, 'Repository UUID')
-
- @classmethod
- def value_from_svn_info(cls, path, field_name):
- svn_info_args = ['svn', 'info', path]
- info_output = run_command(svn_info_args).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')
-
- @staticmethod
- def find_checkout_root(path):
- uuid = SVN.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 != SVN.find_uuid(path):
- return last_path
- last_path = path
- (path, last_component) = os.path.split(path)
- if last_path == path:
- return None
-
- @staticmethod
- def commit_success_regexp():
- return "^Committed revision (?P<svn_revision>\d+)\.$"
-
- def has_authorization_for_realm(self, realm=svn_server_realm, home_directory=os.getenv("HOME")):
- # 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()
- return find_output and os.path.isfile(os.path.join(home_directory, find_output))
-
- @memoized
- def svn_version(self):
- return self.run(['svn', '--version', '--quiet'])
-
- def working_directory_is_clean(self):
- return self.run(["svn", "diff"], cwd=self.checkout_root, decode_output=False) == ""
-
- def clean_working_directory(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 ['svn', '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
- dirname = os.path.dirname(path)
- # We have dirname directry - ensure it added.
- if dirname != path:
- self._add_parent_directories(dirname)
- self.add(path)
-
- def add(self, path, return_exit_code=False):
- self._add_parent_directories(os.path.dirname(os.path.abspath(path)))
- return self.run(["svn", "add", path], return_exit_code=return_exit_code)
-
- def delete(self, path):
- parent, base = os.path.split(os.path.abspath(path))
- return self.run(["svn", "delete", "--force", base], cwd=parent)
-
- def changed_files(self, git_commit=None):
- return self.run_status_and_extract_filenames(self.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 = ["svn", "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 = ['svn', 'log', '--quiet', '--limit=%s' % limit, path]
- for line in self.run(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"
-
- # FIXME: This method should be on Checkout.
- def create_patch(self, git_commit=None, changed_files=[]):
- """Returns a byte array (str()) representing the patch file.
- Patch files are effectively binary since they may contain
- files of multiple different encodings."""
- 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):
- 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")
-
- 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 = ['svn', 'diff']
- if self._bogus_dir:
- args += ['--config-dir', self._bogus_dir]
- args.append(path)
- return self.run(args)
- 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 = ['svn', '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("Running '%s'" % " ".join(svn_merge_args))
- # FIXME: Should this use cwd=self.checkout_root?
- self.run(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, git_commit=None, force_squash=False):
- # git-commit and force are not used by SVN.
- if self.dryrun:
- # Return a string which looks like a commit so that things which parse this output will succeed.
- return "Dry run, no commit.\nCommitted revision 0."
-
- svn_commit_args = ["svn", "commit"]
-
- if not username and not self.has_authorization_for_realm():
- raise AuthenticationError(self.svn_server_host)
- if username:
- svn_commit_args.extend(["--username", username])
-
- svn_commit_args.extend(["-m", message])
- # FIXME: Should this use cwd=self.checkout_root?
- return self.run(svn_commit_args, 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 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")
-
-
-# All git-specific logic should go here.
-class Git(SCM):
- def __init__(self, cwd):
- SCM.__init__(self, cwd)
-
- @classmethod
- def in_working_directory(cls, path):
- return run_command(['git', 'rev-parse', '--is-inside-work-tree'], cwd=path, error_handler=Executive.ignore_error).rstrip() == "true"
-
- @classmethod
- def find_checkout_root(cls, path):
- # "git rev-parse --show-cdup" would be another way to get to the root
- (checkout_root, dot_git) = os.path.split(run_command(['git', 'rev-parse', '--git-dir'], cwd=(path or "./")))
- # If we were using 2.6 # checkout_root = os.path.relpath(checkout_root, path)
- if not os.path.isabs(checkout_root): # Sometimes git returns relative paths
- checkout_root = os.path.join(path, checkout_root)
- return checkout_root
-
- @classmethod
- def to_object_name(cls, filepath):
- root_end_with_slash = os.path.join(cls.find_checkout_root(os.path.dirname(filepath)), '')
- return filepath.replace(root_end_with_slash, '')
-
- @classmethod
- def read_git_config(cls, key):
- # FIXME: This should probably use cwd=self.checkout_root.
- # Pass --get-all for cases where the config has multiple values
- return run_command(["git", "config", "--get-all", key],
- error_handler=Executive.ignore_error).rstrip('\n')
-
- @staticmethod
- def commit_success_regexp():
- return "^Committed r(?P<svn_revision>\d+)$"
-
- def discard_local_commits(self):
- # FIXME: This should probably use cwd=self.checkout_root
- self.run(['git', 'reset', '--hard', self.remote_branch_ref()])
-
- def local_commits(self):
- # FIXME: This should probably use cwd=self.checkout_root
- return self.run(['git', 'log', '--pretty=oneline', 'HEAD...' + self.remote_branch_ref()]).splitlines()
-
- def rebase_in_progress(self):
- return os.path.exists(os.path.join(self.checkout_root, '.git/rebase-apply'))
-
- def working_directory_is_clean(self):
- # FIXME: This should probably use cwd=self.checkout_root
- return self.run(['git', 'diff', 'HEAD', '--name-only']) == ""
-
- def clean_working_directory(self):
- # FIXME: These should probably use cwd=self.checkout_root.
- # Could run git clean here too, but that wouldn't match working_directory_is_clean
- self.run(['git', 'reset', '--hard', 'HEAD'])
- # Aborting rebase even though this does not match working_directory_is_clean
- 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 ["git", "diff", "--name-status", "HEAD"]
-
- def _status_regexp(self, expected_types):
- return '^(?P<status>[%s])\t(?P<filename>.+)$' % expected_types
-
- def add(self, path, return_exit_code=False):
- return self.run(["git", "add", path], return_exit_code=return_exit_code)
-
- def delete(self, path):
- return self.run(["git", "rm", "-f", path])
-
- def _assert_synced(self):
- if len(run_command(['git', 'rev-list', '--max-count=1', self.remote_branch_ref(), '^HEAD'])):
- raise ScriptError(message="Not fully merged/rebased to %s. This branch needs to be synced first." % self.remote_branch_ref())
-
- def merge_base(self, git_commit):
- if git_commit:
- # Special-case HEAD.. to mean working-copy changes only.
- if git_commit.upper() == 'HEAD..':
- return 'HEAD'
-
- if '..' not in git_commit:
- git_commit = git_commit + "^.." + git_commit
- return git_commit
-
- self._assert_synced()
- return self.remote_merge_base()
-
- def changed_files(self, git_commit=None):
- status_command = ['git', 'diff', '-r', '--name-status', '-C', '-M', "--no-ext-diff", "--full-index", self.merge_base(git_commit)]
- 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):
- # 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 = ['git', 'diff', '--name-status', '-C', '-M', '--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 create_patch(self, git_commit=None, changed_files=[]):
- """Returns a byte array (str()) representing the patch file.
- Patch files are effectively binary since they may contain
- files of multiple different encodings."""
- return self.run(['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self.merge_base(git_commit), "--"] + changed_files, 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], cwd=self.checkout_root).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):
- 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', '--', 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, working_directory_is_clean):
- squash = Git.read_git_config('webkit-patch.commit-should-always-squash')
- 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 not working_directory_is_clean):
- raise AmbiguousCommitError(num_local_commits, working_directory_is_clean)
-
- def commit_with_message(self, message, username=None, git_commit=None, force_squash=False):
- # Username is ignored during Git commits.
- working_directory_is_clean = self.working_directory_is_clean()
-
- if git_commit:
- # Special-case HEAD.. to mean working-copy changes only.
- if git_commit.upper() == 'HEAD..':
- if working_directory_is_clean:
- 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')
-
- # Need working directory changes to be committed so we can checkout the merge branch.
- if not working_directory_is_clean:
- # 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)
-
- if not force_squash:
- self._assert_can_squash(working_directory_is_clean)
- self._assert_synced()
- self.run(['git', 'reset', '--soft', self.remote_branch_ref()])
- self.commit_locally_with_message(message)
- return self.push_local_commits_to_server()
-
- def _commit_on_branch(self, message, git_commit):
- branch_ref = self.run(['git', 'symbolic-ref', 'HEAD']).strip()
- branch_name = branch_ref.replace('refs/heads/', '')
- 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.
- os.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()
- except Exception, e:
- log("COMMIT FAILED: " + str(e))
- output = "Commit failed."
- commit_succeeded = False
- finally:
- # And then swap back to the original branch and clean up.
- self.clean_working_directory()
- 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'])
-
- # 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 = Git.read_git_config('svn-remote.svn.fetch')
- 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):
- dcommit_command = ['git', 'svn', 'dcommit']
- if self.dryrun:
- dcommit_command.append('--dry-run')
- output = self.run(dcommit_command, error_handler=commit_error_handler)
- # Return a string which looks like a commit so that things which parse this output will succeed.
- if self.dryrun:
- output += "\nCommitted r0"
- 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-commit-id', commit_id])
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py
deleted file mode 100644
index 46a2acf..0000000
--- a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py
+++ /dev/null
@@ -1,1291 +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.
-
-from __future__ import with_statement
-
-import base64
-import codecs
-import getpass
-import os
-import os.path
-import re
-import stat
-import sys
-import subprocess
-import tempfile
-import unittest
-import urllib
-import shutil
-
-from datetime import date
-from webkitpy.common.checkout.api import Checkout
-from webkitpy.common.checkout.scm import detect_scm_system, SCM, SVN, CheckoutNeedsUpdate, commit_error_handler, AuthenticationError, AmbiguousCommitError, find_checkout_root, default_scm
-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, run_command, ScriptError
-from webkitpy.common.system.outputcapture import OutputCapture
-
-# 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.
-
-# 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.
- 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:
- @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, test_object):
- # Add some test commits
- os.chdir(test_object.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'])
-
- @classmethod
- def setup(cls, test_object):
- # Create an test SVN repository
- test_object.svn_repo_path = tempfile.mkdtemp(suffix="svn_test_repo")
- test_object.svn_repo_url = "file://%s" % test_object.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', test_object.svn_repo_path])
-
- # Create a test svn checkout
- test_object.svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout")
- run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url, test_object.svn_checkout_path])
-
- # Create and checkout a trunk dir to match the standard svn configuration to match git-svn's expectations
- os.chdir(test_object.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.
- # _setup_test_commits will CD back to the svn checkout directory.
- os.chdir('/')
- run_command(['rm', '-rf', test_object.svn_checkout_path])
- run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url + '/trunk', test_object.svn_checkout_path])
-
- cls._setup_test_commits(test_object)
-
- @classmethod
- def tear_down(cls, test_object):
- run_command(['rm', '-rf', test_object.svn_repo_path])
- run_command(['rm', '-rf', test_object.svn_checkout_path])
-
- # 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.
- os.chdir(detect_scm_system(os.path.dirname(__file__)).checkout_root)
-
-
-class StandaloneFunctionsTest(unittest.TestCase):
- """This class tests any standalone/top-level functions in the package."""
- def setUp(self):
- self.orig_cwd = os.path.abspath(os.getcwd())
- self.orig_abspath = os.path.abspath
-
- # We capture but ignore the output from stderr to reduce unwanted
- # logging.
- self.output = OutputCapture()
- self.output.capture_output()
-
- def tearDown(self):
- os.chdir(self.orig_cwd)
- os.path.abspath = self.orig_abspath
- self.output.restore_output()
-
- def test_find_checkout_root(self):
- # Test from inside the tree.
- os.chdir(sys.path[0])
- dir = find_checkout_root()
- self.assertNotEqual(dir, None)
- self.assertTrue(os.path.exists(dir))
-
- # Test from outside the tree.
- os.chdir(os.path.expanduser("~"))
- dir = find_checkout_root()
- self.assertNotEqual(dir, None)
- self.assertTrue(os.path.exists(dir))
-
- # Mock out abspath() to test being not in a checkout at all.
- os.path.abspath = lambda x: "/"
- self.assertRaises(SystemExit, find_checkout_root)
- os.path.abspath = self.orig_abspath
-
- def test_default_scm(self):
- # Test from inside the tree.
- os.chdir(sys.path[0])
- scm = default_scm()
- self.assertNotEqual(scm, None)
-
- # Test from outside the tree.
- os.chdir(os.path.expanduser("~"))
- dir = find_checkout_root()
- self.assertNotEqual(dir, None)
-
- # Mock out abspath() to test being not in a checkout at all.
- os.path.abspath = lambda x: "/"
- self.assertRaises(SystemExit, default_scm)
- os.path.abspath = self.orig_abspath
-
-# 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.assertTrue(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(name="Joe Cool", email_or_emails=None)
- 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.assertEqual(self.scm.changed_files(), ["test_file"])
- write_into_file_at_path("test_dir/test_file3", "new stuff")
- self.assertEqual(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.assertEqual(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.assertEqual(self.scm.added_files(), [])
-
- write_into_file_at_path("added_file", "new stuff")
- self.scm.add("added_file")
-
- 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.assertEqual(added_files, ["added_dir/added_file2", "added_file"])
-
- # Test also to make sure clean_working_directory removes added files
- self.scm.clean_working_directory()
- self.assertEqual(self.scm.added_files(), [])
- self.assertFalse(os.path.exists("added_file"))
- 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.assertEqual(changed_files, ["test_dir/test_file3", "test_file"])
- self.assertEqual(sorted(self.scm.changed_files_for_revision(4)), sorted(["test_file", "test_file2"])) # Git and SVN return different orders.
- self.assertEqual(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.assertEqual(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.assertTrue(re.search('test3', r3_patch))
- self.assertFalse(re.search('test4', r3_patch))
- self.assertTrue(re.search('test2', r3_patch))
- self.assertTrue(re.search('test2', self.scm.diff_for_revision(3)))
-
- 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.assertTrue('fizzbuzz7.gif' in 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.assertTrue('fizzbuzz7.gif' in 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.assertFalse('fizzbuzz7.gif' in 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.assertTrue("added_dir/added_file" in self.scm.added_files())
-
-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.
- 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>
-
-+ Reviewed by NOBODY (OOPS!).
-+
-+ Second most awesome change ever.
-+
-+ * scm_unittest.py:
-+
-+2009-10-26 Eric Seidel <eric@webkit.org>
-+
- Reviewed by Foo Bar.
-
- Most awesome change ever.
-"""
- 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 @@
-
- Reviewed by Foo Bar.
-
-+ Second most awesome change ever.
-+
-+ * scm_unittest.py:
-+
-+2009-10-26 Eric Seidel <eric@webkit.org>
-+
-+ Reviewed by Foo Bar.
-+
- Most awesome change ever.
-
- * scm_unittest.py:
-"""
- 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.assertEquals(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.assertEquals(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)
- # 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, 'WebKitTools', '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):
- scm = detect_scm_system(self.svn_checkout_path)
- self.assertEqual(scm.display_name(), "svn")
- self.assertEqual(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):
- scm = detect_scm_system(self.svn_checkout_path)
- patch = self._create_patch(_svn_diff("-r5:4"))
- self._setup_webkittools_scripts_symlink(scm)
- Checkout(scm).apply_patch(patch)
-
- def test_apply_svn_patch_force(self):
- scm = detect_scm_system(self.svn_checkout_path)
- patch = self._create_patch(_svn_diff("-r3:5"))
- self._setup_webkittools_scripts_symlink(scm)
- self.assertRaises(ScriptError, Checkout(scm).apply_patch, patch, force=True)
-
- def test_commit_logs(self):
- # Commits have dates and usernames in them, so we can't just direct compare.
- self.assertTrue(re.search('fourth commit', self.scm.last_svn_commit_log()))
- self.assertTrue(re.search('second commit', self.scm.svn_commit_log(3)))
-
- 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')
-
- self.scm.dryrun = True
- write_into_file_at_path('test_file', 'still more test content')
- commit_text = self.scm.commit_with_message("yet another test commit", username)
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '0')
-
- 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):
- self.scm.has_authorization_for_realm = lambda: False
- self.assertRaises(AuthenticationError, self._shared_test_commit_with_message)
-
- def test_has_authorization_for_realm(self):
- scm = detect_scm_system(self.svn_checkout_path)
- 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, SVN.svn_server_realm)
- self.assertTrue(scm.has_authorization_for_realm(home_directory=fake_home_dir))
- os.remove(fake_webkit_auth_file)
- os.rmdir(svn_config_dir_path)
- os.rmdir(fake_home_dir)
-
- def test_not_have_authorization_for_realm(self):
- scm = detect_scm_system(self.svn_checkout_path)
- 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(scm.has_authorization_for_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.assertTrue("test_file" in self.scm.deleted_files())
-
- 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.assertTrue("-some content" in diff)
- self.assertTrue("+changed content" in 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):
- 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.clean_working_directory()
- self.assertFalse(os.path.exists(svn_root_lock_path))
- run_command(['svn', 'update']) # Should succeed and not raise.
-
-
-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."""
- 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')
-
-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)
- # 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):
- scm = detect_scm_system(self.git_checkout_path)
- self.assertEqual(scm.display_name(), "git")
- self.assertEqual(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.assertFalse(re.search(r'foo', run_command(['git', 'branch'])))
-
- 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.assertFalse(re.search(r'foo', diff_to_common_base))
- self.assertTrue(re.search(r'foo', diff_to_merge_base))
-
- 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.
-
- scm = detect_scm_system(self.git_checkout_path)
- self.assertTrue(scm.rebase_in_progress())
-
- # Make sure our cleanup works.
- scm.clean_working_directory()
- self.assertFalse(scm.rebase_in_progress())
-
- # Make sure cleanup doesn't throw when no rebase is in progress.
- scm.clean_working_directory()
-
- def test_commitish_parsing(self):
- scm = detect_scm_system(self.git_checkout_path)
-
- # Multiple revisions are cherry-picked.
- self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD~2'])), 1)
- self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD', 'HEAD~2'])), 2)
-
- # ... is an invalid range specifier
- self.assertRaises(ScriptError, scm.commit_ids_from_commitish_arguments, ['trunk...HEAD'])
-
- def test_commitish_order(self):
- scm = detect_scm_system(self.git_checkout_path)
-
- commit_range = 'HEAD~3..HEAD'
-
- actual_commits = 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):
- scm = detect_scm_system(self.git_checkout_path)
- # 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(scm)
- Checkout(scm).apply_patch(patch)
-
- def test_apply_git_patch_force(self):
- scm = detect_scm_system(self.git_checkout_path)
- patch = self._create_patch(_git_diff('HEAD~2..HEAD'))
- self._setup_webkittools_scripts_symlink(scm)
- self.assertRaises(ScriptError, Checkout(scm).apply_patch, patch, force=True)
-
- 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')
-
- self.scm.dryrun = True
- write_into_file_at_path('test_file', 'still more test content')
- commit_text = self.scm.commit_with_message("yet another test commit")
- self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '0')
-
- 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'])
- scm = detect_scm_system(self.git_checkout_path)
- commit_text = scm.commit_with_message("yet another test commit")
-
- self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6')
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertTrue(re.search(r'test_file_commit1', svn_log))
-
- def _one_local_commit(self):
- write_into_file_at_path('test_file_commit1', 'more test content')
- run_command(['git', 'add', 'test_file_commit1'])
- self.scm.commit_locally_with_message("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 _two_local_commits(self):
- self._one_local_commit()
- write_into_file_at_path('test_file_commit2', 'still more test content')
- run_command(['git', 'add', 'test_file_commit2'])
- self.scm.commit_locally_with_message("yet another test commit")
-
- def _three_local_commits(self):
- write_into_file_at_path('test_file_commit0', 'more test content')
- run_command(['git', 'add', 'test_file_commit0'])
- self.scm.commit_locally_with_message("another test commit")
- self._two_local_commits()
-
- def test_revisions_changing_files_with_local_commit(self):
- self._one_local_commit()
- self.assertEquals(self.scm.revisions_changing_file('test_file_commit1'), [])
-
- def test_commit_with_message(self):
- self._one_local_commit_plus_working_copy_changes()
- scm = detect_scm_system(self.git_checkout_path)
- self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "yet another test commit")
- commit_text = scm.commit_with_message("yet another test commit", force_squash=True)
-
- self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6')
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertTrue(re.search(r'test_file_commit2', svn_log))
- self.assertTrue(re.search(r'test_file_commit1', svn_log))
-
- def test_commit_with_message_git_commit(self):
- self._two_local_commits()
-
- scm = detect_scm_system(self.git_checkout_path)
- commit_text = scm.commit_with_message("another test commit", git_commit="HEAD^")
- self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertTrue(re.search(r'test_file_commit1', svn_log))
- self.assertFalse(re.search(r'test_file_commit2', svn_log))
-
- def test_commit_with_message_git_commit_range(self):
- self._three_local_commits()
-
- scm = detect_scm_system(self.git_checkout_path)
- commit_text = scm.commit_with_message("another test commit", git_commit="HEAD~2..HEAD")
- self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertFalse(re.search(r'test_file_commit0', svn_log))
- self.assertTrue(re.search(r'test_file_commit1', svn_log))
- self.assertTrue(re.search(r'test_file_commit2', svn_log))
-
- def test_changed_files_working_copy_only(self):
- self._one_local_commit_plus_working_copy_changes()
- scm = detect_scm_system(self.git_checkout_path)
- commit_text = scm.commit_with_message("another test commit", git_commit="HEAD..")
- self.assertFalse(re.search(r'test_file_commit1', svn_log))
- self.assertTrue(re.search(r'test_file_commit2', svn_log))
-
- def test_commit_with_message_only_local_commit(self):
- self._one_local_commit()
- scm = detect_scm_system(self.git_checkout_path)
- commit_text = scm.commit_with_message("another test commit")
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertTrue(re.search(r'test_file_commit1', svn_log))
-
- 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')
- scm = detect_scm_system(self.git_checkout_path)
-
- self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "another test commit")
- commit_text = scm.commit_with_message("another test commit", force_squash=True)
-
- self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6')
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertTrue(re.search(r'test_file_commit2', svn_log))
- self.assertTrue(re.search(r'test_file_commit1', svn_log))
-
- 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')
- scm = detect_scm_system(self.git_checkout_path)
- self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", git_commit="HEAD^")
-
- def test_commit_with_message_multiple_local_commits_always_squash(self):
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- scm._assert_can_squash = lambda working_directory_is_clean: True
- commit_text = scm.commit_with_message("yet another test commit")
- self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertTrue(re.search(r'test_file_commit2', svn_log))
- self.assertTrue(re.search(r'test_file_commit1', svn_log))
-
- def test_commit_with_message_multiple_local_commits(self):
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "yet another test commit")
- commit_text = scm.commit_with_message("yet another test commit", force_squash=True)
-
- self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6')
-
- svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose'])
- self.assertTrue(re.search(r'test_file_commit2', svn_log))
- self.assertTrue(re.search(r'test_file_commit1', svn_log))
-
- def test_commit_with_message_not_synced(self):
- run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3'])
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "another test commit")
- self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", force_squash=True)
-
- 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()
- scm = detect_scm_system(self.git_checkout_path)
- patch = scm.create_patch()
- self.assertTrue(re.search(r'test_file_commit1', patch))
- self.assertTrue(re.search(r'test_file_commit2', patch))
-
- def test_create_patch(self):
- self._one_local_commit_plus_working_copy_changes()
- scm = detect_scm_system(self.git_checkout_path)
- patch = scm.create_patch()
- self.assertTrue(re.search(r'test_file_commit2', patch))
- self.assertTrue(re.search(r'test_file_commit1', patch))
-
- def test_create_patch_with_changed_files(self):
- self._one_local_commit_plus_working_copy_changes()
- scm = detect_scm_system(self.git_checkout_path)
- patch = scm.create_patch(changed_files=['test_file_commit2'])
- self.assertTrue(re.search(r'test_file_commit2', patch))
-
- def test_create_patch_with_rm_and_changed_files(self):
- self._one_local_commit_plus_working_copy_changes()
- scm = detect_scm_system(self.git_checkout_path)
- os.remove('test_file_commit1')
- patch = scm.create_patch()
- patch_with_changed_files = scm.create_patch(changed_files=['test_file_commit1', 'test_file_commit2'])
- self.assertEquals(patch, patch_with_changed_files)
-
- def test_create_patch_git_commit(self):
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- patch = scm.create_patch(git_commit="HEAD^")
- self.assertTrue(re.search(r'test_file_commit1', patch))
- self.assertFalse(re.search(r'test_file_commit2', patch))
-
- def test_create_patch_git_commit_range(self):
- self._three_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- patch = scm.create_patch(git_commit="HEAD~2..HEAD")
- self.assertFalse(re.search(r'test_file_commit0', patch))
- self.assertTrue(re.search(r'test_file_commit2', patch))
- self.assertTrue(re.search(r'test_file_commit1', patch))
-
- def test_create_patch_working_copy_only(self):
- self._one_local_commit_plus_working_copy_changes()
- scm = detect_scm_system(self.git_checkout_path)
- patch = scm.create_patch(git_commit="HEAD..")
- self.assertFalse(re.search(r'test_file_commit1', patch))
- self.assertTrue(re.search(r'test_file_commit2', patch))
-
- def test_create_patch_multiple_local_commits(self):
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- patch = scm.create_patch()
- self.assertTrue(re.search(r'test_file_commit2', patch))
- self.assertTrue(re.search(r'test_file_commit1', patch))
-
- def test_create_patch_not_synced(self):
- run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3'])
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- self.assertRaises(ScriptError, scm.create_patch)
-
- def test_create_binary_patch(self):
- # Create a git binary patch and check the contents.
- scm = detect_scm_system(self.git_checkout_path)
- 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 = scm.create_patch()
- self.assertTrue(re.search(r'\nliteral 0\n', patch))
- self.assertTrue(re.search(r'\nliteral 256\n', patch))
-
- # Check if we can apply the created patch.
- run_command(['git', 'rm', '-f', test_file_name])
- self._setup_webkittools_scripts_symlink(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 = scm.create_patch('HEAD')
- self.assertTrue(re.search(r'\nliteral 0\n', patch_from_local_commit))
- self.assertTrue(re.search(r'\nliteral 256\n', patch_from_local_commit))
-
- def test_changed_files_local_plus_working_copy(self):
- self._one_local_commit_plus_working_copy_changes()
- scm = detect_scm_system(self.git_checkout_path)
- files = scm.changed_files()
- self.assertTrue('test_file_commit1' in files)
- self.assertTrue('test_file_commit2' in files)
-
- def test_changed_files_git_commit(self):
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- files = scm.changed_files(git_commit="HEAD^")
- self.assertTrue('test_file_commit1' in files)
- self.assertFalse('test_file_commit2' in files)
-
- def test_changed_files_git_commit_range(self):
- self._three_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- files = scm.changed_files(git_commit="HEAD~2..HEAD")
- self.assertTrue('test_file_commit0' not in files)
- self.assertTrue('test_file_commit1' in files)
- self.assertTrue('test_file_commit2' in files)
-
- def test_changed_files_working_copy_only(self):
- self._one_local_commit_plus_working_copy_changes()
- scm = detect_scm_system(self.git_checkout_path)
- files = scm.changed_files(git_commit="HEAD..")
- self.assertFalse('test_file_commit1' in files)
- self.assertTrue('test_file_commit2' in files)
-
- def test_changed_files_multiple_local_commits(self):
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- files = scm.changed_files()
- self.assertTrue('test_file_commit2' in files)
- self.assertTrue('test_file_commit1' in files)
-
- def test_changed_files_not_synced(self):
- run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3'])
- self._two_local_commits()
- scm = detect_scm_system(self.git_checkout_path)
- self.assertRaises(ScriptError, scm.changed_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_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.assertTrue("test_file_commit1" in self.scm.deleted_files())
-
- def test_to_object_name(self):
- relpath = 'test_file_commit1'
- fullpath = os.path.join(self.git_checkout_path, relpath)
- self._two_local_commits()
- 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.assertTrue("+Updated" in diff)
- self.assertTrue("-more test content" in diff)
-
- self.scm.add('test_file_commit1')
-
- cached_diff = self.scm.diff_for_file('test_file_commit1')
- self.assertTrue("+Updated" in cached_diff)
- self.assertTrue("-more test content" in cached_diff)
-
-if __name__ == '__main__':
- unittest.main()