def _filerevision(web, req, tmpl, fctx): f = fctx.path() text = fctx.data() parity = paritygen(web.stripecount) if util.binary(text): mt = mimetypes.guess_type(f)[0] or 'application/octet-stream' text = '(binary:%s)' % mt def lines(): for lineno, t in enumerate(text.splitlines(True)): yield {"line": t, "lineid": "l%d" % (lineno + 1), "linenumber": "% 6d" % (lineno + 1), "parity": parity.next()} return tmpl("filerevision", file=f, path=webutil.up(f), text=lines(), rev=fctx.rev(), symrev=webutil.symrevorshortnode(req, fctx), node=fctx.hex(), author=fctx.user(), date=fctx.date(), desc=fctx.description(), extra=fctx.extra(), branch=webutil.nodebranchnodefault(fctx), parent=webutil.parents(fctx), child=webutil.children(fctx), rename=webutil.renamelink(fctx), tags=webutil.nodetagsdict(web.repo, fctx.node()), bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()), permissions=fctx.manifest().flags(f))
def filediff(web, req, tmpl): """ /diff/{revision}/{path} ----------------------- Show how a file changed in a particular commit. The ``filediff`` template is rendered. This hander is registered under both the ``/diff`` and ``/filediff`` paths. ``/diff`` is used in modern code. """ fctx, ctx = None, None try: fctx = webutil.filectx(web.repo, req) except LookupError: ctx = webutil.changectx(web.repo, req) path = webutil.cleanpath(web.repo, req.form['file'][0]) if path not in ctx.files(): raise if fctx is not None: n = fctx.node() path = fctx.path() ctx = fctx.changectx() else: n = ctx.node() # path already defined in except clause parity = paritygen(web.stripecount) style = web.config('web', 'style', 'paper') if 'style' in req.form: style = req.form['style'][0] diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style) if fctx: rename = webutil.renamelink(fctx) ctx = fctx else: rename = [] ctx = ctx return tmpl("filediff", file=path, node=hex(n), rev=ctx.rev(), symrev=webutil.symrevorshortnode(req, ctx), date=ctx.date(), desc=ctx.description(), extra=ctx.extra(), author=ctx.user(), rename=rename, branch=webutil.nodebranchnodefault(ctx), parent=webutil.parents(ctx), child=webutil.children(ctx), tags=webutil.nodetagsdict(web.repo, n), bookmarks=webutil.nodebookmarksdict(web.repo, n), diff=diffs)
def filelog(web, req, tmpl): """ /filelog/{revision}/{path} -------------------------- Show information about the history of a file in the repository. The ``revcount`` query string argument can be defined to control the maximum number of entries to show. The ``filelog`` template will be rendered. """ try: fctx = webutil.filectx(web.repo, req) f = fctx.path() fl = fctx.filelog() except error.LookupError: f = webutil.cleanpath(web.repo, req.form['file'][0]) fl = web.repo.file(f) numrevs = len(fl) if not numrevs: # file doesn't exist at all raise rev = webutil.changectx(web.repo, req).rev() first = fl.linkrev(0) if rev < first: # current rev is from before file existed raise frev = numrevs - 1 while fl.linkrev(frev) > rev: frev -= 1 fctx = web.repo.filectx(f, fl.linkrev(frev)) 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 = fctx.filerev() + 1 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page end = min(count, start + revcount) # last rev on this page parity = paritygen(web.stripecount, offset=start - end) def entries(): l = [] repo = web.repo revs = fctx.filelog().revs(start, end - 1) for i in revs: iterfctx = fctx.filectx(i) l.append({ "parity": parity.next(), "filerev": i, "file": f, "node": iterfctx.hex(), "author": iterfctx.user(), "date": iterfctx.date(), "rename": webutil.renamelink(iterfctx), "parent": webutil.parents(iterfctx), "child": webutil.children(iterfctx), "desc": iterfctx.description(), "extra": iterfctx.extra(), "tags": webutil.nodetagsdict(repo, iterfctx.node()), "bookmarks": webutil.nodebookmarksdict(repo, iterfctx.node()), "branch": webutil.nodebranchnodefault(iterfctx), "inbranch": webutil.nodeinbranch(repo, iterfctx), "branches": webutil.nodebranchdict(repo, iterfctx) }) for e in reversed(l): yield e entries = list(entries()) latestentry = entries[:1] revnav = webutil.filerevnav(web.repo, fctx.path()) nav = revnav.gen(end - 1, revcount, count) return tmpl("filelog", file=f, node=fctx.hex(), nav=nav, symrev=webutil.symrevorshortnode(req, fctx), entries=entries, latestentry=latestentry, revcount=revcount, morevars=morevars, lessvars=lessvars)
def annotate(web, req, tmpl): """ /annotate/{revision}/{path} --------------------------- Show changeset information for each line in a file. The ``fileannotate`` template is rendered. """ fctx = webutil.filectx(web.repo, req) f = fctx.path() parity = paritygen(web.stripecount) diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True, section='annotate', whitespace=True) def annotate(**map): last = None if util.binary(fctx.data()): mt = (mimetypes.guess_type(fctx.path())[0] or 'application/octet-stream') lines = enumerate([((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]) else: lines = enumerate( fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)) for lineno, ((f, targetline), l) in lines: fnode = f.filenode() if last != fnode: last = fnode yield { "parity": parity.next(), "node": f.hex(), "rev": f.rev(), "author": f.user(), "desc": f.description(), "extra": f.extra(), "file": f.path(), "targetline": targetline, "line": l, "lineno": lineno + 1, "lineid": "l%d" % (lineno + 1), "linenumber": "% 6d" % (lineno + 1), "revdate": f.date() } return tmpl("fileannotate", file=f, annotate=annotate, path=webutil.up(f), rev=fctx.rev(), symrev=webutil.symrevorshortnode(req, fctx), node=fctx.hex(), author=fctx.user(), date=fctx.date(), desc=fctx.description(), extra=fctx.extra(), rename=webutil.renamelink(fctx), branch=webutil.nodebranchnodefault(fctx), parent=webutil.parents(fctx), child=webutil.children(fctx), tags=webutil.nodetagsdict(web.repo, fctx.node()), bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()), permissions=fctx.manifest().flags(f))
def comparison(web, req, tmpl): """ /comparison/{revision}/{path} ----------------------------- Show a comparison between the old and new versions of a file from changes made on a particular revision. This is similar to the ``diff`` handler. However, this form features a split or side-by-side diff rather than a unified diff. The ``context`` query string argument can be used to control the lines of context in the diff. The ``filecomparison`` template is rendered. """ ctx = webutil.changectx(web.repo, req) if 'file' not in req.form: raise ErrorResponse(HTTP_NOT_FOUND, 'file not given') path = webutil.cleanpath(web.repo, req.form['file'][0]) rename = path in ctx and webutil.renamelink(ctx[path]) or [] parsecontext = lambda v: v == 'full' and -1 or int(v) if 'context' in req.form: context = parsecontext(req.form['context'][0]) else: context = parsecontext(web.config('web', 'comparisoncontext', '5')) def filelines(f): if util.binary(f.data()): mt = mimetypes.guess_type(f.path())[0] if not mt: mt = 'application/octet-stream' return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))] return f.data().splitlines() parent = ctx.p1() leftrev = parent.rev() leftnode = parent.node() rightrev = ctx.rev() rightnode = ctx.node() if path in ctx: fctx = ctx[path] rightlines = filelines(fctx) if path not in parent: leftlines = () else: pfctx = parent[path] leftlines = filelines(pfctx) else: rightlines = () fctx = ctx.parents()[0][path] leftlines = filelines(fctx) comparison = webutil.compare(tmpl, context, leftlines, rightlines) return tmpl('filecomparison', file=path, node=hex(ctx.node()), rev=ctx.rev(), symrev=webutil.symrevorshortnode(req, ctx), date=ctx.date(), desc=ctx.description(), extra=ctx.extra(), author=ctx.user(), rename=rename, branch=webutil.nodebranchnodefault(ctx), parent=webutil.parents(fctx), child=webutil.children(fctx), tags=webutil.nodetagsdict(web.repo, ctx.node()), bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()), leftrev=leftrev, leftnode=hex(leftnode), rightrev=rightrev, rightnode=hex(rightnode), comparison=comparison)
def manifest(web, req, tmpl): """ /manifest[/{revision}[/{path}]] ------------------------------- Show information about a directory. If the URL path arguments are omitted, information about the root directory for the ``tip`` changeset will be shown. Because this handler can only show information for directories, it is recommended to use the ``file`` handler instead, as it can handle both directories and files. The ``manifest`` template will be rendered for this handler. """ if 'node' in req.form: ctx = webutil.changectx(web.repo, req) symrev = webutil.symrevorshortnode(req, ctx) else: ctx = web.repo['tip'] symrev = 'tip' path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) mf = ctx.manifest() node = ctx.node() files = {} dirs = {} parity = paritygen(web.stripecount) if path and path[-1] != "/": path += "/" l = len(path) abspath = "/" + path for full, n in mf.iteritems(): # the virtual path (working copy path) used for the full # (repository) path f = decodepath(full) if f[:l] != path: continue remain = f[l:] elements = remain.split('/') if len(elements) == 1: files[remain] = full else: h = dirs # need to retain ref to dirs (root) for elem in elements[0:-1]: if elem not in h: h[elem] = {} h = h[elem] if len(h) > 1: break h[None] = None # denotes files present if mf and not files and not dirs: raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) def filelist(**map): for f in sorted(files): full = files[f] fctx = ctx.filectx(full) yield { "file": full, "parity": parity.next(), "basename": f, "date": fctx.date(), "size": fctx.size(), "permissions": mf.flags(full) } def dirlist(**map): for d in sorted(dirs): emptydirs = [] h = dirs[d] while isinstance(h, dict) and len(h) == 1: k, v = h.items()[0] if v: emptydirs.append(k) h = v path = "%s%s" % (abspath, d) yield { "parity": parity.next(), "path": path, "emptydirs": "/".join(emptydirs), "basename": d } return tmpl("manifest", rev=ctx.rev(), symrev=symrev, node=hex(node), path=abspath, up=webutil.up(abspath), upparity=parity.next(), fentries=filelist, dentries=dirlist, archives=web.archivelist(hex(node)), tags=webutil.nodetagsdict(web.repo, node), bookmarks=webutil.nodebookmarksdict(web.repo, node), branch=webutil.nodebranchnodefault(ctx), inbranch=webutil.nodeinbranch(web.repo, ctx), branches=webutil.nodebranchdict(web.repo, ctx))
def changelog(web, req, tmpl, shortlog=False): """ /changelog[/{revision}] ----------------------- Show information about multiple changesets. If the optional ``revision`` URL argument is absent, information about all changesets starting at ``tip`` will be rendered. If the ``revision`` argument is present, changesets will be shown starting from the specified revision. If ``revision`` is absent, the ``rev`` query string argument may be defined. This will perform a search for changesets. The argument for ``rev`` can be a single revision, a revision set, or a literal keyword to search for in changeset data (equivalent to :hg:`log -k`). The ``revcount`` query string argument defines the maximum numbers of changesets to render. For non-searches, the ``changelog`` template will be rendered. """ query = '' if 'node' in req.form: ctx = webutil.changectx(web.repo, req) symrev = webutil.symrevorshortnode(req, ctx) elif 'rev' in req.form: return _search(web, req, tmpl) else: ctx = web.repo['tip'] symrev = 'tip' def changelist(): revs = [] if pos != -1: revs = web.repo.changelog.revs(pos, 0) curcount = 0 for rev in revs: curcount += 1 if curcount > revcount + 1: break entry = webutil.changelistentry(web, web.repo[rev], tmpl) entry['parity'] = parity.next() yield entry if shortlog: revcount = web.maxshortchanges else: revcount = web.maxchanges 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 = ctx.rev() parity = paritygen(web.stripecount) changenav = webutil.revnav(web.repo).gen(pos, revcount, count) entries = list(changelist()) latestentry = entries[:1] if len(entries) > revcount: nextentry = entries[-1:] entries = entries[:-1] else: nextentry = [] return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav, node=ctx.hex(), rev=pos, symrev=symrev, changesets=count, entries=entries, latestentry=latestentry, nextentry=nextentry, archives=web.archivelist("tip"), revcount=revcount, morevars=morevars, lessvars=lessvars, query=query)
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)
def filelog(web, req, tmpl): """ /filelog/{revision}/{path} -------------------------- Show information about the history of a file in the repository. The ``revcount`` query string argument can be defined to control the maximum number of entries to show. The ``filelog`` template will be rendered. """ try: fctx = webutil.filectx(web.repo, req) f = fctx.path() fl = fctx.filelog() except error.LookupError: f = webutil.cleanpath(web.repo, req.form['file'][0]) fl = web.repo.file(f) numrevs = len(fl) if not numrevs: # file doesn't exist at all raise rev = webutil.changectx(web.repo, req).rev() first = fl.linkrev(0) if rev < first: # current rev is from before file existed raise frev = numrevs - 1 while fl.linkrev(frev) > rev: frev -= 1 fctx = web.repo.filectx(f, fl.linkrev(frev)) 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 = fctx.filerev() + 1 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page end = min(count, start + revcount) # last rev on this page parity = paritygen(web.stripecount, offset=start - end) def entries(): l = [] repo = web.repo revs = fctx.filelog().revs(start, end - 1) for i in revs: iterfctx = fctx.filectx(i) l.append({"parity": parity.next(), "filerev": i, "file": f, "node": iterfctx.hex(), "author": iterfctx.user(), "date": iterfctx.date(), "rename": webutil.renamelink(iterfctx), "parent": webutil.parents(iterfctx), "child": webutil.children(iterfctx), "desc": iterfctx.description(), "extra": iterfctx.extra(), "tags": webutil.nodetagsdict(repo, iterfctx.node()), "bookmarks": webutil.nodebookmarksdict( repo, iterfctx.node()), "branch": webutil.nodebranchnodefault(iterfctx), "inbranch": webutil.nodeinbranch(repo, iterfctx), "branches": webutil.nodebranchdict(repo, iterfctx)}) for e in reversed(l): yield e entries = list(entries()) latestentry = entries[:1] revnav = webutil.filerevnav(web.repo, fctx.path()) nav = revnav.gen(end - 1, revcount, count) return tmpl("filelog", file=f, node=fctx.hex(), nav=nav, symrev=webutil.symrevorshortnode(req, fctx), entries=entries, latestentry=latestentry, revcount=revcount, morevars=morevars, lessvars=lessvars)
def annotate(web, req, tmpl): """ /annotate/{revision}/{path} --------------------------- Show changeset information for each line in a file. The ``fileannotate`` template is rendered. """ fctx = webutil.filectx(web.repo, req) f = fctx.path() parity = paritygen(web.stripecount) diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True, section='annotate', whitespace=True) def annotate(**map): last = None if util.binary(fctx.data()): mt = (mimetypes.guess_type(fctx.path())[0] or 'application/octet-stream') lines = enumerate([((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]) else: lines = enumerate(fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)) for lineno, ((f, targetline), l) in lines: fnode = f.filenode() if last != fnode: last = fnode yield {"parity": parity.next(), "node": f.hex(), "rev": f.rev(), "author": f.user(), "desc": f.description(), "extra": f.extra(), "file": f.path(), "targetline": targetline, "line": l, "lineno": lineno + 1, "lineid": "l%d" % (lineno + 1), "linenumber": "% 6d" % (lineno + 1), "revdate": f.date()} return tmpl("fileannotate", file=f, annotate=annotate, path=webutil.up(f), rev=fctx.rev(), symrev=webutil.symrevorshortnode(req, fctx), node=fctx.hex(), author=fctx.user(), date=fctx.date(), desc=fctx.description(), extra=fctx.extra(), rename=webutil.renamelink(fctx), branch=webutil.nodebranchnodefault(fctx), parent=webutil.parents(fctx), child=webutil.children(fctx), tags=webutil.nodetagsdict(web.repo, fctx.node()), bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()), permissions=fctx.manifest().flags(f))
def manifest(web, req, tmpl): """ /manifest[/{revision}[/{path}]] ------------------------------- Show information about a directory. If the URL path arguments are omitted, information about the root directory for the ``tip`` changeset will be shown. Because this handler can only show information for directories, it is recommended to use the ``file`` handler instead, as it can handle both directories and files. The ``manifest`` template will be rendered for this handler. """ if 'node' in req.form: ctx = webutil.changectx(web.repo, req) symrev = webutil.symrevorshortnode(req, ctx) else: ctx = web.repo['tip'] symrev = 'tip' path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) mf = ctx.manifest() node = ctx.node() files = {} dirs = {} parity = paritygen(web.stripecount) if path and path[-1] != "/": path += "/" l = len(path) abspath = "/" + path for full, n in mf.iteritems(): # the virtual path (working copy path) used for the full # (repository) path f = decodepath(full) if f[:l] != path: continue remain = f[l:] elements = remain.split('/') if len(elements) == 1: files[remain] = full else: h = dirs # need to retain ref to dirs (root) for elem in elements[0:-1]: if elem not in h: h[elem] = {} h = h[elem] if len(h) > 1: break h[None] = None # denotes files present if mf and not files and not dirs: raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) def filelist(**map): for f in sorted(files): full = files[f] fctx = ctx.filectx(full) yield {"file": full, "parity": parity.next(), "basename": f, "date": fctx.date(), "size": fctx.size(), "permissions": mf.flags(full)} def dirlist(**map): for d in sorted(dirs): emptydirs = [] h = dirs[d] while isinstance(h, dict) and len(h) == 1: k, v = h.items()[0] if v: emptydirs.append(k) h = v path = "%s%s" % (abspath, d) yield {"parity": parity.next(), "path": path, "emptydirs": "/".join(emptydirs), "basename": d} return tmpl("manifest", rev=ctx.rev(), symrev=symrev, node=hex(node), path=abspath, up=webutil.up(abspath), upparity=parity.next(), fentries=filelist, dentries=dirlist, archives=web.archivelist(hex(node)), tags=webutil.nodetagsdict(web.repo, node), bookmarks=webutil.nodebookmarksdict(web.repo, node), branch=webutil.nodebranchnodefault(ctx), inbranch=webutil.nodeinbranch(web.repo, ctx), branches=webutil.nodebranchdict(web.repo, ctx))
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)