diff options
author | John Reck <jreck@google.com> | 2010-11-04 12:00:17 -0700 |
---|---|---|
committer | John Reck <jreck@google.com> | 2010-11-09 11:35:04 -0800 |
commit | e14391e94c850b8bd03680c23b38978db68687a8 (patch) | |
tree | 3fed87e6620fecaf3edc7259ae58a11662bedcb2 /WebKitTools/Scripts/webkitpy/common | |
parent | 1bd705833a68f07850cf7e204b26f8d328d16951 (diff) | |
download | external_webkit-e14391e94c850b8bd03680c23b38978db68687a8.zip external_webkit-e14391e94c850b8bd03680c23b38978db68687a8.tar.gz external_webkit-e14391e94c850b8bd03680c23b38978db68687a8.tar.bz2 |
Merge Webkit at r70949: Initial merge by git.
Change-Id: I77b8645c083b5d0da8dba73ed01d4014aab9848e
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/common')
15 files changed, 233 insertions, 185 deletions
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py b/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py index ef65bee..597dcbd 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py @@ -1 +1,3 @@ # 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 index 72cad8d..dbe1e84 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/api.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/api.py @@ -32,6 +32,7 @@ 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 @@ -59,6 +60,7 @@ class Checkout(object): 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) @@ -98,8 +100,8 @@ class Checkout(object): 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): - changelog_paths = self.modified_changelogs(git_commit) + 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" @@ -120,16 +122,16 @@ class Checkout(object): 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 = self.modified_non_changelogs(git_commit) + 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): + 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).message()) + return parse_bug_id(self.commit_message_for_this_commit(git_commit, changed_files).message()) except ScriptError, e: pass # We might not have ChangeLogs. diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py index d7bd95e..1f97abd 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py @@ -114,7 +114,7 @@ class CommitMessageForThisCommitTest(unittest.TestCase): # ChangeLog is difficult to mock at current. def test_commit_message_for_this_commit(self): checkout = Checkout(None) - checkout.modified_changelogs = lambda git_commit: ["ChangeLog1", "ChangeLog2"] + 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, @@ -163,7 +163,7 @@ class CheckoutTest(unittest.TestCase): def test_bug_id_for_this_commit(self): scm = Mock() checkout = Checkout(scm) - checkout.commit_message_for_this_commit = lambda git_commit: CommitMessage(ChangeLogEntry(_changelog1entry1).contents().splitlines()) + 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): diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py index 4bd9ed6..9b602c3 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py @@ -36,6 +36,7 @@ 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(): @@ -319,7 +320,6 @@ class SVN(SCM): def __init__(self, cwd): SCM.__init__(self, cwd) - self.cached_version = None self._bogus_dir = None @staticmethod @@ -369,16 +369,20 @@ class SVN(SCM): 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): - if not self.cached_version: - self.cached_version = self.run(['svn', '--version', '--quiet']) - - return self.cached_version + 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 @@ -432,6 +436,7 @@ class SVN(SCM): 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) @@ -565,6 +570,7 @@ class SVN(SCM): 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): @@ -667,7 +673,8 @@ class Git(SCM): return self._changes_files_for_commit(commit_id) def revisions_changing_file(self, path, limit=5): - commit_ids = self.run(["git", "log", "--pretty=format:%H", "-%s" % limit, path]).splitlines() + # 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): @@ -696,21 +703,29 @@ class Git(SCM): # FIXME: This should probably use cwd=self.checkout_root return self.run(['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self.merge_base(git_commit), "--"] + changed_files, decode_output=False) - @classmethod - def git_commit_from_svn_revision(cls, revision): - # FIXME: This should probably use cwd=self.checkout_root - git_commit = run_command(['git', 'svn', 'find-rev', 'r%s' % revision]).rstrip() - # git svn find-rev always exits 0, even when the revision is not found. - if not git_commit: - raise ScriptError(message='Failed to find git commit for revision %s, your checkout likely needs an update.' % revision) - return git_commit + 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 svn_revision_from_git_commit(self, commit_id): + def _string_to_int_or_none(self, string): try: - return int(self.run(['git', 'svn', 'find-rev', commit_id]).rstrip()) + 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.""" diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py index 4aa5279..8af9ad5 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py @@ -760,6 +760,15 @@ Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== 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): diff --git a/WebKitTools/Scripts/webkitpy/common/config/__init__.py b/WebKitTools/Scripts/webkitpy/common/config/__init__.py index 62d129e..ef65bee 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/__init__.py +++ b/WebKitTools/Scripts/webkitpy/common/config/__init__.py @@ -1,6 +1 @@ # Required for Python to search this directory for module files - -import re - -codereview_server_host = "wkrietveld.appspot.com" -codereview_server_url = "https://%s/" % codereview_server_host diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py index f768cf9..71d764c 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/committers.py +++ b/WebKitTools/Scripts/webkitpy/common/config/committers.py @@ -137,6 +137,7 @@ committers_unable_to_review = [ Committer("Keishi Hattori", "keishi@webkit.org", "keishi"), Committer("Kelly Norton", "knorton@google.com"), Committer("Kent Hansen", "kent.hansen@nokia.com", "khansen"), + Committer("Kimmo Kinnunen", ["kimmo.t.kinnunen@nokia.com", "kimmok@iki.fi", "ktkinnun@webkit.org"], "kimmok"), Committer("Kinuko Yasuda", "kinuko@chromium.org", "kinuko"), Committer("Krzysztof Kowalczyk", "kkowalczyk@gmail.com"), Committer("Kwang Yul Seo", ["kwangyul.seo@gmail.com", "skyul@company100.net", "kseo@webkit.org"], "kwangseo"), @@ -146,6 +147,7 @@ committers_unable_to_review = [ Committer("Luiz Agostini", ["luiz@webkit.org", "luiz.agostini@openbossa.org"], "lca"), Committer("Mads Ager", "ager@chromium.org"), Committer("Marcus Voltis Bulach", "bulach@chromium.org"), + Committer("Mario Sanchez Prada", ["msanchez@igalia.com", "mario@webkit.org"]), Committer("Matt Delaney", "mdelaney@apple.com"), Committer("Matt Lilek", ["webkit@mattlilek.com", "pewtermoose@webkit.org"]), Committer("Matt Perry", "mpcomplete@chromium.org"), @@ -213,7 +215,7 @@ reviewers_list = [ Reviewer("Andreas Kling", ["kling@webkit.org", "andreas.kling@nokia.com"], "kling"), Reviewer("Antonio Gomes", ["tonikitoo@webkit.org", "agomes@rim.com"], "tonikitoo"), Reviewer("Antti Koivisto", ["koivisto@iki.fi", "antti@apple.com", "antti.j.koivisto@nokia.com"], "anttik"), - Reviewer("Ariya Hidayat", ["ariya@sencha.com", "ariya.hidayat@gmail.com", "ariya@webkit.org"], "ariya"), + Reviewer("Ariya Hidayat", ["ariya.hidayat@gmail.com", "ariya@sencha.com", "ariya@webkit.org"], "ariya"), Reviewer("Beth Dakin", "bdakin@apple.com", "dethbakin"), Reviewer("Brady Eidson", "beidson@apple.com", "bradee-oh"), Reviewer("Cameron Zwarich", ["zwarich@apple.com", "cwzwarich@apple.com", "cwzwarich@webkit.org"]), diff --git a/WebKitTools/Scripts/webkitpy/common/net/rietveld_unittest.py b/WebKitTools/Scripts/webkitpy/common/memoized.py index 9c5a29e..dc844a5 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/rietveld_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/memoized.py @@ -26,14 +26,30 @@ # (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 +# Python does not (yet) seem to provide automatic memoization. So we've +# written a small decorator to do so. -from webkitpy.common.net.rietveld import Rietveld -from webkitpy.thirdparty.mock import Mock +import functools -class RietveldTest(unittest.TestCase): - def test_url_for_issue(self): - rietveld = Rietveld(Mock()) - self.assertEqual(rietveld.url_for_issue(34223), - "https://wkrietveld.appspot.com/34223") +class memoized(object): + def __init__(self, function): + self._function = function + self._results_cache = {} + + def __call__(self, *args): + try: + return self._results_cache[args] + except KeyError: + # If we didn't find the args in our cache, call and save the results. + result = self._function(*args) + self._results_cache[args] = result + return result + # FIXME: We may need to handle TypeError here in the case + # that "args" is not a valid dictionary key. + + # Use python "descriptor" protocol __get__ to appear + # invisible during property access. + def __get__(self, instance, owner): + # Return a function partial with obj already bound as self. + return functools.partial(self.__call__, instance) diff --git a/WebKitTools/Scripts/webkitpy/common/memoized_unittest.py b/WebKitTools/Scripts/webkitpy/common/memoized_unittest.py new file mode 100644 index 0000000..dd7c793 --- /dev/null +++ b/WebKitTools/Scripts/webkitpy/common/memoized_unittest.py @@ -0,0 +1,65 @@ +# 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.memoized import memoized + + +class _TestObject(object): + def __init__(self): + self.callCount = 0 + + @memoized + def memoized_add(self, argument): + """testing docstring""" + self.callCount += 1 + if argument is None: + return None # Avoid the TypeError from None + 1 + return argument + 1 + + +class MemoizedTest(unittest.TestCase): + def test_caching(self): + test = _TestObject() + test.callCount = 0 + self.assertEqual(test.memoized_add(1), 2) + self.assertEqual(test.callCount, 1) + self.assertEqual(test.memoized_add(1), 2) + self.assertEqual(test.callCount, 1) + + # Validate that callCount is working as expected. + self.assertEqual(test.memoized_add(2), 3) + self.assertEqual(test.callCount, 2) + + def test_tearoff(self): + test = _TestObject() + # Make sure that get()/tear-offs work: + tearoff = test.memoized_add + self.assertEqual(tearoff(4), 5) + self.assertEqual(test.callCount, 1) diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py index 94519a7..1cc8e2e 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py @@ -113,9 +113,6 @@ class Attachment(object): def commit_queue(self): return self._attachment_dictionary.get("commit-queue") - def in_rietveld(self): - return self._attachment_dictionary.get("in-rietveld") - def url(self): # FIXME: This should just return # self._bugzilla().attachment_url_for_id(self.id()). scm_unittest.py @@ -221,9 +218,6 @@ class Bug(object): # a valid committer. return filter(lambda patch: patch.committer(), patches) - def in_rietveld_queue_patches(self): - return [patch for patch in self.patches() if patch.in_rietveld() == None] - # A container for all of the logic for making and parsing buzilla queries. class BugzillaQueries(object): @@ -287,16 +281,6 @@ class BugzillaQueries(object): return sum([self._fetch_bug(bug_id).commit_queued_patches() for bug_id in self.fetch_bug_ids_from_commit_queue()], []) - def fetch_first_patch_from_rietveld_queue(self): - # rietveld-queue processes all patches that don't have in-rietveld set. - query_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=notsubstring&value0-0-0=in-rietveld&field0-1-0=attachments.ispatch&type0-1-0=equals&value0-1-0=1&order=Last+Changed&field0-2-0=attachments.isobsolete&type0-2-0=equals&value0-2-0=0" - bugs = self._fetch_bug_ids_advanced_query(query_url) - if not len(bugs): - return None - - patches = self._fetch_bug(bugs[0]).in_rietveld_queue_patches() - return patches[0] if len(patches) else None - def _fetch_bug_ids_from_review_queue(self): review_queue_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=equals&value0-0-0=review?" return self._fetch_bug_ids_advanced_query(review_queue_url) @@ -502,8 +486,6 @@ class Bugzilla(object): self._parse_attachment_flag( element, 'review', attachment, 'reviewer_email') self._parse_attachment_flag( - element, 'in-rietveld', attachment, 'rietveld_uploader_email') - self._parse_attachment_flag( element, 'commit-queue', attachment, 'committer_email') return attachment @@ -591,11 +573,12 @@ class Bugzilla(object): self.authenticated = True return + credentials = Credentials(self.bug_server_host, git_prefix="bugzilla") + attempts = 0 while not self.authenticated: attempts += 1 - (username, password) = Credentials( - self.bug_server_host, git_prefix="bugzilla").read_credentials() + username, password = credentials.read_credentials() log("Logging in as %s..." % username) self.browser.open(self.bug_server_url + @@ -766,8 +749,6 @@ class Bugzilla(object): return self.browser.find_control(type='select', nr=0) elif flag_name == "commit-queue": return self.browser.find_control(type='select', nr=1) - elif flag_name == "in-rietveld": - return self.browser.find_control(type='select', nr=2) raise Exception("Don't know how to find flag named \"%s\"" % flag_name) def clear_attachment_flags(self, diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py index 3a454d6..df1fcf6 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py @@ -83,11 +83,6 @@ class BugzillaTest(unittest.TestCase): status="+" setter="two@test.com" /> - <flag name="in-rietveld" - id="17933" - status="+" - setter="three@test.com" - /> </attachment> ''' _expected_example_attachment_parsing = { @@ -103,8 +98,6 @@ class BugzillaTest(unittest.TestCase): 'reviewer_email' : 'one@test.com', 'commit-queue' : '+', 'committer_email' : 'two@test.com', - 'in-rietveld': '+', - 'rietveld_uploader_email': 'three@test.com', 'attacher_email' : 'christian.plesner.hansen@gmail.com', } diff --git a/WebKitTools/Scripts/webkitpy/common/net/credentials.py b/WebKitTools/Scripts/webkitpy/common/net/credentials.py index 1c3e6c0..30480b3 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/credentials.py +++ b/WebKitTools/Scripts/webkitpy/common/net/credentials.py @@ -48,6 +48,7 @@ except ImportError: class Credentials(object): + _environ_prefix = "webkit_bugzilla_" def __init__(self, host, git_prefix=None, executive=None, cwd=os.getcwd(), keyring=keyring): @@ -58,8 +59,17 @@ class Credentials(object): self._keyring = keyring def _credentials_from_git(self): - return [Git.read_git_config(self.git_prefix + "username"), - Git.read_git_config(self.git_prefix + "password")] + try: + if not Git.in_working_directory(self.cwd): + return (None, None) + return (Git.read_git_config(self.git_prefix + "username"), + Git.read_git_config(self.git_prefix + "password")) + except OSError, e: + # Catch and ignore OSError exceptions such as "no such file + # or directory" (OSError errno 2), which imply that the Git + # command cannot be found/is not installed. + pass + return (None, None) def _keychain_value_with_label(self, label, source_text): match = re.search("%s\"(?P<value>.+)\"" % label, @@ -110,21 +120,28 @@ class Credentials(object): else: return [None, None] - def read_credentials(self): - username = None - password = None + def _read_environ(self, key): + environ_key = self._environ_prefix + key + return os.environ.get(environ_key.upper()) - try: - if Git.in_working_directory(self.cwd): - (username, password) = self._credentials_from_git() - except OSError, e: - # Catch and ignore OSError exceptions such as "no such file - # or directory" (OSError errno 2), which imply that the Git - # command cannot be found/is not installed. - pass + def _credentials_from_environment(self): + return (self._read_environ("username"), self._read_environ("password")) + + def _offer_to_store_credentials_in_keyring(self, username, password): + if not self._keyring: + return + if not User().confirm("Store password in system keyring?", User.DEFAULT_NO): + return + self._keyring.set_password(self.host, username, password) + def read_credentials(self): + username, password = self._credentials_from_environment() + # FIXME: We don't currently support pulling the username from one + # source and the password from a separate source. + if not username or not password: + username, password = self._credentials_from_git() if not username or not password: - (username, password) = self._credentials_from_keychain(username) + username, password = self._credentials_from_keychain(username) if username and not password and self._keyring: password = self._keyring.get_password(self.host, username) @@ -132,13 +149,7 @@ class Credentials(object): if not username: username = User.prompt("%s login: " % self.host) if not password: - password = getpass.getpass("%s password for %s: " % (self.host, - username)) + password = getpass.getpass("%s password for %s: " % (self.host, username)) + self._offer_to_store_credentials_in_keyring(username, password) - if self._keyring: - store_password = User().confirm( - "Store password in system keyring?", User.DEFAULT_NO) - if store_password: - self._keyring.set_password(self.host, username, password) - - return [username, password] + return (username, password) diff --git a/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py index d30291b..6f2d909 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py @@ -26,6 +26,8 @@ # (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 os import tempfile import unittest @@ -34,6 +36,21 @@ from webkitpy.common.system.executive import Executive from webkitpy.common.system.outputcapture import OutputCapture from webkitpy.thirdparty.mock import Mock + +# FIXME: Other unit tests probably want this class. +class _TemporaryDirectory(object): + def __init__(self, **kwargs): + self._kwargs = kwargs + self._directory_path = None + + def __enter__(self): + self._directory_path = tempfile.mkdtemp(**self._kwargs) + return self._directory_path + + def __exit__(self, type, value, traceback): + os.rmdir(self._directory_path) + + class CredentialsTest(unittest.TestCase): example_security_output = """keychain: "/Users/test/Library/Keychains/login.keychain" class: "inet" @@ -101,40 +118,59 @@ password: "SECRETSAUCE" self._assert_security_call() self._assert_security_call(username="foo") + def test_credentials_from_environment(self): + executive_mock = Mock() + credentials = Credentials("example.com", executive=executive_mock) + + saved_environ = os.environ.copy() + os.environ['WEBKIT_BUGZILLA_USERNAME'] = "foo" + os.environ['WEBKIT_BUGZILLA_PASSWORD'] = "bar" + username, password = credentials._credentials_from_environment() + self.assertEquals(username, "foo") + self.assertEquals(password, "bar") + os.environ = saved_environ + def test_read_credentials_without_git_repo(self): + # FIXME: This should share more code with test_keyring_without_git_repo class FakeCredentials(Credentials): def _is_mac_os_x(self): return True + def _credentials_from_keychain(self, username): - return ["test@webkit.org", "SECRETSAUCE"] + return ("test@webkit.org", "SECRETSAUCE") + + def _credentials_from_environment(self): + return (None, None) + + with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: + credentials = FakeCredentials("bugs.webkit.org", cwd=temp_dir_path) + # FIXME: Using read_credentials here seems too broad as higher-priority + # credential source could be affected by the user's environment. + self.assertEqual(credentials.read_credentials(), ("test@webkit.org", "SECRETSAUCE")) - temp_dir_path = tempfile.mkdtemp(suffix="not_a_git_repo") - credentials = FakeCredentials("bugs.webkit.org", cwd=temp_dir_path) - self.assertEqual(credentials.read_credentials(), ["test@webkit.org", "SECRETSAUCE"]) - os.rmdir(temp_dir_path) def test_keyring_without_git_repo(self): + # FIXME: This should share more code with test_read_credentials_without_git_repo class MockKeyring(object): def get_password(self, host, username): return "NOMNOMNOM" class FakeCredentials(Credentials): - def __init__(self, cwd): - Credentials.__init__(self, "fake.hostname", cwd=cwd, - keyring=MockKeyring()) - def _is_mac_os_x(self): return True def _credentials_from_keychain(self, username): return ("test@webkit.org", None) - temp_dir_path = tempfile.mkdtemp(suffix="not_a_git_repo") - credentials = FakeCredentials(temp_dir_path) - try: - self.assertEqual(credentials.read_credentials(), ["test@webkit.org", "NOMNOMNOM"]) - finally: - os.rmdir(temp_dir_path) + def _credentials_from_environment(self): + return (None, None) + + with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: + credentials = FakeCredentials("fake.hostname", cwd=temp_dir_path, keyring=MockKeyring()) + # FIXME: Using read_credentials here seems too broad as higher-priority + # credential source could be affected by the user's environment. + self.assertEqual(credentials.read_credentials(), ("test@webkit.org", "NOMNOMNOM")) + if __name__ == '__main__': unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/net/rietveld.py b/WebKitTools/Scripts/webkitpy/common/net/rietveld.py deleted file mode 100644 index b9a0821..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/rietveld.py +++ /dev/null @@ -1,79 +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 logging -import os -import re -import stat - -import webkitpy.common.config as config -from webkitpy.common.system.deprecated_logging import log -from webkitpy.common.system.executive import ScriptError -import webkitpy.thirdparty.autoinstalled.rietveld.upload as upload - - -class Rietveld(object): - def __init__(self, executive, dryrun=False): - self.dryrun = dryrun - self._executive = executive - - def url_for_issue(self, codereview_issue): - if not codereview_issue: - return None - return "%s%s" % (config.codereview_server_url, codereview_issue) - - def post(self, diff, patch_id, codereview_issue, message=None, cc=None): - if not message: - raise ScriptError("Rietveld requires a message.") - - # Rietveld has a 100 character limit on message length. - if len(message) > 100: - message = message[:100] - - args = [ - # First argument is empty string to mimic sys.argv. - "", - "--assume_yes", - "--server=%s" % config.codereview_server_host, - "--message=%s" % message, - "--webkit_patch_id=%s" % patch_id, - ] - if codereview_issue: - args.append("--issue=%s" % codereview_issue) - if cc: - args.append("--cc=%s" % cc) - - if self.dryrun: - log("Would have run %s" % args) - return - - # Use RealMain instead of calling upload from the commandline so that - # we can pass in the diff ourselves. Otherwise, upload will just use - # git diff for git checkouts, which doesn't respect --git-commit. - issue, patchset = upload.RealMain(args, data=diff) - return issue diff --git a/WebKitTools/Scripts/webkitpy/common/net/statusserver.py b/WebKitTools/Scripts/webkitpy/common/net/statusserver.py index 3d03dcd..64dd77b 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/statusserver.py +++ b/WebKitTools/Scripts/webkitpy/common/net/statusserver.py @@ -127,7 +127,7 @@ class StatusServer: self._browser.submit() def release_work_item(self, queue_name, patch): - _log.debug("Releasing work item %s from %s" % (patch.id(), queue_name)) + _log.info("Releasing work item %s from %s" % (patch.id(), queue_name)) return NetworkTransaction(convert_404_to_None=True).run(lambda: self._post_release_work_item(queue_name, patch)) def update_work_items(self, queue_name, work_items): |