comparison mercurial/cmdutil.py @ 45650:0356b41fe01d

cmdutil: rewrite walkchangerevs() by using logcmdutil functions cmdutil.walkchangerevs() now takes (revs, makefilematcher) in place of (match, opts), and only provides the "windowing" functionality. Unused classes and functions will be removed by the next patch. "hg grep --follow" (--all-files) is still broken since there is no logic to follow copies while traversing changelog, but at least, it does follow the DAG.
author Yuya Nishihara <yuya@tcha.org>
date Thu, 10 Sep 2020 18:01:43 +0900
parents 3a024d7cd08e
children c7413ffe0402
comparison
equal deleted inserted replaced
45649:bba730d7a6f4 45650:0356b41fe01d
2426 return True 2426 return True
2427 2427
2428 return False 2428 return False
2429 2429
2430 2430
2431 def walkchangerevs(repo, match, opts, prepare): 2431 def walkchangerevs(repo, revs, makefilematcher, prepare):
2432 '''Iterate over files and the revs in which they changed. 2432 '''Iterate over files and the revs in a "windowed" way.
2433 2433
2434 Callers most commonly need to iterate backwards over the history 2434 Callers most commonly need to iterate backwards over the history
2435 in which they are interested. Doing so has awful (quadratic-looking) 2435 in which they are interested. Doing so has awful (quadratic-looking)
2436 performance, so we use iterators in a "windowed" way. 2436 performance, so we use iterators in a "windowed" way.
2437 2437
2441 2441
2442 This function returns an iterator yielding contexts. Before 2442 This function returns an iterator yielding contexts. Before
2443 yielding each context, the iterator will first call the prepare 2443 yielding each context, the iterator will first call the prepare
2444 function on each context in the window in forward order.''' 2444 function on each context in the window in forward order.'''
2445 2445
2446 allfiles = opts.get(b'all_files')
2447 follow = opts.get(b'follow') or opts.get(b'follow_first')
2448 revs = _walkrevs(repo, opts)
2449 if not revs: 2446 if not revs:
2450 return [] 2447 return []
2451 wanted = set()
2452 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2453 fncache = {}
2454 change = repo.__getitem__ 2448 change = repo.__getitem__
2455 2449
2456 # First step is to fill wanted, the set of revisions that we want to yield.
2457 # When it does not induce extra cost, we also fill fncache for revisions in
2458 # wanted: a cache of filenames that were changed (ctx.files()) and that
2459 # match the file filtering conditions.
2460
2461 if match.always() or allfiles:
2462 # No files, no patterns. Display all revs.
2463 wanted = revs
2464 elif not slowpath:
2465 # We only have to read through the filelog to find wanted revisions
2466
2467 try:
2468 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2469 except FileWalkError:
2470 slowpath = True
2471
2472 # We decided to fall back to the slowpath because at least one
2473 # of the paths was not a file. Check to see if at least one of them
2474 # existed in history, otherwise simply return
2475 for path in match.files():
2476 if path == b'.' or path in repo.store:
2477 break
2478 else:
2479 return []
2480
2481 if slowpath:
2482 # We have to read the changelog to match filenames against
2483 # changed files
2484
2485 if follow:
2486 raise error.Abort(
2487 _(b'can only follow copies/renames for explicit filenames')
2488 )
2489
2490 # The slow path checks files modified in every changeset.
2491 # This is really slow on large repos, so compute the set lazily.
2492 class lazywantedset(object):
2493 def __init__(self):
2494 self.set = set()
2495 self.revs = set(revs)
2496
2497 # No need to worry about locality here because it will be accessed
2498 # in the same order as the increasing window below.
2499 def __contains__(self, value):
2500 if value in self.set:
2501 return True
2502 elif not value in self.revs:
2503 return False
2504 else:
2505 self.revs.discard(value)
2506 ctx = change(value)
2507 if allfiles:
2508 matches = list(ctx.manifest().walk(match))
2509 else:
2510 matches = [f for f in ctx.files() if match(f)]
2511 if matches:
2512 fncache[value] = matches
2513 self.set.add(value)
2514 return True
2515 return False
2516
2517 def discard(self, value):
2518 self.revs.discard(value)
2519 self.set.discard(value)
2520
2521 wanted = lazywantedset()
2522
2523 # it might be worthwhile to do this in the iterator if the rev range
2524 # is descending and the prune args are all within that range
2525 for rev in opts.get(b'prune', ()):
2526 rev = repo[rev].rev()
2527 ff = _followfilter(repo)
2528 stop = min(revs[0], revs[-1])
2529 for x in pycompat.xrange(rev, stop - 1, -1):
2530 if ff.match(x):
2531 wanted = wanted - [x]
2532
2533 # Now that wanted is correctly initialized, we can iterate over the
2534 # revision range, yielding only revisions in wanted.
2535 def iterate(): 2450 def iterate():
2536 if follow and match.always():
2537 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2538
2539 def want(rev):
2540 return ff.match(rev) and rev in wanted
2541
2542 else:
2543
2544 def want(rev):
2545 return rev in wanted
2546
2547 it = iter(revs) 2451 it = iter(revs)
2548 stopiteration = False 2452 stopiteration = False
2549 for windowsize in increasingwindows(): 2453 for windowsize in increasingwindows():
2550 nrevs = [] 2454 nrevs = []
2551 for i in pycompat.xrange(windowsize): 2455 for i in pycompat.xrange(windowsize):
2552 rev = next(it, None) 2456 rev = next(it, None)
2553 if rev is None: 2457 if rev is None:
2554 stopiteration = True 2458 stopiteration = True
2555 break 2459 break
2556 elif want(rev): 2460 nrevs.append(rev)
2557 nrevs.append(rev)
2558 for rev in sorted(nrevs): 2461 for rev in sorted(nrevs):
2559 fns = fncache.get(rev)
2560 ctx = change(rev) 2462 ctx = change(rev)
2561 if not fns: 2463 prepare(ctx, makefilematcher(ctx))
2562
2563 def fns_generator():
2564 if allfiles:
2565
2566 def bad(f, msg):
2567 pass
2568
2569 for f in ctx.matches(matchmod.badmatch(match, bad)):
2570 yield f
2571 else:
2572 for f in ctx.files():
2573 if match(f):
2574 yield f
2575
2576 fns = fns_generator()
2577 prepare(ctx, scmutil.matchfiles(repo, fns))
2578 for rev in nrevs: 2464 for rev in nrevs:
2579 yield change(rev) 2465 yield change(rev)
2580 2466
2581 if stopiteration: 2467 if stopiteration:
2582 break 2468 break