diff options
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/common')
10 files changed, 129 insertions, 32 deletions
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py index ac9c42e..e68ccfa 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py @@ -584,25 +584,32 @@ class Git(SCM): def revert_files(self, file_paths): self.run(['git', 'checkout', 'HEAD'] + file_paths) + def _get_squash_error_message(self, num_local_commits): + working_directory_message = "" if self.working_directory_is_clean() else " and working copy changes" + return ("""There are %s local commits%s. Do one of the following: + 1) Use --squash or --no-squash + 2) git config webkit-patch.squash true/false + """ % (num_local_commits, working_directory_message)) + def should_squash(self, squash): - if squash is not None: - # Squash is specified on the command-line. - return squash - - config_squash = Git.read_git_config('webkit-patch.squash') - if (config_squash and config_squash is not ""): - return config_squash.lower() == "true" - - # Only raise an error 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 self.working_directory_is_clean(): - working_directory_message = "" if self.working_directory_is_clean() else " and working copy changes" - raise ScriptError(message="""There are %s local commits%s. Do one of the following: -1) Use --squash or --no-squash -2) git config webkit-patch.squash true/false -""" % (num_local_commits, working_directory_message)) - - return None + if squash is None: + config_squash = Git.read_git_config('webkit-patch.squash') + if (config_squash and config_squash is not ""): + squash = config_squash.lower() == "true" + else: + # Only raise an error 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 self.working_directory_is_clean()): + raise ScriptError(message=self._get_squash_error_message(num_local_commits)) + + if squash and self._svn_branch_has_extra_commits(): + raise ScriptError(message="Cannot use --squash when HEAD is not fully merged/rebased to %s. " + "This branch needs to be synced first." % self.svn_branch_name()) + + return squash + + def _svn_branch_has_extra_commits(self): + return len(run_command(['git', 'rev-list', '--max-count=1', self.svn_branch_name(), '^head'])) def commit_with_message(self, message, username=None, git_commit=None, squash=None): # Username is ignored during Git commits. diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py index 5a2c094..b6ae388 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py @@ -875,6 +875,12 @@ class GitTest(SCMTest): 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_squash(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.commit_with_message, "another test commit", squash=True) + def test_reverse_diff(self): self._shared_test_reverse_diff() @@ -937,6 +943,12 @@ class GitTest(SCMTest): self.assertTrue(re.search(r'test_file_commit2', patch)) self.assertTrue(re.search(r'test_file_commit1', patch)) + def test_create_patch_not_synced_squash(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, squash=True) + def test_create_binary_patch(self): # Create a git binary patch and check the contents. scm = detect_scm_system(self.git_checkout_path) @@ -1016,6 +1028,12 @@ class GitTest(SCMTest): self.assertTrue('test_file_commit2' in files) self.assertTrue('test_file_commit1' in files) + def test_changed_files_not_synced_squash(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, squash=True) + def test_changed_files(self): self._shared_test_changed_files() diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py index c33d2a6..02f1aed 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/committers.py +++ b/WebKitTools/Scripts/webkitpy/common/config/committers.py @@ -85,6 +85,7 @@ committers_unable_to_review = [ Committer("Cameron McCormack", "cam@webkit.org", "heycam"), Committer("Carol Szabo", "carol.szabo@nokia.com"), Committer("Chang Shu", "Chang.Shu@nokia.com"), + Committer("Chris Evans", "cevans@google.com"), Committer("Chris Fleizach", "cfleizach@apple.com"), Committer("Chris Marrin", "cmarrin@apple.com", "cmarrin"), Committer("Chris Petersen", "cpetersen@apple.com", "cpetersen"), @@ -163,7 +164,7 @@ committers_unable_to_review = [ Committer("Xiaomei Ji", "xji@chromium.org", "xji"), Committer("Yael Aharon", "yael.aharon@nokia.com"), Committer("Yaar Schnitman", ["yaar@chromium.org", "yaar@google.com"]), - Committer("Yong Li", ["yong.li@torchmobile.com", "yong.li.webkit@gmail.com"], "yong"), + Committer("Yong Li", ["yong.li.webkit@gmail.com", "yong.li@torchmobile.com"], "yong"), Committer("Yongjun Zhang", "yongjun.zhang@nokia.com"), Committer("Yuzo Fujishima", "yuzo@google.com", "yuzo"), Committer("Zoltan Herczeg", "zherczeg@webkit.org", "zherczeg"), diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py index 074a021..26d3652 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py @@ -104,6 +104,9 @@ class Attachment(object): def name(self): return self._attachment_dictionary.get("name") + def attach_date(self): + return self._attachment_dictionary.get("attach_date") + def review(self): return self._attachment_dictionary.get("review") @@ -430,6 +433,7 @@ class Bugzilla(object): attachment[flag_name] = flag['status'] if flag['status'] == '+': attachment[result_key] = flag['setter'] + # Sadly show_bug.cgi?ctype=xml does not expose the flag modification date. def _string_contents(self, soup): # WebKit's bugzilla instance uses UTF-8. @@ -439,8 +443,23 @@ class Bugzilla(object): # convert from NavigableString to a real unicode() object using unicode(). return unicode(soup.string) - def _parse_attachment_element(self, element, bug_id): + # Example: 2010-01-20 14:31 PST + # FIXME: Some bugzilla dates seem to have seconds in them? + # Python does not support timezones out of the box. + # Assume that bugzilla always uses PST (which is true for bugs.webkit.org) + _bugzilla_date_format = "%Y-%m-%d %H:%M" + + @classmethod + def _parse_date(cls, date_string): + (date, time, time_zone) = date_string.split(" ") + # Ignore the timezone because python doesn't understand timezones out of the box. + date_string = "%s %s" % (date, time) + return datetime.strptime(date_string, cls._bugzilla_date_format) + def _date_contents(self, soup): + return self._parse_date(self._string_contents(soup)) + + def _parse_attachment_element(self, element, bug_id): attachment = {} attachment['bug_id'] = bug_id attachment['is_obsolete'] = (element.has_key('isobsolete') and element['isobsolete'] == "1") @@ -448,6 +467,7 @@ class Bugzilla(object): attachment['id'] = int(element.find('attachid').string) # FIXME: No need to parse out the url here. attachment['url'] = self.attachment_url_for_id(attachment['id']) + attachment["attach_date"] = self._date_contents(element.find("date")) attachment['name'] = self._string_contents(element.find('desc')) attachment['attacher_email'] = self._string_contents(element.find('attacher')) attachment['type'] = self._string_contents(element.find('type')) @@ -564,6 +584,7 @@ class Bugzilla(object): raise Exception(errorMessage) else: self.authenticated = True + self.username = username def _fill_attachment_form(self, description, @@ -657,6 +678,7 @@ class Bugzilla(object): patch_description=None, cc=None, blocked=None, + assignee=None, mark_for_review=False, mark_for_commit_queue=False): self.authenticate() @@ -679,6 +701,10 @@ class Bugzilla(object): self.browser["cc"] = cc if blocked: self.browser["blocked"] = unicode(blocked) + if assignee == None: + assignee = self.username + if assignee: + self.browser["assigned_to"] = assignee self.browser["short_desc"] = bug_title self.browser["comment"] = bug_description diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py index 62a0746..ce992e7 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py @@ -28,6 +28,8 @@ import unittest +import datetime + from webkitpy.common.config.committers import CommitterList, Reviewer, Committer from webkitpy.common.net.bugzilla import Bugzilla, BugzillaQueries, parse_bug_id, CommitterValidator, Bug from webkitpy.common.system.outputcapture import OutputCapture @@ -97,6 +99,7 @@ class BugzillaTest(unittest.TestCase): </attachment> ''' _expected_example_attachment_parsing = { + 'attach_date': datetime.datetime(2009, 07, 29, 10, 23), 'bug_id' : 100, 'is_obsolete' : True, 'is_patch' : True, @@ -204,6 +207,7 @@ ZEZpbmlzaExvYWRXaXRoUmVhc29uOnJlYXNvbl07Cit9CisKIEBlbmQKIAogI2VuZGlmCg== "reporter_email" : "eric@webkit.org", "assigned_to_email" : "webkit-unassigned@lists.webkit.org", "attachments" : [{ + "attach_date": datetime.datetime(2009, 12, 27, 23, 51), 'name': u'Patch', 'url' : "https://bugs.webkit.org/attachment.cgi?id=45548", 'is_obsolete': False, diff --git a/WebKitTools/Scripts/webkitpy/common/net/statusserver.py b/WebKitTools/Scripts/webkitpy/common/net/statusserver.py index d9b52a2..0bd68d1 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/statusserver.py +++ b/WebKitTools/Scripts/webkitpy/common/net/statusserver.py @@ -31,9 +31,13 @@ from webkitpy.common.system.deprecated_logging import log from webkitpy.thirdparty.autoinstalled.mechanize import Browser from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup +import logging import urllib2 +_log = logging.getLogger("webkitpy.common.net.statusserver") + + class StatusServer: default_host = "webkit-commit-queue.appspot.com" @@ -83,6 +87,19 @@ class StatusServer: self.browser["broken_bot"] = broken_bot return self.browser.submit().read() + def _post_work_items_to_server(self, queue_name, work_items): + update_work_items_url = "%s/update-work-items" % self.url + self.browser.open(update_work_items_url) + self.browser.select_form(name="update_work_items") + self.browser["queue_name"] = queue_name + work_items = map(unicode, work_items) # .join expects strings + self.browser["work_items"] = " ".join(work_items) + return self.browser.submit().read() + + def update_work_items(self, queue_name, work_items): + _log.debug("Recording work items: %s for %s" % (work_items, queue_name)) + return NetworkTransaction().run(lambda: self._post_work_items_to_server(queue_name, work_items)) + def update_status(self, queue_name, status, patch=None, results_file=None): log(status) return NetworkTransaction().run(lambda: self._post_status_to_server(queue_name, status, patch, results_file)) diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive.py b/WebKitTools/Scripts/webkitpy/common/system/executive.py index c7a7aec..9c5889b 100644 --- a/WebKitTools/Scripts/webkitpy/common/system/executive.py +++ b/WebKitTools/Scripts/webkitpy/common/system/executive.py @@ -41,6 +41,7 @@ import StringIO import signal import subprocess import sys +import time from webkitpy.common.system.deprecated_logging import tee @@ -255,6 +256,18 @@ class Executive(object): input = input.encode("utf-8") return (subprocess.PIPE, input) + def _command_for_printing(self, args): + """Returns a print-ready string representing command args. + The string should be copy/paste ready for execution in a shell.""" + escaped_args = [] + for arg in args: + if isinstance(arg, unicode): + # Escape any non-ascii characters for easy copy/paste + arg = arg.encode("unicode_escape") + # FIXME: Do we need to fix quotes here? + escaped_args.append(arg) + return " ".join(escaped_args) + # FIXME: run_and_throw_if_fail should be merged into this method. def run_command(self, args, @@ -265,6 +278,8 @@ class Executive(object): return_stderr=True, decode_output=True): """Popen wrapper for convenience and to work around python bugs.""" + assert(isinstance(args, list) or isinstance(args, tuple)) + start_time = time.time() args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int()) stdin, string_to_communicate = self._compute_stdin(input) stderr = subprocess.STDOUT if return_stderr else None @@ -283,6 +298,8 @@ class Executive(object): # http://bugs.python.org/issue1731717 exit_code = process.wait() + _log.debug('"%s" took %.2fs' % (self._command_for_printing(args), time.time() - start_time)) + if return_exit_code: return exit_code diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py index 30468ce..32f8f51 100644 --- a/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py @@ -32,7 +32,7 @@ import subprocess import sys import unittest -from webkitpy.common.system.executive import Executive, run_command +from webkitpy.common.system.executive import Executive, run_command, ScriptError class ExecutiveTest(unittest.TestCase): @@ -42,6 +42,13 @@ class ExecutiveTest(unittest.TestCase): run_command(["foo_bar_command_blah"], error_handler=Executive.ignore_error, return_exit_code=True) self.failUnlessRaises(OSError, run_bad_command) + def test_run_command_args_type(self): + executive = Executive() + self.assertRaises(AssertionError, executive.run_command, "echo") + self.assertRaises(AssertionError, executive.run_command, u"echo") + executive.run_command(["echo", "foo"]) + executive.run_command(("echo", "foo")) + def test_run_command_with_unicode(self): """Validate that it is safe to pass unicode() objects to Executive.run* methods, and they will return unicode() diff --git a/WebKitTools/Scripts/webkitpy/common/system/user.py b/WebKitTools/Scripts/webkitpy/common/system/user.py index edce93d..4fa2fa3 100644 --- a/WebKitTools/Scripts/webkitpy/common/system/user.py +++ b/WebKitTools/Scripts/webkitpy/common/system/user.py @@ -30,6 +30,7 @@ import logging import os import shlex import subprocess +import sys import webbrowser diff --git a/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue.py b/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue.py index 6cb6f8c..17b6277 100644 --- a/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue.py +++ b/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue.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 threading @@ -36,20 +38,17 @@ class ThreadedMessageQueue(object): self._lock = threading.Lock() def post(self, message): - self._lock.acquire() - self._messages.append(message) - self._lock.release() + with self._lock: + self._messages.append(message) def stop(self): - self._lock.acquire() - self._is_running = False - self._lock.release() + with self._lock: + self._is_running = False def take_all(self): - self._lock.acquire() - messages = self._messages - is_running = self._is_running - self._messages = [] - self._lock.release() + with self._lock: + messages = self._messages + is_running = self._is_running + self._messages = [] return (messages, is_running) |