blob: 3567cb396072020fba798dd450f26a28e40dae78 [file] [log] [blame]
Josip Sokcevic4de5dea2022-03-23 21:15:14 +00001#!/usr/bin/env python3
maruel@chromium.org725f1c32011-04-01 20:24:54 +00002# Copyright (c) 2011 The Chromium Authors. All rights reserved.
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +00005"""Watchlists
6
7Watchlists is a mechanism that allow a developer (a "watcher") to watch over
Chris Hallba97f602019-10-15 15:22:16 +00008portions of code that they are interested in. A "watcher" will be cc-ed to
9changes that modify that portion of code, thereby giving them an opportunity
James Zernfdd89462021-01-13 01:38:34 +000010to make comments on chromium-review.googlesource.com even before the change is
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000011committed.
James Zernfdd89462021-01-13 01:38:34 +000012Refer:
13https://chromium.googlesource.com/chromium/src/+/HEAD/docs/infra/watchlists.md
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000014
15When invoked directly from the base of a repository, this script lists out
16the watchers for files given on the command line. This is useful to verify
17changes to WATCHLISTS files.
18"""
19
20import logging
21import os
22import re
23import sys
24
25
26class Watchlists(object):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000027 """Manage Watchlists.
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000028
29 This class provides mechanism to load watchlists for a repo and identify
30 watchers.
31 Usage:
32 wl = Watchlists("/path/to/repo/root")
33 watchers = wl.GetWatchersForPaths(["/path/to/file1",
34 "/path/to/file2",])
35 """
36
Mike Frysinger124bb8e2023-09-06 05:48:55 +000037 _RULES = "WATCHLISTS"
38 _RULES_FILENAME = _RULES
39 _repo_root = None
40 _defns = {} # Definitions
41 _path_regexps = {} # Name -> Regular expression mapping
42 _watchlists = {} # name to email mapping
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000043
Mike Frysinger124bb8e2023-09-06 05:48:55 +000044 def __init__(self, repo_root):
45 self._repo_root = repo_root
46 self._LoadWatchlistRules()
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000047
Mike Frysinger124bb8e2023-09-06 05:48:55 +000048 def _GetRulesFilePath(self):
49 """Returns path to WATCHLISTS file."""
50 return os.path.join(self._repo_root, self._RULES_FILENAME)
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000051
Mike Frysinger124bb8e2023-09-06 05:48:55 +000052 def _HasWatchlistsFile(self):
53 """Determine if watchlists are available for this repo."""
54 return os.path.exists(self._GetRulesFilePath())
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000055
Mike Frysinger124bb8e2023-09-06 05:48:55 +000056 def _ContentsOfWatchlistsFile(self):
57 """Read the WATCHLISTS file and return its contents."""
58 try:
59 watchlists_file = open(self._GetRulesFilePath())
60 contents = watchlists_file.read()
61 watchlists_file.close()
62 return contents
63 except IOError as e:
64 logging.error("Cannot read %s: %s" % (self._GetRulesFilePath(), e))
65 return ''
nirnimesh@chromium.orgb82e4092009-06-18 14:24:08 +000066
Mike Frysinger124bb8e2023-09-06 05:48:55 +000067 def _LoadWatchlistRules(self):
68 """Load watchlists from WATCHLISTS file. Does nothing if not present."""
69 if not self._HasWatchlistsFile():
70 return
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000071
Mike Frysinger124bb8e2023-09-06 05:48:55 +000072 contents = self._ContentsOfWatchlistsFile()
73 watchlists_data = None
74 try:
75 watchlists_data = eval(contents, {'__builtins__': None}, None)
76 except SyntaxError as e:
77 logging.error("Cannot parse %s. %s" % (self._GetRulesFilePath(), e))
78 return
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000079
Mike Frysinger124bb8e2023-09-06 05:48:55 +000080 defns = watchlists_data.get("WATCHLIST_DEFINITIONS")
81 if not defns:
82 logging.error("WATCHLIST_DEFINITIONS not defined in %s" %
83 self._GetRulesFilePath())
84 return
85 watchlists = watchlists_data.get("WATCHLISTS")
86 if not watchlists:
87 logging.error("WATCHLISTS not defined in %s" %
88 self._GetRulesFilePath())
89 return
90 self._defns = defns
91 self._watchlists = watchlists
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000092
Mike Frysinger124bb8e2023-09-06 05:48:55 +000093 # Compile the regular expressions ahead of time to avoid creating them
94 # on-the-fly multiple times per file.
95 self._path_regexps = {}
96 for name, rule in defns.items():
97 filepath = rule.get('filepath')
98 if not filepath:
99 continue
100 self._path_regexps[name] = re.compile(filepath)
Raphael Kubo da Costaae979432017-11-02 19:49:57 +0100101
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000102 # Verify that all watchlist names are defined
103 for name in watchlists:
104 if name not in defns:
105 logging.error("%s not defined in %s" %
106 (name, self._GetRulesFilePath()))
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +0000107
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000108 def GetWatchersForPaths(self, paths):
109 """Fetch the list of watchers for |paths|
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +0000110
111 Args:
112 paths: [path1, path2, ...]
113
114 Returns:
115 [u1@chromium.org, u2@gmail.com, ...]
116 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000117 watchers = set() # A set, to avoid duplicates
118 for path in paths:
119 path = path.replace(os.sep, '/')
120 for name, rule in self._path_regexps.items():
121 if name not in self._watchlists:
122 continue
123 if rule.search(path):
124 for watchlist in self._watchlists[name]:
125 watchers.add(watchlist)
126 return sorted(watchers)
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +0000127
128
129def main(argv):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000130 # Confirm that watchlists can be parsed and spew out the watchers
131 if len(argv) < 2:
132 print("Usage (from the base of repo):")
133 print(" %s [file-1] [file-2] ...." % argv[0])
134 return 1
135 wl = Watchlists(os.getcwd())
136 watchers = wl.GetWatchersForPaths(argv[1:])
137 print(watchers)
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +0000138
139
140if __name__ == '__main__':
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000141 main(sys.argv)