Merge lp:~parthm/bzr/503670-grep-builtin into lp:bzr

Proposed by Parth Malwankar
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
Reviewer Review Type Date Requested Status
Vincent Ladeuil Needs Fixing
Review via email: mp+20420@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Parth Malwankar (parthm) wrote :

=== Fixes #503670 ===
Ports the lp:bzr-grep (https://launchpad.net/bzr-grep) plugin to be a builtin.

[bzrlib]% ../bzr help grep
Purpose: Print lines matching PATTERN for specified files and revisions.
Usage: bzr grep PATTERN [PATH...]

Options:
  --from-root Search for pattern starting from the root of the
                        branch. (implies --recursive)
  -Z, --null Write an ascii NUL (\0) separator between output lines
                        rather than a newline.
  -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
                        collapsed (default).
  --usage Show usage message and options.
  -n, --line-number show 1-based line number.
  -r ARG, --revision=ARG
                        See "help revisionspec" for details.

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.
  If a revision is explicitly searched, the output is shown as 'filepath~N:string',
  where N is the revision number.

  [1] http://docs.python.org/library/re.html#regular-expression-syntax

[bzrlib]%

Revision history for this message
Parth Malwankar (parthm) wrote :
lp:~parthm/bzr/503670-grep-builtin updated
5069. By Parth Malwankar

levels=0 now shows revision number

5070. By Parth Malwankar

added test for --levels=0

Revision history for this message
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.

review: Needs Fixing
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+