diff --git a/WATCHLISTS b/WATCHLISTS new file mode 100755 index 000000000..797f528e8 --- /dev/null +++ b/WATCHLISTS @@ -0,0 +1,24 @@ +# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Watchlist Rules +# Refer: http://dev.chromium.org/developers/contributing-code/watchlists + +{ + + 'WATCHLIST_DEFINITIONS': { + 'this_file': { + 'filepath': '^WATCHLISTS$', + }, + 'depot_tools': { + 'filepath': '.+', + }, + }, + + 'WATCHLISTS': { + 'this_file': ['nirnimesh@chromium.org'], + 'depot_tools': ['maruel@chromium.org'], + }, + +} diff --git a/gcl.py b/gcl.py index 7223585ad..7170032c2 100755 --- a/gcl.py +++ b/gcl.py @@ -615,6 +615,7 @@ Basic commands: gcl upload change_name [-r reviewer1@gmail.com,reviewer2@gmail.com,...] [--send_mail] [--no_try] [--no_presubmit] + [--no_watchlists] Uploads the changelist to the server for review. gcl commit change_name [--no_presubmit] @@ -738,6 +739,8 @@ def UploadCL(change_info, args): if not OptionallyDoPresubmitChecks(change_info, False, args): return no_try = FilterFlag(args, "--no_try") or FilterFlag(args, "--no-try") + no_watchlists = FilterFlag(args, "--no_watchlists") or \ + FilterFlag(args, "--no-watchlists") # Map --send-mail to --send_mail if FilterFlag(args, "--send-mail"): @@ -775,7 +778,17 @@ def UploadCL(change_info, args): os.write(handle, change_info.description) os.close(handle) + # Watchlist processing -- CC people interested in this changeset + # http://dev.chromium.org/developers/contributing-code/watchlists + if not no_watchlists: + import watchlists + watchlist = watchlists.Watchlists(GetRepositoryRoot()) + watchers = watchlist.GetWatchersForPaths(change_info.FileList()) + cc_list = GetCodeReviewSetting("CC_LIST") + if not no_watchlists and watchers: + # Filter out all empty elements and join by ',' + cc_list = ','.join(filter(None, [cc_list] + watchers)) if cc_list: upload_arg.append("--cc=" + cc_list) upload_arg.append("--description_file=" + desc_file + "") diff --git a/watchlists.py b/watchlists.py new file mode 100755 index 000000000..a0a127ead --- /dev/null +++ b/watchlists.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# Copyright (c) 2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Watchlists + +Watchlists is a mechanism that allow a developer (a "watcher") to watch over +portions of code that he is interested in. A "watcher" will be cc-ed to +changes that modify that portion of code, thereby giving him an opportunity +to make comments on codereview.chromium.org even before the change is +committed. +Refer: http://dev.chromium.org/developers/contributing-code/watchlists + +When invoked directly from the base of a repository, this script lists out +the watchers for files given on the command line. This is useful to verify +changes to WATCHLISTS files. +""" + +import logging +import os +import re +import sys + + +class Watchlists(object): + """Manage Watchlists. + + This class provides mechanism to load watchlists for a repo and identify + watchers. + Usage: + wl = Watchlists("/path/to/repo/root") + watchers = wl.GetWatchersForPaths(["/path/to/file1", + "/path/to/file2",]) + """ + + _RULES = "WATCHLISTS" + _RULES_FILENAME = _RULES + _repo_root = None + _defns = {} # Definitions + _watchlists = {} # name to email mapping + + def __init__(self, repo_root): + self._repo_root = repo_root + self._LoadWatchlistRules() + + def _GetRulesFilePath(self): + return os.path.join(self._repo_root, self._RULES_FILENAME) + + def _HasWatchlistsFile(self): + """Determine if watchlists are available for this repo.""" + return os.path.exists(self._GetRulesFilePath()) + + def _LoadWatchlistRules(self): + if not self._HasWatchlistsFile(): + return + watchlists_file = open(self._GetRulesFilePath()) + contents = watchlists_file.read() + watchlists_file.close() + + watchlists_data = None + try: + watchlists_data = eval(contents, {'__builtins__': None}, None) + except SyntaxError, e: + logging.error("Cannot parse %s. %s" % (self._GetRulesFilePath(), e)) + return + + defns = watchlists_data.get("WATCHLIST_DEFINITIONS") + if not defns: + logging.error("WATCHLIST_DEFINITIONS not defined in %s" % + self._GetRulesFilePath()) + return + watchlists = watchlists_data.get("WATCHLISTS") + if not watchlists: + logging.error("WATCHLISTS not defined in %s" % self._GetRulesFilePath()) + return + self._defns = defns + self._watchlists = watchlists + + # Verify that all watchlist names are defined + for name in watchlists: + if name not in defns: + logging.error("%s not defined in %s" % (name, self._GetRulesFilePath())) + + def GetWatchersForPaths(self, paths): + """Fetch the list of watchers for |paths| + + Args: + paths: [path1, path2, ...] + + Returns: + [u1@chromium.org, u2@gmail.com, ...] + """ + watchers = set() # A set, to avoid duplicates + for path in paths: + for name, rule in self._defns.iteritems(): + if name not in self._watchlists: continue + rex_str = rule.get('filepath') + if not rex_str: continue + if re.search(rex_str, path): + map(watchers.add, self._watchlists[name]) + return list(watchers) + + +def main(argv): + # Confirm that watchlists can be parsed and spew out the watchers + if len(argv) < 2: + print "Usage (from the base of repo):" + print " %s [file-1] [file-2] ...." % argv[0] + return 1 + wl = Watchlists(os.getcwd()) + watchers = wl.GetWatchersForPaths(argv[1:]) + print watchers + + +if __name__ == '__main__': + main(sys.argv)