Merge lp:~parthm/bzr/503670-grep-builtin into lp:bzr
- 503670-grep-builtin
- Merge into bzr.dev
Status: | Rejected | ||||
---|---|---|---|---|---|
Rejected by: | Parth Malwankar | ||||
Proposed branch: | lp:~parthm/bzr/503670-grep-builtin | ||||
Merge into: | lp:bzr | ||||
Diff against target: |
747 lines (+676/-1) 5 files modified
NEWS (+3/-0) bzrlib/builtins.py (+134/-1) bzrlib/grep.py (+103/-0) bzrlib/tests/blackbox/__init__.py (+1/-0) bzrlib/tests/blackbox/test_grep.py (+435/-0) |
||||
To merge this branch: | bzr merge lp:~parthm/bzr/503670-grep-builtin | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vincent Ladeuil | Needs Fixing | ||
Review via email: mp+20420@code.launchpad.net |
Commit message
Description of the change
Parth Malwankar (parthm) wrote : | # |
Parth Malwankar (parthm) wrote : | # |
Other related proposals that have relevant discussion but can be rejected:
https:/
https:/
- 5069. By Parth Malwankar
-
levels=0 now shows revision number
- 5070. By Parth Malwankar
-
added test for --levels=0
Vincent Ladeuil (vila) wrote : | # |
I'd rather see that as a true plugin in bzrlib/plugins itself
(if only to avoid yet another import(s) in builtins.py).
Trimming the cmd_grep.run() method can be good too and will allow
whitebox testing, let me know if you need help there.
Parth Malwankar (parthm) wrote : | # |
This can be rejected as lp:bzr-grep has been added to "Bazaar VCS and Tools"
based on irc discussion between vila, lifeless and parthm.
A separate mp will be raised to review lp:bzr-grep.
Unmerged revisions
- 5070. By Parth Malwankar
-
added test for --levels=0
- 5069. By Parth Malwankar
-
levels=0 now shows revision number
- 5068. By Parth Malwankar
-
updated NEWS
- 5067. By Parth Malwankar
-
ported lp:bzr-grep to be a builtin.
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2010-03-02 10:21:39 +0000 |
3 | +++ NEWS 2010-03-02 12:44:17 +0000 |
4 | @@ -34,6 +34,9 @@ |
5 | New Features |
6 | ************ |
7 | |
8 | +* bzr now has a builtin command ``grep`` which can be used to search for files |
9 | + and revisions containing a pattern. (Parth Malwankar, #503670) |
10 | + |
11 | * If the Apport crash-reporting tool is available, bzr crashes are now |
12 | stored into the ``/var/crash`` apport spool directory, and the user is |
13 | invited to report them to the developers from there, either |
14 | |
15 | === modified file 'bzrlib/builtins.py' |
16 | --- bzrlib/builtins.py 2010-03-01 16:18:43 +0000 |
17 | +++ bzrlib/builtins.py 2010-03-02 12:44:17 +0000 |
18 | @@ -22,6 +22,7 @@ |
19 | lazy_import(globals(), """ |
20 | import codecs |
21 | import cStringIO |
22 | +import re |
23 | import sys |
24 | import time |
25 | |
26 | @@ -36,6 +37,7 @@ |
27 | config, |
28 | errors, |
29 | globbing, |
30 | + grep, |
31 | hooks, |
32 | log, |
33 | merge as _mod_merge, |
34 | @@ -47,6 +49,7 @@ |
35 | static_tuple, |
36 | symbol_versioning, |
37 | timestamp, |
38 | + trace, |
39 | transport, |
40 | ui, |
41 | urlutils, |
42 | @@ -55,7 +58,7 @@ |
43 | from bzrlib.branch import Branch |
44 | from bzrlib.conflicts import ConflictList |
45 | from bzrlib.transport import memory |
46 | -from bzrlib.revisionspec import RevisionSpec, RevisionInfo |
47 | +from bzrlib.revisionspec import RevisionSpec, RevisionInfo, RevisionSpec_revid |
48 | from bzrlib.smtp_connection import SMTPConnection |
49 | from bzrlib.workingtree import WorkingTree |
50 | """) |
51 | @@ -5917,6 +5920,136 @@ |
52 | for path, location in sorted(ref_list): |
53 | self.outf.write('%s %s\n' % (path, location)) |
54 | |
55 | +class cmd_grep(Command): |
56 | + """Print lines matching PATTERN for specified files and revisions. |
57 | + |
58 | + This command searches the specified files and revisions for a given pattern. |
59 | + The pattern is specified as a Python regular expressions[1]. |
60 | + If the file name is not specified the file revisions in the current directory |
61 | + are searched. If the revision number is not specified, the latest revision is |
62 | + searched. |
63 | + |
64 | + Note that this command is different from POSIX grep in that it searches the |
65 | + revisions of the branch and not the working copy. Unversioned files and |
66 | + uncommitted changes are not seen. |
67 | + |
68 | + When searching a pattern, the output is shown in the 'filepath:string' format. |
69 | + If a revision is explicitly searched, the output is shown as 'filepath~N:string', |
70 | + where N is the revision number. |
71 | + |
72 | + [1] http://docs.python.org/library/re.html#regular-expression-syntax |
73 | + """ |
74 | + |
75 | + takes_args = ['pattern', 'path*'] |
76 | + takes_options = [ |
77 | + 'verbose', |
78 | + 'revision', |
79 | + Option('line-number', short_name='n', |
80 | + help='show 1-based line number.'), |
81 | + Option('ignore-case', short_name='i', |
82 | + help='ignore case distinctions while matching.'), |
83 | + Option('recursive', short_name='R', |
84 | + help='Recurse into subdirectories.'), |
85 | + Option('from-root', |
86 | + help='Search for pattern starting from the root of the branch. ' |
87 | + '(implies --recursive)'), |
88 | + Option('null', short_name='Z', |
89 | + help='Write an ascii NUL (\\0) separator ' |
90 | + 'between output lines rather than a newline.'), |
91 | + Option('levels', |
92 | + help='Number of levels to display - 0 for all, 1 for collapsed (default).', |
93 | + argname='N', |
94 | + type=_parse_levels), |
95 | + ] |
96 | + |
97 | + |
98 | + @display_command |
99 | + def run(self, verbose=False, ignore_case=False, recursive=False, from_root=False, |
100 | + null=False, levels=None, line_number=False, path_list=None, revision=None, pattern=None): |
101 | + |
102 | + if levels==None: |
103 | + levels=1 |
104 | + |
105 | + if path_list == None: |
106 | + path_list = ['.'] |
107 | + else: |
108 | + if from_root: |
109 | + raise errors.BzrCommandError('cannot specify both --from-root and PATH.') |
110 | + |
111 | + print_revno = False |
112 | + if levels==0: |
113 | + # if multiple levels are checked user should be able to |
114 | + # know which version is being shown |
115 | + print_revno=True |
116 | + |
117 | + if revision == None: |
118 | + # grep on latest revision by default |
119 | + revision = [RevisionSpec.from_string("last:1")] |
120 | + else: |
121 | + print_revno = True # used to print revno in output. |
122 | + |
123 | + start_rev = revision[0] |
124 | + end_rev = revision[0] |
125 | + if len(revision) == 2: |
126 | + end_rev = revision[1] |
127 | + |
128 | + eol_marker = '\n' |
129 | + if null: |
130 | + eol_marker = '\0' |
131 | + |
132 | + re_flags = 0 |
133 | + if ignore_case: |
134 | + re_flags = re.IGNORECASE |
135 | + patternc = grep.compile_pattern(pattern, re_flags) |
136 | + |
137 | + wt, relpath = WorkingTree.open_containing('.') |
138 | + |
139 | + start_revid = start_rev.as_revision_id(wt.branch) |
140 | + end_revid = end_rev.as_revision_id(wt.branch) |
141 | + |
142 | + given_revs = log._graph_view_revisions(wt.branch, start_revid, end_revid) |
143 | + |
144 | + # edge case: we have a repo created with 'bzr init' and it has no |
145 | + # revisions (revno: 0) |
146 | + try: |
147 | + given_revs = list(given_revs) |
148 | + except errors.NoSuchRevision, e: |
149 | + raise errors.BzrCommandError('No revisions found for grep.') |
150 | + |
151 | + for revid, revno, merge_depth in given_revs: |
152 | + if levels == 1 and merge_depth != 0: |
153 | + # with level=1 show only top level |
154 | + continue |
155 | + |
156 | + wt.lock_read() |
157 | + rev = RevisionSpec_revid.from_string("revid:"+revid) |
158 | + try: |
159 | + for path in path_list: |
160 | + tree = rev.as_tree(wt.branch) |
161 | + path_for_id = osutils.pathjoin(relpath, path) |
162 | + id = tree.path2id(path_for_id) |
163 | + if not id: |
164 | + self._skip_file(path) |
165 | + continue |
166 | + |
167 | + if osutils.isdir(path): |
168 | + path_prefix = path |
169 | + grep.dir_grep(tree, path, relpath, recursive, line_number, |
170 | + patternc, from_root, eol_marker, revno, print_revno, |
171 | + self.outf, path_prefix) |
172 | + else: |
173 | + tree.lock_read() |
174 | + try: |
175 | + grep.file_grep(tree, id, '.', path, patternc, eol_marker, |
176 | + line_number, revno, print_revno, self.outf) |
177 | + finally: |
178 | + tree.unlock() |
179 | + finally: |
180 | + wt.unlock() |
181 | + |
182 | + def _skip_file(self, path): |
183 | + trace.warning("warning: skipped unknown file '%s'." % path) |
184 | + |
185 | |
186 | # these get imported and then picked up by the scan for cmd_* |
187 | # TODO: Some more consistent way to split command definitions across files; |
188 | |
189 | === added file 'bzrlib/grep.py' |
190 | --- bzrlib/grep.py 1970-01-01 00:00:00 +0000 |
191 | +++ bzrlib/grep.py 2010-03-02 12:44:17 +0000 |
192 | @@ -0,0 +1,103 @@ |
193 | +# Copyright (C) 2010 Canonical Ltd |
194 | +# |
195 | +# This program is free software; you can redistribute it and/or modify |
196 | +# it under the terms of the GNU General Public License as published by |
197 | +# the Free Software Foundation; either version 2 of the License, or |
198 | +# (at your option) any later version. |
199 | +# |
200 | +# This program is distributed in the hope that it will be useful, |
201 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
202 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
203 | +# GNU General Public License for more details. |
204 | +# |
205 | +# You should have received a copy of the GNU General Public License |
206 | +# along with this program; if not, write to the Free Software |
207 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
208 | +"""bzr grep""" |
209 | + |
210 | + |
211 | +from bzrlib.lazy_import import lazy_import |
212 | +lazy_import(globals(), """ |
213 | +import os |
214 | +import re |
215 | + |
216 | +from bzrlib import ( |
217 | + osutils, |
218 | + errors, |
219 | + lazy_regex, |
220 | + ) |
221 | +""") |
222 | + |
223 | +def compile_pattern(pattern, flags=0): |
224 | + patternc = None |
225 | + try: |
226 | + # use python's re.compile as we need to catch re.error in case of bad pattern |
227 | + lazy_regex.reset_compile() |
228 | + patternc = re.compile(pattern, flags) |
229 | + except re.error, e: |
230 | + raise errors.BzrError("Invalid pattern: '%s'" % pattern) |
231 | + return patternc |
232 | + |
233 | +def dir_grep(tree, path, relpath, recursive, line_number, compiled_pattern, |
234 | + from_root, eol_marker, revno, print_revno, outf, path_prefix): |
235 | + # setup relpath to open files relative to cwd |
236 | + rpath = relpath |
237 | + if relpath: |
238 | + rpath = osutils.pathjoin('..',relpath) |
239 | + |
240 | + tree.lock_read() |
241 | + try: |
242 | + from_dir = osutils.pathjoin(relpath, path) |
243 | + if from_root: |
244 | + # start searching recursively from root |
245 | + from_dir=None |
246 | + recursive=True |
247 | + |
248 | + for fp, fc, fkind, fid, entry in tree.list_files(include_root=False, |
249 | + from_dir=from_dir, recursive=recursive): |
250 | + if fc == 'V' and fkind == 'file': |
251 | + file_grep(tree, fid, rpath, fp, compiled_pattern, |
252 | + eol_marker, line_number, revno, print_revno, outf, path_prefix) |
253 | + finally: |
254 | + tree.unlock() |
255 | + |
256 | + |
257 | +def file_grep(tree, id, relpath, path, patternc, eol_marker, |
258 | + line_number, revno, print_revno, outf, path_prefix = None): |
259 | + |
260 | + if relpath: |
261 | + path = osutils.normpath(osutils.pathjoin(relpath, path)) |
262 | + path = path.replace('\\', '/') |
263 | + path = path.replace(relpath + '/', '', 1) |
264 | + |
265 | + revfmt = '' |
266 | + if print_revno: |
267 | + revfmt = "~%s" |
268 | + |
269 | + if path_prefix and path_prefix != '.': |
270 | + # user has passed a dir arg, show that as result prefix |
271 | + path = osutils.pathjoin(path_prefix, path) |
272 | + |
273 | + fmt_with_n = path + revfmt + ":%d:%s" + eol_marker |
274 | + fmt_without_n = path + revfmt + ":%s" + eol_marker |
275 | + |
276 | + index = 1 |
277 | + for line in tree.get_file_lines(id): |
278 | + res = patternc.search(line) |
279 | + if res: |
280 | + if line_number: |
281 | + if print_revno: |
282 | + out = (revno, index, line.strip()) |
283 | + else: |
284 | + out = (index, line.strip()) |
285 | + outf.write(fmt_with_n % out) |
286 | + else: |
287 | + if print_revno: |
288 | + out = (revno, line.strip()) |
289 | + else: |
290 | + out = (line.strip(),) |
291 | + outf.write(fmt_without_n % out) |
292 | + |
293 | + index += 1 |
294 | + |
295 | + |
296 | |
297 | === modified file 'bzrlib/tests/blackbox/__init__.py' |
298 | --- bzrlib/tests/blackbox/__init__.py 2009-04-29 20:31:34 +0000 |
299 | +++ bzrlib/tests/blackbox/__init__.py 2010-03-02 12:44:17 +0000 |
300 | @@ -60,6 +60,7 @@ |
301 | 'bzrlib.tests.blackbox.test_filesystem_cicp', |
302 | 'bzrlib.tests.blackbox.test_filtered_view_ops', |
303 | 'bzrlib.tests.blackbox.test_find_merge_base', |
304 | + 'bzrlib.tests.blackbox.test_grep', |
305 | 'bzrlib.tests.blackbox.test_help', |
306 | 'bzrlib.tests.blackbox.test_hooks', |
307 | 'bzrlib.tests.blackbox.test_ignore', |
308 | |
309 | === added file 'bzrlib/tests/blackbox/test_grep.py' |
310 | --- bzrlib/tests/blackbox/test_grep.py 1970-01-01 00:00:00 +0000 |
311 | +++ bzrlib/tests/blackbox/test_grep.py 2010-03-02 12:44:17 +0000 |
312 | @@ -0,0 +1,435 @@ |
313 | +# Copyright (C) 2010 Canonical Ltd |
314 | +# |
315 | +# This program is free software; you can redistribute it and/or modify |
316 | +# it under the terms of the GNU General Public License as published by |
317 | +# the Free Software Foundation; either version 2 of the License, or |
318 | +# (at your option) any later version. |
319 | +# |
320 | +# This program is distributed in the hope that it will be useful, |
321 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
322 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
323 | +# GNU General Public License for more details. |
324 | +# |
325 | +# You should have received a copy of the GNU General Public License |
326 | +# along with this program; if not, write to the Free Software |
327 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
328 | + |
329 | +import os |
330 | +import re |
331 | + |
332 | +from bzrlib import tests, osutils |
333 | + |
334 | +class TestGrep(tests.TestCaseWithTransport): |
335 | + def _str_contains(self, base, pattern, flags=re.MULTILINE|re.DOTALL): |
336 | + res = re.findall(pattern, base, flags) |
337 | + return res != [] |
338 | + |
339 | + def _mk_file(self, path, line_prefix, total_lines, versioned): |
340 | + text='' |
341 | + for i in range(total_lines): |
342 | + text += line_prefix + str(i+1) + "\n" |
343 | + |
344 | + open(path, 'w').write(text) |
345 | + if versioned: |
346 | + self.run_bzr(['add', path]) |
347 | + self.run_bzr(['ci', '-m', '"' + path + '"']) |
348 | + |
349 | + def _update_file(self, path, text): |
350 | + """append text to file 'path' and check it in""" |
351 | + open(path, 'a').write(text) |
352 | + self.run_bzr(['ci', '-m', '"' + path + '"']) |
353 | + |
354 | + def _mk_unknown_file(self, path, line_prefix='line', total_lines=10): |
355 | + self._mk_file(path, line_prefix, total_lines, versioned=False) |
356 | + |
357 | + def _mk_versioned_file(self, path, line_prefix='line', total_lines=10): |
358 | + self._mk_file(path, line_prefix, total_lines, versioned=True) |
359 | + |
360 | + def _mk_dir(self, path, versioned): |
361 | + os.mkdir(path) |
362 | + if versioned: |
363 | + self.run_bzr(['add', path]) |
364 | + self.run_bzr(['ci', '-m', '"' + path + '"']) |
365 | + |
366 | + def _mk_unknown_dir(self, path): |
367 | + self._mk_dir(path, versioned=False) |
368 | + |
369 | + def _mk_versioned_dir(self, path): |
370 | + self._mk_dir(path, versioned=True) |
371 | + |
372 | + def test_basic_unknown_file(self): |
373 | + """search for pattern in specfic file. should issue warning.""" |
374 | + wd = 'foobar0' |
375 | + self.make_branch_and_tree(wd) |
376 | + os.chdir(wd) |
377 | + self._mk_versioned_file('filex.txt') # force rev to revno:1 and not revno:0 |
378 | + self._mk_unknown_file('file0.txt') |
379 | + out, err = self.run_bzr(['grep', 'line1', 'file0.txt']) |
380 | + self.assertFalse(self._str_contains(out, "file0.txt:line1")) |
381 | + self.assertTrue(self._str_contains(err, "warning: skipped.*file0.txt.*\.")) |
382 | + |
383 | + def test_revno0(self): |
384 | + """search for pattern in when only revno0 is present""" |
385 | + wd = 'foobar0' |
386 | + self.make_branch_and_tree(wd) # only revno 0 in branch |
387 | + os.chdir(wd) |
388 | + out, err = self.run_bzr(['grep', 'line1'], retcode=3) |
389 | + self.assertTrue(self._str_contains(err, "ERROR: No revisions found")) |
390 | + |
391 | + def test_basic_versioned_file(self): |
392 | + """search for pattern in specfic file""" |
393 | + wd = 'foobar0' |
394 | + self.make_branch_and_tree(wd) |
395 | + os.chdir(wd) |
396 | + self._mk_versioned_file('file0.txt') |
397 | + out, err = self.run_bzr(['grep', 'line1', 'file0.txt']) |
398 | + self.assertTrue(self._str_contains(out, "file0.txt:line1")) |
399 | + self.assertFalse(self._str_contains(err, "warning: skipped.*file0.txt.*\.")) |
400 | + |
401 | + def test_multiple_files(self): |
402 | + """search for pattern in multiple files""" |
403 | + wd = 'foobar0' |
404 | + self.make_branch_and_tree(wd) |
405 | + os.chdir(wd) |
406 | + self._mk_versioned_file('file0.txt', total_lines=2) |
407 | + self._mk_versioned_file('file1.txt', total_lines=2) |
408 | + self._mk_versioned_file('file2.txt', total_lines=2) |
409 | + out, err = self.run_bzr(['grep', 'line[1-2]']) |
410 | + |
411 | + self.assertTrue(self._str_contains(out, "file0.txt:line1")) |
412 | + self.assertTrue(self._str_contains(out, "file0.txt:line2")) |
413 | + self.assertTrue(self._str_contains(out, "file1.txt:line1")) |
414 | + self.assertTrue(self._str_contains(out, "file1.txt:line2")) |
415 | + self.assertTrue(self._str_contains(out, "file2.txt:line1")) |
416 | + self.assertTrue(self._str_contains(out, "file2.txt:line2")) |
417 | + |
418 | + def test_null_option(self): |
419 | + """--null option should use NUL instead of newline""" |
420 | + wd = 'foobar0' |
421 | + self.make_branch_and_tree(wd) |
422 | + os.chdir(wd) |
423 | + self._mk_versioned_file('file0.txt', total_lines=3) |
424 | + |
425 | + out, err = self.run_bzr(['grep', '--null', 'line[1-3]']) |
426 | + self.assertTrue(out == "file0.txt:line1\0file0.txt:line2\0file0.txt:line3\0") |
427 | + |
428 | + out, err = self.run_bzr(['grep', '-Z', 'line[1-3]']) |
429 | + self.assertTrue(out == "file0.txt:line1\0file0.txt:line2\0file0.txt:line3\0") |
430 | + |
431 | + def test_versioned_file_in_dir_no_recurse(self): |
432 | + """should not recurse without -R""" |
433 | + wd = 'foobar0' |
434 | + self.make_branch_and_tree(wd) |
435 | + os.chdir(wd) |
436 | + self._mk_versioned_dir('dir0') |
437 | + self._mk_versioned_file('dir0/file0.txt') |
438 | + out, err = self.run_bzr(['grep', 'line1']) |
439 | + self.assertFalse(self._str_contains(out, "file0.txt:line1")) |
440 | + |
441 | + def test_versioned_file_in_dir_recurse(self): |
442 | + """should find pattern in hierarchy with -R""" |
443 | + wd = 'foobar0' |
444 | + self.make_branch_and_tree(wd) |
445 | + os.chdir(wd) |
446 | + self._mk_versioned_dir('dir0') |
447 | + self._mk_versioned_file('dir0/file0.txt') |
448 | + out, err = self.run_bzr(['grep', '-R', 'line1']) |
449 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt:line1")) |
450 | + out, err = self.run_bzr(['grep', '--recursive', 'line1']) |
451 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt:line1")) |
452 | + |
453 | + def test_versioned_file_within_dir(self): |
454 | + """search for pattern while in nested dir""" |
455 | + wd = 'foobar0' |
456 | + self.make_branch_and_tree(wd) |
457 | + os.chdir(wd) |
458 | + self._mk_versioned_dir('dir0') |
459 | + self._mk_versioned_file('dir0/file0.txt') |
460 | + os.chdir('dir0') |
461 | + out, err = self.run_bzr(['grep', 'line1']) |
462 | + self.assertTrue(self._str_contains(out, "^file0.txt:line1")) |
463 | + |
464 | + def test_versioned_files_from_outside_dir(self): |
465 | + """grep for pattern with dirs passed as argument""" |
466 | + wd = 'foobar0' |
467 | + self.make_branch_and_tree(wd) |
468 | + os.chdir(wd) |
469 | + |
470 | + self._mk_versioned_dir('dir0') |
471 | + self._mk_versioned_file('dir0/file0.txt') |
472 | + |
473 | + self._mk_versioned_dir('dir1') |
474 | + self._mk_versioned_file('dir1/file1.txt') |
475 | + |
476 | + out, err = self.run_bzr(['grep', 'line1', 'dir0', 'dir1']) |
477 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt:line1")) |
478 | + self.assertTrue(self._str_contains(out, "^dir1/file1.txt:line1")) |
479 | + |
480 | + def test_versioned_files_from_outside_dir(self): |
481 | + """grep for pattern with dirs passed as argument""" |
482 | + wd = 'foobar0' |
483 | + self.make_branch_and_tree(wd) |
484 | + os.chdir(wd) |
485 | + |
486 | + self._mk_versioned_dir('dir0') |
487 | + self._mk_versioned_file('dir0/file0.txt') |
488 | + |
489 | + self._mk_versioned_dir('dir1') |
490 | + self._mk_versioned_file('dir1/file1.txt') |
491 | + |
492 | + out, err = self.run_bzr(['grep', 'line1', 'dir0', 'dir1']) |
493 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt:line1")) |
494 | + self.assertTrue(self._str_contains(out, "^dir1/file1.txt:line1")) |
495 | + |
496 | + def test_versioned_files_from_outside_two_dirs(self): |
497 | + """grep for pattern with two levels of nested dir""" |
498 | + wd = 'foobar0' |
499 | + self.make_branch_and_tree(wd) |
500 | + os.chdir(wd) |
501 | + |
502 | + self._mk_versioned_dir('dir0') |
503 | + self._mk_versioned_file('dir0/file0.txt') |
504 | + |
505 | + self._mk_versioned_dir('dir1') |
506 | + self._mk_versioned_file('dir1/file1.txt') |
507 | + |
508 | + self._mk_versioned_dir('dir0/dir00') |
509 | + self._mk_versioned_file('dir0/dir00/file0.txt') |
510 | + |
511 | + out, err = self.run_bzr(['grep', 'line1', 'dir0/dir00']) |
512 | + self.assertTrue(self._str_contains(out, "^dir0/dir00/file0.txt:line1")) |
513 | + |
514 | + out, err = self.run_bzr(['grep', '-R', 'line1']) |
515 | + self.assertTrue(self._str_contains(out, "^dir0/dir00/file0.txt:line1")) |
516 | + |
517 | + def test_versioned_file_within_dir_two_levels(self): |
518 | + """search for pattern while in nested dir (two levels)""" |
519 | + wd = 'foobar0' |
520 | + self.make_branch_and_tree(wd) |
521 | + os.chdir(wd) |
522 | + self._mk_versioned_dir('dir0') |
523 | + self._mk_versioned_dir('dir0/dir1') |
524 | + self._mk_versioned_file('dir0/dir1/file0.txt') |
525 | + os.chdir('dir0') |
526 | + out, err = self.run_bzr(['grep', '-R', 'line1']) |
527 | + self.assertTrue(self._str_contains(out, "^dir1/file0.txt:line1")) |
528 | + out, err = self.run_bzr(['grep', '--from-root', 'line1']) |
529 | + self.assertTrue(self._str_contains(out, "^dir0/dir1/file0.txt:line1")) |
530 | + out, err = self.run_bzr(['grep', 'line1']) |
531 | + self.assertFalse(self._str_contains(out, "file0.txt")) |
532 | + |
533 | + def test_ignore_case_no_match(self): |
534 | + """match fails without --ignore-case""" |
535 | + wd = 'foobar0' |
536 | + self.make_branch_and_tree(wd) |
537 | + os.chdir(wd) |
538 | + self._mk_versioned_file('file0.txt') |
539 | + out, err = self.run_bzr(['grep', 'LinE1', 'file0.txt']) |
540 | + self.assertFalse(self._str_contains(out, "file0.txt:line1")) |
541 | + |
542 | + def test_ignore_case_match(self): |
543 | + """match fails without --ignore-case""" |
544 | + wd = 'foobar0' |
545 | + self.make_branch_and_tree(wd) |
546 | + os.chdir(wd) |
547 | + self._mk_versioned_file('file0.txt') |
548 | + out, err = self.run_bzr(['grep', '-i', 'LinE1', 'file0.txt']) |
549 | + self.assertTrue(self._str_contains(out, "file0.txt:line1")) |
550 | + out, err = self.run_bzr(['grep', '--ignore-case', 'LinE1', 'file0.txt']) |
551 | + self.assertTrue(self._str_contains(out, "^file0.txt:line1")) |
552 | + |
553 | + def test_from_root_fail(self): |
554 | + """match should fail without --from-root""" |
555 | + wd = 'foobar0' |
556 | + self.make_branch_and_tree(wd) |
557 | + os.chdir(wd) |
558 | + self._mk_versioned_file('file0.txt') |
559 | + self._mk_versioned_dir('dir0') |
560 | + os.chdir('dir0') |
561 | + out, err = self.run_bzr(['grep', 'line1']) |
562 | + self.assertFalse(self._str_contains(out, ".*file0.txt:line1")) |
563 | + |
564 | + def test_from_root_pass(self): |
565 | + """match pass with --from-root""" |
566 | + wd = 'foobar0' |
567 | + self.make_branch_and_tree(wd) |
568 | + os.chdir(wd) |
569 | + self._mk_versioned_file('file0.txt') |
570 | + self._mk_versioned_dir('dir0') |
571 | + os.chdir('dir0') |
572 | + out, err = self.run_bzr(['grep', '--from-root', 'line1']) |
573 | + self.assertTrue(self._str_contains(out, ".*file0.txt:line1")) |
574 | + |
575 | + def test_with_line_number(self): |
576 | + """search for pattern with --line-number""" |
577 | + wd = 'foobar0' |
578 | + self.make_branch_and_tree(wd) |
579 | + os.chdir(wd) |
580 | + self._mk_versioned_file('file0.txt') |
581 | + |
582 | + out, err = self.run_bzr(['grep', '--line-number', 'line3', 'file0.txt']) |
583 | + self.assertTrue(self._str_contains(out, "file0.txt:3:line3")) |
584 | + |
585 | + out, err = self.run_bzr(['grep', '-n', 'line1', 'file0.txt']) |
586 | + self.assertTrue(self._str_contains(out, "file0.txt:1:line1")) |
587 | + |
588 | + def test_revno_basic_history_grep_file(self): |
589 | + """search for pattern in specific revision number in a file""" |
590 | + wd = 'foobar0' |
591 | + fname = 'file0.txt' |
592 | + self.make_branch_and_tree(wd) |
593 | + os.chdir(wd) |
594 | + self._mk_versioned_file(fname, total_lines=0) |
595 | + self._update_file(fname, text="v2 text\n") |
596 | + self._update_file(fname, text="v3 text\n") |
597 | + self._update_file(fname, text="v4 text\n") |
598 | + |
599 | + # rev 2 should not have text 'v3' |
600 | + out, err = self.run_bzr(['grep', '-r', '2', 'v3', fname]) |
601 | + self.assertFalse(self._str_contains(out, "file0.txt")) |
602 | + |
603 | + # rev 3 should not have text 'v3' |
604 | + out, err = self.run_bzr(['grep', '-r', '3', 'v3', fname]) |
605 | + self.assertTrue(self._str_contains(out, "file0.txt~3:v3.*")) |
606 | + |
607 | + # rev 3 should not have text 'v3' with line number |
608 | + out, err = self.run_bzr(['grep', '-r', '3', '-n', 'v3', fname]) |
609 | + self.assertTrue(self._str_contains(out, "file0.txt~3:2:v3.*")) |
610 | + |
611 | + def test_revno_basic_history_grep_full(self): |
612 | + """search for pattern in specific revision number in a file""" |
613 | + wd = 'foobar0' |
614 | + fname = 'file0.txt' |
615 | + self.make_branch_and_tree(wd) |
616 | + os.chdir(wd) |
617 | + self._mk_versioned_file(fname, total_lines=0) # rev1 |
618 | + self._mk_versioned_file('file1.txt') # rev2 |
619 | + self._update_file(fname, text="v3 text\n") # rev3 |
620 | + self._update_file(fname, text="v4 text\n") # rev4 |
621 | + self._update_file(fname, text="v5 text\n") # rev5 |
622 | + |
623 | + # rev 2 should not have text 'v3' |
624 | + out, err = self.run_bzr(['grep', '-r', '2', 'v3']) |
625 | + self.assertFalse(self._str_contains(out, "file0.txt")) |
626 | + |
627 | + # rev 3 should not have text 'v3' |
628 | + out, err = self.run_bzr(['grep', '-r', '3', 'v3']) |
629 | + self.assertTrue(self._str_contains(out, "file0.txt~3:v3")) |
630 | + |
631 | + # rev 3 should not have text 'v3' with line number |
632 | + out, err = self.run_bzr(['grep', '-r', '3', '-n', 'v3']) |
633 | + self.assertTrue(self._str_contains(out, "file0.txt~3:1:v3")) |
634 | + |
635 | + def test_revno_versioned_file_in_dir(self): |
636 | + """we create a file 'foobar0/dir0/file0.txt' and grep specific version of content""" |
637 | + wd = 'foobar0' |
638 | + self.make_branch_and_tree(wd) |
639 | + os.chdir(wd) |
640 | + self._mk_versioned_dir('dir0') # rev1 |
641 | + self._mk_versioned_file('dir0/file0.txt') # rev2 |
642 | + self._update_file('dir0/file0.txt', "v3 text\n") # rev3 |
643 | + self._update_file('dir0/file0.txt', "v4 text\n") # rev4 |
644 | + self._update_file('dir0/file0.txt', "v5 text\n") # rev5 |
645 | + |
646 | + # v4 should not be present in revno 3 |
647 | + out, err = self.run_bzr(['grep', '-r', 'last:3', '-R', 'v4']) |
648 | + self.assertFalse(self._str_contains(out, "^dir0/file0.txt")) |
649 | + |
650 | + # v4 should be present in revno 4 |
651 | + out, err = self.run_bzr(['grep', '-r', 'last:2', '-R', 'v4']) |
652 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt~4:v4")) |
653 | + |
654 | + def test_revno_range_basic_history_grep(self): |
655 | + """search for pattern in revision range for file""" |
656 | + wd = 'foobar0' |
657 | + fname = 'file0.txt' |
658 | + self.make_branch_and_tree(wd) |
659 | + os.chdir(wd) |
660 | + self._mk_versioned_file(fname, total_lines=0) # rev1 |
661 | + self._mk_versioned_file('file1.txt') # rev2 |
662 | + self._update_file(fname, text="v3 text\n") # rev3 |
663 | + self._update_file(fname, text="v4 text\n") # rev4 |
664 | + self._update_file(fname, text="v5 text\n") # rev5 |
665 | + self._update_file(fname, text="v6 text\n") # rev6 |
666 | + |
667 | + out, err = self.run_bzr(['grep', '-r', '1..', 'v3']) |
668 | + self.assertTrue(self._str_contains(out, "file0.txt~3:v3")) |
669 | + self.assertTrue(self._str_contains(out, "file0.txt~4:v3")) |
670 | + self.assertTrue(self._str_contains(out, "file0.txt~5:v3")) |
671 | + |
672 | + out, err = self.run_bzr(['grep', '-r', '1..5', 'v3']) |
673 | + self.assertTrue(self._str_contains(out, "file0.txt~3:v3")) |
674 | + self.assertTrue(self._str_contains(out, "file0.txt~4:v3")) |
675 | + self.assertTrue(self._str_contains(out, "file0.txt~5:v3")) |
676 | + self.assertFalse(self._str_contains(out, "file0.txt~6:v3")) |
677 | + |
678 | + def test_revno_range_versioned_file_in_dir(self): |
679 | + """we create a file 'foobar0/dir0/file0.txt' and grep rev-range for pattern""" |
680 | + wd = 'foobar0' |
681 | + self.make_branch_and_tree(wd) |
682 | + os.chdir(wd) |
683 | + self._mk_versioned_dir('dir0') # rev1 |
684 | + self._mk_versioned_file('dir0/file0.txt') # rev2 |
685 | + self._update_file('dir0/file0.txt', "v3 text\n") # rev3 |
686 | + self._update_file('dir0/file0.txt', "v4 text\n") # rev4 |
687 | + self._update_file('dir0/file0.txt', "v5 text\n") # rev5 |
688 | + self._update_file('dir0/file0.txt', "v6 text\n") # rev6 |
689 | + |
690 | + out, err = self.run_bzr(['grep', '-R', '-r', '2..5', 'v3']) |
691 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt~3:v3")) |
692 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt~4:v3")) |
693 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt~5:v3")) |
694 | + self.assertFalse(self._str_contains(out, "^dir0/file0.txt~6:v3")) |
695 | + |
696 | + def test_revno_range_versioned_file_from_outside_dir(self): |
697 | + """grep rev-range for pattern from outside dir""" |
698 | + wd = 'foobar0' |
699 | + self.make_branch_and_tree(wd) |
700 | + os.chdir(wd) |
701 | + self._mk_versioned_dir('dir0') # rev1 |
702 | + self._mk_versioned_file('dir0/file0.txt') # rev2 |
703 | + self._update_file('dir0/file0.txt', "v3 text\n") # rev3 |
704 | + self._update_file('dir0/file0.txt', "v4 text\n") # rev4 |
705 | + self._update_file('dir0/file0.txt', "v5 text\n") # rev5 |
706 | + self._update_file('dir0/file0.txt', "v6 text\n") # rev6 |
707 | + |
708 | + out, err = self.run_bzr(['grep', '-r', '2..5', 'v3', 'dir0']) |
709 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt~3:v3")) |
710 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt~4:v3")) |
711 | + self.assertTrue(self._str_contains(out, "^dir0/file0.txt~5:v3")) |
712 | + self.assertFalse(self._str_contains(out, "^dir0/file0.txt~6:v3")) |
713 | + |
714 | + def test_levels(self): |
715 | + """levels=0 should show findings from merged revision""" |
716 | + wd0 = 'foobar0' |
717 | + wd1 = 'foobar1' |
718 | + |
719 | + self.make_branch_and_tree(wd0) |
720 | + os.chdir(wd0) |
721 | + self._mk_versioned_file('file0.txt') |
722 | + os.chdir('..') |
723 | + |
724 | + out, err = self.run_bzr(['branch', wd0, wd1]) |
725 | + os.chdir(wd1) |
726 | + self._mk_versioned_file('file1.txt') |
727 | + os.chdir(osutils.pathjoin('..', wd0)) |
728 | + |
729 | + out, err = self.run_bzr(['merge', osutils.pathjoin('..', wd1)]) |
730 | + out, err = self.run_bzr(['ci', '-m', 'merged']) |
731 | + |
732 | + out, err = self.run_bzr(['grep', 'line1']) |
733 | + self.assertTrue(self._str_contains(out, "file0.txt:line1")) |
734 | + self.assertTrue(self._str_contains(out, "file1.txt:line1")) |
735 | + |
736 | + out, err = self.run_bzr(['grep', '--levels=0', 'line1']) |
737 | + self.assertTrue(self._str_contains(out, "file0.txt~2:line1")) |
738 | + self.assertTrue(self._str_contains(out, "file1.txt~2:line1")) |
739 | + self.assertTrue(self._str_contains(out, "file0.txt~1.1.1:line1")) |
740 | + self.assertTrue(self._str_contains(out, "file1.txt~1.1.1:line1")) |
741 | + |
742 | + out, err = self.run_bzr(['grep', '-n', '--levels=0', 'line1']) |
743 | + self.assertTrue(self._str_contains(out, "file0.txt~2:1:line1")) |
744 | + self.assertTrue(self._str_contains(out, "file1.txt~2:1:line1")) |
745 | + self.assertTrue(self._str_contains(out, "file0.txt~1.1.1:1:line1")) |
746 | + self.assertTrue(self._str_contains(out, "file1.txt~1.1.1:1:line1")) |
747 | + |
=== Fixes #503670 === /launchpad. net/bzr- grep) plugin to be a builtin.
Ports the lp:bzr-grep (https:/
[bzrlib]% ../bzr help grep
Purpose: Print lines matching PATTERN for specified files and revisions.
Usage: bzr grep PATTERN [PATH...]
Options:
branch. (implies --recursive)
rather than a newline.
collapsed (default).
See "help revisionspec" for details.
--from-root Search for pattern starting from the root of the
-Z, --null Write an ascii NUL (\0) separator between output lines
-v, --verbose Display more information.
-R, --recursive Recurse into subdirectories.
-h, --help Show help message.
-q, --quiet Only display errors and warnings.
-i, --ignore-case ignore case distinctions while matching.
--levels=N Number of levels to display - 0 for all, 1 for
--usage Show usage message and options.
-n, --line-number show 1-based line number.
-r ARG, --revision=ARG
Description:
This command searches the specified files and revisions for a given pattern.
The pattern is specified as a Python regular expressions[1].
If the file name is not specified the file revisions in the current directory
are searched. If the revision number is not specified, the latest revision is
searched.
Note that this command is different from POSIX grep in that it searches the
revisions of the branch and not the working copy. Unversioned files and
uncommitted changes are not seen.
When searching a pattern, the output is shown in the 'filepath:string' format. N:string' ,
If a revision is explicitly searched, the output is shown as 'filepath~
where N is the revision number.
[1] http:// docs.python. org/library/ re.html# regular- expression- syntax
[bzrlib]%