1643ca7872b450ea4efacab6188849e5aac2ba161Steve Block#!/usr/bin/env python
2643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
3643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# Copyright (c) 2009, Google Inc. All rights reserved.
4643ca7872b450ea4efacab6188849e5aac2ba161Steve Block#
5643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# Redistribution and use in source and binary forms, with or without
6643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# modification, are permitted provided that the following conditions are
7643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# met:
8643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# 
9643ca7872b450ea4efacab6188849e5aac2ba161Steve Block#     * Redistributions of source code must retain the above copyright
10643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# notice, this list of conditions and the following disclaimer.
11643ca7872b450ea4efacab6188849e5aac2ba161Steve Block#     * Redistributions in binary form must reproduce the above
12643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# copyright notice, this list of conditions and the following disclaimer
13643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# in the documentation and/or other materials provided with the
14643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# distribution.
15643ca7872b450ea4efacab6188849e5aac2ba161Steve Block#     * Neither the name of Google Inc. nor the names of its
16643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# contributors may be used to endorse or promote products derived from
17643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# this software without specific prior written permission.
18643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# 
19643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30643ca7872b450ea4efacab6188849e5aac2ba161Steve Block#
31643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# Checks Python's known list of committers against lists.webkit.org and SVN history.
32643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
33643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
34643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockimport os
35643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockimport subprocess
36643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockimport re
37643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockimport urllib2
38643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockfrom datetime import date, datetime, timedelta
39dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.common.config.committers import CommitterList
40dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.common.system.deprecated_logging import log, error
415abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrickfrom webkitpy.common.checkout.scm import Git
42643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
43d0825bca7fe65beaee391d30da42e937db621564Steve Block# WebKit includes a built copy of BeautifulSoup in Scripts/webkitpy
44643ca7872b450ea4efacab6188849e5aac2ba161Steve Block# so this import should always succeed.
45dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.thirdparty.BeautifulSoup import BeautifulSoup
46643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
47643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockdef print_list_if_non_empty(title, list_to_print):
48643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    if not list_to_print:
49643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        return
50643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    print # Newline before the list
51643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    print title
52643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    for item in list_to_print:
53643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print item
54643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
55643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockclass CommitterListFromMailingList:
56643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    committers_list_url = "http://lists.webkit.org/mailman/roster.cgi/webkit-committers"
57643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    reviewers_list_url = "http://lists.webkit.org/mailman/roster.cgi/webkit-reviewers"
58643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
59643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def _fetch_emails_from_page(self, url):
60643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        page = urllib2.urlopen(url)
61643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        soup = BeautifulSoup(page)
62643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
63643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        emails = []
64643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        # Grab the cells in the first column (which happens to be the bug ids).
65643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        for email_item in soup('li'):
66643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            email_link = email_item.find("a")
67643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            email = email_link.string.replace(" at ", "@") # The email is obfuscated using " at " instead of "@".
68643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            emails.append(email)
69643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        return emails
70643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
71643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    @staticmethod
72643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def _commiters_not_found_in_email_list(committers, emails):
73643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        missing_from_mailing_list = []
74643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        for committer in committers:
75643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            for email in committer.emails:
76643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                if email in emails:
77643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                    break
78643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            else:
79643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                missing_from_mailing_list.append(committer)
80643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        return missing_from_mailing_list
81643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
82643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    @staticmethod
83643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def _emails_not_found_in_committer_list(committers, emails):
84643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        email_to_committer_map = {}
85643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        for committer in committers:
86643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            for email in committer.emails:
87643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                email_to_committer_map[email] = committer
88643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
89643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        return filter(lambda email: not email_to_committer_map.get(email), emails)
90643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
91643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def check_for_emails_missing_from_list(self, committer_list):
92643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        committer_emails = self._fetch_emails_from_page(self.committers_list_url)
93643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        list_name = "webkit-committers@lists.webkit.org"
94643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
95643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        missing_from_mailing_list = self._commiters_not_found_in_email_list(committer_list.committers(), committer_emails)
96643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print_list_if_non_empty("Committers missing from %s:" % list_name, missing_from_mailing_list)
97643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
98643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        users_missing_from_committers = self._emails_not_found_in_committer_list(committer_list.committers(), committer_emails)
99643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print_list_if_non_empty("Subcribers to %s missing from committer.py:" % list_name, users_missing_from_committers)
100643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
101643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
102643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        reviewer_emails = self._fetch_emails_from_page(self.reviewers_list_url)
103643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        list_name = "webkit-reviewers@lists.webkit.org"
104643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
105643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        missing_from_mailing_list = self._commiters_not_found_in_email_list(committer_list.reviewers(), reviewer_emails)
106643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print_list_if_non_empty("Reviewers missing from %s:" % list_name, missing_from_mailing_list)
107643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
108643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        missing_from_reviewers = self._emails_not_found_in_committer_list(committer_list.reviewers(), reviewer_emails)
109643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print_list_if_non_empty("Subcribers to %s missing from reviewers in committer.py:" % list_name, missing_from_reviewers)
110643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
111643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        missing_from_committers = self._emails_not_found_in_committer_list(committer_list.committers(), reviewer_emails)
112643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print_list_if_non_empty("Subcribers to %s completely missing from committers.py" % list_name, missing_from_committers)
113643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
114643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
115643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockclass CommitterListFromGit:
116643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    login_to_email_address = {
117643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'aliceli1' : 'alice.liu@apple.com',
118643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'bdash' : 'mrowe@apple.com',
119643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'bdibello' : 'bdibello@apple.com', # Bruce DiBello, only 4 commits: r10023, r9548, r9538, r9535
120643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'cblu' : 'cblu@apple.com',
121643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'cpeterse' : 'cpetersen@apple.com',
122643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'eseidel' : 'eric@webkit.org',
123643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'gdennis' : 'gdennis@webkit.org',
124643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'goldsmit' : 'goldsmit@apple.com', # Debbie Goldsmith, only one commit r8839
125643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'gramps' : 'gramps@apple.com',
126643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'honeycutt' : 'jhoneycutt@apple.com',
127643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'jdevalk' : 'joost@webkit.org',
128643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'jens' : 'jens@apple.com',
129643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'justing' : 'justin.garcia@apple.com',
130643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'kali' : 'kali@apple.com', # Christy Warren, did BIDI work, 5 commits: r8815, r8802, r8801, r8791, r8773, r8603
131643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'kjk' : 'kkowalczyk@gmail.com',
132643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'kmccullo' : 'kmccullough@apple.com',
133643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'kocienda' : 'kocienda@apple.com',
134643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'lamadio' : 'lamadio@apple.com', # Lou Amadio, only 2 commits: r17949 and r17783
135643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'lars' : 'lars@kde.org',
136643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'lweintraub' : 'lweintraub@apple.com',
137643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'lypanov' : 'lypanov@kde.org',
138643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'mhay' : 'mhay@apple.com', # Mike Hay, 3 commits: r3813, r2552, r2548
139643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'ouch' : 'ouch@apple.com', # John Louch
140643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'pyeh' : 'patti@apple.com', # Patti Yeh, did VoiceOver work in WebKit
141643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'rjw' : 'rjw@apple.com',
142643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'seangies' : 'seangies@apple.com', # Sean Gies?, only 5 commits: r16600, r16592, r16511, r16489, r16484
143643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'sheridan' : 'sheridan@apple.com', # Shelly Sheridan
144643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'thatcher' : 'timothy@apple.com',
145643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'tomernic' : 'timo@apple.com',
146643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'trey' : 'trey@usa.net',
147643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'tristan' : 'tristan@apple.com',
148643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'vicki' : 'vicki@apple.com',
149643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'voas' : 'voas@apple.com', # Ed Voas, did some Carbon work in WebKit
150643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'zack' : 'zack@kde.org',
151643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        'zimmermann' : 'zimmermann@webkit.org',
152643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    }
153643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
154643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def __init__(self):
155643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        self._last_commit_time_by_author_cache = {}
156643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
157643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def _fetch_authors_and_last_commit_time_from_git_log(self):
158643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        last_commit_dates = {}
159643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        git_log_args = ['git', 'log', '--reverse', '--pretty=format:%ae %at']
160643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        process = subprocess.Popen(git_log_args, stdout=subprocess.PIPE)
161643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
162643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        # eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc 1257090899
163643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        line_regexp = re.compile("^(?P<author>.+)@\S+ (?P<timestamp>\d+)$")
164643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        while True:
165643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            output_line = process.stdout.readline()
166643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            if output_line == '' and process.poll() != None:
167643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                return last_commit_dates
168643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
169643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            match_result = line_regexp.match(output_line)
170643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            if not match_result:
171643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                error("Failed to match line: %s" % output_line)
172643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            last_commit_dates[match_result.group('author')] = float(match_result.group('timestamp'))
173643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
174643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def _fill_in_emails_for_old_logins(self):
175643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        authors_missing_email = filter(lambda author: author.find('@') == -1, self._last_commit_time_by_author_cache)
176643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        authors_with_email = filter(lambda author: author.find('@') != -1, self._last_commit_time_by_author_cache)
177643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        prefixes_of_authors_with_email = map(lambda author: author.split('@')[0], authors_with_email)
178643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
179643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        for author in authors_missing_email:
180643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            # First check to see if we have a manual mapping from login to email.
181643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            author_email = self.login_to_email_address.get(author)
182643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
183643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            # Most old logins like 'darin' are now just 'darin@apple.com', so check for a prefix match if a manual mapping was not found.
184643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            if not author_email and author in prefixes_of_authors_with_email:
185643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                author_email_index = prefixes_of_authors_with_email.index(author)
186643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                author_email = authors_with_email[author_email_index]
187643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
188643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            if not author_email:
189643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                # No known email mapping, likely not an active committer.  We could log here.
190643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                continue
191643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
192643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            # log("%s -> %s" % (author, author_email)) # For sanity checking.
193643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            no_email_commit_time = self._last_commit_time_by_author_cache.get(author)
194643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            email_commit_time = self._last_commit_time_by_author_cache.get(author_email)
195643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            # 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.
196643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            if not email_commit_time or email_commit_time < no_email_commit_time:
197643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                self._last_commit_time_by_author_cache[author_email] = no_email_commit_time
198643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            del self._last_commit_time_by_author_cache[author]
199643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
200643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def _last_commit_by_author(self):
201643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        if not self._last_commit_time_by_author_cache:
202643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            self._last_commit_time_by_author_cache = self._fetch_authors_and_last_commit_time_from_git_log()
203643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            self._fill_in_emails_for_old_logins()
204643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            del self._last_commit_time_by_author_cache['(no author)'] # The initial svn import isn't very useful.
205643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        return self._last_commit_time_by_author_cache
206643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
207643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    @staticmethod
208643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def _print_three_column_row(widths, values):
209643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print "%s%s%s" % (values[0].ljust(widths[0]), values[1].ljust(widths[1]), values[2])
210643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
211643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def print_possibly_expired_committers(self, committer_list):
212643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        authors_and_last_commits = self._last_commit_by_author().items()
213643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        authors_and_last_commits.sort(lambda a,b: cmp(a[1], b[1]), reverse=True)
214643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        committer_cuttof = date.today() - timedelta(days=365)
215643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        column_widths = [13, 25]
216643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print
217643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print "Committers who have not committed within one year:"
218643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        self._print_three_column_row(column_widths, ("Last Commit", "Committer Email", "Committer Record"))
219643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        for (author, last_commit) in authors_and_last_commits:
220643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            last_commit_date = date.fromtimestamp(last_commit)
221643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            if committer_cuttof > last_commit_date:
222643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                committer_record = committer_list.committer_by_email(author)
223643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                self._print_three_column_row(column_widths, (str(last_commit_date), author, committer_record))
224643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
225643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    def print_committers_missing_from_committer_list(self, committer_list):
226643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        missing_from_committers_py = []
227643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        last_commit_time_by_author = self._last_commit_by_author()
228643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        for author in last_commit_time_by_author:
229643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            if not committer_list.committer_by_email(author):
230643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                missing_from_committers_py.append(author)
231643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
232643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        never_committed = []
233643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        for committer in committer_list.committers():
234643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            for email in committer.emails:
235643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                if last_commit_time_by_author.get(email):
236643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                    break
237643ca7872b450ea4efacab6188849e5aac2ba161Steve Block            else:
238643ca7872b450ea4efacab6188849e5aac2ba161Steve Block                never_committed.append(committer)
239643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
240643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print_list_if_non_empty("Historical committers missing from committer.py:", missing_from_committers_py)
241643ca7872b450ea4efacab6188849e5aac2ba161Steve Block        print_list_if_non_empty("Committers in committer.py who have never committed:", never_committed)
242643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
243643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
244643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockdef main():
245643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    committer_list = CommitterList()
246643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    CommitterListFromMailingList().check_for_emails_missing_from_list(committer_list)
247d0825bca7fe65beaee391d30da42e937db621564Steve Block 
248d0825bca7fe65beaee391d30da42e937db621564Steve Block    if not Git.in_working_directory("."):
249d0825bca7fe65beaee391d30da42e937db621564Steve Block        print """\n\nWARNING: validate-committer-lists requires a git checkout.
250d0825bca7fe65beaee391d30da42e937db621564Steve BlockThe following checks are disabled:
251d0825bca7fe65beaee391d30da42e937db621564Steve Block - List of committers ordered by last commit
252d0825bca7fe65beaee391d30da42e937db621564Steve Block - List of historical committers missing from committers.py
253d0825bca7fe65beaee391d30da42e937db621564Steve Block"""
254d0825bca7fe65beaee391d30da42e937db621564Steve Block        return 1
255643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    svn_committer_list = CommitterListFromGit()
256643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    svn_committer_list.print_possibly_expired_committers(committer_list)
257643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    svn_committer_list.print_committers_missing_from_committer_list(committer_list)
258643ca7872b450ea4efacab6188849e5aac2ba161Steve Block
259643ca7872b450ea4efacab6188849e5aac2ba161Steve Blockif __name__ == "__main__":
260643ca7872b450ea4efacab6188849e5aac2ba161Steve Block    main()
261