[PATCH] grep: introduce a manifest search method

Steve Borho steve at borho.org
Mon May 17 21:21:32 CDT 2010


# HG changeset patch
# User Steve Borho <steve at borho.org>
# Date 1273812860 18000
# Node ID 7abcaef62d30d58f9247c42d5ae8269c55f65717
# Parent  c45a47bc41148b6037cd7337c4529a5e67b92222
grep: introduce a manifest search method

If no revision is specified, perform a workingctx scan.
If one revision is specified, perform a changectx scan.

If --all or --rev N:M are specified, fall back to original behavior.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1311,18 +1311,22 @@
 def grep(ui, repo, pattern, *pats, **opts):
     """search for a pattern in specified files and revisions
 
-    Search revisions of files for a regular expression.
+    Search files or revisions for a regular expression.
 
     This command behaves differently than Unix grep. It only accepts
-    Python/Perl regexps. It searches repository history, not the
-    working directory. It always prints the revision number in which a
-    match appears.
-
-    By default, grep only prints output for the first revision of a
-    file in which it finds a match. To get it to print every revision
-    that contains a change in match status ("-" for a match that
-    becomes a non-match, or "+" for a non-match that becomes a match),
-    use the --all flag.
+    Python/Perl regexps.
+
+    If no revision is specified, grep searches the working directory.
+    If one revision is specified, grep searches the file contents at
+    that revision.
+
+    If a revision range is specified, grep walks the changesets in the
+    specified order and reports the first match in each file, only
+    inspecting the files modified at each changeset.
+
+    To get it to print every revision that contains a change in match
+    status ("-" for a match that becomes a non-match, or "+" for a
+    non-match that becomes a match), use the --all flag.
 
     Returns 0 if a match is found, 1 otherwise.
     """
@@ -1340,6 +1344,70 @@
 
     getfile = util.lrucachefunc(repo.file)
 
+    cache = {}
+    def annotateline(fn, lineno, rev):
+        if opts.get('user') or opts.get('date') or opts.get('follow'):
+            if rev is None:
+                raise util.Abort(_("grep: cannot annotate working changes, "
+                                   "try hg grep --rev ."))
+            elif fn in cache:
+                data = cache[fn]
+            else:
+                annopts = {'user':opts.get('user'), 'date':opts.get('date'),
+                           'file':opts.get('follow'), 'number':True,
+                           'rev':str(rev)}
+                data = []
+                ui.pushbuffer()
+                annotate(ui, repo, fn, **annopts)
+                for l in ui.popbuffer().splitlines():
+                    prefix = l.split(':', 1)[0]
+                    data.append([s.strip() for s in prefix.split()])
+                cache.clear()
+                cache[fn] = data
+            if opts.get('follow'):
+                # annotate lists the "followed" filename last
+                cols = data[lineno][-1:] + data[lineno][:-1]
+            else:
+                cols = [fn] + data[lineno]
+        else:
+            cols = [fn]
+        if opts.get('line_number'):
+            cols.append(str(lineno))
+        return cols
+
+    def manifestgrep(ctx, matchfn):
+        total = len(ctx.manifest())
+        found = False
+        for count, fn in enumerate(ctx):
+            ui.progress(_('searching'), count, fn, _('files'), total)
+            if not matchfn(fn):
+                continue
+            data = ctx[fn].data()
+            if '\0' in data:
+                continue
+            for i, line in enumerate(data.splitlines()):
+                if opts.get('files_with_matches'):
+                    if regexp.search(line):
+                        ui.write(fn + eol)
+                        found = True
+                        break
+                    continue
+                pos = 0
+                t = []
+                for m in regexp.finditer(line):
+                    s, e = m.span()
+                    t.append(line[pos:s])
+                    t.append(ui.label(line[s:e], label='grep.match'))
+                    pos = e
+                if pos:
+                    t.append(line[pos:])
+                    cols = annotateline(fn, i, ctx.rev())
+                    cols.append(''.join(t))
+                    ui.write(sep.join(cols) + eol)
+                    found = True
+        ui.progress(_('searching'), None)
+        return found
+
     def matchlines(body):
         begin = 0
         linenum = 0
@@ -1434,6 +1502,16 @@
     matchfn = cmdutil.match(repo, pats, opts)
     found = False
     follow = opts.get('follow')
+    revs = opts.get('rev')
+
+    node1, node2 = cmdutil.revpair(repo, revs)
+    if not opts.get('all') and not node2:
+        # no annotation required, do fast grep
+        if revs:
+            ctx = repo[node1]
+        else:
+            ctx = repo[None]
+        return not manifestgrep(ctx, matchfn)
 
     def prep(ctx, fns):
         rev = ctx.rev()
diff --git a/tests/test-grep.out b/tests/test-grep.out
--- a/tests/test-grep.out
+++ b/tests/test-grep.out
@@ -1,13 +1,13 @@
 % pattern error
 grep: invalid match pattern: nothing to repeat
 % simple
-port:4:export
-port:4:vaportight
-port:4:import/export
+port:export
+port:vaportight
+port:import/export
 % simple with color
-port:4:export
-port:4:vaportight
-port:4:import/export
+port:export
+port:vaportight
+port:import/export
 % all
 port:4:4:-:spam:import/export
 port:3:4:+:eggs:import/export
@@ -19,7 +19,7 @@
 port:1:2:+:eggs:export
 port:0:1:+:spam:import
 % other
-port:4:import/export
+port:import/export
 % follow
 port:0:import
 port2:6:4:+:eggs:deport
@@ -32,18 +32,17 @@
 port:2:3:+:spam:import/export
 port:1:2:+:eggs:export
 port:0:1:+:spam:import
-color:3:orange
+color:orange
 color:3:+:orange
 color:2:-:orange
 color:1:+:orange
 % match in last line without newline
 adding noeol
 % last character omitted in output to avoid infinite loop
-noeol:4:no infinite loo
+noeol:no infinite loop
 % issue 685
 adding color
-colour:1:octarine
-color:0:octarine
+colour:octarine
 colour:1:octarine
 % issue 337
 adding color


More information about the Mercurial-devel mailing list