summaryrefslogtreecommitdiffstats
path: root/Tools/Scripts/webkitpy/common/config/committers.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/Scripts/webkitpy/common/config/committers.py')
-rw-r--r--Tools/Scripts/webkitpy/common/config/committers.py273
1 files changed, 0 insertions, 273 deletions
diff --git a/Tools/Scripts/webkitpy/common/config/committers.py b/Tools/Scripts/webkitpy/common/config/committers.py
deleted file mode 100644
index 62a09329e..000000000
--- a/Tools/Scripts/webkitpy/common/config/committers.py
+++ /dev/null
@@ -1,273 +0,0 @@
-# Copyright (c) 2011, Apple Inc. All rights reserved.
-# Copyright (c) 2009, 2011, 2012 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 committer and reviewer validation.
-
-import fnmatch
-import json
-
-from webkitpy.common.editdistance import edit_distance
-from webkitpy.common.memoized import memoized
-from webkitpy.common.system.filesystem import FileSystem
-
-
-# The list of contributors have been moved to contributors.json
-
-
-class Contributor(object):
- def __init__(self, name, email_or_emails, irc_nickname_or_nicknames=None):
- assert(name)
- assert(email_or_emails)
- self.full_name = name
- if isinstance(email_or_emails, str):
- self.emails = [email_or_emails]
- else:
- self.emails = email_or_emails
- self.emails = map(lambda email: email.lower(), self.emails) # Emails are case-insensitive.
- if isinstance(irc_nickname_or_nicknames, str):
- self.irc_nicknames = [irc_nickname_or_nicknames]
- else:
- self.irc_nicknames = irc_nickname_or_nicknames
- self.can_commit = False
- self.can_review = False
-
- def bugzilla_email(self):
- # FIXME: We're assuming the first email is a valid bugzilla email,
- # which might not be right.
- return self.emails[0]
-
- def __str__(self):
- return unicode(self).encode('utf-8')
-
- def __unicode__(self):
- return '"%s" <%s>' % (self.full_name, self.emails[0])
-
- def contains_string(self, search_string):
- string = search_string.lower()
- if string in self.full_name.lower():
- return True
- if self.irc_nicknames:
- for nickname in self.irc_nicknames:
- if string in nickname.lower():
- return True
- for email in self.emails:
- if string in email:
- return True
- return False
-
- def matches_glob(self, glob_string):
- if fnmatch.fnmatch(self.full_name, glob_string):
- return True
- if self.irc_nicknames:
- for nickname in self.irc_nicknames:
- if fnmatch.fnmatch(nickname, glob_string):
- return True
- for email in self.emails:
- if fnmatch.fnmatch(email, glob_string):
- return True
- return False
-
-
-class Committer(Contributor):
- def __init__(self, name, email_or_emails, irc_nickname=None):
- Contributor.__init__(self, name, email_or_emails, irc_nickname)
- self.can_commit = True
-
-
-class Reviewer(Committer):
- def __init__(self, name, email_or_emails, irc_nickname=None):
- Committer.__init__(self, name, email_or_emails, irc_nickname)
- self.can_review = True
-
-
-class CommitterList(object):
-
- # Committers and reviewers are passed in to allow easy testing
- def __init__(self,
- committers=[],
- reviewers=[],
- contributors=[]):
- # FIXME: These arguments only exist for testing. Clean it up.
- if not (committers or reviewers or contributors):
- loaded_data = self.load_json()
- contributors = loaded_data['Contributors']
- committers = loaded_data['Committers']
- reviewers = loaded_data['Reviewers']
-
- self._contributors = contributors + committers + reviewers
- self._committers = committers + reviewers
- self._reviewers = reviewers
- self._contributors_by_name = {}
- self._accounts_by_email = {}
- self._accounts_by_login = {}
-
- @staticmethod
- @memoized
- def load_json():
- filesystem = FileSystem()
- json_path = filesystem.join(filesystem.dirname(filesystem.path_to_module('webkitpy.common.config')), 'contributors.json')
- contributors = json.loads(filesystem.read_text_file(json_path))
-
- return {
- 'Contributors': [Contributor(name, data.get('emails'), data.get('nicks')) for name, data in contributors['Contributors'].iteritems()],
- 'Committers': [Committer(name, data.get('emails'), data.get('nicks')) for name, data in contributors['Committers'].iteritems()],
- 'Reviewers': [Reviewer(name, data.get('emails'), data.get('nicks')) for name, data in contributors['Reviewers'].iteritems()],
- }
-
- def contributors(self):
- return self._contributors
-
- def committers(self):
- return self._committers
-
- def reviewers(self):
- return self._reviewers
-
- def _name_to_contributor_map(self):
- if not len(self._contributors_by_name):
- for contributor in self._contributors:
- assert(contributor.full_name)
- assert(contributor.full_name.lower() not in self._contributors_by_name) # We should never have duplicate names.
- self._contributors_by_name[contributor.full_name.lower()] = contributor
- return self._contributors_by_name
-
- def _email_to_account_map(self):
- if not len(self._accounts_by_email):
- for account in self._contributors:
- for email in account.emails:
- assert(email not in self._accounts_by_email) # We should never have duplicate emails.
- self._accounts_by_email[email] = account
- return self._accounts_by_email
-
- def _login_to_account_map(self):
- if not len(self._accounts_by_login):
- for account in self._contributors:
- if account.emails:
- login = account.bugzilla_email()
- assert(login not in self._accounts_by_login) # We should never have duplicate emails.
- self._accounts_by_login[login] = account
- return self._accounts_by_login
-
- def _committer_only(self, record):
- if record and not record.can_commit:
- return None
- return record
-
- def _reviewer_only(self, record):
- if record and not record.can_review:
- return None
- return record
-
- def committer_by_name(self, name):
- return self._committer_only(self.contributor_by_name(name))
-
- def contributor_by_irc_nickname(self, irc_nickname):
- for contributor in self.contributors():
- # FIXME: This should do case-insensitive comparison or assert that all IRC nicknames are in lowercase
- if contributor.irc_nicknames and irc_nickname in contributor.irc_nicknames:
- return contributor
- return None
-
- def contributors_by_search_string(self, string):
- glob_matches = filter(lambda contributor: contributor.matches_glob(string), self.contributors())
- return glob_matches or filter(lambda contributor: contributor.contains_string(string), self.contributors())
-
- def contributors_by_email_username(self, string):
- string = string + '@'
- result = []
- for contributor in self.contributors():
- for email in contributor.emails:
- if email.startswith(string):
- result.append(contributor)
- break
- return result
-
- def _contributor_name_shorthands(self, contributor):
- if ' ' not in contributor.full_name:
- return []
- split_fullname = contributor.full_name.split()
- first_name = split_fullname[0]
- last_name = split_fullname[-1]
- return first_name, last_name, first_name + last_name[0], first_name + ' ' + last_name[0]
-
- def _tokenize_contributor_name(self, contributor):
- full_name_in_lowercase = contributor.full_name.lower()
- tokens = [full_name_in_lowercase] + full_name_in_lowercase.split()
- if contributor.irc_nicknames:
- return tokens + [nickname.lower() for nickname in contributor.irc_nicknames if len(nickname) > 5]
- return tokens
-
- def contributors_by_fuzzy_match(self, string):
- string_in_lowercase = string.lower()
-
- # 1. Exact match for fullname, email and irc_nicknames
- account = self.contributor_by_name(string_in_lowercase) or self.contributor_by_email(string_in_lowercase) or self.contributor_by_irc_nickname(string_in_lowercase)
- if account:
- return [account], 0
-
- # 2. Exact match for email username (before @)
- accounts = self.contributors_by_email_username(string_in_lowercase)
- if accounts and len(accounts) == 1:
- return accounts, 0
-
- # 3. Exact match for first name, last name, and first name + initial combinations such as "Dan B" and "Tim H"
- accounts = [contributor for contributor in self.contributors() if string in self._contributor_name_shorthands(contributor)]
- if accounts and len(accounts) == 1:
- return accounts, 0
-
- # 4. Finally, fuzzy-match using edit-distance
- string = string_in_lowercase
- contributorWithMinDistance = []
- minDistance = len(string) / 2 - 1
- for contributor in self.contributors():
- tokens = self._tokenize_contributor_name(contributor)
- editdistances = [edit_distance(token, string) for token in tokens if abs(len(token) - len(string)) <= minDistance]
- if not editdistances:
- continue
- distance = min(editdistances)
- if distance == minDistance:
- contributorWithMinDistance.append(contributor)
- elif distance < minDistance:
- contributorWithMinDistance = [contributor]
- minDistance = distance
- if not len(contributorWithMinDistance):
- return [], len(string)
- return contributorWithMinDistance, minDistance
-
- def contributor_by_email(self, email):
- return self._email_to_account_map().get(email.lower()) if email else None
-
- def contributor_by_name(self, name):
- return self._name_to_contributor_map().get(name.lower()) if name else None
-
- def committer_by_email(self, email):
- return self._committer_only(self.contributor_by_email(email))
-
- def reviewer_by_email(self, email):
- return self._reviewer_only(self.contributor_by_email(email))