blob: 7c6d776d6957f0cfa1060a65b970ffc0d4c79fcf [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
Raul Tambre80ee78e2019-05-06 22:41:05 +000020from __future__ import print_function
21
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000022import logging
23import os
24import re
25import sys
26
27
28class Watchlists(object):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000029 """Manage Watchlists.
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000030
31 This class provides mechanism to load watchlists for a repo and identify
32 watchers.
33 Usage:
34 wl = Watchlists("/path/to/repo/root")
35 watchers = wl.GetWatchersForPaths(["/path/to/file1",
36 "/path/to/file2",])
37 """
38
Mike Frysinger124bb8e2023-09-06 05:48:55 +000039 _RULES = "WATCHLISTS"
40 _RULES_FILENAME = _RULES
41 _repo_root = None
42 _defns = {} # Definitions
43 _path_regexps = {} # Name -> Regular expression mapping
44 _watchlists = {} # name to email mapping
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000045
Mike Frysinger124bb8e2023-09-06 05:48:55 +000046 def __init__(self, repo_root):
47 self._repo_root = repo_root
48 self._LoadWatchlistRules()
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000049
Mike Frysinger124bb8e2023-09-06 05:48:55 +000050 def _GetRulesFilePath(self):
51 """Returns path to WATCHLISTS file."""
52 return os.path.join(self._repo_root, self._RULES_FILENAME)
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000053
Mike Frysinger124bb8e2023-09-06 05:48:55 +000054 def _HasWatchlistsFile(self):
55 """Determine if watchlists are available for this repo."""
56 return os.path.exists(self._GetRulesFilePath())
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000057
Mike Frysinger124bb8e2023-09-06 05:48:55 +000058 def _ContentsOfWatchlistsFile(self):
59 """Read the WATCHLISTS file and return its contents."""
60 try:
61 watchlists_file = open(self._GetRulesFilePath())
62 contents = watchlists_file.read()
63 watchlists_file.close()
64 return contents
65 except IOError as e:
66 logging.error("Cannot read %s: %s" % (self._GetRulesFilePath(), e))
67 return ''
nirnimesh@chromium.orgb82e4092009-06-18 14:24:08 +000068
Mike Frysinger124bb8e2023-09-06 05:48:55 +000069 def _LoadWatchlistRules(self):
70 """Load watchlists from WATCHLISTS file. Does nothing if not present."""
71 if not self._HasWatchlistsFile():
72 return
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000073
Mike Frysinger124bb8e2023-09-06 05:48:55 +000074 contents = self._ContentsOfWatchlistsFile()
75 watchlists_data = None
76 try:
77 watchlists_data = eval(contents, {'__builtins__': None}, None)
78 except SyntaxError as e:
79 logging.error("Cannot parse %s. %s" % (self._GetRulesFilePath(), e))
80 return
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000081
Mike Frysinger124bb8e2023-09-06 05:48:55 +000082 defns = watchlists_data.get("WATCHLIST_DEFINITIONS")
83 if not defns:
84 logging.error("WATCHLIST_DEFINITIONS not defined in %s" %
85 self._GetRulesFilePath())
86 return
87 watchlists = watchlists_data.get("WATCHLISTS")
88 if not watchlists:
89 logging.error("WATCHLISTS not defined in %s" %
90 self._GetRulesFilePath())
91 return
92 self._defns = defns
93 self._watchlists = watchlists
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +000094
Mike Frysinger124bb8e2023-09-06 05:48:55 +000095 # Compile the regular expressions ahead of time to avoid creating them
96 # on-the-fly multiple times per file.
97 self._path_regexps = {}
98 for name, rule in defns.items():
99 filepath = rule.get('filepath')
100 if not filepath:
101 continue
102 self._path_regexps[name] = re.compile(filepath)
Raphael Kubo da Costaae979432017-11-02 19:49:57 +0100103
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000104 # Verify that all watchlist names are defined
105 for name in watchlists:
106 if name not in defns:
107 logging.error("%s not defined in %s" %
108 (name, self._GetRulesFilePath()))
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +0000109
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000110 def GetWatchersForPaths(self, paths):
111 """Fetch the list of watchers for |paths|
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +0000112
113 Args:
114 paths: [path1, path2, ...]
115
116 Returns:
117 [u1@chromium.org, u2@gmail.com, ...]
118 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000119 watchers = set() # A set, to avoid duplicates
120 for path in paths:
121 path = path.replace(os.sep, '/')
122 for name, rule in self._path_regexps.items():
123 if name not in self._watchlists:
124 continue
125 if rule.search(path):
126 for watchlist in self._watchlists[name]:
127 watchers.add(watchlist)
128 return sorted(watchers)
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +0000129
130
131def main(argv):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000132 # Confirm that watchlists can be parsed and spew out the watchers
133 if len(argv) < 2:
134 print("Usage (from the base of repo):")
135 print(" %s [file-1] [file-2] ...." % argv[0])
136 return 1
137 wl = Watchlists(os.getcwd())
138 watchers = wl.GetWatchersForPaths(argv[1:])
139 print(watchers)
nirnimesh@chromium.orgb2ab4942009-06-11 21:39:19 +0000140
141
142if __name__ == '__main__':
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000143 main(sys.argv)