def underwayrevset(repo, subset, x): args = revset.getargsdict(x, 'underway', 'commitage headage') if 'commitage' not in args: args['commitage'] = None if 'headage' not in args: args['headage'] = None # We assume callers of this revset add a topographical sort on the # result. This means there is no benefit to making the revset lazy # since the topographical sort needs to consume all revs. # # With this in mind, we build up the set manually instead of constructing # a complex revset. This enables faster execution. # Mutable changesets (non-public) are the most important changesets # to return. ``not public()`` will also pull in obsolete changesets if # there is a non-obsolete changeset with obsolete ancestors. This is # why we exclude obsolete changesets from this query. rs = 'not public() and not obsolete()' rsargs = [] if args['commitage']: rs += ' and date(%s)' rsargs.append( revsetlang.getstring(args['commitage'], _('commitage requires a string'))) mutable = repo.revs(rs, *rsargs) relevant = revset.baseset(mutable) # Add parents of mutable changesets to provide context. relevant += repo.revs('parents(%ld)', mutable) # We also pull in (public) heads if they a) aren't closing a branch # b) are recent. rs = 'head() and not closed()' rsargs = [] if args['headage']: rs += ' and date(%s)' rsargs.append( revsetlang.getstring(args['headage'], _('headage requires a string'))) relevant += repo.revs(rs, *rsargs) # Add working directory parent. wdirrev = repo['.'].rev() if wdirrev != nullrev: relevant += revset.baseset(set([wdirrev])) return subset & relevant
def pulledrevsetsymbol(repo, subset, x): """``pulled()`` Changesets that just has been pulled. Only available with largefiles from pull --lfrev expressions. .. container:: verbose Some examples: - pull largefiles for all new changesets:: hg pull -lfrev "pulled()" - pull largefiles for all new branch heads:: hg pull -lfrev "head(pulled()) and not closed()" """ try: firstpulled = repo.firstpulled except AttributeError: raise util.Abort(_("pulled() only available in --lfrev")) return revset.baseset([r for r in subset if r >= firstpulled])
def upstream_revs(filt, repo, subset, x): upstream_tips = [node.hex(n) for name, n in repo._remotebranches.iteritems() if filt(name)] if not upstream_tips: [] if smartset is not None: # 4.2 codepath return repo.revs('::%ln', map(node.bin, upstream_tips)) if getattr(revset, 'baseset', False): # 3.5 codepath return revset.baseset( repo.revs('::%ln', map(node.bin, upstream_tips))) ls = getattr(revset, 'lazyset', False) if ls: # If revset.lazyset exists (hg 3.0), use lazysets instead for # speed. tipancestors = repo.revs('::%ln', map(node.bin, upstream_tips)) def cond(n): return n in tipancestors return ls(subset, cond) # 2.9 and earlier codepath upstream = reduce(lambda x, y: x.update(y) or x, map(lambda x: set(revset.ancestors(repo, subset, x)), [('string', n) for n in upstream_tips]), set()) return [r for r in subset if r in upstream]
def revset_automationrelevant(repo, subset, x): """``automationrelevant(set)`` Changesets relevant to scheduling in automation. Given a revset that evaluates to a single revision, will return that revision and any ancestors that are part of the same push unioned with non-public ancestors. """ s = revset.getset(repo, revset.fullreposet(repo), x) if len(s) > 1: raise util.Abort('can only evaluate single changeset') ctx = repo[s.first()] revs = set([ctx.rev()]) # The pushlog is used to get revisions part of the same push as # the requested revision. pushlog = getattr(repo, 'pushlog', None) if pushlog: pushinfo = repo.pushlog.pushfromchangeset(ctx) for n in pushinfo[3]: pctx = repo[n] if pctx.rev() <= ctx.rev(): revs.add(pctx.rev()) # Union with non-public ancestors. for rev in repo.revs('::%d & not public()', ctx.rev()): revs.add(rev) return subset & revset.baseset(revs)
def revset_pushid(repo, subset, x): """``pushid(int)`` Changesets that were part of the specified numeric push id. """ l = revset.getargs(x, 1, 1, 'pushid requires one argument') try: pushid = int(revset.getstring(l[0], 'pushid requires a number')) except (TypeError, ValueError): raise error.ParseError('pushid expects a number') with repo.pushlog.conn(readonly=True) as conn: push = repo.pushlog.pushfromid(conn, pushid) if conn else None if not push: return revset.baseset() to_rev = repo.changelog.rev pushrevs = set() for node in push.nodes: try: pushrevs.add(to_rev(bin(node))) except RepoLookupError: pass return subset & pushrevs
def revset_gitnode(repo, subset, x): '''``gitnode(hash)`` Select the changeset that originates in the given Git revision. The hash may be abbreviated: `gitnode(a5b)` selects the revision whose Git hash starts with `a5b`. Aborts if multiple changesets match the abbreviation. ''' args = revset.getargs(x, 1, 1, b"gitnode takes one argument") rev = revset.getstring(args[0], b"the argument to gitnode() must be a hash") git = repo.githandler node = repo.changelog.node def matches(r): gitnode = git.map_git_get(hex(node(r))) if gitnode is None: return False return gitnode.startswith(rev) result = revset.baseset(r for r in subset if matches(r)) if 0 <= len(result) < 2: return result raise error.AmbiguousPrefixLookupError( rev, git.map_file, _(b'ambiguous identifier'), )
def revset_automationrelevant(repo, subset, x): """``automationrelevant(set)`` Changesets relevant to scheduling in automation. Given a revset that evaluates to a single revision, will return that revision and any ancestors that are part of the same push unioned with non-public ancestors. """ s = revset.getset(repo, revset.fullreposet(repo), x) if len(s) > 1: raise error.Abort(b'can only evaluate single changeset') ctx = repo[s.first()] revs = {ctx.rev()} # The pushlog is used to get revisions part of the same push as # the requested revision. pushlog = getattr(repo, 'pushlog', None) if pushlog: push = repo.pushlog.pushfromchangeset(ctx) for n in push.nodes: pctx = repo[n] if pctx.rev() <= ctx.rev(): revs.add(pctx.rev()) # Union with non-public ancestors if configured. By default, we only # consider changesets from the push. However, on special repositories # (namely Try), we want changesets from previous pushes to come into # play too. if repo.ui.configbool(b'hgmo', b'automationrelevantdraftancestors', False): for rev in repo.revs(b'::%d & not public()', ctx.rev()): revs.add(rev) return subset & revset.baseset(revs)
def fxheadsrevset(repo, subset, x): """``fxheads()`` Last known head commits of pulled Firefox trees. """ revset.getargs(x, 0, 0, _("fxheads takes no arguments")) r = revset.baseset(repo[node].rev() for t, node, tr, u in get_firefoxtrees(repo)) return r & subset
def revsettransplanted(repo, subset, x): """Transplanted changesets in set, or all transplanted changesets. """ if x: s = revset.getset(repo, subset, x) else: s = subset return revset.baseset([r for r in s if repo[r].extra().get('transplant_source')])
def revset_fromgit(repo, subset, x): '''``fromgit()`` Select changesets that originate from Git. ''' revset.getargs(x, 0, 0, "fromgit takes no arguments") git = repo.githandler node = repo.changelog.node return revset.baseset(r for r in subset if git.map_git_get(hex(node(r))) is not None)
def revset_firefoxrelease(repo, subset, x): """``firefoxrelease([channel=], [platform=]) Changesets that have Firefox releases built from them. Accepts the following named arguments: channel Which release channel to look at. e.g. ``nightly``. Multiple channels can be delimited by spaces. platform Which platform to limit builds to. e.g. ``win32``. Multiple platforms can be delimited by spaces. If multiple filters are requested filters are combined using logical AND. If no filters are specified, all revisions having a Firefox release are matched. """ args = revset.getargsdict(x, 'firefoxrelease', 'channel platform') channels = set() if 'channel' in args: channels = set( revset.getstring(args['channel'], _('channel requires a string')).split()) platforms = set() if 'platform' in args: platforms = set( revset.getstring(args['platform'], _('platform requires a ' 'string')).split()) db = db_for_repo(repo) if not db: repo.ui.warn(_('(warning: firefoxrelease() revset not available)\n')) return revset.baseset() def get_revs(): for rev, builds in release_builds_by_revision(db, repo).iteritems(): for build in builds: if channels and build.channel not in channels: continue if platforms and build.platform not in platforms: continue yield rev break return subset & revset.generatorset(get_revs())
def _revsetdestrebase(repo, subset, x): # ``_rebasedefaultdest()`` # default destination for rebase. # # XXX: Currently private because I expect the signature to change. # # XXX: - taking rev as arguments, # # XXX: - bailing out in case of ambiguity vs returning all data. # # XXX: - probably merging with the merge destination. # i18n: "_rebasedefaultdest" is a keyword revset.getargs(x, 0, 0, _("_rebasedefaultdest takes no arguments")) return subset & revset.baseset([_destrebase(repo)])
def revset_tree(repo, subset, x): """``tree(X)`` Changesets currently in the specified Mozilla tree. A tree is the name of a repository. e.g. ``central``. """ err = _('tree() requires a string argument.') tree = revset.getstring(x, err) tree, uri = resolve_trees_to_uris([tree])[0] if not uri: raise util.Abort(_("Don't know about tree: %s") % tree) ref = '%s/default' % tree head = repo[ref].rev() ancestors = set(repo.changelog.ancestors([head], inclusive=True)) return subset & revset.baseset(ancestors)
def revset_svnrev(repo, subset, x): '''``svnrev(number)`` Select changesets that originate in the given Subversion revision. ''' args = revset.getargs(x, 1, 1, "svnrev takes one argument") rev = revset.getstring(args[0], "the argument to svnrev() must be a number") try: revnum = int(rev) except ValueError: raise error.ParseError("the argument to svnrev() must be a number") meta = repo.svnmeta(skiperrorcheck=True) if not meta.revmapexists: raise error.Abort("svn metadata is missing - " "run 'hg svn rebuildmeta' to reconstruct it") torev = repo.changelog.rev revs = revset.baseset(torev(r) for r in meta.revmap.revhashes(revnum)) return subset & revs
def getsearchmode(query): try: ctx = web.repo[query] except (error.RepoError, error.LookupError): # query is not an exact revision pointer, need to # decide if it's a revset expression or keywords pass else: return MODE_REVISION, ctx revdef = 'reverse(%s)' % query try: tree, pos = revset.parse(revdef) except ParseError: # can't parse to a revset tree return MODE_KEYWORD, query if revset.depth(tree) <= 2: # no revset syntax used return MODE_KEYWORD, query if util.any((token, (value or '')[:3]) == ('string', 're:') for token, value, pos in revset.tokenize(revdef)): return MODE_KEYWORD, query funcsused = revset.funcsused(tree) if not funcsused.issubset(revset.safesymbols): return MODE_KEYWORD, query mfunc = revset.match(web.repo.ui, revdef) try: revs = mfunc(web.repo, revset.baseset(web.repo)) return MODE_REVSET, revs # ParseError: wrongly placed tokens, wrongs arguments, etc # RepoLookupError: no such revision, e.g. in 'revision:' # Abort: bookmark/tag not exists # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo except (ParseError, RepoLookupError, Abort, LookupError): return MODE_KEYWORD, query
def graph(web, req, tmpl): ctx = webutil.changectx(web.repo, req) rev = ctx.rev() bg_height = 39 revcount = web.maxshortchanges if 'revcount' in req.form: try: revcount = int(req.form.get('revcount', [revcount])[0]) revcount = max(revcount, 1) tmpl.defaults['sessionvars']['revcount'] = revcount except ValueError: pass lessvars = copy.copy(tmpl.defaults['sessionvars']) lessvars['revcount'] = max(revcount / 2, 1) morevars = copy.copy(tmpl.defaults['sessionvars']) morevars['revcount'] = revcount * 2 count = len(web.repo) pos = rev uprev = min(max(0, count - 1), rev + revcount) downrev = max(0, rev - revcount) changenav = webutil.revnav(web.repo).gen(pos, revcount, count) tree = [] if pos != -1: allrevs = web.repo.changelog.revs(pos, 0) revs = [] for i in allrevs: revs.append(i) if len(revs) >= revcount: break # We have to feed a baseset to dagwalker as it is expecting smartset # object. This does not have a big impact on hgweb performance itself # since hgweb graphing code is not itself lazy yet. dag = graphmod.dagwalker(web.repo, revset.baseset(revs)) # As we said one line above... not lazy. tree = list(graphmod.colored(dag, web.repo)) def getcolumns(tree): cols = 0 for (id, type, ctx, vtx, edges) in tree: if type != graphmod.CHANGESET: continue cols = max(cols, max([edge[0] for edge in edges] or [0]), max([edge[1] for edge in edges] or [0])) return cols def graphdata(usetuples, **map): data = [] row = 0 for (id, type, ctx, vtx, edges) in tree: if type != graphmod.CHANGESET: continue node = str(ctx) age = templatefilters.age(ctx.date()) desc = templatefilters.firstline(ctx.description()) desc = cgi.escape(templatefilters.nonempty(desc)) user = cgi.escape(templatefilters.person(ctx.user())) branch = cgi.escape(ctx.branch()) try: branchnode = web.repo.branchtip(branch) except error.RepoLookupError: branchnode = None branch = branch, branchnode == ctx.node() if usetuples: data.append((node, vtx, edges, desc, user, age, branch, [cgi.escape(x) for x in ctx.tags()], [cgi.escape(x) for x in ctx.bookmarks()])) else: edgedata = [{'col': edge[0], 'nextcol': edge[1], 'color': (edge[2] - 1) % 6 + 1, 'width': edge[3], 'bcolor': edge[4]} for edge in edges] data.append( {'node': node, 'col': vtx[0], 'color': (vtx[1] - 1) % 6 + 1, 'edges': edgedata, 'row': row, 'nextrow': row + 1, 'desc': desc, 'user': user, 'age': age, 'bookmarks': webutil.nodebookmarksdict( web.repo, ctx.node()), 'branches': webutil.nodebranchdict(web.repo, ctx), 'inbranch': webutil.nodeinbranch(web.repo, ctx), 'tags': webutil.nodetagsdict(web.repo, ctx.node())}) row += 1 return data cols = getcolumns(tree) rows = len(tree) canvasheight = (rows + 1) * bg_height - 27 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev, lessvars=lessvars, morevars=morevars, downrev=downrev, cols=cols, rows=rows, canvaswidth=(cols + 1) * bg_height, truecanvasheight=rows * bg_height, canvasheight=canvasheight, bg_height=bg_height, jsdata=lambda **x: graphdata(True, **x), nodes=lambda **x: graphdata(False, **x), node=ctx.hex(), changenav=changenav)
def graph(web, req, tmpl): """ /graph[/{revision}] ------------------- Show information about the graphical topology of the repository. Information rendered by this handler can be used to create visual representations of repository topology. The ``revision`` URL parameter controls the starting changeset. The ``revcount`` query string argument can define the number of changesets to show information for. This handler will render the ``graph`` template. """ if 'node' in req.form: ctx = webutil.changectx(web.repo, req) symrev = webutil.symrevorshortnode(req, ctx) else: ctx = web.repo['tip'] symrev = 'tip' rev = ctx.rev() bg_height = 39 revcount = web.maxshortchanges if 'revcount' in req.form: try: revcount = int(req.form.get('revcount', [revcount])[0]) revcount = max(revcount, 1) tmpl.defaults['sessionvars']['revcount'] = revcount except ValueError: pass lessvars = copy.copy(tmpl.defaults['sessionvars']) lessvars['revcount'] = max(revcount / 2, 1) morevars = copy.copy(tmpl.defaults['sessionvars']) morevars['revcount'] = revcount * 2 count = len(web.repo) pos = rev uprev = min(max(0, count - 1), rev + revcount) downrev = max(0, rev - revcount) changenav = webutil.revnav(web.repo).gen(pos, revcount, count) tree = [] if pos != -1: allrevs = web.repo.changelog.revs(pos, 0) revs = [] for i in allrevs: revs.append(i) if len(revs) >= revcount: break # We have to feed a baseset to dagwalker as it is expecting smartset # object. This does not have a big impact on hgweb performance itself # since hgweb graphing code is not itself lazy yet. dag = graphmod.dagwalker(web.repo, revset.baseset(revs)) # As we said one line above... not lazy. tree = list(graphmod.colored(dag, web.repo)) def getcolumns(tree): cols = 0 for (id, type, ctx, vtx, edges) in tree: if type != graphmod.CHANGESET: continue cols = max(cols, max([edge[0] for edge in edges] or [0]), max([edge[1] for edge in edges] or [0])) return cols def graphdata(usetuples, **map): data = [] row = 0 for (id, type, ctx, vtx, edges) in tree: if type != graphmod.CHANGESET: continue node = str(ctx) age = templatefilters.age(ctx.date()) desc = templatefilters.firstline(ctx.description()) desc = cgi.escape(templatefilters.nonempty(desc)) user = cgi.escape(templatefilters.person(ctx.user())) branch = cgi.escape(ctx.branch()) try: branchnode = web.repo.branchtip(branch) except error.RepoLookupError: branchnode = None branch = branch, branchnode == ctx.node() if usetuples: data.append((node, vtx, edges, desc, user, age, branch, [cgi.escape(x) for x in ctx.tags() ], [cgi.escape(x) for x in ctx.bookmarks()])) else: edgedata = [{ 'col': edge[0], 'nextcol': edge[1], 'color': (edge[2] - 1) % 6 + 1, 'width': edge[3], 'bcolor': edge[4] } for edge in edges] data.append({ 'node': node, 'col': vtx[0], 'color': (vtx[1] - 1) % 6 + 1, 'edges': edgedata, 'row': row, 'nextrow': row + 1, 'desc': desc, 'user': user, 'age': age, 'bookmarks': webutil.nodebookmarksdict(web.repo, ctx.node()), 'branches': webutil.nodebranchdict(web.repo, ctx), 'inbranch': webutil.nodeinbranch(web.repo, ctx), 'tags': webutil.nodetagsdict(web.repo, ctx.node()) }) row += 1 return data cols = getcolumns(tree) rows = len(tree) canvasheight = (rows + 1) * bg_height - 27 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount, uprev=uprev, lessvars=lessvars, morevars=morevars, downrev=downrev, cols=cols, rows=rows, canvaswidth=(cols + 1) * bg_height, truecanvasheight=rows * bg_height, canvasheight=canvasheight, bg_height=bg_height, jsdata=lambda **x: graphdata(True, **x), nodes=lambda **x: graphdata(False, **x), node=ctx.hex(), changenav=changenav)