eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/hgweb/webcommands.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 #
       
     2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
       
     3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 import os, mimetypes, re, cgi, copy
       
     9 import webutil
       
    10 from mercurial import error, encoding, archival, templater, templatefilters
       
    11 from mercurial.node import short, hex
       
    12 from mercurial.util import binary
       
    13 from common import paritygen, staticfile, get_contact, ErrorResponse
       
    14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
       
    15 from mercurial import graphmod
       
    16 from mercurial import help as helpmod
       
    17 from mercurial.i18n import _
       
    18 
       
    19 # __all__ is populated with the allowed commands. Be sure to add to it if
       
    20 # you're adding a new command, or the new command won't work.
       
    21 
       
    22 __all__ = [
       
    23    'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
       
    24    'manifest', 'tags', 'branches', 'summary', 'filediff', 'diff', 'annotate',
       
    25    'filelog', 'archive', 'static', 'graph', 'help',
       
    26 ]
       
    27 
       
    28 def log(web, req, tmpl):
       
    29     if 'file' in req.form and req.form['file'][0]:
       
    30         return filelog(web, req, tmpl)
       
    31     else:
       
    32         return changelog(web, req, tmpl)
       
    33 
       
    34 def rawfile(web, req, tmpl):
       
    35     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
       
    36     if not path:
       
    37         content = manifest(web, req, tmpl)
       
    38         req.respond(HTTP_OK, web.ctype)
       
    39         return content
       
    40 
       
    41     try:
       
    42         fctx = webutil.filectx(web.repo, req)
       
    43     except error.LookupError, inst:
       
    44         try:
       
    45             content = manifest(web, req, tmpl)
       
    46             req.respond(HTTP_OK, web.ctype)
       
    47             return content
       
    48         except ErrorResponse:
       
    49             raise inst
       
    50 
       
    51     path = fctx.path()
       
    52     text = fctx.data()
       
    53     mt = mimetypes.guess_type(path)[0]
       
    54     if mt is None:
       
    55         mt = binary(text) and 'application/octet-stream' or 'text/plain'
       
    56     if mt.startswith('text/'):
       
    57         mt += '; charset="%s"' % encoding.encoding
       
    58 
       
    59     req.respond(HTTP_OK, mt, path, len(text))
       
    60     return [text]
       
    61 
       
    62 def _filerevision(web, tmpl, fctx):
       
    63     f = fctx.path()
       
    64     text = fctx.data()
       
    65     parity = paritygen(web.stripecount)
       
    66 
       
    67     if binary(text):
       
    68         mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
       
    69         text = '(binary:%s)' % mt
       
    70 
       
    71     def lines():
       
    72         for lineno, t in enumerate(text.splitlines(True)):
       
    73             yield {"line": t,
       
    74                    "lineid": "l%d" % (lineno + 1),
       
    75                    "linenumber": "% 6d" % (lineno + 1),
       
    76                    "parity": parity.next()}
       
    77 
       
    78     return tmpl("filerevision",
       
    79                 file=f,
       
    80                 path=webutil.up(f),
       
    81                 text=lines(),
       
    82                 rev=fctx.rev(),
       
    83                 node=hex(fctx.node()),
       
    84                 author=fctx.user(),
       
    85                 date=fctx.date(),
       
    86                 desc=fctx.description(),
       
    87                 branch=webutil.nodebranchnodefault(fctx),
       
    88                 parent=webutil.parents(fctx),
       
    89                 child=webutil.children(fctx),
       
    90                 rename=webutil.renamelink(fctx),
       
    91                 permissions=fctx.manifest().flags(f))
       
    92 
       
    93 def file(web, req, tmpl):
       
    94     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
       
    95     if not path:
       
    96         return manifest(web, req, tmpl)
       
    97     try:
       
    98         return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
       
    99     except error.LookupError, inst:
       
   100         try:
       
   101             return manifest(web, req, tmpl)
       
   102         except ErrorResponse:
       
   103             raise inst
       
   104 
       
   105 def _search(web, req, tmpl):
       
   106 
       
   107     query = req.form['rev'][0]
       
   108     revcount = web.maxchanges
       
   109     if 'revcount' in req.form:
       
   110         revcount = int(req.form.get('revcount', [revcount])[0])
       
   111         tmpl.defaults['sessionvars']['revcount'] = revcount
       
   112 
       
   113     lessvars = copy.copy(tmpl.defaults['sessionvars'])
       
   114     lessvars['revcount'] = revcount / 2
       
   115     lessvars['rev'] = query
       
   116     morevars = copy.copy(tmpl.defaults['sessionvars'])
       
   117     morevars['revcount'] = revcount * 2
       
   118     morevars['rev'] = query
       
   119 
       
   120     def changelist(**map):
       
   121         count = 0
       
   122         qw = query.lower().split()
       
   123 
       
   124         def revgen():
       
   125             for i in xrange(len(web.repo) - 1, 0, -100):
       
   126                 l = []
       
   127                 for j in xrange(max(0, i - 100), i + 1):
       
   128                     ctx = web.repo[j]
       
   129                     l.append(ctx)
       
   130                 l.reverse()
       
   131                 for e in l:
       
   132                     yield e
       
   133 
       
   134         for ctx in revgen():
       
   135             miss = 0
       
   136             for q in qw:
       
   137                 if not (q in ctx.user().lower() or
       
   138                         q in ctx.description().lower() or
       
   139                         q in " ".join(ctx.files()).lower()):
       
   140                     miss = 1
       
   141                     break
       
   142             if miss:
       
   143                 continue
       
   144 
       
   145             count += 1
       
   146             n = ctx.node()
       
   147             showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
       
   148             files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
       
   149 
       
   150             yield tmpl('searchentry',
       
   151                        parity=parity.next(),
       
   152                        author=ctx.user(),
       
   153                        parent=webutil.parents(ctx),
       
   154                        child=webutil.children(ctx),
       
   155                        changelogtag=showtags,
       
   156                        desc=ctx.description(),
       
   157                        date=ctx.date(),
       
   158                        files=files,
       
   159                        rev=ctx.rev(),
       
   160                        node=hex(n),
       
   161                        tags=webutil.nodetagsdict(web.repo, n),
       
   162                        inbranch=webutil.nodeinbranch(web.repo, ctx),
       
   163                        branches=webutil.nodebranchdict(web.repo, ctx))
       
   164 
       
   165             if count >= revcount:
       
   166                 break
       
   167 
       
   168     tip = web.repo['tip']
       
   169     parity = paritygen(web.stripecount)
       
   170 
       
   171     return tmpl('search', query=query, node=tip.hex(),
       
   172                 entries=changelist, archives=web.archivelist("tip"),
       
   173                 morevars=morevars, lessvars=lessvars)
       
   174 
       
   175 def changelog(web, req, tmpl, shortlog=False):
       
   176 
       
   177     if 'node' in req.form:
       
   178         ctx = webutil.changectx(web.repo, req)
       
   179     else:
       
   180         if 'rev' in req.form:
       
   181             hi = req.form['rev'][0]
       
   182         else:
       
   183             hi = len(web.repo) - 1
       
   184         try:
       
   185             ctx = web.repo[hi]
       
   186         except error.RepoError:
       
   187             return _search(web, req, tmpl) # XXX redirect to 404 page?
       
   188 
       
   189     def changelist(limit=0, **map):
       
   190         l = [] # build a list in forward order for efficiency
       
   191         for i in xrange(start, end):
       
   192             ctx = web.repo[i]
       
   193             n = ctx.node()
       
   194             showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
       
   195             files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
       
   196 
       
   197             l.insert(0, {"parity": parity.next(),
       
   198                          "author": ctx.user(),
       
   199                          "parent": webutil.parents(ctx, i - 1),
       
   200                          "child": webutil.children(ctx, i + 1),
       
   201                          "changelogtag": showtags,
       
   202                          "desc": ctx.description(),
       
   203                          "date": ctx.date(),
       
   204                          "files": files,
       
   205                          "rev": i,
       
   206                          "node": hex(n),
       
   207                          "tags": webutil.nodetagsdict(web.repo, n),
       
   208                          "inbranch": webutil.nodeinbranch(web.repo, ctx),
       
   209                          "branches": webutil.nodebranchdict(web.repo, ctx)
       
   210                         })
       
   211 
       
   212         if limit > 0:
       
   213             l = l[:limit]
       
   214 
       
   215         for e in l:
       
   216             yield e
       
   217 
       
   218     revcount = shortlog and web.maxshortchanges or web.maxchanges
       
   219     if 'revcount' in req.form:
       
   220         revcount = int(req.form.get('revcount', [revcount])[0])
       
   221         tmpl.defaults['sessionvars']['revcount'] = revcount
       
   222 
       
   223     lessvars = copy.copy(tmpl.defaults['sessionvars'])
       
   224     lessvars['revcount'] = revcount / 2
       
   225     morevars = copy.copy(tmpl.defaults['sessionvars'])
       
   226     morevars['revcount'] = revcount * 2
       
   227 
       
   228     count = len(web.repo)
       
   229     pos = ctx.rev()
       
   230     start = max(0, pos - revcount + 1)
       
   231     end = min(count, start + revcount)
       
   232     pos = end - 1
       
   233     parity = paritygen(web.stripecount, offset=start - end)
       
   234 
       
   235     changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
       
   236 
       
   237     return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
       
   238                 node=hex(ctx.node()), rev=pos, changesets=count,
       
   239                 entries=lambda **x: changelist(limit=0,**x),
       
   240                 latestentry=lambda **x: changelist(limit=1,**x),
       
   241                 archives=web.archivelist("tip"), revcount=revcount,
       
   242                 morevars=morevars, lessvars=lessvars)
       
   243 
       
   244 def shortlog(web, req, tmpl):
       
   245     return changelog(web, req, tmpl, shortlog = True)
       
   246 
       
   247 def changeset(web, req, tmpl):
       
   248     ctx = webutil.changectx(web.repo, req)
       
   249     showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
       
   250     showbranch = webutil.nodebranchnodefault(ctx)
       
   251 
       
   252     files = []
       
   253     parity = paritygen(web.stripecount)
       
   254     for f in ctx.files():
       
   255         template = f in ctx and 'filenodelink' or 'filenolink'
       
   256         files.append(tmpl(template,
       
   257                           node=ctx.hex(), file=f,
       
   258                           parity=parity.next()))
       
   259 
       
   260     parity = paritygen(web.stripecount)
       
   261     style = web.config('web', 'style', 'paper')
       
   262     if 'style' in req.form:
       
   263         style = req.form['style'][0]
       
   264 
       
   265     diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
       
   266     return tmpl('changeset',
       
   267                 diff=diffs,
       
   268                 rev=ctx.rev(),
       
   269                 node=ctx.hex(),
       
   270                 parent=webutil.parents(ctx),
       
   271                 child=webutil.children(ctx),
       
   272                 changesettag=showtags,
       
   273                 changesetbranch=showbranch,
       
   274                 author=ctx.user(),
       
   275                 desc=ctx.description(),
       
   276                 date=ctx.date(),
       
   277                 files=files,
       
   278                 archives=web.archivelist(ctx.hex()),
       
   279                 tags=webutil.nodetagsdict(web.repo, ctx.node()),
       
   280                 branch=webutil.nodebranchnodefault(ctx),
       
   281                 inbranch=webutil.nodeinbranch(web.repo, ctx),
       
   282                 branches=webutil.nodebranchdict(web.repo, ctx))
       
   283 
       
   284 rev = changeset
       
   285 
       
   286 def manifest(web, req, tmpl):
       
   287     ctx = webutil.changectx(web.repo, req)
       
   288     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
       
   289     mf = ctx.manifest()
       
   290     node = ctx.node()
       
   291 
       
   292     files = {}
       
   293     dirs = {}
       
   294     parity = paritygen(web.stripecount)
       
   295 
       
   296     if path and path[-1] != "/":
       
   297         path += "/"
       
   298     l = len(path)
       
   299     abspath = "/" + path
       
   300 
       
   301     for f, n in mf.iteritems():
       
   302         if f[:l] != path:
       
   303             continue
       
   304         remain = f[l:]
       
   305         elements = remain.split('/')
       
   306         if len(elements) == 1:
       
   307             files[remain] = f
       
   308         else:
       
   309             h = dirs # need to retain ref to dirs (root)
       
   310             for elem in elements[0:-1]:
       
   311                 if elem not in h:
       
   312                     h[elem] = {}
       
   313                 h = h[elem]
       
   314                 if len(h) > 1:
       
   315                     break
       
   316             h[None] = None # denotes files present
       
   317 
       
   318     if mf and not files and not dirs:
       
   319         raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
       
   320 
       
   321     def filelist(**map):
       
   322         for f in sorted(files):
       
   323             full = files[f]
       
   324 
       
   325             fctx = ctx.filectx(full)
       
   326             yield {"file": full,
       
   327                    "parity": parity.next(),
       
   328                    "basename": f,
       
   329                    "date": fctx.date(),
       
   330                    "size": fctx.size(),
       
   331                    "permissions": mf.flags(full)}
       
   332 
       
   333     def dirlist(**map):
       
   334         for d in sorted(dirs):
       
   335 
       
   336             emptydirs = []
       
   337             h = dirs[d]
       
   338             while isinstance(h, dict) and len(h) == 1:
       
   339                 k, v = h.items()[0]
       
   340                 if v:
       
   341                     emptydirs.append(k)
       
   342                 h = v
       
   343 
       
   344             path = "%s%s" % (abspath, d)
       
   345             yield {"parity": parity.next(),
       
   346                    "path": path,
       
   347                    "emptydirs": "/".join(emptydirs),
       
   348                    "basename": d}
       
   349 
       
   350     return tmpl("manifest",
       
   351                 rev=ctx.rev(),
       
   352                 node=hex(node),
       
   353                 path=abspath,
       
   354                 up=webutil.up(abspath),
       
   355                 upparity=parity.next(),
       
   356                 fentries=filelist,
       
   357                 dentries=dirlist,
       
   358                 archives=web.archivelist(hex(node)),
       
   359                 tags=webutil.nodetagsdict(web.repo, node),
       
   360                 inbranch=webutil.nodeinbranch(web.repo, ctx),
       
   361                 branches=webutil.nodebranchdict(web.repo, ctx))
       
   362 
       
   363 def tags(web, req, tmpl):
       
   364     i = web.repo.tagslist()
       
   365     i.reverse()
       
   366     parity = paritygen(web.stripecount)
       
   367 
       
   368     def entries(notip=False, limit=0, **map):
       
   369         count = 0
       
   370         for k, n in i:
       
   371             if notip and k == "tip":
       
   372                 continue
       
   373             if limit > 0 and count >= limit:
       
   374                 continue
       
   375             count = count + 1
       
   376             yield {"parity": parity.next(),
       
   377                    "tag": k,
       
   378                    "date": web.repo[n].date(),
       
   379                    "node": hex(n)}
       
   380 
       
   381     return tmpl("tags",
       
   382                 node=hex(web.repo.changelog.tip()),
       
   383                 entries=lambda **x: entries(False, 0, **x),
       
   384                 entriesnotip=lambda **x: entries(True, 0, **x),
       
   385                 latestentry=lambda **x: entries(True, 1, **x))
       
   386 
       
   387 def branches(web, req, tmpl):
       
   388     tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
       
   389     heads = web.repo.heads()
       
   390     parity = paritygen(web.stripecount)
       
   391     sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
       
   392 
       
   393     def entries(limit, **map):
       
   394         count = 0
       
   395         for ctx in sorted(tips, key=sortkey, reverse=True):
       
   396             if limit > 0 and count >= limit:
       
   397                 return
       
   398             count += 1
       
   399             if ctx.node() not in heads:
       
   400                 status = 'inactive'
       
   401             elif not web.repo.branchheads(ctx.branch()):
       
   402                 status = 'closed'
       
   403             else:
       
   404                 status = 'open'
       
   405             yield {'parity': parity.next(),
       
   406                    'branch': ctx.branch(),
       
   407                    'status': status,
       
   408                    'node': ctx.hex(),
       
   409                    'date': ctx.date()}
       
   410 
       
   411     return tmpl('branches', node=hex(web.repo.changelog.tip()),
       
   412                 entries=lambda **x: entries(0, **x),
       
   413                 latestentry=lambda **x: entries(1, **x))
       
   414 
       
   415 def summary(web, req, tmpl):
       
   416     i = web.repo.tagslist()
       
   417     i.reverse()
       
   418 
       
   419     def tagentries(**map):
       
   420         parity = paritygen(web.stripecount)
       
   421         count = 0
       
   422         for k, n in i:
       
   423             if k == "tip": # skip tip
       
   424                 continue
       
   425 
       
   426             count += 1
       
   427             if count > 10: # limit to 10 tags
       
   428                 break
       
   429 
       
   430             yield tmpl("tagentry",
       
   431                        parity=parity.next(),
       
   432                        tag=k,
       
   433                        node=hex(n),
       
   434                        date=web.repo[n].date())
       
   435 
       
   436     def branches(**map):
       
   437         parity = paritygen(web.stripecount)
       
   438 
       
   439         b = web.repo.branchtags()
       
   440         l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
       
   441         for r, n, t in sorted(l):
       
   442             yield {'parity': parity.next(),
       
   443                    'branch': t,
       
   444                    'node': hex(n),
       
   445                    'date': web.repo[n].date()}
       
   446 
       
   447     def changelist(**map):
       
   448         parity = paritygen(web.stripecount, offset=start - end)
       
   449         l = [] # build a list in forward order for efficiency
       
   450         for i in xrange(start, end):
       
   451             ctx = web.repo[i]
       
   452             n = ctx.node()
       
   453             hn = hex(n)
       
   454 
       
   455             l.insert(0, tmpl(
       
   456                'shortlogentry',
       
   457                 parity=parity.next(),
       
   458                 author=ctx.user(),
       
   459                 desc=ctx.description(),
       
   460                 date=ctx.date(),
       
   461                 rev=i,
       
   462                 node=hn,
       
   463                 tags=webutil.nodetagsdict(web.repo, n),
       
   464                 inbranch=webutil.nodeinbranch(web.repo, ctx),
       
   465                 branches=webutil.nodebranchdict(web.repo, ctx)))
       
   466 
       
   467         yield l
       
   468 
       
   469     tip = web.repo['tip']
       
   470     count = len(web.repo)
       
   471     start = max(0, count - web.maxchanges)
       
   472     end = min(count, start + web.maxchanges)
       
   473 
       
   474     return tmpl("summary",
       
   475                 desc=web.config("web", "description", "unknown"),
       
   476                 owner=get_contact(web.config) or "unknown",
       
   477                 lastchange=tip.date(),
       
   478                 tags=tagentries,
       
   479                 branches=branches,
       
   480                 shortlog=changelist,
       
   481                 node=tip.hex(),
       
   482                 archives=web.archivelist("tip"))
       
   483 
       
   484 def filediff(web, req, tmpl):
       
   485     fctx, ctx = None, None
       
   486     try:
       
   487         fctx = webutil.filectx(web.repo, req)
       
   488     except LookupError:
       
   489         ctx = webutil.changectx(web.repo, req)
       
   490         path = webutil.cleanpath(web.repo, req.form['file'][0])
       
   491         if path not in ctx.files():
       
   492             raise
       
   493 
       
   494     if fctx is not None:
       
   495         n = fctx.node()
       
   496         path = fctx.path()
       
   497     else:
       
   498         n = ctx.node()
       
   499         # path already defined in except clause
       
   500 
       
   501     parity = paritygen(web.stripecount)
       
   502     style = web.config('web', 'style', 'paper')
       
   503     if 'style' in req.form:
       
   504         style = req.form['style'][0]
       
   505 
       
   506     diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style)
       
   507     rename = fctx and webutil.renamelink(fctx) or []
       
   508     ctx = fctx and fctx or ctx
       
   509     return tmpl("filediff",
       
   510                 file=path,
       
   511                 node=hex(n),
       
   512                 rev=ctx.rev(),
       
   513                 date=ctx.date(),
       
   514                 desc=ctx.description(),
       
   515                 author=ctx.user(),
       
   516                 rename=rename,
       
   517                 branch=webutil.nodebranchnodefault(ctx),
       
   518                 parent=webutil.parents(ctx),
       
   519                 child=webutil.children(ctx),
       
   520                 diff=diffs)
       
   521 
       
   522 diff = filediff
       
   523 
       
   524 def annotate(web, req, tmpl):
       
   525     fctx = webutil.filectx(web.repo, req)
       
   526     f = fctx.path()
       
   527     parity = paritygen(web.stripecount)
       
   528 
       
   529     def annotate(**map):
       
   530         last = None
       
   531         if binary(fctx.data()):
       
   532             mt = (mimetypes.guess_type(fctx.path())[0]
       
   533                   or 'application/octet-stream')
       
   534             lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
       
   535                                 '(binary:%s)' % mt)])
       
   536         else:
       
   537             lines = enumerate(fctx.annotate(follow=True, linenumber=True))
       
   538         for lineno, ((f, targetline), l) in lines:
       
   539             fnode = f.filenode()
       
   540 
       
   541             if last != fnode:
       
   542                 last = fnode
       
   543 
       
   544             yield {"parity": parity.next(),
       
   545                    "node": hex(f.node()),
       
   546                    "rev": f.rev(),
       
   547                    "author": f.user(),
       
   548                    "desc": f.description(),
       
   549                    "file": f.path(),
       
   550                    "targetline": targetline,
       
   551                    "line": l,
       
   552                    "lineid": "l%d" % (lineno + 1),
       
   553                    "linenumber": "% 6d" % (lineno + 1)}
       
   554 
       
   555     return tmpl("fileannotate",
       
   556                 file=f,
       
   557                 annotate=annotate,
       
   558                 path=webutil.up(f),
       
   559                 rev=fctx.rev(),
       
   560                 node=hex(fctx.node()),
       
   561                 author=fctx.user(),
       
   562                 date=fctx.date(),
       
   563                 desc=fctx.description(),
       
   564                 rename=webutil.renamelink(fctx),
       
   565                 branch=webutil.nodebranchnodefault(fctx),
       
   566                 parent=webutil.parents(fctx),
       
   567                 child=webutil.children(fctx),
       
   568                 permissions=fctx.manifest().flags(f))
       
   569 
       
   570 def filelog(web, req, tmpl):
       
   571 
       
   572     try:
       
   573         fctx = webutil.filectx(web.repo, req)
       
   574         f = fctx.path()
       
   575         fl = fctx.filelog()
       
   576     except error.LookupError:
       
   577         f = webutil.cleanpath(web.repo, req.form['file'][0])
       
   578         fl = web.repo.file(f)
       
   579         numrevs = len(fl)
       
   580         if not numrevs: # file doesn't exist at all
       
   581             raise
       
   582         rev = webutil.changectx(web.repo, req).rev()
       
   583         first = fl.linkrev(0)
       
   584         if rev < first: # current rev is from before file existed
       
   585             raise
       
   586         frev = numrevs - 1
       
   587         while fl.linkrev(frev) > rev:
       
   588             frev -= 1
       
   589         fctx = web.repo.filectx(f, fl.linkrev(frev))
       
   590 
       
   591     revcount = web.maxshortchanges
       
   592     if 'revcount' in req.form:
       
   593         revcount = int(req.form.get('revcount', [revcount])[0])
       
   594         tmpl.defaults['sessionvars']['revcount'] = revcount
       
   595 
       
   596     lessvars = copy.copy(tmpl.defaults['sessionvars'])
       
   597     lessvars['revcount'] = revcount / 2
       
   598     morevars = copy.copy(tmpl.defaults['sessionvars'])
       
   599     morevars['revcount'] = revcount * 2
       
   600 
       
   601     count = fctx.filerev() + 1
       
   602     start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
       
   603     end = min(count, start + revcount) # last rev on this page
       
   604     parity = paritygen(web.stripecount, offset=start - end)
       
   605 
       
   606     def entries(limit=0, **map):
       
   607         l = []
       
   608 
       
   609         repo = web.repo
       
   610         for i in xrange(start, end):
       
   611             iterfctx = fctx.filectx(i)
       
   612 
       
   613             l.insert(0, {"parity": parity.next(),
       
   614                          "filerev": i,
       
   615                          "file": f,
       
   616                          "node": hex(iterfctx.node()),
       
   617                          "author": iterfctx.user(),
       
   618                          "date": iterfctx.date(),
       
   619                          "rename": webutil.renamelink(iterfctx),
       
   620                          "parent": webutil.parents(iterfctx),
       
   621                          "child": webutil.children(iterfctx),
       
   622                          "desc": iterfctx.description(),
       
   623                          "tags": webutil.nodetagsdict(repo, iterfctx.node()),
       
   624                          "branch": webutil.nodebranchnodefault(iterfctx),
       
   625                          "inbranch": webutil.nodeinbranch(repo, iterfctx),
       
   626                          "branches": webutil.nodebranchdict(repo, iterfctx)})
       
   627 
       
   628         if limit > 0:
       
   629             l = l[:limit]
       
   630 
       
   631         for e in l:
       
   632             yield e
       
   633 
       
   634     nodefunc = lambda x: fctx.filectx(fileid=x)
       
   635     nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
       
   636     return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
       
   637                 entries=lambda **x: entries(limit=0, **x),
       
   638                 latestentry=lambda **x: entries(limit=1, **x),
       
   639                 revcount=revcount, morevars=morevars, lessvars=lessvars)
       
   640 
       
   641 def archive(web, req, tmpl):
       
   642     type_ = req.form.get('type', [None])[0]
       
   643     allowed = web.configlist("web", "allow_archive")
       
   644     key = req.form['node'][0]
       
   645 
       
   646     if type_ not in web.archives:
       
   647         msg = 'Unsupported archive type: %s' % type_
       
   648         raise ErrorResponse(HTTP_NOT_FOUND, msg)
       
   649 
       
   650     if not ((type_ in allowed or
       
   651         web.configbool("web", "allow" + type_, False))):
       
   652         msg = 'Archive type not allowed: %s' % type_
       
   653         raise ErrorResponse(HTTP_FORBIDDEN, msg)
       
   654 
       
   655     reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
       
   656     cnode = web.repo.lookup(key)
       
   657     arch_version = key
       
   658     if cnode == key or key == 'tip':
       
   659         arch_version = short(cnode)
       
   660     name = "%s-%s" % (reponame, arch_version)
       
   661     mimetype, artype, extension, encoding = web.archive_specs[type_]
       
   662     headers = [
       
   663         ('Content-Type', mimetype),
       
   664         ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
       
   665     ]
       
   666     if encoding:
       
   667         headers.append(('Content-Encoding', encoding))
       
   668     req.header(headers)
       
   669     req.respond(HTTP_OK)
       
   670     archival.archive(web.repo, req, cnode, artype, prefix=name)
       
   671     return []
       
   672 
       
   673 
       
   674 def static(web, req, tmpl):
       
   675     fname = req.form['file'][0]
       
   676     # a repo owner may set web.static in .hg/hgrc to get any file
       
   677     # readable by the user running the CGI script
       
   678     static = web.config("web", "static", None, untrusted=False)
       
   679     if not static:
       
   680         tp = web.templatepath or templater.templatepath()
       
   681         if isinstance(tp, str):
       
   682             tp = [tp]
       
   683         static = [os.path.join(p, 'static') for p in tp]
       
   684     return [staticfile(static, fname, req)]
       
   685 
       
   686 def graph(web, req, tmpl):
       
   687 
       
   688     rev = webutil.changectx(web.repo, req).rev()
       
   689     bg_height = 39
       
   690     revcount = web.maxshortchanges
       
   691     if 'revcount' in req.form:
       
   692         revcount = int(req.form.get('revcount', [revcount])[0])
       
   693         tmpl.defaults['sessionvars']['revcount'] = revcount
       
   694 
       
   695     lessvars = copy.copy(tmpl.defaults['sessionvars'])
       
   696     lessvars['revcount'] = revcount / 2
       
   697     morevars = copy.copy(tmpl.defaults['sessionvars'])
       
   698     morevars['revcount'] = revcount * 2
       
   699 
       
   700     max_rev = len(web.repo) - 1
       
   701     revcount = min(max_rev, revcount)
       
   702     revnode = web.repo.changelog.node(rev)
       
   703     revnode_hex = hex(revnode)
       
   704     uprev = min(max_rev, rev + revcount)
       
   705     downrev = max(0, rev - revcount)
       
   706     count = len(web.repo)
       
   707     changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
       
   708 
       
   709     dag = graphmod.revisions(web.repo, rev, downrev)
       
   710     tree = list(graphmod.colored(dag))
       
   711     canvasheight = (len(tree) + 1) * bg_height - 27
       
   712     data = []
       
   713     for (id, type, ctx, vtx, edges) in tree:
       
   714         if type != graphmod.CHANGESET:
       
   715             continue
       
   716         node = short(ctx.node())
       
   717         age = templatefilters.age(ctx.date())
       
   718         desc = templatefilters.firstline(ctx.description())
       
   719         desc = cgi.escape(templatefilters.nonempty(desc))
       
   720         user = cgi.escape(templatefilters.person(ctx.user()))
       
   721         branch = ctx.branch()
       
   722         branch = branch, web.repo.branchtags().get(branch) == ctx.node()
       
   723         data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
       
   724 
       
   725     return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
       
   726                 lessvars=lessvars, morevars=morevars, downrev=downrev,
       
   727                 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
       
   728                 node=revnode_hex, changenav=changenav)
       
   729 
       
   730 def _getdoc(e):
       
   731     doc = e[0].__doc__
       
   732     if doc:
       
   733         doc = doc.split('\n')[0]
       
   734     else:
       
   735         doc = _('(no help text available)')
       
   736     return doc
       
   737 
       
   738 def help(web, req, tmpl):
       
   739     from mercurial import commands # avoid cycle
       
   740 
       
   741     topicname = req.form.get('node', [None])[0]
       
   742     if not topicname:
       
   743         topic = []
       
   744 
       
   745         def topics(**map):
       
   746             for entries, summary, _ in helpmod.helptable:
       
   747                 entries = sorted(entries, key=len)
       
   748                 yield {'topic': entries[-1], 'summary': summary}
       
   749 
       
   750         early, other = [], []
       
   751         primary = lambda s: s.split('|')[0]
       
   752         for c, e in commands.table.iteritems():
       
   753             doc = _getdoc(e)
       
   754             if 'DEPRECATED' in doc or c.startswith('debug'):
       
   755                 continue
       
   756             cmd = primary(c)
       
   757             if cmd.startswith('^'):
       
   758                 early.append((cmd[1:], doc))
       
   759             else:
       
   760                 other.append((cmd, doc))
       
   761 
       
   762         early.sort()
       
   763         other.sort()
       
   764 
       
   765         def earlycommands(**map):
       
   766             for c, doc in early:
       
   767                 yield {'topic': c, 'summary': doc}
       
   768 
       
   769         def othercommands(**map):
       
   770             for c, doc in other:
       
   771                 yield {'topic': c, 'summary': doc}
       
   772 
       
   773         return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
       
   774                     othercommands=othercommands, title='Index')
       
   775 
       
   776     u = webutil.wsgiui()
       
   777     u.pushbuffer()
       
   778     try:
       
   779         commands.help_(u, topicname)
       
   780     except error.UnknownCommand:
       
   781         raise ErrorResponse(HTTP_NOT_FOUND)
       
   782     doc = u.popbuffer()
       
   783     return tmpl('help', topic=topicname, doc=doc)