Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/cmdutil.py @ 45652: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
45651:bba730d7a6f4 | 45652: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 |