From 643ca7872b450ea4efacab6188849e5aac2ba161 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Tue, 15 Dec 2009 10:12:09 +0000 Subject: Merge webkit.org at r51976 : Initial merge by git. Change-Id: Ib0e7e2f0fb4bee5a186610272edf3186f0986b43 --- WebKitTools/Scripts/validate-committer-lists | 252 +++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100755 WebKitTools/Scripts/validate-committer-lists (limited to 'WebKitTools/Scripts/validate-committer-lists') diff --git a/WebKitTools/Scripts/validate-committer-lists b/WebKitTools/Scripts/validate-committer-lists new file mode 100755 index 0000000..05f2b36 --- /dev/null +++ b/WebKitTools/Scripts/validate-committer-lists @@ -0,0 +1,252 @@ +#!/usr/bin/env python + +# 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. +# +# Checks Python's known list of committers against lists.webkit.org and SVN history. + + +import os +import subprocess +import re +import urllib2 +from datetime import date, datetime, timedelta +from modules.committers import CommitterList +from modules.logging import log, error + +# WebKit includes a built copy of BeautifulSoup in Scripts/modules +# so this import should always succeed. +from modules.BeautifulSoup import BeautifulSoup + +def print_list_if_non_empty(title, list_to_print): + if not list_to_print: + return + print # Newline before the list + print title + for item in list_to_print: + print item + +class CommitterListFromMailingList: + committers_list_url = "http://lists.webkit.org/mailman/roster.cgi/webkit-committers" + reviewers_list_url = "http://lists.webkit.org/mailman/roster.cgi/webkit-reviewers" + + def _fetch_emails_from_page(self, url): + page = urllib2.urlopen(url) + soup = BeautifulSoup(page) + + emails = [] + # Grab the cells in the first column (which happens to be the bug ids). + for email_item in soup('li'): + email_link = email_item.find("a") + email = email_link.string.replace(" at ", "@") # The email is obfuscated using " at " instead of "@". + emails.append(email) + return emails + + @staticmethod + def _commiters_not_found_in_email_list(committers, emails): + missing_from_mailing_list = [] + for committer in committers: + for email in committer.emails: + if email in emails: + break + else: + missing_from_mailing_list.append(committer) + return missing_from_mailing_list + + @staticmethod + def _emails_not_found_in_committer_list(committers, emails): + email_to_committer_map = {} + for committer in committers: + for email in committer.emails: + email_to_committer_map[email] = committer + + return filter(lambda email: not email_to_committer_map.get(email), emails) + + def check_for_emails_missing_from_list(self, committer_list): + committer_emails = self._fetch_emails_from_page(self.committers_list_url) + list_name = "webkit-committers@lists.webkit.org" + + missing_from_mailing_list = self._commiters_not_found_in_email_list(committer_list.committers(), committer_emails) + print_list_if_non_empty("Committers missing from %s:" % list_name, missing_from_mailing_list) + + users_missing_from_committers = self._emails_not_found_in_committer_list(committer_list.committers(), committer_emails) + print_list_if_non_empty("Subcribers to %s missing from committer.py:" % list_name, users_missing_from_committers) + + + reviewer_emails = self._fetch_emails_from_page(self.reviewers_list_url) + list_name = "webkit-reviewers@lists.webkit.org" + + missing_from_mailing_list = self._commiters_not_found_in_email_list(committer_list.reviewers(), reviewer_emails) + print_list_if_non_empty("Reviewers missing from %s:" % list_name, missing_from_mailing_list) + + missing_from_reviewers = self._emails_not_found_in_committer_list(committer_list.reviewers(), reviewer_emails) + print_list_if_non_empty("Subcribers to %s missing from reviewers in committer.py:" % list_name, missing_from_reviewers) + + missing_from_committers = self._emails_not_found_in_committer_list(committer_list.committers(), reviewer_emails) + print_list_if_non_empty("Subcribers to %s completely missing from committers.py" % list_name, missing_from_committers) + + +class CommitterListFromGit: + login_to_email_address = { + 'aliceli1' : 'alice.liu@apple.com', + 'bdash' : 'mrowe@apple.com', + 'bdibello' : 'bdibello@apple.com', # Bruce DiBello, only 4 commits: r10023, r9548, r9538, r9535 + 'cblu' : 'cblu@apple.com', + 'cpeterse' : 'cpetersen@apple.com', + 'eseidel' : 'eric@webkit.org', + 'gdennis' : 'gdennis@webkit.org', + 'goldsmit' : 'goldsmit@apple.com', # Debbie Goldsmith, only one commit r8839 + 'gramps' : 'gramps@apple.com', + 'honeycutt' : 'jhoneycutt@apple.com', + 'jdevalk' : 'joost@webkit.org', + 'jens' : 'jens@apple.com', + 'justing' : 'justin.garcia@apple.com', + 'kali' : 'kali@apple.com', # Christy Warren, did BIDI work, 5 commits: r8815, r8802, r8801, r8791, r8773, r8603 + 'kjk' : 'kkowalczyk@gmail.com', + 'kmccullo' : 'kmccullough@apple.com', + 'kocienda' : 'kocienda@apple.com', + 'lamadio' : 'lamadio@apple.com', # Lou Amadio, only 2 commits: r17949 and r17783 + 'lars' : 'lars@kde.org', + 'lweintraub' : 'lweintraub@apple.com', + 'lypanov' : 'lypanov@kde.org', + 'mhay' : 'mhay@apple.com', # Mike Hay, 3 commits: r3813, r2552, r2548 + 'ouch' : 'ouch@apple.com', # John Louch + 'pyeh' : 'patti@apple.com', # Patti Yeh, did VoiceOver work in WebKit + 'rjw' : 'rjw@apple.com', + 'seangies' : 'seangies@apple.com', # Sean Gies?, only 5 commits: r16600, r16592, r16511, r16489, r16484 + 'sheridan' : 'sheridan@apple.com', # Shelly Sheridan + 'thatcher' : 'timothy@apple.com', + 'tomernic' : 'timo@apple.com', + 'trey' : 'trey@usa.net', + 'tristan' : 'tristan@apple.com', + 'vicki' : 'vicki@apple.com', + 'voas' : 'voas@apple.com', # Ed Voas, did some Carbon work in WebKit + 'zack' : 'zack@kde.org', + 'zimmermann' : 'zimmermann@webkit.org', + } + + def __init__(self): + self._last_commit_time_by_author_cache = {} + + def _fetch_authors_and_last_commit_time_from_git_log(self): + last_commit_dates = {} + git_log_args = ['git', 'log', '--reverse', '--pretty=format:%ae %at'] + process = subprocess.Popen(git_log_args, stdout=subprocess.PIPE) + + # eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc 1257090899 + line_regexp = re.compile("^(?P.+)@\S+ (?P\d+)$") + while True: + output_line = process.stdout.readline() + if output_line == '' and process.poll() != None: + return last_commit_dates + + match_result = line_regexp.match(output_line) + if not match_result: + error("Failed to match line: %s" % output_line) + last_commit_dates[match_result.group('author')] = float(match_result.group('timestamp')) + + def _fill_in_emails_for_old_logins(self): + authors_missing_email = filter(lambda author: author.find('@') == -1, self._last_commit_time_by_author_cache) + authors_with_email = filter(lambda author: author.find('@') != -1, self._last_commit_time_by_author_cache) + prefixes_of_authors_with_email = map(lambda author: author.split('@')[0], authors_with_email) + + for author in authors_missing_email: + # First check to see if we have a manual mapping from login to email. + author_email = self.login_to_email_address.get(author) + + # Most old logins like 'darin' are now just 'darin@apple.com', so check for a prefix match if a manual mapping was not found. + if not author_email and author in prefixes_of_authors_with_email: + author_email_index = prefixes_of_authors_with_email.index(author) + author_email = authors_with_email[author_email_index] + + if not author_email: + # No known email mapping, likely not an active committer. We could log here. + continue + + # log("%s -> %s" % (author, author_email)) # For sanity checking. + no_email_commit_time = self._last_commit_time_by_author_cache.get(author) + email_commit_time = self._last_commit_time_by_author_cache.get(author_email) + # We compare the timestamps for extra sanity even though we could assume commits before email address were used for login are always going to be older. + if not email_commit_time or email_commit_time < no_email_commit_time: + self._last_commit_time_by_author_cache[author_email] = no_email_commit_time + del self._last_commit_time_by_author_cache[author] + + def _last_commit_by_author(self): + if not self._last_commit_time_by_author_cache: + self._last_commit_time_by_author_cache = self._fetch_authors_and_last_commit_time_from_git_log() + self._fill_in_emails_for_old_logins() + del self._last_commit_time_by_author_cache['(no author)'] # The initial svn import isn't very useful. + return self._last_commit_time_by_author_cache + + @staticmethod + def _print_three_column_row(widths, values): + print "%s%s%s" % (values[0].ljust(widths[0]), values[1].ljust(widths[1]), values[2]) + + def print_possibly_expired_committers(self, committer_list): + authors_and_last_commits = self._last_commit_by_author().items() + authors_and_last_commits.sort(lambda a,b: cmp(a[1], b[1]), reverse=True) + committer_cuttof = date.today() - timedelta(days=365) + column_widths = [13, 25] + print + print "Committers who have not committed within one year:" + self._print_three_column_row(column_widths, ("Last Commit", "Committer Email", "Committer Record")) + for (author, last_commit) in authors_and_last_commits: + last_commit_date = date.fromtimestamp(last_commit) + if committer_cuttof > last_commit_date: + committer_record = committer_list.committer_by_email(author) + self._print_three_column_row(column_widths, (str(last_commit_date), author, committer_record)) + + def print_committers_missing_from_committer_list(self, committer_list): + missing_from_committers_py = [] + last_commit_time_by_author = self._last_commit_by_author() + for author in last_commit_time_by_author: + if not committer_list.committer_by_email(author): + missing_from_committers_py.append(author) + + never_committed = [] + for committer in committer_list.committers(): + for email in committer.emails: + if last_commit_time_by_author.get(email): + break + else: + never_committed.append(committer) + + print_list_if_non_empty("Historical committers missing from committer.py:", missing_from_committers_py) + print_list_if_non_empty("Committers in committer.py who have never committed:", never_committed) + + +def main(): + committer_list = CommitterList() + CommitterListFromMailingList().check_for_emails_missing_from_list(committer_list) + + svn_committer_list = CommitterListFromGit() + svn_committer_list.print_possibly_expired_committers(committer_list) + svn_committer_list.print_committers_missing_from_committer_list(committer_list) + +if __name__ == "__main__": + main() -- cgit v1.1