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 |