hgext/graphlog.py
changeset 16186 af3e67354beb
parent 16184 6863caf01daa
child 16314 16ec050490fc
equal deleted inserted replaced
16185:352053e6cd8e 16186:af3e67354beb
   240     for op in ["newest_first"]:
   240     for op in ["newest_first"]:
   241         if op in opts and opts[op]:
   241         if op in opts and opts[op]:
   242             raise util.Abort(_("-G/--graph option is incompatible with --%s")
   242             raise util.Abort(_("-G/--graph option is incompatible with --%s")
   243                              % op.replace("_", "-"))
   243                              % op.replace("_", "-"))
   244 
   244 
       
   245 def makefilematcher(repo, pats, followfirst):
       
   246     # When displaying a revision with --patch --follow FILE, we have
       
   247     # to know which file of the revision must be diffed. With
       
   248     # --follow, we want the names of the ancestors of FILE in the
       
   249     # revision, stored in "fcache". "fcache" is populated by
       
   250     # reproducing the graph traversal already done by --follow revset
       
   251     # and relating linkrevs to file names (which is not "correct" but
       
   252     # good enough).
       
   253     fcache = {}
       
   254     fcacheready = [False]
       
   255     pctx = repo['.']
       
   256     wctx = repo[None]
       
   257 
       
   258     def populate():
       
   259         for fn in pats:
       
   260             for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
       
   261                 for c in i:
       
   262                     fcache.setdefault(c.linkrev(), set()).add(c.path())
       
   263 
       
   264     def filematcher(rev):
       
   265         if not fcacheready[0]:
       
   266             # Lazy initialization
       
   267             fcacheready[0] = True
       
   268             populate()
       
   269         return scmutil.match(wctx, fcache.get(rev, []), default='path')
       
   270 
       
   271     return filematcher
       
   272 
   245 def revset(repo, pats, opts):
   273 def revset(repo, pats, opts):
   246     """Return revset str built of revisions, log options and file patterns.
   274     """Return (expr, filematcher) where expr is a revset string built
       
   275     of revisions, log options and file patterns. If --stat or --patch
       
   276     are not passed filematcher is None. Otherwise it a a callable
       
   277     taking a revision number and returning a match objects filtering
       
   278     the files to be detailed when displaying the revision.
   247     """
   279     """
   248     opt2revset = {
   280     opt2revset = {
   249         'follow':           ('follow()', None),
   281         'follow':           ('follow()', None),
   250         'follow_first':     ('_followfirst()', None),
   282         'follow_first':     ('_followfirst()', None),
   251         'no_merges':        ('not merge()', None),
   283         'no_merges':        ('not merge()', None),
   327                 else:
   359                 else:
   328                     opts['follow'] = True
   360                     opts['follow'] = True
   329         else:
   361         else:
   330             opts['_patslog'] = list(pats)
   362             opts['_patslog'] = list(pats)
   331 
   363 
       
   364     filematcher = None
       
   365     if opts.get('patch') or opts.get('stat'):
       
   366         if follow:
       
   367             filematcher = makefilematcher(repo, pats, followfirst)
       
   368         else:
       
   369             filematcher = lambda rev: match
       
   370 
   332     revset = []
   371     revset = []
   333     for op, val in opts.iteritems():
   372     for op, val in opts.iteritems():
   334         if not val:
   373         if not val:
   335             continue
   374             continue
   336         if op not in opt2revset:
   375         if op not in opt2revset:
   347 
   386 
   348     if revset:
   387     if revset:
   349         revset = '(' + ' and '.join(revset) + ')'
   388         revset = '(' + ' and '.join(revset) + ')'
   350     else:
   389     else:
   351         revset = 'all()'
   390         revset = 'all()'
   352     return revset
   391     return revset, filematcher
   353 
   392 
   354 def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None):
   393 def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None,
       
   394              filematcher=None):
   355     seen, state = [], asciistate()
   395     seen, state = [], asciistate()
   356     for rev, type, ctx, parents in dag:
   396     for rev, type, ctx, parents in dag:
   357         char = ctx.node() in showparents and '@' or 'o'
   397         char = ctx.node() in showparents and '@' or 'o'
   358         copies = None
   398         copies = None
   359         if getrenamed and ctx.rev():
   399         if getrenamed and ctx.rev():
   360             copies = []
   400             copies = []
   361             for fn in ctx.files():
   401             for fn in ctx.files():
   362                 rename = getrenamed(fn, ctx.rev())
   402                 rename = getrenamed(fn, ctx.rev())
   363                 if rename:
   403                 if rename:
   364                     copies.append((fn, rename[0]))
   404                     copies.append((fn, rename[0]))
   365         displayer.show(ctx, copies=copies)
   405         revmatchfn = None
       
   406         if filematcher is not None:
       
   407             revmatchfn = filematcher(ctx.rev())
       
   408         displayer.show(ctx, copies=copies, matchfn=revmatchfn)
   366         lines = displayer.hunk.pop(rev).split('\n')[:-1]
   409         lines = displayer.hunk.pop(rev).split('\n')[:-1]
   367         displayer.flush(rev)
   410         displayer.flush(rev)
   368         edges = edgefn(type, char, lines, seen, rev, parents)
   411         edges = edgefn(type, char, lines, seen, rev, parents)
   369         for type, char, lines, coldata in edges:
   412         for type, char, lines, coldata in edges:
   370             ascii(ui, state, type, char, lines, coldata)
   413             ascii(ui, state, type, char, lines, coldata)
   387     directory.
   430     directory.
   388     """
   431     """
   389 
   432 
   390     check_unsupported_flags(pats, opts)
   433     check_unsupported_flags(pats, opts)
   391 
   434 
   392     revs = sorted(scmutil.revrange(repo, [revset(repo, pats, opts)]), reverse=1)
   435     expr, filematcher = revset(repo, pats, opts)
       
   436     revs = sorted(scmutil.revrange(repo, [expr]), reverse=1)
   393     limit = cmdutil.loglimit(opts)
   437     limit = cmdutil.loglimit(opts)
   394     if limit is not None:
   438     if limit is not None:
   395         revs = revs[:limit]
   439         revs = revs[:limit]
   396     revdag = graphmod.dagwalker(repo, revs)
   440     revdag = graphmod.dagwalker(repo, revs)
   397 
   441 
   401         if opts.get('rev'):
   445         if opts.get('rev'):
   402             endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
   446             endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
   403         getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
   447         getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
   404     displayer = show_changeset(ui, repo, opts, buffered=True)
   448     displayer = show_changeset(ui, repo, opts, buffered=True)
   405     showparents = [ctx.node() for ctx in repo[None].parents()]
   449     showparents = [ctx.node() for ctx in repo[None].parents()]
   406     generate(ui, revdag, displayer, showparents, asciiedges, getrenamed)
   450     generate(ui, revdag, displayer, showparents, asciiedges, getrenamed,
       
   451              filematcher)
   407 
   452 
   408 def graphrevs(repo, nodes, opts):
   453 def graphrevs(repo, nodes, opts):
   409     limit = cmdutil.loglimit(opts)
   454     limit = cmdutil.loglimit(opts)
   410     nodes.reverse()
   455     nodes.reverse()
   411     if limit is not None:
   456     if limit is not None: