blob: 9a4a8a36b079475127953505fad5712d9c133605 [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
16import multiprocessing
Shawn O. Pearceb812a362009-04-10 20:37:47 -070017import sys
Mike Frysinger72ab8522019-10-01 00:18:46 -040018
Shawn O. Pearceb812a362009-04-10 20:37:47 -070019from color import Coloring
Mike Frysingerd246d1f2021-02-24 12:50:30 -050020from command import DEFAULT_LOCAL_JOBS, PagedCommand, WORKER_BATCH_SIZE
Mike Frysinger72ab8522019-10-01 00:18:46 -040021from error import GitError
Mike Frysinger6f1c6262020-02-04 00:09:23 -050022from git_command import GitCommand
Shawn O. Pearceb812a362009-04-10 20:37:47 -070023
David Pursehouse819827a2020-02-12 15:20:19 +090024
Shawn O. Pearceb812a362009-04-10 20:37:47 -070025class GrepColoring(Coloring):
26 def __init__(self, config):
27 Coloring.__init__(self, config, 'grep')
28 self.project = self.printer('project', attr='bold')
Mike Frysinger72ab8522019-10-01 00:18:46 -040029 self.fail = self.printer('fail', fg='red')
Shawn O. Pearceb812a362009-04-10 20:37:47 -070030
David Pursehouse819827a2020-02-12 15:20:19 +090031
Shawn O. Pearceb812a362009-04-10 20:37:47 -070032class Grep(PagedCommand):
33 common = True
34 helpSummary = "Print lines matching a pattern"
35 helpUsage = """
36%prog {pattern | -e pattern} [<project>...]
37"""
38 helpDescription = """
39Search for the specified patterns in all project files.
40
Mike Frysingerb8f7bb02018-10-10 01:05:11 -040041# Boolean Options
Shawn O. Pearceb812a362009-04-10 20:37:47 -070042
43The following options can appear as often as necessary to express
44the pattern to locate:
45
46 -e PATTERN
47 --and, --or, --not, -(, -)
48
49Further, the -r/--revision option may be specified multiple times
50in order to scan multiple trees. If the same file matches in more
51than one tree, only the first result is reported, prefixed by the
52revision name it was found under.
53
Mike Frysingerb8f7bb02018-10-10 01:05:11 -040054# Examples
Shawn O. Pearceb812a362009-04-10 20:37:47 -070055
56Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
57
David Pursehouse1d947b32012-10-25 12:23:11 +090058 repo grep -e '#define' --and -\\( -e MAX_PATH -e PATH_MAX \\)
Shawn O. Pearceb812a362009-04-10 20:37:47 -070059
60Look for a line that has 'NODE' or 'Unexpected' in files that
61contain a line that matches both expressions:
62
63 repo grep --all-match -e NODE -e Unexpected
64
65"""
Mike Frysingerd246d1f2021-02-24 12:50:30 -050066 PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
Shawn O. Pearceb812a362009-04-10 20:37:47 -070067
Mike Frysinger45ad1542021-02-24 12:47:01 -050068 @staticmethod
69 def _carry_option(_option, opt_str, value, parser):
70 pt = getattr(parser.values, 'cmd_argv', None)
71 if pt is None:
72 pt = []
73 setattr(parser.values, 'cmd_argv', pt)
74
75 if opt_str == '-(':
76 pt.append('(')
77 elif opt_str == '-)':
78 pt.append(')')
79 else:
80 pt.append(opt_str)
81
82 if value is not None:
83 pt.append(value)
84
Mike Frysinger9180a072021-04-13 14:57:40 -040085 def _CommonOptions(self, p):
86 """Override common options slightly."""
87 super()._CommonOptions(p, opt_v=False)
88
Shawn O. Pearceb812a362009-04-10 20:37:47 -070089 def _Options(self, p):
Shawn O. Pearceb812a362009-04-10 20:37:47 -070090 g = p.add_option_group('Sources')
91 g.add_option('--cached',
Mike Frysinger45ad1542021-02-24 12:47:01 -050092 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -070093 help='Search the index, instead of the work tree')
David Pursehouse8f62fb72012-11-14 12:09:38 +090094 g.add_option('-r', '--revision',
Shawn O. Pearceb812a362009-04-10 20:37:47 -070095 dest='revision', action='append', metavar='TREEish',
96 help='Search TREEish, instead of the work tree')
97
98 g = p.add_option_group('Pattern')
99 g.add_option('-e',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500100 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700101 metavar='PATTERN', type='str',
102 help='Pattern to search for')
103 g.add_option('-i', '--ignore-case',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500104 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700105 help='Ignore case differences')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900106 g.add_option('-a', '--text',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500107 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700108 help="Process binary files as if they were text")
109 g.add_option('-I',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500110 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700111 help="Don't match the pattern in binary files")
112 g.add_option('-w', '--word-regexp',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500113 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700114 help='Match the pattern only at word boundaries')
115 g.add_option('-v', '--invert-match',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500116 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700117 help='Select non-matching lines')
118 g.add_option('-G', '--basic-regexp',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500119 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700120 help='Use POSIX basic regexp for patterns (default)')
121 g.add_option('-E', '--extended-regexp',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500122 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700123 help='Use POSIX extended regexp for patterns')
124 g.add_option('-F', '--fixed-strings',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500125 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700126 help='Use fixed strings (not regexp) for pattern')
127
128 g = p.add_option_group('Pattern Grouping')
129 g.add_option('--all-match',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500130 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700131 help='Limit match to lines that have all patterns')
132 g.add_option('--and', '--or', '--not',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500133 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700134 help='Boolean operators to combine patterns')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900135 g.add_option('-(', '-)',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500136 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700137 help='Boolean operator grouping')
138
139 g = p.add_option_group('Output')
140 g.add_option('-n',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500141 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700142 help='Prefix the line number to matching lines')
143 g.add_option('-C',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500144 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700145 metavar='CONTEXT', type='str',
146 help='Show CONTEXT lines around match')
147 g.add_option('-B',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500148 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700149 metavar='CONTEXT', type='str',
150 help='Show CONTEXT lines before match')
151 g.add_option('-A',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500152 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700153 metavar='CONTEXT', type='str',
154 help='Show CONTEXT lines after match')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900155 g.add_option('-l', '--name-only', '--files-with-matches',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500156 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700157 help='Show only file names containing matching lines')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900158 g.add_option('-L', '--files-without-match',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500159 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700160 help='Show only file names not containing matching lines')
161
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500162 def _ExecuteOne(self, cmd_argv, project):
163 """Process one project."""
164 try:
165 p = GitCommand(project,
166 cmd_argv,
167 bare=False,
168 capture_stdout=True,
169 capture_stderr=True)
170 except GitError as e:
171 return (project, -1, None, str(e))
172
173 return (project, p.Wait(), p.stdout, p.stderr)
174
175 @staticmethod
176 def _ProcessResults(out, full_name, have_rev, results):
177 git_failed = False
178 bad_rev = False
179 have_match = False
180
181 for project, rc, stdout, stderr in results:
182 if rc < 0:
183 git_failed = True
184 out.project('--- project %s ---' % project.relpath)
185 out.nl()
186 out.fail('%s', stderr)
187 out.nl()
188 continue
189
190 if rc:
191 # no results
192 if stderr:
193 if have_rev and 'fatal: ambiguous argument' in stderr:
194 bad_rev = True
195 else:
196 out.project('--- project %s ---' % project.relpath)
197 out.nl()
198 out.fail('%s', stderr.strip())
199 out.nl()
200 continue
201 have_match = True
202
203 # We cut the last element, to avoid a blank line.
204 r = stdout.split('\n')
205 r = r[0:-1]
206
207 if have_rev and full_name:
208 for line in r:
209 rev, line = line.split(':', 1)
210 out.write("%s", rev)
211 out.write(':')
212 out.project(project.relpath)
213 out.write('/')
214 out.write("%s", line)
215 out.nl()
216 elif full_name:
217 for line in r:
218 out.project(project.relpath)
219 out.write('/')
220 out.write("%s", line)
221 out.nl()
222 else:
223 for line in r:
224 print(line)
225
226 return (git_failed, bad_rev, have_match)
227
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700228 def Execute(self, opt, args):
229 out = GrepColoring(self.manifest.manifestProject.config)
230
231 cmd_argv = ['grep']
Mike Frysinger6f1c6262020-02-04 00:09:23 -0500232 if out.is_on:
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700233 cmd_argv.append('--color')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900234 cmd_argv.extend(getattr(opt, 'cmd_argv', []))
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700235
236 if '-e' not in cmd_argv:
237 if not args:
238 self.Usage()
239 cmd_argv.append('-e')
240 cmd_argv.append(args[0])
241 args = args[1:]
242
243 projects = self.GetProjects(args)
244
245 full_name = False
246 if len(projects) > 1:
247 cmd_argv.append('--full-name')
248 full_name = True
249
250 have_rev = False
251 if opt.revision:
252 if '--cached' in cmd_argv:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700253 print('fatal: cannot combine --cached and --revision', file=sys.stderr)
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700254 sys.exit(1)
255 have_rev = True
256 cmd_argv.extend(opt.revision)
257 cmd_argv.append('--')
258
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500259 process_results = functools.partial(
260 self._ProcessResults, out, full_name, have_rev)
261 # NB: Multiprocessing is heavy, so don't spin it up for one job.
262 if len(projects) == 1 or opt.jobs == 1:
263 git_failed, bad_rev, have_match = process_results(
264 self._ExecuteOne(cmd_argv, x) for x in projects)
265 else:
266 with multiprocessing.Pool(opt.jobs) as pool:
267 results = pool.imap(
268 functools.partial(self._ExecuteOne, cmd_argv), projects,
269 chunksize=WORKER_BATCH_SIZE)
270 git_failed, bad_rev, have_match = process_results(results)
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700271
Mike Frysinger72ab8522019-10-01 00:18:46 -0400272 if git_failed:
273 sys.exit(1)
274 elif have_match:
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700275 sys.exit(0)
276 elif have_rev and bad_rev:
277 for r in opt.revision:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700278 print("error: can't search revision %s" % r, file=sys.stderr)
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700279 sys.exit(1)
280 else:
281 sys.exit(1)