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