blob: 49feaf6bb8630ab8385f7d3a2c77c7848a2d5af4 [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
Shawn O. Pearceb812a362009-04-10 20:37:47 -070085 def _Options(self, p):
Mike Frysingerd246d1f2021-02-24 12:50:30 -050086 super()._Options(p)
Shawn O. Pearceb812a362009-04-10 20:37:47 -070087 g = p.add_option_group('Sources')
88 g.add_option('--cached',
Mike Frysinger45ad1542021-02-24 12:47:01 -050089 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -070090 help='Search the index, instead of the work tree')
David Pursehouse8f62fb72012-11-14 12:09:38 +090091 g.add_option('-r', '--revision',
Shawn O. Pearceb812a362009-04-10 20:37:47 -070092 dest='revision', action='append', metavar='TREEish',
93 help='Search TREEish, instead of the work tree')
94
95 g = p.add_option_group('Pattern')
96 g.add_option('-e',
Mike Frysinger45ad1542021-02-24 12:47:01 -050097 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -070098 metavar='PATTERN', type='str',
99 help='Pattern to search for')
100 g.add_option('-i', '--ignore-case',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500101 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700102 help='Ignore case differences')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900103 g.add_option('-a', '--text',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500104 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700105 help="Process binary files as if they were text")
106 g.add_option('-I',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500107 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700108 help="Don't match the pattern in binary files")
109 g.add_option('-w', '--word-regexp',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500110 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700111 help='Match the pattern only at word boundaries')
112 g.add_option('-v', '--invert-match',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500113 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700114 help='Select non-matching lines')
115 g.add_option('-G', '--basic-regexp',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500116 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700117 help='Use POSIX basic regexp for patterns (default)')
118 g.add_option('-E', '--extended-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 extended regexp for patterns')
121 g.add_option('-F', '--fixed-strings',
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 fixed strings (not regexp) for pattern')
124
125 g = p.add_option_group('Pattern Grouping')
126 g.add_option('--all-match',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500127 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700128 help='Limit match to lines that have all patterns')
129 g.add_option('--and', '--or', '--not',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500130 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700131 help='Boolean operators to combine patterns')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900132 g.add_option('-(', '-)',
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 operator grouping')
135
136 g = p.add_option_group('Output')
137 g.add_option('-n',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500138 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700139 help='Prefix the line number to matching lines')
140 g.add_option('-C',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500141 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700142 metavar='CONTEXT', type='str',
143 help='Show CONTEXT lines around match')
144 g.add_option('-B',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500145 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700146 metavar='CONTEXT', type='str',
147 help='Show CONTEXT lines before match')
148 g.add_option('-A',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500149 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700150 metavar='CONTEXT', type='str',
151 help='Show CONTEXT lines after match')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900152 g.add_option('-l', '--name-only', '--files-with-matches',
Mike Frysinger45ad1542021-02-24 12:47:01 -0500153 action='callback', callback=self._carry_option,
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700154 help='Show only file names containing matching lines')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900155 g.add_option('-L', '--files-without-match',
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 not containing matching lines')
158
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500159 def _ExecuteOne(self, cmd_argv, project):
160 """Process one project."""
161 try:
162 p = GitCommand(project,
163 cmd_argv,
164 bare=False,
165 capture_stdout=True,
166 capture_stderr=True)
167 except GitError as e:
168 return (project, -1, None, str(e))
169
170 return (project, p.Wait(), p.stdout, p.stderr)
171
172 @staticmethod
173 def _ProcessResults(out, full_name, have_rev, results):
174 git_failed = False
175 bad_rev = False
176 have_match = False
177
178 for project, rc, stdout, stderr in results:
179 if rc < 0:
180 git_failed = True
181 out.project('--- project %s ---' % project.relpath)
182 out.nl()
183 out.fail('%s', stderr)
184 out.nl()
185 continue
186
187 if rc:
188 # no results
189 if stderr:
190 if have_rev and 'fatal: ambiguous argument' in stderr:
191 bad_rev = True
192 else:
193 out.project('--- project %s ---' % project.relpath)
194 out.nl()
195 out.fail('%s', stderr.strip())
196 out.nl()
197 continue
198 have_match = True
199
200 # We cut the last element, to avoid a blank line.
201 r = stdout.split('\n')
202 r = r[0:-1]
203
204 if have_rev and full_name:
205 for line in r:
206 rev, line = line.split(':', 1)
207 out.write("%s", rev)
208 out.write(':')
209 out.project(project.relpath)
210 out.write('/')
211 out.write("%s", line)
212 out.nl()
213 elif full_name:
214 for line in r:
215 out.project(project.relpath)
216 out.write('/')
217 out.write("%s", line)
218 out.nl()
219 else:
220 for line in r:
221 print(line)
222
223 return (git_failed, bad_rev, have_match)
224
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700225 def Execute(self, opt, args):
226 out = GrepColoring(self.manifest.manifestProject.config)
227
228 cmd_argv = ['grep']
Mike Frysinger6f1c6262020-02-04 00:09:23 -0500229 if out.is_on:
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700230 cmd_argv.append('--color')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900231 cmd_argv.extend(getattr(opt, 'cmd_argv', []))
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700232
233 if '-e' not in cmd_argv:
234 if not args:
235 self.Usage()
236 cmd_argv.append('-e')
237 cmd_argv.append(args[0])
238 args = args[1:]
239
240 projects = self.GetProjects(args)
241
242 full_name = False
243 if len(projects) > 1:
244 cmd_argv.append('--full-name')
245 full_name = True
246
247 have_rev = False
248 if opt.revision:
249 if '--cached' in cmd_argv:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700250 print('fatal: cannot combine --cached and --revision', file=sys.stderr)
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700251 sys.exit(1)
252 have_rev = True
253 cmd_argv.extend(opt.revision)
254 cmd_argv.append('--')
255
Mike Frysingerd246d1f2021-02-24 12:50:30 -0500256 process_results = functools.partial(
257 self._ProcessResults, out, full_name, have_rev)
258 # NB: Multiprocessing is heavy, so don't spin it up for one job.
259 if len(projects) == 1 or opt.jobs == 1:
260 git_failed, bad_rev, have_match = process_results(
261 self._ExecuteOne(cmd_argv, x) for x in projects)
262 else:
263 with multiprocessing.Pool(opt.jobs) as pool:
264 results = pool.imap(
265 functools.partial(self._ExecuteOne, cmd_argv), projects,
266 chunksize=WORKER_BATCH_SIZE)
267 git_failed, bad_rev, have_match = process_results(results)
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700268
Mike Frysinger72ab8522019-10-01 00:18:46 -0400269 if git_failed:
270 sys.exit(1)
271 elif have_match:
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700272 sys.exit(0)
273 elif have_rev and bad_rev:
274 for r in opt.revision:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700275 print("error: can't search revision %s" % r, file=sys.stderr)
Shawn O. Pearceb812a362009-04-10 20:37:47 -0700276 sys.exit(1)
277 else:
278 sys.exit(1)