summaryrefslogtreecommitdiffstats
path: root/subcmds/grep.py
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds/grep.py')
-rw-r--r--subcmds/grep.py529
1 files changed, 309 insertions, 220 deletions
diff --git a/subcmds/grep.py b/subcmds/grep.py
index 93c9ae51..5cd33763 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -22,19 +22,19 @@ from git_command import GitCommand
22 22
23 23
24class GrepColoring(Coloring): 24class GrepColoring(Coloring):
25 def __init__(self, config): 25 def __init__(self, config):
26 Coloring.__init__(self, config, 'grep') 26 Coloring.__init__(self, config, "grep")
27 self.project = self.printer('project', attr='bold') 27 self.project = self.printer("project", attr="bold")
28 self.fail = self.printer('fail', fg='red') 28 self.fail = self.printer("fail", fg="red")
29 29
30 30
31class Grep(PagedCommand): 31class Grep(PagedCommand):
32 COMMON = True 32 COMMON = True
33 helpSummary = "Print lines matching a pattern" 33 helpSummary = "Print lines matching a pattern"
34 helpUsage = """ 34 helpUsage = """
35%prog {pattern | -e pattern} [<project>...] 35%prog {pattern | -e pattern} [<project>...]
36""" 36"""
37 helpDescription = """ 37 helpDescription = """
38Search for the specified patterns in all project files. 38Search for the specified patterns in all project files.
39 39
40# Boolean Options 40# Boolean Options
@@ -62,215 +62,304 @@ contain a line that matches both expressions:
62 repo grep --all-match -e NODE -e Unexpected 62 repo grep --all-match -e NODE -e Unexpected
63 63
64""" 64"""
65 PARALLEL_JOBS = DEFAULT_LOCAL_JOBS 65 PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
66 66
67 @staticmethod 67 @staticmethod
68 def _carry_option(_option, opt_str, value, parser): 68 def _carry_option(_option, opt_str, value, parser):
69 pt = getattr(parser.values, 'cmd_argv', None) 69 pt = getattr(parser.values, "cmd_argv", None)
70 if pt is None: 70 if pt is None:
71 pt = [] 71 pt = []
72 setattr(parser.values, 'cmd_argv', pt) 72 setattr(parser.values, "cmd_argv", pt)
73 73
74 if opt_str == '-(': 74 if opt_str == "-(":
75 pt.append('(') 75 pt.append("(")
76 elif opt_str == '-)': 76 elif opt_str == "-)":
77 pt.append(')') 77 pt.append(")")
78 else: 78 else:
79 pt.append(opt_str) 79 pt.append(opt_str)
80 80
81 if value is not None: 81 if value is not None:
82 pt.append(value) 82 pt.append(value)
83 83
84 def _CommonOptions(self, p): 84 def _CommonOptions(self, p):
85 """Override common options slightly.""" 85 """Override common options slightly."""
86 super()._CommonOptions(p, opt_v=False) 86 super()._CommonOptions(p, opt_v=False)
87 87
88 def _Options(self, p): 88 def _Options(self, p):
89 g = p.add_option_group('Sources') 89 g = p.add_option_group("Sources")
90 g.add_option('--cached', 90 g.add_option(
91 action='callback', callback=self._carry_option, 91 "--cached",
92 help='Search the index, instead of the work tree') 92 action="callback",
93 g.add_option('-r', '--revision', 93 callback=self._carry_option,
94 dest='revision', action='append', metavar='TREEish', 94 help="Search the index, instead of the work tree",
95 help='Search TREEish, instead of the work tree') 95 )
96 96 g.add_option(
97 g = p.add_option_group('Pattern') 97 "-r",
98 g.add_option('-e', 98 "--revision",
99 action='callback', callback=self._carry_option, 99 dest="revision",
100 metavar='PATTERN', type='str', 100 action="append",
101 help='Pattern to search for') 101 metavar="TREEish",
102 g.add_option('-i', '--ignore-case', 102 help="Search TREEish, instead of the work tree",
103 action='callback', callback=self._carry_option, 103 )
104 help='Ignore case differences') 104
105 g.add_option('-a', '--text', 105 g = p.add_option_group("Pattern")
106 action='callback', callback=self._carry_option, 106 g.add_option(
107 help="Process binary files as if they were text") 107 "-e",
108 g.add_option('-I', 108 action="callback",
109 action='callback', callback=self._carry_option, 109 callback=self._carry_option,
110 help="Don't match the pattern in binary files") 110 metavar="PATTERN",
111 g.add_option('-w', '--word-regexp', 111 type="str",
112 action='callback', callback=self._carry_option, 112 help="Pattern to search for",
113 help='Match the pattern only at word boundaries') 113 )
114 g.add_option('-v', '--invert-match', 114 g.add_option(
115 action='callback', callback=self._carry_option, 115 "-i",
116 help='Select non-matching lines') 116 "--ignore-case",
117 g.add_option('-G', '--basic-regexp', 117 action="callback",
118 action='callback', callback=self._carry_option, 118 callback=self._carry_option,
119 help='Use POSIX basic regexp for patterns (default)') 119 help="Ignore case differences",
120 g.add_option('-E', '--extended-regexp', 120 )
121 action='callback', callback=self._carry_option, 121 g.add_option(
122 help='Use POSIX extended regexp for patterns') 122 "-a",
123 g.add_option('-F', '--fixed-strings', 123 "--text",
124 action='callback', callback=self._carry_option, 124 action="callback",
125 help='Use fixed strings (not regexp) for pattern') 125 callback=self._carry_option,
126 126 help="Process binary files as if they were text",
127 g = p.add_option_group('Pattern Grouping') 127 )
128 g.add_option('--all-match', 128 g.add_option(
129 action='callback', callback=self._carry_option, 129 "-I",
130 help='Limit match to lines that have all patterns') 130 action="callback",
131 g.add_option('--and', '--or', '--not', 131 callback=self._carry_option,
132 action='callback', callback=self._carry_option, 132 help="Don't match the pattern in binary files",
133 help='Boolean operators to combine patterns') 133 )
134 g.add_option('-(', '-)', 134 g.add_option(
135 action='callback', callback=self._carry_option, 135 "-w",
136 help='Boolean operator grouping') 136 "--word-regexp",
137 137 action="callback",
138 g = p.add_option_group('Output') 138 callback=self._carry_option,
139 g.add_option('-n', 139 help="Match the pattern only at word boundaries",
140 action='callback', callback=self._carry_option, 140 )
141 help='Prefix the line number to matching lines') 141 g.add_option(
142 g.add_option('-C', 142 "-v",
143 action='callback', callback=self._carry_option, 143 "--invert-match",
144 metavar='CONTEXT', type='str', 144 action="callback",
145 help='Show CONTEXT lines around match') 145 callback=self._carry_option,
146 g.add_option('-B', 146 help="Select non-matching lines",
147 action='callback', callback=self._carry_option, 147 )
148 metavar='CONTEXT', type='str', 148 g.add_option(
149 help='Show CONTEXT lines before match') 149 "-G",
150 g.add_option('-A', 150 "--basic-regexp",
151 action='callback', callback=self._carry_option, 151 action="callback",
152 metavar='CONTEXT', type='str', 152 callback=self._carry_option,
153 help='Show CONTEXT lines after match') 153 help="Use POSIX basic regexp for patterns (default)",
154 g.add_option('-l', '--name-only', '--files-with-matches', 154 )
155 action='callback', callback=self._carry_option, 155 g.add_option(
156 help='Show only file names containing matching lines') 156 "-E",
157 g.add_option('-L', '--files-without-match', 157 "--extended-regexp",
158 action='callback', callback=self._carry_option, 158 action="callback",
159 help='Show only file names not containing matching lines') 159 callback=self._carry_option,
160 160 help="Use POSIX extended regexp for patterns",
161 def _ExecuteOne(self, cmd_argv, project): 161 )
162 """Process one project.""" 162 g.add_option(
163 try: 163 "-F",
164 p = GitCommand(project, 164 "--fixed-strings",
165 cmd_argv, 165 action="callback",
166 bare=False, 166 callback=self._carry_option,
167 capture_stdout=True, 167 help="Use fixed strings (not regexp) for pattern",
168 capture_stderr=True) 168 )
169 except GitError as e: 169
170 return (project, -1, None, str(e)) 170 g = p.add_option_group("Pattern Grouping")
171 171 g.add_option(
172 return (project, p.Wait(), p.stdout, p.stderr) 172 "--all-match",
173 173 action="callback",
174 @staticmethod 174 callback=self._carry_option,
175 def _ProcessResults(full_name, have_rev, opt, _pool, out, results): 175 help="Limit match to lines that have all patterns",
176 git_failed = False 176 )
177 bad_rev = False 177 g.add_option(
178 have_match = False 178 "--and",
179 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) 179 "--or",
180 180 "--not",
181 for project, rc, stdout, stderr in results: 181 action="callback",
182 if rc < 0: 182 callback=self._carry_option,
183 git_failed = True 183 help="Boolean operators to combine patterns",
184 out.project('--- project %s ---' % _RelPath(project)) 184 )
185 out.nl() 185 g.add_option(
186 out.fail('%s', stderr) 186 "-(",
187 out.nl() 187 "-)",
188 continue 188 action="callback",
189 189 callback=self._carry_option,
190 if rc: 190 help="Boolean operator grouping",
191 # no results 191 )
192 if stderr: 192
193 if have_rev and 'fatal: ambiguous argument' in stderr: 193 g = p.add_option_group("Output")
194 bad_rev = True 194 g.add_option(
195 else: 195 "-n",
196 out.project('--- project %s ---' % _RelPath(project)) 196 action="callback",
197 out.nl() 197 callback=self._carry_option,
198 out.fail('%s', stderr.strip()) 198 help="Prefix the line number to matching lines",
199 out.nl() 199 )
200 continue 200 g.add_option(
201 have_match = True 201 "-C",
202 202 action="callback",
203 # We cut the last element, to avoid a blank line. 203 callback=self._carry_option,
204 r = stdout.split('\n') 204 metavar="CONTEXT",
205 r = r[0:-1] 205 type="str",
206 206 help="Show CONTEXT lines around match",
207 if have_rev and full_name: 207 )
208 for line in r: 208 g.add_option(
209 rev, line = line.split(':', 1) 209 "-B",
210 out.write("%s", rev) 210 action="callback",
211 out.write(':') 211 callback=self._carry_option,
212 out.project(_RelPath(project)) 212 metavar="CONTEXT",
213 out.write('/') 213 type="str",
214 out.write("%s", line) 214 help="Show CONTEXT lines before match",
215 out.nl() 215 )
216 elif full_name: 216 g.add_option(
217 for line in r: 217 "-A",
218 out.project(_RelPath(project)) 218 action="callback",
219 out.write('/') 219 callback=self._carry_option,
220 out.write("%s", line) 220 metavar="CONTEXT",
221 out.nl() 221 type="str",
222 else: 222 help="Show CONTEXT lines after match",
223 for line in r: 223 )
224 print(line) 224 g.add_option(
225 225 "-l",
226 return (git_failed, bad_rev, have_match) 226 "--name-only",
227 227 "--files-with-matches",
228 def Execute(self, opt, args): 228 action="callback",
229 out = GrepColoring(self.manifest.manifestProject.config) 229 callback=self._carry_option,
230 230 help="Show only file names containing matching lines",
231 cmd_argv = ['grep'] 231 )
232 if out.is_on: 232 g.add_option(
233 cmd_argv.append('--color') 233 "-L",
234 cmd_argv.extend(getattr(opt, 'cmd_argv', [])) 234 "--files-without-match",
235 235 action="callback",
236 if '-e' not in cmd_argv: 236 callback=self._carry_option,
237 if not args: 237 help="Show only file names not containing matching lines",
238 self.Usage() 238 )
239 cmd_argv.append('-e') 239
240 cmd_argv.append(args[0]) 240 def _ExecuteOne(self, cmd_argv, project):
241 args = args[1:] 241 """Process one project."""
242 242 try:
243 projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) 243 p = GitCommand(
244 244 project,
245 full_name = False 245 cmd_argv,
246 if len(projects) > 1: 246 bare=False,
247 cmd_argv.append('--full-name') 247 capture_stdout=True,
248 full_name = True 248 capture_stderr=True,
249 249 )
250 have_rev = False 250 except GitError as e:
251 if opt.revision: 251 return (project, -1, None, str(e))
252 if '--cached' in cmd_argv: 252
253 print('fatal: cannot combine --cached and --revision', file=sys.stderr) 253 return (project, p.Wait(), p.stdout, p.stderr)
254 sys.exit(1) 254
255 have_rev = True 255 @staticmethod
256 cmd_argv.extend(opt.revision) 256 def _ProcessResults(full_name, have_rev, opt, _pool, out, results):
257 cmd_argv.append('--') 257 git_failed = False
258 258 bad_rev = False
259 git_failed, bad_rev, have_match = self.ExecuteInParallel( 259 have_match = False
260 opt.jobs, 260 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
261 functools.partial(self._ExecuteOne, cmd_argv), 261
262 projects, 262 for project, rc, stdout, stderr in results:
263 callback=functools.partial(self._ProcessResults, full_name, have_rev, opt), 263 if rc < 0:
264 output=out, 264 git_failed = True
265 ordered=True) 265 out.project("--- project %s ---" % _RelPath(project))
266 266 out.nl()
267 if git_failed: 267 out.fail("%s", stderr)
268 sys.exit(1) 268 out.nl()
269 elif have_match: 269 continue
270 sys.exit(0) 270
271 elif have_rev and bad_rev: 271 if rc:
272 for r in opt.revision: 272 # no results
273 print("error: can't search revision %s" % r, file=sys.stderr) 273 if stderr:
274 sys.exit(1) 274 if have_rev and "fatal: ambiguous argument" in stderr:
275 else: 275 bad_rev = True
276 sys.exit(1) 276 else:
277 out.project("--- project %s ---" % _RelPath(project))
278 out.nl()
279 out.fail("%s", stderr.strip())
280 out.nl()
281 continue
282 have_match = True
283
284 # We cut the last element, to avoid a blank line.
285 r = stdout.split("\n")
286 r = r[0:-1]
287
288 if have_rev and full_name:
289 for line in r:
290 rev, line = line.split(":", 1)
291 out.write("%s", rev)
292 out.write(":")
293 out.project(_RelPath(project))
294 out.write("/")
295 out.write("%s", line)
296 out.nl()
297 elif full_name:
298 for line in r:
299 out.project(_RelPath(project))
300 out.write("/")
301 out.write("%s", line)
302 out.nl()
303 else:
304 for line in r:
305 print(line)
306
307 return (git_failed, bad_rev, have_match)
308
309 def Execute(self, opt, args):
310 out = GrepColoring(self.manifest.manifestProject.config)
311
312 cmd_argv = ["grep"]
313 if out.is_on:
314 cmd_argv.append("--color")
315 cmd_argv.extend(getattr(opt, "cmd_argv", []))
316
317 if "-e" not in cmd_argv:
318 if not args:
319 self.Usage()
320 cmd_argv.append("-e")
321 cmd_argv.append(args[0])
322 args = args[1:]
323
324 projects = self.GetProjects(
325 args, all_manifests=not opt.this_manifest_only
326 )
327
328 full_name = False
329 if len(projects) > 1:
330 cmd_argv.append("--full-name")
331 full_name = True
332
333 have_rev = False
334 if opt.revision:
335 if "--cached" in cmd_argv:
336 print(
337 "fatal: cannot combine --cached and --revision",
338 file=sys.stderr,
339 )
340 sys.exit(1)
341 have_rev = True
342 cmd_argv.extend(opt.revision)
343 cmd_argv.append("--")
344
345 git_failed, bad_rev, have_match = self.ExecuteInParallel(
346 opt.jobs,
347 functools.partial(self._ExecuteOne, cmd_argv),
348 projects,
349 callback=functools.partial(
350 self._ProcessResults, full_name, have_rev, opt
351 ),
352 output=out,
353 ordered=True,
354 )
355
356 if git_failed:
357 sys.exit(1)
358 elif have_match:
359 sys.exit(0)
360 elif have_rev and bad_rev:
361 for r in opt.revision:
362 print("error: can't search revision %s" % r, file=sys.stderr)
363 sys.exit(1)
364 else:
365 sys.exit(1)