blob: 43f5e968cf7a911019505b4ab92decf422213863 [file] [log] [blame]
Shawn O. Pearceb812a362009-04-10 20:37:47 -07001#
2# Copyright (C) 2009 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17from optparse import SUPPRESS_HELP
18from color import Coloring
19from command import PagedCommand
20from git_command import GitCommand
21
22class GrepColoring(Coloring):
23 def __init__(self, config):
24 Coloring.__init__(self, config, 'grep')
25 self.project = self.printer('project', attr='bold')
26
27class Grep(PagedCommand):
28 common = True
29 helpSummary = "Print lines matching a pattern"
30 helpUsage = """
31%prog {pattern | -e pattern} [<project>...]
32"""
33 helpDescription = """
34Search for the specified patterns in all project files.
35
36Options
37-------
38
39The following options can appear as often as necessary to express
40the pattern to locate:
41
42 -e PATTERN
43 --and, --or, --not, -(, -)
44
45Further, the -r/--revision option may be specified multiple times
46in order to scan multiple trees. If the same file matches in more
47than one tree, only the first result is reported, prefixed by the
48revision name it was found under.
49
50Examples
51-------
52
53Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
54
55 repo grep -e '#define' --and -\( -e MAX_PATH -e PATH_MAX \)
56
57Look for a line that has 'NODE' or 'Unexpected' in files that
58contain a line that matches both expressions:
59
60 repo grep --all-match -e NODE -e Unexpected
61
62"""
63
64 def _Options(self, p):
65 def carry(option,
66 opt_str,
67 value,
68 parser):
69 pt = getattr(parser.values, 'cmd_argv', None)
70 if pt is None:
71 pt = []
72 setattr(parser.values, 'cmd_argv', pt)
73
74 if opt_str == '-(':
75 pt.append('(')
76 elif opt_str == '-)':
77 pt.append(')')
78 else:
79 pt.append(opt_str)
80
81 if value is not None:
82 pt.append(value)
83
84 g = p.add_option_group('Sources')
85 g.add_option('--cached',
86 action='callback', callback=carry,
87 help='Search the index, instead of the work tree')
88 g.add_option('-r','--revision',
89 dest='revision', action='append', metavar='TREEish',
90 help='Search TREEish, instead of the work tree')
91
92 g = p.add_option_group('Pattern')
93 g.add_option('-e',
94 action='callback', callback=carry,
95 metavar='PATTERN', type='str',
96 help='Pattern to search for')
97 g.add_option('-i', '--ignore-case',
98 action='callback', callback=carry,
99 help='Ignore case differences')
100 g.add_option('-a','--text',
101 action='callback', callback=carry,
102 help="Process binary files as if they were text")
103 g.add_option('-I',
104 action='callback', callback=carry,
105 help="Don't match the pattern in binary files")
106 g.add_option('-w', '--word-regexp',
107 action='callback', callback=carry,
108 help='Match the pattern only at word boundaries')
109 g.add_option('-v', '--invert-match',
110 action='callback', callback=carry,
111 help='Select non-matching lines')
112 g.add_option('-G', '--basic-regexp',
113 action='callback', callback=carry,
114 help='Use POSIX basic regexp for patterns (default)')
115 g.add_option('-E', '--extended-regexp',
116 action='callback', callback=carry,
117 help='Use POSIX extended regexp for patterns')
118 g.add_option('-F', '--fixed-strings',
119 action='callback', callback=carry,
120 help='Use fixed strings (not regexp) for pattern')
121
122 g = p.add_option_group('Pattern Grouping')
123 g.add_option('--all-match',
124 action='callback', callback=carry,
125 help='Limit match to lines that have all patterns')
126 g.add_option('--and', '--or', '--not',
127 action='callback', callback=carry,
128 help='Boolean operators to combine patterns')
129 g.add_option('-(','-)',
130 action='callback', callback=carry,
131 help='Boolean operator grouping')
132
133 g = p.add_option_group('Output')
134 g.add_option('-n',
135 action='callback', callback=carry,
136 help='Prefix the line number to matching lines')
137 g.add_option('-C',
138 action='callback', callback=carry,
139 metavar='CONTEXT', type='str',
140 help='Show CONTEXT lines around match')
141 g.add_option('-B',
142 action='callback', callback=carry,
143 metavar='CONTEXT', type='str',
144 help='Show CONTEXT lines before match')
145 g.add_option('-A',
146 action='callback', callback=carry,
147 metavar='CONTEXT', type='str',
148 help='Show CONTEXT lines after match')
149 g.add_option('-l','--name-only','--files-with-matches',
150 action='callback', callback=carry,
151 help='Show only file names containing matching lines')
152 g.add_option('-L','--files-without-match',
153 action='callback', callback=carry,
154 help='Show only file names not containing matching lines')
155
156
157 def Execute(self, opt, args):
158 out = GrepColoring(self.manifest.manifestProject.config)
159
160 cmd_argv = ['grep']
161 if out.is_on:
162 cmd_argv.append('--color')
163 cmd_argv.extend(getattr(opt,'cmd_argv',[]))
164
165 if '-e' not in cmd_argv:
166 if not args:
167 self.Usage()
168 cmd_argv.append('-e')
169 cmd_argv.append(args[0])
170 args = args[1:]
171
172 projects = self.GetProjects(args)
173
174 full_name = False
175 if len(projects) > 1:
176 cmd_argv.append('--full-name')
177 full_name = True
178
179 have_rev = False
180 if opt.revision:
181 if '--cached' in cmd_argv:
182 print >>sys.stderr,\
183 'fatal: cannot combine --cached and --revision'
184 sys.exit(1)
185 have_rev = True
186 cmd_argv.extend(opt.revision)
187 cmd_argv.append('--')
188
189 bad_rev = False
190 have_match = False
191
192 for project in projects:
193 p = GitCommand(project,
194 cmd_argv,
195 bare = False,
196 capture_stdout = True,
197 capture_stderr = True)
198 if p.Wait() != 0:
199 # no results
200 #
201 if p.stderr:
202 if have_rev and 'fatal: ambiguous argument' in p.stderr:
203 bad_rev = True
204 else:
205 out.project('--- project %s ---' % project.relpath)
206 out.nl()
207 out.write(p.stderr)
208 out.nl()
209 continue
210 have_match = True
211
212 # We cut the last element, to avoid a blank line.
213 #
214 r = p.stdout.split('\n')
215 r = r[0:-1]
216
217 if have_rev and full_name:
218 for line in r:
219 rev, line = line.split(':', 1)
220 out.write(rev)
221 out.write(':')
222 out.project(project.relpath)
223 out.write('/')
224 out.write(line)
225 out.nl()
226 elif full_name:
227 for line in r:
228 out.project(project.relpath)
229 out.write('/')
230 out.write(line)
231 out.nl()
232 else:
233 for line in r:
234 print line
235
236 if have_match:
237 sys.exit(0)
238 elif have_rev and bad_rev:
239 for r in opt.revision:
240 print >>sys.stderr, "error: can't search revision %s" % r
241 sys.exit(1)
242 else:
243 sys.exit(1)