Mercurial > hg
comparison mercurial/cmdutil.py @ 35690:3e394e0558d7
log: build follow-log filematcher at once
We no longer need to replay copy tracing to build filematcher as we can
walk (rev, fctxs) pairs.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Thu, 04 Jan 2018 14:20:58 +0900 |
parents | 5fe6f946f111 |
children | 3c2a6246fd63 |
comparison
equal
deleted
inserted
replaced
35689:5fe6f946f111 | 35690:3e394e0558d7 |
---|---|
2386 def _fileancestors(repo, revs, match, followfirst): | 2386 def _fileancestors(repo, revs, match, followfirst): |
2387 fctxs = [] | 2387 fctxs = [] |
2388 for r in revs: | 2388 for r in revs: |
2389 ctx = repo[r] | 2389 ctx = repo[r] |
2390 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match)) | 2390 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match)) |
2391 return dagop.filerevancestors(fctxs, followfirst=followfirst) | 2391 |
2392 | |
2393 def _makefollowlogfilematcher(repo, files, followfirst): | |
2394 # When displaying a revision with --patch --follow FILE, we have | 2392 # When displaying a revision with --patch --follow FILE, we have |
2395 # to know which file of the revision must be diffed. With | 2393 # to know which file of the revision must be diffed. With |
2396 # --follow, we want the names of the ancestors of FILE in the | 2394 # --follow, we want the names of the ancestors of FILE in the |
2397 # revision, stored in "fcache". "fcache" is populated by | 2395 # revision, stored in "fcache". "fcache" is populated as a side effect |
2398 # reproducing the graph traversal already done by --follow revset | 2396 # of the graph traversal. |
2399 # and relating revs to file names (which is not "correct" but | |
2400 # good enough). | |
2401 fcache = {} | 2397 fcache = {} |
2402 fcacheready = [False] | |
2403 pctx = repo['.'] | |
2404 | |
2405 def populate(): | |
2406 for fn in files: | |
2407 fctx = pctx[fn] | |
2408 fcache.setdefault(fctx.introrev(), set()).add(fctx.path()) | |
2409 for c in fctx.ancestors(followfirst=followfirst): | |
2410 fcache.setdefault(c.rev(), set()).add(c.path()) | |
2411 | |
2412 def filematcher(rev): | 2398 def filematcher(rev): |
2413 if not fcacheready[0]: | |
2414 # Lazy initialization | |
2415 fcacheready[0] = True | |
2416 populate() | |
2417 return scmutil.matchfiles(repo, fcache.get(rev, [])) | 2399 return scmutil.matchfiles(repo, fcache.get(rev, [])) |
2418 | 2400 |
2419 return filematcher | 2401 def revgen(): |
2402 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst): | |
2403 fcache[rev] = [c.path() for c in cs] | |
2404 yield rev | |
2405 return smartset.generatorset(revgen(), iterasc=False), filematcher | |
2420 | 2406 |
2421 def _makenofollowlogfilematcher(repo, pats, opts): | 2407 def _makenofollowlogfilematcher(repo, pats, opts): |
2422 '''hook for extensions to override the filematcher for non-follow cases''' | 2408 '''hook for extensions to override the filematcher for non-follow cases''' |
2423 return None | 2409 return None |
2424 | 2410 |
2433 'prune': ('ancestors(%s)', 'not %lr'), | 2419 'prune': ('ancestors(%s)', 'not %lr'), |
2434 'user': ('user(%s)', '%lr'), | 2420 'user': ('user(%s)', '%lr'), |
2435 } | 2421 } |
2436 | 2422 |
2437 def _makelogrevset(repo, match, pats, slowpath, opts): | 2423 def _makelogrevset(repo, match, pats, slowpath, opts): |
2438 """Return (expr, filematcher) where expr is a revset string built | 2424 """Return a revset string built from log options and file patterns""" |
2439 from log options and file patterns or None. If --stat or --patch | |
2440 are not passed filematcher is None. Otherwise it is a callable | |
2441 taking a revision number and returning a match objects filtering | |
2442 the files to be detailed when displaying the revision. | |
2443 """ | |
2444 opts = dict(opts) | 2425 opts = dict(opts) |
2445 # follow or not follow? | 2426 # follow or not follow? |
2446 follow = opts.get('follow') or opts.get('follow_first') | 2427 follow = opts.get('follow') or opts.get('follow_first') |
2447 | 2428 |
2448 # branch and only_branch are really aliases and must be handled at | 2429 # branch and only_branch are really aliases and must be handled at |
2468 matchargs.append('x:' + p) | 2449 matchargs.append('x:' + p) |
2469 opts['_matchfiles'] = matchargs | 2450 opts['_matchfiles'] = matchargs |
2470 elif not follow: | 2451 elif not follow: |
2471 opts['_patslog'] = list(pats) | 2452 opts['_patslog'] = list(pats) |
2472 | 2453 |
2473 filematcher = None | |
2474 if opts.get('patch') or opts.get('stat'): | |
2475 # When following files, track renames via a special matcher. | |
2476 # If we're forced to take the slowpath it means we're following | |
2477 # at least one pattern/directory, so don't bother with rename tracking. | |
2478 if follow and not match.always() and not slowpath: | |
2479 # _makefollowlogfilematcher expects its files argument to be | |
2480 # relative to the repo root, so use match.files(), not pats. | |
2481 filematcher = _makefollowlogfilematcher(repo, match.files(), | |
2482 opts.get('follow_first')) | |
2483 else: | |
2484 filematcher = _makenofollowlogfilematcher(repo, pats, opts) | |
2485 if filematcher is None: | |
2486 filematcher = lambda rev: match | |
2487 | |
2488 expr = [] | 2454 expr = [] |
2489 for op, val in sorted(opts.iteritems()): | 2455 for op, val in sorted(opts.iteritems()): |
2490 if not val: | 2456 if not val: |
2491 continue | 2457 continue |
2492 if op not in _opt2logrevset: | 2458 if op not in _opt2logrevset: |
2503 | 2469 |
2504 if expr: | 2470 if expr: |
2505 expr = '(' + ' and '.join(expr) + ')' | 2471 expr = '(' + ' and '.join(expr) + ')' |
2506 else: | 2472 else: |
2507 expr = None | 2473 expr = None |
2508 return expr, filematcher | 2474 return expr |
2509 | 2475 |
2510 def _logrevs(repo, opts): | 2476 def _logrevs(repo, opts): |
2511 """Return the initial set of revisions to be filtered or followed""" | 2477 """Return the initial set of revisions to be filtered or followed""" |
2512 follow = opts.get('follow') or opts.get('follow_first') | 2478 follow = opts.get('follow') or opts.get('follow_first') |
2513 if opts.get('rev'): | 2479 if opts.get('rev'): |
2522 return revs | 2488 return revs |
2523 | 2489 |
2524 def getlogrevs(repo, pats, opts): | 2490 def getlogrevs(repo, pats, opts): |
2525 """Return (revs, filematcher) where revs is a smartset | 2491 """Return (revs, filematcher) where revs is a smartset |
2526 | 2492 |
2527 If --stat or --patch is not passed, filematcher is None. Otherwise it | 2493 filematcher is a callable taking a revision number and returning a match |
2528 is a callable taking a revision number and returning a match objects | 2494 objects filtering the files to be detailed when displaying the revision. |
2529 filtering the files to be detailed when displaying the revision. | |
2530 """ | 2495 """ |
2531 follow = opts.get('follow') or opts.get('follow_first') | 2496 follow = opts.get('follow') or opts.get('follow_first') |
2532 followfirst = opts.get('follow_first') | 2497 followfirst = opts.get('follow_first') |
2533 limit = loglimit(opts) | 2498 limit = loglimit(opts) |
2534 revs = _logrevs(repo, opts) | 2499 revs = _logrevs(repo, opts) |
2535 if not revs: | 2500 if not revs: |
2536 return smartset.baseset(), None | 2501 return smartset.baseset(), None |
2537 match, pats, slowpath = _makelogmatcher(repo, revs, pats, opts) | 2502 match, pats, slowpath = _makelogmatcher(repo, revs, pats, opts) |
2503 filematcher = None | |
2538 if follow: | 2504 if follow: |
2539 if slowpath or match.always(): | 2505 if slowpath or match.always(): |
2540 revs = dagop.revancestors(repo, revs, followfirst=followfirst) | 2506 revs = dagop.revancestors(repo, revs, followfirst=followfirst) |
2541 else: | 2507 else: |
2542 revs = _fileancestors(repo, revs, match, followfirst) | 2508 revs, filematcher = _fileancestors(repo, revs, match, followfirst) |
2543 revs.reverse() | 2509 revs.reverse() |
2544 expr, filematcher = _makelogrevset(repo, match, pats, slowpath, opts) | 2510 if filematcher is None: |
2511 filematcher = _makenofollowlogfilematcher(repo, pats, opts) | |
2512 if filematcher is None: | |
2513 def filematcher(rev): | |
2514 return match | |
2515 | |
2516 expr = _makelogrevset(repo, match, pats, slowpath, opts) | |
2545 if opts.get('graph') and opts.get('rev'): | 2517 if opts.get('graph') and opts.get('rev'): |
2546 # User-specified revs might be unsorted, but don't sort before | 2518 # User-specified revs might be unsorted, but don't sort before |
2547 # _makelogrevset because it might depend on the order of revs | 2519 # _makelogrevset because it might depend on the order of revs |
2548 if not (revs.isdescending() or revs.istopo()): | 2520 if not (revs.isdescending() or revs.istopo()): |
2549 revs.sort(reverse=True) | 2521 revs.sort(reverse=True) |