diff -r 0849d725e2f9 -r ae0629161090 hgext/graphlog.py --- a/hgext/graphlog.py Wed Jul 11 17:13:39 2012 +0200 +++ b/hgext/graphlog.py Sat Jul 14 18:55:21 2012 +0200 @@ -15,7 +15,7 @@ from mercurial.cmdutil import show_changeset from mercurial.i18n import _ from mercurial import cmdutil, commands, extensions, scmutil -from mercurial import hg, util, graphmod, templatekw, revset +from mercurial import hg, util, graphmod, templatekw cmdtable = {} command = cmdutil.command(cmdtable) @@ -27,244 +27,6 @@ raise util.Abort(_("-G/--graph option is incompatible with --%s") % op.replace("_", "-")) -def _makefilematcher(repo, pats, followfirst): - # When displaying a revision with --patch --follow FILE, we have - # to know which file of the revision must be diffed. With - # --follow, we want the names of the ancestors of FILE in the - # revision, stored in "fcache". "fcache" is populated by - # reproducing the graph traversal already done by --follow revset - # and relating linkrevs to file names (which is not "correct" but - # good enough). - fcache = {} - fcacheready = [False] - pctx = repo['.'] - wctx = repo[None] - - def populate(): - for fn in pats: - for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)): - for c in i: - fcache.setdefault(c.linkrev(), set()).add(c.path()) - - def filematcher(rev): - if not fcacheready[0]: - # Lazy initialization - fcacheready[0] = True - populate() - return scmutil.match(wctx, fcache.get(rev, []), default='path') - - return filematcher - -def _makelogrevset(repo, pats, opts, revs): - """Return (expr, filematcher) where expr is a revset string built - from log options and file patterns or None. If --stat or --patch - are not passed filematcher is None. Otherwise it is a callable - taking a revision number and returning a match objects filtering - the files to be detailed when displaying the revision. - """ - opt2revset = { - 'no_merges': ('not merge()', None), - 'only_merges': ('merge()', None), - '_ancestors': ('ancestors(%(val)s)', None), - '_fancestors': ('_firstancestors(%(val)s)', None), - '_descendants': ('descendants(%(val)s)', None), - '_fdescendants': ('_firstdescendants(%(val)s)', None), - '_matchfiles': ('_matchfiles(%(val)s)', None), - 'date': ('date(%(val)r)', None), - 'branch': ('branch(%(val)r)', ' or '), - '_patslog': ('filelog(%(val)r)', ' or '), - '_patsfollow': ('follow(%(val)r)', ' or '), - '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '), - 'keyword': ('keyword(%(val)r)', ' or '), - 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '), - 'user': ('user(%(val)r)', ' or '), - } - - opts = dict(opts) - # follow or not follow? - follow = opts.get('follow') or opts.get('follow_first') - followfirst = opts.get('follow_first') and 1 or 0 - # --follow with FILE behaviour depends on revs... - startrev = revs[0] - followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0 - - # branch and only_branch are really aliases and must be handled at - # the same time - opts['branch'] = opts.get('branch', []) + opts.get('only_branch', []) - opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']] - # pats/include/exclude are passed to match.match() directly in - # _matchfile() revset but walkchangerevs() builds its matcher with - # scmutil.match(). The difference is input pats are globbed on - # platforms without shell expansion (windows). - pctx = repo[None] - match, pats = scmutil.matchandpats(pctx, pats, opts) - slowpath = match.anypats() or (match.files() and opts.get('removed')) - if not slowpath: - for f in match.files(): - if follow and f not in pctx: - raise util.Abort(_('cannot follow file not in parent ' - 'revision: "%s"') % f) - filelog = repo.file(f) - if not len(filelog): - # A zero count may be a directory or deleted file, so - # try to find matching entries on the slow path. - if follow: - raise util.Abort( - _('cannot follow nonexistent file: "%s"') % f) - slowpath = True - if slowpath: - # See cmdutil.walkchangerevs() slow path. - # - if follow: - raise util.Abort(_('can only follow copies/renames for explicit ' - 'filenames')) - # pats/include/exclude cannot be represented as separate - # revset expressions as their filtering logic applies at file - # level. For instance "-I a -X a" matches a revision touching - # "a" and "b" while "file(a) and not file(b)" does - # not. Besides, filesets are evaluated against the working - # directory. - matchargs = ['r:', 'd:relpath'] - for p in pats: - matchargs.append('p:' + p) - for p in opts.get('include', []): - matchargs.append('i:' + p) - for p in opts.get('exclude', []): - matchargs.append('x:' + p) - matchargs = ','.join(('%r' % p) for p in matchargs) - opts['_matchfiles'] = matchargs - else: - if follow: - fpats = ('_patsfollow', '_patsfollowfirst') - fnopats = (('_ancestors', '_fancestors'), - ('_descendants', '_fdescendants')) - if pats: - # follow() revset inteprets its file argument as a - # manifest entry, so use match.files(), not pats. - opts[fpats[followfirst]] = list(match.files()) - else: - opts[fnopats[followdescendants][followfirst]] = str(startrev) - else: - opts['_patslog'] = list(pats) - - filematcher = None - if opts.get('patch') or opts.get('stat'): - if follow: - filematcher = _makefilematcher(repo, pats, followfirst) - else: - filematcher = lambda rev: match - - expr = [] - for op, val in opts.iteritems(): - if not val: - continue - if op not in opt2revset: - continue - revop, andor = opt2revset[op] - if '%(val)' not in revop: - expr.append(revop) - else: - if not isinstance(val, list): - e = revop % {'val': val} - else: - e = '(' + andor.join((revop % {'val': v}) for v in val) + ')' - expr.append(e) - - if expr: - expr = '(' + ' and '.join(expr) + ')' - else: - expr = None - return expr, filematcher - -def getlogrevs(repo, pats, opts): - """Return (revs, expr, filematcher) where revs is an iterable of - revision numbers, expr is a revset string built from log options - and file patterns or None, and used to filter 'revs'. If --stat or - --patch are not passed filematcher is None. Otherwise it is a - callable taking a revision number and returning a match objects - filtering the files to be detailed when displaying the revision. - """ - def increasingrevs(repo, revs, matcher): - # The sorted input rev sequence is chopped in sub-sequences - # which are sorted in ascending order and passed to the - # matcher. The filtered revs are sorted again as they were in - # the original sub-sequence. This achieve several things: - # - # - getlogrevs() now returns a generator which behaviour is - # adapted to log need. First results come fast, last ones - # are batched for performances. - # - # - revset matchers often operate faster on revision in - # changelog order, because most filters deal with the - # changelog. - # - # - revset matchers can reorder revisions. "A or B" typically - # returns returns the revision matching A then the revision - # matching B. We want to hide this internal implementation - # detail from the caller, and sorting the filtered revision - # again achieves this. - for i, window in cmdutil.increasingwindows(0, len(revs), windowsize=1): - orevs = revs[i:i + window] - nrevs = set(matcher(repo, sorted(orevs))) - for rev in orevs: - if rev in nrevs: - yield rev - - if not len(repo): - return iter([]), None, None - # Default --rev value depends on --follow but --follow behaviour - # depends on revisions resolved from --rev... - follow = opts.get('follow') or opts.get('follow_first') - if opts.get('rev'): - revs = scmutil.revrange(repo, opts['rev']) - else: - if follow and len(repo) > 0: - revs = scmutil.revrange(repo, ['.:0']) - else: - revs = range(len(repo) - 1, -1, -1) - if not revs: - return iter([]), None, None - expr, filematcher = _makelogrevset(repo, pats, opts, revs) - if expr: - matcher = revset.match(repo.ui, expr) - revs = increasingrevs(repo, revs, matcher) - if not opts.get('hidden'): - # --hidden is still experimental and not worth a dedicated revset - # yet. Fortunately, filtering revision number is fast. - revs = (r for r in revs if r not in repo.changelog.hiddenrevs) - else: - revs = iter(revs) - return revs, expr, filematcher - -def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None, - filematcher=None): - seen, state = [], graphmod.asciistate() - for rev, type, ctx, parents in dag: - char = 'o' - if ctx.node() in showparents: - char = '@' - elif ctx.obsolete(): - char = 'x' - copies = None - if getrenamed and ctx.rev(): - copies = [] - for fn in ctx.files(): - rename = getrenamed(fn, ctx.rev()) - if rename: - copies.append((fn, rename[0])) - revmatchfn = None - if filematcher is not None: - revmatchfn = filematcher(ctx.rev()) - displayer.show(ctx, copies=copies, matchfn=revmatchfn) - lines = displayer.hunk.pop(rev).split('\n') - if not lines[-1]: - del lines[-1] - displayer.flush(rev) - edges = edgefn(type, char, lines, seen, rev, parents) - for type, char, lines, coldata in edges: - graphmod.ascii(ui, state, type, char, lines, coldata) - displayer.close() - @command('glog', [('f', 'follow', None, _('follow changeset history, or file history across copies and renames')), @@ -298,7 +60,7 @@ directory. """ - revs, expr, filematcher = getlogrevs(repo, pats, opts) + revs, expr, filematcher = cmdutil.getgraphlogrevs(repo, pats, opts) revs = sorted(revs, reverse=1) limit = cmdutil.loglimit(opts) if limit is not None: @@ -313,8 +75,8 @@ getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) displayer = show_changeset(ui, repo, opts, buffered=True) showparents = [ctx.node() for ctx in repo[None].parents()] - generate(ui, revdag, displayer, showparents, graphmod.asciiedges, - getrenamed, filematcher) + cmdutil.displaygraph(ui, revdag, displayer, showparents, + graphmod.asciiedges, getrenamed, filematcher) def graphrevs(repo, nodes, opts): limit = cmdutil.loglimit(opts) @@ -341,7 +103,8 @@ revdag = graphrevs(repo, o, opts) displayer = show_changeset(ui, repo, opts, buffered=True) showparents = [ctx.node() for ctx in repo[None].parents()] - generate(ui, revdag, displayer, showparents, graphmod.asciiedges) + cmdutil.displaygraph(ui, revdag, displayer, showparents, + graphmod.asciiedges) def gincoming(ui, repo, source="default", **opts): """show the incoming changesets alongside an ASCII revision graph @@ -359,7 +122,8 @@ def display(other, chlist, displayer): revdag = graphrevs(other, chlist, opts) showparents = [ctx.node() for ctx in repo[None].parents()] - generate(ui, revdag, displayer, showparents, graphmod.asciiedges) + cmdutil.displaygraph(ui, revdag, displayer, showparents, + graphmod.asciiedges) hg._incoming(display, subreporecurse, ui, repo, source, opts, buffered=True)