| # Copyright (c) 2009 Google Inc. All rights reserved. |
| # Copyright (c) 2009 Apple Inc. All rights reserved. |
| # Copyright (c) 2010 Research In Motion Limited. All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import re |
| |
| from webkitpy.common.net.bugzilla.attachment import Attachment |
| |
| |
| class Bug(object): |
| # FIXME: This class is kinda a hack for now. It exists so we have one |
| # place to hold bug logic, even if much of the code deals with |
| # dictionaries still. |
| |
| def __init__(self, bug_dictionary, bugzilla): |
| self.bug_dictionary = bug_dictionary |
| self._bugzilla = bugzilla |
| |
| def id(self): |
| return self.bug_dictionary["id"] |
| |
| def title(self): |
| # FIXME: Do we need to HTML unescape the title? |
| return self.bug_dictionary["title"] |
| |
| def reporter_email(self): |
| return self.bug_dictionary["reporter_email"] |
| |
| def assigned_to_email(self): |
| return self.bug_dictionary["assigned_to_email"] |
| |
| def cc_emails(self): |
| return self.bug_dictionary["cc_emails"] |
| |
| # FIXME: This information should be stored in some sort of webkit_config.py instead of here. |
| unassigned_emails = frozenset([ |
| "webkit-unassigned@lists.webkit.org", |
| ]) |
| |
| def is_unassigned(self): |
| return self.assigned_to_email() in self.unassigned_emails |
| |
| def status(self): |
| return self.bug_dictionary["bug_status"] |
| |
| def groups(self): |
| return self.bug_dictionary.get('groups', frozenset()) |
| |
| def is_security_sensitive(self): |
| return 'Security-Sensitive' in self.groups() |
| |
| # Bugzilla has many status states we don't really use in WebKit: |
| # https://bugs.webkit.org/page.cgi?id=fields.html#status |
| _open_states = ["UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED"] |
| _closed_states = ["RESOLVED", "VERIFIED", "CLOSED"] |
| |
| def is_open(self): |
| return self.status() in self._open_states |
| |
| def is_closed(self): |
| return not self.is_open() |
| |
| def duplicate_of(self): |
| return self.bug_dictionary.get('dup_id', None) |
| |
| # Rarely do we actually want obsolete attachments |
| def attachments(self, include_obsolete=False): |
| attachments = self.bug_dictionary["attachments"] |
| if not include_obsolete: |
| attachments = filter(lambda attachment: |
| not attachment["is_obsolete"], attachments) |
| return [Attachment(attachment, self) for attachment in attachments] |
| |
| def patches(self, include_obsolete=False): |
| return [patch for patch in self.attachments(include_obsolete) |
| if patch.is_patch()] |
| |
| def unreviewed_patches(self): |
| return [patch for patch in self.patches() if patch.review() == "?"] |
| |
| def reviewed_patches(self, include_invalid=False): |
| patches = [patch for patch in self.patches() if patch.review() == "+"] |
| if include_invalid: |
| return patches |
| # Checking reviewer() ensures that it was both reviewed and has a valid |
| # reviewer. |
| return filter(lambda patch: patch.reviewer(), patches) |
| |
| def commit_queued_patches(self, include_invalid=False): |
| patches = [patch for patch in self.patches() |
| if patch.commit_queue() == "+"] |
| if include_invalid: |
| return patches |
| # Checking committer() ensures that it was both commit-queue+'d and has |
| # a valid committer. |
| return filter(lambda patch: patch.committer(), patches) |
| |
| def comments(self): |
| return self.bug_dictionary["comments"] |
| |
| def is_in_comments(self, message): |
| for comment in self.comments(): |
| if message in comment["text"]: |
| return True |
| return False |
| |
| def commit_revision(self): |
| # Sort the comments in reverse order as we want the latest committed revision. |
| r = re.compile("Committed r(?P<svn_revision>\d+)") |
| for comment in sorted(self.comments(), key=lambda comment: comment['text'], reverse=True): |
| rev = r.search(comment['text']) |
| if rev: |
| return int(rev.group('svn_revision')) |
| |
| return None |