mercurial/cmdutil.py
changeset 35690 3e394e0558d7
parent 35689 5fe6f946f111
child 35743 3c2a6246fd63
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)