diff options
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/common/checkout')
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() |