in eden/scm/edenscm/mercurial/commands/__init__.py [0:0]
def histgrep(ui, repo, pattern, *pats, **opts):
"""search backwards through history for a pattern in the specified files
Search revision history for a regular expression in the specified
files or the entire project.
By default, grep prints the most recent revision number for each
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.
PATTERN can be any Python (roughly Perl-compatible) regular
expression.
If no FILEs are specified (and -f/--follow isn't set), all files in
the repository are searched, including those that don't exist in the
current branch or have been deleted in a prior changeset.
.. container:: verbose
``histgrep.allowfullrepogrep`` controls whether the entire repo can be
queried without any patterns, which can be expensive in big repositories.
Returns 0 if a match is found, 1 otherwise.
"""
if not util.istest():
ui.deprecate(
"hg-histgrep",
"histgrep is deprecated because it does not scale - use diffgrep instead",
)
if not pats and not ui.configbool("histgrep", "allowfullrepogrep"):
m = _("can't run histgrep on the whole repo, please provide filenames")
h = _("this is disabled to avoid very slow greps over the whole repo")
raise error.Abort(m, hint=h)
reflags = re.M
if opts.get("ignore_case"):
reflags |= re.I
try:
regexp = re.compile(pattern, reflags)
except re.error as inst:
ui.warn(_("grep: invalid match pattern: %s\n") % inst)
return 1
sep, eol = ":", "\n"
if opts.get("print0"):
sep = eol = "\0"
getfile = util.lrucachefunc(repo.file)
def matchlines(body):
body = pycompat.decodeutf8(body, errors="replace")
begin = 0
linenum = 0
while begin < len(body):
match = regexp.search(body, begin)
if not match:
break
mstart, mend = match.span()
linenum += body.count("\n", begin, mstart) + 1
lstart = body.rfind("\n", begin, mstart) + 1 or begin
begin = body.find("\n", mend) + 1 or len(body) + 1
lend = begin - 1
yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
class linestate(object):
def __init__(self, line, linenum, colstart, colend):
self.line = line
self.linenum = linenum
self.colstart = colstart
self.colend = colend
def __hash__(self):
return hash((self.linenum, self.line))
def __eq__(self, other):
return self.line == other.line
def findpos(self):
"""Iterate all (start, end) indices of matches"""
yield self.colstart, self.colend
p = self.colend
while p < len(self.line):
m = regexp.search(self.line, p)
if not m:
break
yield m.span()
p = m.end()
matches = {}
copies = {}
def grepbody(fn, rev, body):
matches[rev].setdefault(fn, [])
m = matches[rev][fn]
for lnum, cstart, cend, line in matchlines(body):
s = linestate(line, lnum, cstart, cend)
m.append(s)
def difflinestates(a, b):
sm = difflib.SequenceMatcher(None, a, b)
for tag, alo, ahi, blo, bhi in sm.get_opcodes():
if tag == "insert":
for i in range(blo, bhi):
yield ("+", b[i])
elif tag == "delete":
for i in range(alo, ahi):
yield ("-", a[i])
elif tag == "replace":
for i in range(alo, ahi):
yield ("-", a[i])
for i in range(blo, bhi):
yield ("+", b[i])
def display(fm, fn, ctx, pstates, states):
rev = ctx.rev()
node = ctx.node()
if fm.isplain():
formatuser = ui.shortuser
else:
formatuser = str
if ui.quiet:
datefmt = "%Y-%m-%d"
else:
datefmt = "%a %b %d %H:%M:%S %Y %1%2"
found = False
@util.cachefunc
def binary():
flog = getfile(fn)
return util.binary(flog.read(ctx.filenode(fn)))
fieldnamemap = {"filename": "file", "linenumber": "line_number"}
if opts.get("all"):
iter = difflinestates(pstates, states)
else:
iter = [("", l) for l in states]
for change, l in iter:
fm.startitem()
fm.data(node=fm.hexfunc(ctx.node()))
cols = [
("filename", fn, True),
("rev", rev, False),
("node", fm.hexfunc(node), True),
("linenumber", l.linenum, opts.get("line_number")),
]
if opts.get("all"):
cols.append(("change", change, True))
cols.extend(
[
("user", formatuser(ctx.user()), opts.get("user")),
("date", fm.formatdate(ctx.date(), datefmt), opts.get("date")),
]
)
lastcol = next(name for name, data, cond in reversed(cols) if cond)
for name, data, cond in cols:
field = fieldnamemap.get(name, name)
fm.condwrite(cond, field, "%s", data, label="grep.%s" % name)
if cond and name != lastcol:
fm.plain(sep, label="grep.sep")
if not opts.get("files_with_matches"):
fm.plain(sep, label="grep.sep")
if not opts.get("text") and binary():
fm.plain(_(" Binary file matches"))
else:
displaymatches(fm.nested("texts"), l)
fm.plain(eol)
found = True
if opts.get("files_with_matches"):
break
return found
def displaymatches(fm, l):
p = 0
for s, e in l.findpos():
if p < s:
fm.startitem()
fm.write("text", "%s", l.line[p:s])
fm.data(matched=False)
fm.startitem()
fm.write("text", "%s", l.line[s:e], label="grep.match")
fm.data(matched=True)
p = e
if p < len(l.line):
fm.startitem()
fm.write("text", "%s", l.line[p:])
fm.data(matched=False)
fm.end()
skip = {}
revfiles = {}
match = scmutil.match(repo[None], pats, opts)
found = False
follow = opts.get("follow")
def prep(ctx, fns):
rev = ctx.rev()
pctx = ctx.p1()
parent = pctx.rev()
matches.setdefault(rev, {})
matches.setdefault(parent, {})
files = revfiles.setdefault(rev, [])
for fn in fns:
flog = getfile(fn)
try:
fnode = ctx.filenode(fn)
except error.LookupError:
continue
copied = flog.renamed(fnode)
copy = follow and copied and copied[0]
if copy:
copies.setdefault(rev, {})[fn] = copy
if fn in skip:
if copy:
skip[copy] = True
continue
files.append(fn)
if fn not in matches[rev]:
grepbody(fn, rev, flog.read(fnode))
pfn = copy or fn
if pfn not in matches[parent]:
try:
fnode = pctx.filenode(pfn)
grepbody(pfn, parent, flog.read(fnode))
except error.LookupError:
pass
ui.pager("grep")
fm = ui.formatter("grep", opts)
for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
rev = ctx.rev()
parent = ctx.p1().rev()
for fn in sorted(revfiles.get(rev, [])):
states = matches[rev][fn]
copy = copies.get(rev, {}).get(fn)
if fn in skip:
if copy:
skip[copy] = True
continue
pstates = matches.get(parent, {}).get(copy or fn, [])
if pstates or states:
r = display(fm, fn, ctx, pstates, states)
found = found or r
if r and not opts.get("all"):
skip[fn] = True
if copy:
skip[copy] = True
del matches[rev]
del revfiles[rev]
fm.end()
return not found