blob: b677b6bd9b2c041a5cbe170e2edf699b62efb3ac [file] [log] [blame]
Shawn O. Pearceb812a362009-04-10 20:37:47 -07001# Copyright (C) 2009 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Mike Frysingerd246d1f2021-02-24 12:50:30 -050015import functools
Shawn O. Pearceb812a362009-04-10 20:37:47 -070016import sys
Mike Frysinger64477332023-08-21 21:20:32 -040017from typing import NamedTuple
Mike Frysinger72ab8522019-10-01 00:18:46 -040018
Shawn O. Pearceb812a362009-04-10 20:37:47 -070019from color import Coloring
Mike Frysinger64477332023-08-21 21:20:32 -040020from command import DEFAULT_LOCAL_JOBS
21from command import PagedCommand
22from error import GitError
23from error import InvalidArgumentsError
24from error import SilentRepoExitError
Mike Frysinger6f1c6262020-02-04 00:09:23 -050025from git_command import GitCommand
Jason Chang1a3612f2023-08-08 14:12:53 -070026from project import Project
Aravind Vasudevanc993c502023-09-14 08:46:44 +000027from repo_logging import RepoLogger
28
29
30logger = RepoLogger(__file__)
Shawn O. Pearceb812a362009-04-10 20:37:47 -070031
David Pursehouse819827a2020-02-12 15:20:19 +090032
Shawn O. Pearceb812a362009-04-10 20:37:47 -070033class GrepColoring(Coloring):
Gavin Makea2e3302023-03-11 06:46:20 +000034 def __init__(self, config):
35 Coloring.__init__(self, config, "grep")
36 self.project = self.printer("project", attr="bold")
37 self.fail = self.printer("fail", fg="red")
Shawn O. Pearceb812a362009-04-10 20:37:47 -070038
David Pursehouse819827a2020-02-12 15:20:19 +090039
Jason Chang1a3612f2023-08-08 14:12:53 -070040class ExecuteOneResult(NamedTuple):
41 """Result from an execute instance."""
42
43 project: Project
44 rc: int
45 stdout: str
46 stderr: str
47 error: GitError
48
49
50class GrepCommandError(SilentRepoExitError):
51 """Grep command failure. Since Grep command
52 output already outputs errors ensure that
53 aggregate errors exit silently."""
54
55
Shawn O. Pearceb812a362009-04-10 20:37:47 -070056class Grep(PagedCommand):
Gavin Makea2e3302023-03-11 06:46:20 +000057 COMMON = True
58 helpSummary = "Print lines matching a pattern"
59 helpUsage = """
Shawn O. Pearceb812a362009-04-10 20:37:47 -070060%prog {pattern | -e pattern} [<project>...]
61"""
Gavin Makea2e3302023-03-11 06:46:20 +000062 helpDescription = """
Shawn O. Pearceb812a362009-04-10 20:37:47 -070063Search for the specified patterns in all project files.
64
Mike Frysingerb8f7bb02018-10-10 01:05:11 -040065# Boolean Options
Shawn O. Pearceb812a362009-04-10 20:37:47 -070066
67The following options can appear as often as necessary to express
68the pattern to locate:
69
70 -e PATTERN
71 --and, --or, --not, -(, -)
72
73Further, the -r/--revision option may be specified multiple times
74in order to scan multiple trees. If the same file matches in more
75than one tree, only the first result is reported, prefixed by the
76revision name it was found under.
77
Mike Frysingerb8f7bb02018-10-10 01:05:11 -040078# Examples
Shawn O. Pearceb812a362009-04-10 20:37:47 -070079
80Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
81
David Pursehouse1d947b32012-10-25 12:23:11 +090082 repo grep -e '#define' --and -\\( -e MAX_PATH -e PATH_MAX \\)
Shawn O. Pearceb812a362009-04-10 20:37:47 -070083
84Look for a line that has 'NODE' or 'Unexpected' in files that
85contain a line that matches both expressions:
86
87 repo grep --all-match -e NODE -e Unexpected
88
89"""
Gavin Makea2e3302023-03-11 06:46:20 +000090 PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
Shawn O. Pearceb812a362009-04-10 20:37:47 -070091
Gavin Makea2e3302023-03-11 06:46:20 +000092 @staticmethod
93 def _carry_option(_option, opt_str, value, parser):
94 pt = getattr(parser.values, "cmd_argv", None)
95 if pt is None:
96 pt = []
97 setattr(parser.values, "cmd_argv", pt)
Mike Frysinger45ad1542021-02-24 12:47:01 -050098
Gavin Makea2e3302023-03-11 06:46:20 +000099 if opt_str == "-(":
100 pt.append("(")
101 elif opt_str == "-)":
102 pt.append(")")
103 else:
104 pt.append(opt_str)
Mike Frysinger45ad1542021-02-24 12:47:01 -0500105
Gavin Makea2e3302023-03-11 06:46:20 +0000106 if value is not None:
107 pt.append(value)
Mike Frysinger45ad1542021-02-24 12:47:01 -0500108
Gavin Makea2e3302023-03-11 06:46:20 +0000109 def _CommonOptions(self, p):
110 """Override common options slightly."""
111 super()._CommonOptions(p, opt_v=False)
Mike Frysinger9180a072021-04-13 14:57:40 -0400112
Gavin Makea2e3302023-03-11 06:46:20 +0000113 def _Options(self, p):
114 g = p.add_option_group("Sources")
115 g.add_option(
116 "--cached",
117 action="callback",
118 callback=self._carry_option,
119 help="Search the index, instead of the work tree",
120 )
121 g.add_option(
122 "-r",
123 "--revision",
124 dest="revision",
125 action="append",
126 metavar="TREEish",
127 help="Search TREEish, instead of the work tree",
128 )
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700129
Gavin Makea2e3302023-03-11 06:46:20 +0000130 g = p.add_option_group("Pattern")
131 g.add_option(
132 "-e",
133 action="callback",
134 callback=self._carry_option,
135 metavar="PATTERN",
136 type="str",
137 help="Pattern to search for",
138 )
139 g.add_option(
140 "-i",
141 "--ignore-case",
142 action="callback",
143 callback=self._carry_option,
144 help="Ignore case differences",
145 )
146 g.add_option(
147 "-a",
148 "--text",
149 action="callback",
150 callback=self._carry_option,
151 help="Process binary files as if they were text",
152 )
153 g.add_option(
154 "-I",
155 action="callback",
156 callback=self._carry_option,
157 help="Don't match the pattern in binary files",
158 )
159 g.add_option(
160 "-w",
161 "--word-regexp",
162 action="callback",
163 callback=self._carry_option,
164 help="Match the pattern only at word boundaries",
165 )
166 g.add_option(
167 "-v",
168 "--invert-match",
169 action="callback",
170 callback=self._carry_option,
171 help="Select non-matching lines",
172 )
173 g.add_option(
174 "-G",
175 "--basic-regexp",
176 action="callback",
177 callback=self._carry_option,
178 help="Use POSIX basic regexp for patterns (default)",
179 )
180 g.add_option(
181 "-E",
182 "--extended-regexp",
183 action="callback",
184 callback=self._carry_option,
185 help="Use POSIX extended regexp for patterns",
186 )
187 g.add_option(
188 "-F",
189 "--fixed-strings",
190 action="callback",
191 callback=self._carry_option,
192 help="Use fixed strings (not regexp) for pattern",
193 )
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700194
Gavin Makea2e3302023-03-11 06:46:20 +0000195 g = p.add_option_group("Pattern Grouping")
196 g.add_option(
197 "--all-match",
198 action="callback",
199 callback=self._carry_option,
200 help="Limit match to lines that have all patterns",
201 )
202 g.add_option(
203 "--and",
204 "--or",
205 "--not",
206 action="callback",
207 callback=self._carry_option,
208 help="Boolean operators to combine patterns",
209 )
210 g.add_option(
211 "-(",
212 "-)",
213 action="callback",
214 callback=self._carry_option,
215 help="Boolean operator grouping",
216 )
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700217
Gavin Makea2e3302023-03-11 06:46:20 +0000218 g = p.add_option_group("Output")
219 g.add_option(
220 "-n",
221 action="callback",
222 callback=self._carry_option,
223 help="Prefix the line number to matching lines",
224 )
225 g.add_option(
226 "-C",
227 action="callback",
228 callback=self._carry_option,
229 metavar="CONTEXT",
230 type="str",
231 help="Show CONTEXT lines around match",
232 )
233 g.add_option(
234 "-B",
235 action="callback",
236 callback=self._carry_option,
237 metavar="CONTEXT",
238 type="str",
239 help="Show CONTEXT lines before match",
240 )
241 g.add_option(
242 "-A",
243 action="callback",
244 callback=self._carry_option,
245 metavar="CONTEXT",
246 type="str",
247 help="Show CONTEXT lines after match",
248 )
249 g.add_option(
250 "-l",
251 "--name-only",
252 "--files-with-matches",
253 action="callback",
254 callback=self._carry_option,
255 help="Show only file names containing matching lines",
256 )
257 g.add_option(
258 "-L",
259 "--files-without-match",
260 action="callback",
261 callback=self._carry_option,
262 help="Show only file names not containing matching lines",
263 )
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700264
Gavin Makea2e3302023-03-11 06:46:20 +0000265 def _ExecuteOne(self, cmd_argv, project):
266 """Process one project."""
267 try:
268 p = GitCommand(
269 project,
270 cmd_argv,
271 bare=False,
272 capture_stdout=True,
273 capture_stderr=True,
Jason Chang1a3612f2023-08-08 14:12:53 -0700274 verify_command=True,
Gavin Makea2e3302023-03-11 06:46:20 +0000275 )
276 except GitError as e:
Jason Chang1a3612f2023-08-08 14:12:53 -0700277 return ExecuteOneResult(project, -1, None, str(e), e)
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500278
Jason Chang1a3612f2023-08-08 14:12:53 -0700279 try:
280 error = None
281 rc = p.Wait()
282 except GitError as e:
283 rc = 1
284 error = e
285 return ExecuteOneResult(project, rc, p.stdout, p.stderr, error)
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500286
Gavin Makea2e3302023-03-11 06:46:20 +0000287 @staticmethod
288 def _ProcessResults(full_name, have_rev, opt, _pool, out, results):
289 git_failed = False
290 bad_rev = False
291 have_match = False
292 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
Jason Chang1a3612f2023-08-08 14:12:53 -0700293 errors = []
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500294
Jason Chang1a3612f2023-08-08 14:12:53 -0700295 for result in results:
296 if result.rc < 0:
Gavin Makea2e3302023-03-11 06:46:20 +0000297 git_failed = True
Jason Chang1a3612f2023-08-08 14:12:53 -0700298 out.project("--- project %s ---" % _RelPath(result.project))
Gavin Makea2e3302023-03-11 06:46:20 +0000299 out.nl()
Jason Chang1a3612f2023-08-08 14:12:53 -0700300 out.fail("%s", result.stderr)
Gavin Makea2e3302023-03-11 06:46:20 +0000301 out.nl()
Jason Chang1a3612f2023-08-08 14:12:53 -0700302 errors.append(result.error)
Gavin Makea2e3302023-03-11 06:46:20 +0000303 continue
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500304
Jason Chang1a3612f2023-08-08 14:12:53 -0700305 if result.rc:
Gavin Makea2e3302023-03-11 06:46:20 +0000306 # no results
Jason Chang1a3612f2023-08-08 14:12:53 -0700307 if result.stderr:
308 if (
309 have_rev
310 and "fatal: ambiguous argument" in result.stderr
311 ):
Gavin Makea2e3302023-03-11 06:46:20 +0000312 bad_rev = True
313 else:
Jason Chang1a3612f2023-08-08 14:12:53 -0700314 out.project(
315 "--- project %s ---" % _RelPath(result.project)
316 )
Gavin Makea2e3302023-03-11 06:46:20 +0000317 out.nl()
Jason Chang1a3612f2023-08-08 14:12:53 -0700318 out.fail("%s", result.stderr.strip())
Gavin Makea2e3302023-03-11 06:46:20 +0000319 out.nl()
Jason Chang1a3612f2023-08-08 14:12:53 -0700320 if result.error is not None:
321 errors.append(result.error)
Gavin Makea2e3302023-03-11 06:46:20 +0000322 continue
323 have_match = True
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500324
Gavin Makea2e3302023-03-11 06:46:20 +0000325 # We cut the last element, to avoid a blank line.
Jason Chang1a3612f2023-08-08 14:12:53 -0700326 r = result.stdout.split("\n")
Gavin Makea2e3302023-03-11 06:46:20 +0000327 r = r[0:-1]
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500328
Gavin Makea2e3302023-03-11 06:46:20 +0000329 if have_rev and full_name:
330 for line in r:
331 rev, line = line.split(":", 1)
332 out.write("%s", rev)
333 out.write(":")
Jason Chang1a3612f2023-08-08 14:12:53 -0700334 out.project(_RelPath(result.project))
Gavin Makea2e3302023-03-11 06:46:20 +0000335 out.write("/")
336 out.write("%s", line)
337 out.nl()
338 elif full_name:
339 for line in r:
Jason Chang1a3612f2023-08-08 14:12:53 -0700340 out.project(_RelPath(result.project))
Gavin Makea2e3302023-03-11 06:46:20 +0000341 out.write("/")
342 out.write("%s", line)
343 out.nl()
344 else:
345 for line in r:
346 print(line)
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500347
Jason Chang1a3612f2023-08-08 14:12:53 -0700348 return (git_failed, bad_rev, have_match, errors)
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500349
Gavin Makea2e3302023-03-11 06:46:20 +0000350 def Execute(self, opt, args):
351 out = GrepColoring(self.manifest.manifestProject.config)
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700352
Gavin Makea2e3302023-03-11 06:46:20 +0000353 cmd_argv = ["grep"]
354 if out.is_on:
355 cmd_argv.append("--color")
356 cmd_argv.extend(getattr(opt, "cmd_argv", []))
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700357
Gavin Makea2e3302023-03-11 06:46:20 +0000358 if "-e" not in cmd_argv:
359 if not args:
360 self.Usage()
361 cmd_argv.append("-e")
362 cmd_argv.append(args[0])
363 args = args[1:]
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700364
Gavin Makea2e3302023-03-11 06:46:20 +0000365 projects = self.GetProjects(
366 args, all_manifests=not opt.this_manifest_only
367 )
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700368
Gavin Makea2e3302023-03-11 06:46:20 +0000369 full_name = False
370 if len(projects) > 1:
371 cmd_argv.append("--full-name")
372 full_name = True
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700373
Gavin Makea2e3302023-03-11 06:46:20 +0000374 have_rev = False
375 if opt.revision:
376 if "--cached" in cmd_argv:
Jason Chang1a3612f2023-08-08 14:12:53 -0700377 msg = "fatal: cannot combine --cached and --revision"
Aravind Vasudevanc993c502023-09-14 08:46:44 +0000378 logger.error(msg)
Jason Chang1a3612f2023-08-08 14:12:53 -0700379 raise InvalidArgumentsError(msg)
Gavin Makea2e3302023-03-11 06:46:20 +0000380 have_rev = True
381 cmd_argv.extend(opt.revision)
382 cmd_argv.append("--")
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700383
Jason Chang1a3612f2023-08-08 14:12:53 -0700384 git_failed, bad_rev, have_match, errors = self.ExecuteInParallel(
Gavin Makea2e3302023-03-11 06:46:20 +0000385 opt.jobs,
386 functools.partial(self._ExecuteOne, cmd_argv),
387 projects,
388 callback=functools.partial(
389 self._ProcessResults, full_name, have_rev, opt
390 ),
391 output=out,
392 ordered=True,
393 )
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700394
Gavin Makea2e3302023-03-11 06:46:20 +0000395 if git_failed:
Jason Chang1a3612f2023-08-08 14:12:53 -0700396 raise GrepCommandError(
397 "error: git failures", aggregate_errors=errors
398 )
Gavin Makea2e3302023-03-11 06:46:20 +0000399 elif have_match:
400 sys.exit(0)
401 elif have_rev and bad_rev:
402 for r in opt.revision:
Aravind Vasudevanc993c502023-09-14 08:46:44 +0000403 logger.error("error: can't search revision %s", r)
Jason Chang1a3612f2023-08-08 14:12:53 -0700404 raise GrepCommandError(aggregate_errors=errors)