comparison mercurial/commands.py @ 45696:de6f2afc0247

grep: move match and diff logic to new module commands.grep() has lots of functions and classes. Let's split it into reusable components so we can leverage them to implement a revset predicate for 'hg grep --diff'. I want to do 'hg log -r "diff(pattern)"'.
author Yuya Nishihara <yuya@tcha.org>
date Wed, 09 Sep 2020 15:23:49 +0900
parents 760bb4d74aad
children 494642ed3c50
comparison
equal deleted inserted replaced
45695:760bb4d74aad 45696:de6f2afc0247
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from __future__ import absolute_import 8 from __future__ import absolute_import
9 9
10 import difflib
11 import errno 10 import errno
12 import os 11 import os
13 import re 12 import re
14 import sys 13 import sys
15 14
39 exchange, 38 exchange,
40 extensions, 39 extensions,
41 filemerge, 40 filemerge,
42 formatter, 41 formatter,
43 graphmod, 42 graphmod,
43 grep as grepmod,
44 hbisect, 44 hbisect,
45 help, 45 help,
46 hg, 46 hg,
47 logcmdutil, 47 logcmdutil,
48 match as matchmod, 48 match as matchmod,
3397 sep, eol = b':', b'\n' 3397 sep, eol = b':', b'\n'
3398 if opts.get(b'print0'): 3398 if opts.get(b'print0'):
3399 sep = eol = b'\0' 3399 sep = eol = b'\0'
3400 3400
3401 getfile = util.lrucachefunc(repo.file) 3401 getfile = util.lrucachefunc(repo.file)
3402
3403 def matchlines(body, regexp):
3404 begin = 0
3405 linenum = 0
3406 while begin < len(body):
3407 match = regexp.search(body, begin)
3408 if not match:
3409 break
3410 mstart, mend = match.span()
3411 linenum += body.count(b'\n', begin, mstart) + 1
3412 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3413 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3414 lend = begin - 1
3415 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3416
3417 class linestate(object):
3418 def __init__(self, line, linenum, colstart, colend):
3419 self.line = line
3420 self.linenum = linenum
3421 self.colstart = colstart
3422 self.colend = colend
3423
3424 def __hash__(self):
3425 return hash(self.line)
3426
3427 def __eq__(self, other):
3428 return self.line == other.line
3429
3430 def findpos(self, regexp):
3431 """Iterate all (start, end) indices of matches"""
3432 yield self.colstart, self.colend
3433 p = self.colend
3434 while p < len(self.line):
3435 m = regexp.search(self.line, p)
3436 if not m:
3437 break
3438 if m.end() == p:
3439 p += 1
3440 else:
3441 yield m.span()
3442 p = m.end()
3443
3444 matches = {} 3402 matches = {}
3445 copies = {} 3403 copies = {}
3446 3404
3447 def grepbody(fn, rev, body): 3405 def grepbody(fn, rev, body):
3448 matches[rev].setdefault(fn, []) 3406 matches[rev].setdefault(fn, [])
3449 m = matches[rev][fn] 3407 m = matches[rev][fn]
3450 if body is None: 3408 if body is None:
3451 return 3409 return
3452 3410
3453 for lnum, cstart, cend, line in matchlines(body, regexp): 3411 for lnum, cstart, cend, line in grepmod.matchlines(body, regexp):
3454 s = linestate(line, lnum, cstart, cend) 3412 s = grepmod.linestate(line, lnum, cstart, cend)
3455 m.append(s) 3413 m.append(s)
3456
3457 def difflinestates(a, b):
3458 sm = difflib.SequenceMatcher(None, a, b)
3459 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3460 if tag == 'insert':
3461 for i in pycompat.xrange(blo, bhi):
3462 yield (b'+', b[i])
3463 elif tag == 'delete':
3464 for i in pycompat.xrange(alo, ahi):
3465 yield (b'-', a[i])
3466 elif tag == 'replace':
3467 for i in pycompat.xrange(alo, ahi):
3468 yield (b'-', a[i])
3469 for i in pycompat.xrange(blo, bhi):
3470 yield (b'+', b[i])
3471 3414
3472 uipathfn = scmutil.getuipathfn(repo) 3415 uipathfn = scmutil.getuipathfn(repo)
3473 3416
3474 def display(fm, fn, ctx, pstates, states): 3417 def display(fm, fn, ctx, pstates, states):
3475 rev = scmutil.intrev(ctx) 3418 rev = scmutil.intrev(ctx)
3491 except error.WdirUnsupported: 3434 except error.WdirUnsupported:
3492 return ctx[fn].isbinary() 3435 return ctx[fn].isbinary()
3493 3436
3494 fieldnamemap = {b'linenumber': b'lineno'} 3437 fieldnamemap = {b'linenumber': b'lineno'}
3495 if diff: 3438 if diff:
3496 iter = difflinestates(pstates, states) 3439 iter = grepmod.difflinestates(pstates, states)
3497 else: 3440 else:
3498 iter = [(b'', l) for l in states] 3441 iter = [(b'', l) for l in states]
3499 for change, l in iter: 3442 for change, l in iter:
3500 fm.startitem() 3443 fm.startitem()
3501 fm.context(ctx=ctx) 3444 fm.context(ctx=ctx)