mercurial/hgweb.py
changeset 138 c77a679e9cfa
parent 137 b45b1b00fc9e
child 142 529bf610092e
equal deleted inserted replaced
137:b45b1b00fc9e 138:c77a679e9cfa
     9 # useful for debugging
     9 # useful for debugging
    10 import cgitb
    10 import cgitb
    11 cgitb.enable()
    11 cgitb.enable()
    12 
    12 
    13 import os, cgi, time, re, difflib, sys, zlib
    13 import os, cgi, time, re, difflib, sys, zlib
    14 from mercurial import hg, mdiff
    14 from mercurial.hg import *
       
    15 
       
    16 def age(t):
       
    17     def plural(t, c):
       
    18         if c == 1: return t
       
    19         return t + "s"
       
    20     def fmt(t, c):
       
    21         return "%d %s" % (c, plural(t, c))
       
    22 
       
    23     now = time.time()
       
    24     delta = max(1, int(now - t))
       
    25 
       
    26     scales = [["second", 1],
       
    27               ["minute", 60],
       
    28               ["hour", 3600],
       
    29               ["day", 3600 * 24],
       
    30               ["week", 3600 * 24 * 7],
       
    31               ["month", 3600 * 24 * 30],
       
    32               ["year", 3600 * 24 * 365]]
       
    33 
       
    34     scales.reverse()
       
    35 
       
    36     for t, s in scales:
       
    37         n = delta / s
       
    38         if n >= 1: return fmt(t, n)
    15 
    39 
    16 def nl2br(text):
    40 def nl2br(text):
    17     return re.sub('\n', '<br />', text)
    41     return text.replace('\n', '<br/>')
    18 
    42 
    19 def obfuscate(text):
    43 def obfuscate(text):
    20     l = []
    44     return ''.join([ '&#%d' % ord(c) for c in text ])
    21     for c in text:
    45 
    22         l.append('&#%d;' % ord(c))
    46 def up(p):
    23     return ''.join(l)
    47     if p[0] != "/": p = "/" + p
       
    48     if p[-1] == "/": p = p[:-1]
       
    49     up = os.path.dirname(p)
       
    50     if up == "/":
       
    51         return "/"
       
    52     return up + "/"
    24 
    53 
    25 def httphdr(type):
    54 def httphdr(type):
    26     print 'Content-type: %s\n' % type
    55     print 'Content-type: %s\n' % type
    27 
    56 
    28 def write(*things):
    57 def write(*things):
    31             for part in thing:
    60             for part in thing:
    32                 write(part)
    61                 write(part)
    33         else:
    62         else:
    34             sys.stdout.write(str(thing))
    63             sys.stdout.write(str(thing))
    35 
    64 
    36 class template:
    65 def template(tmpl, **map):
    37     def __init__(self, tmpl_dir):
    66     while tmpl:
    38         self.tmpl_dir = tmpl_dir
    67         m = re.search(r"#([a-zA-Z0-9]+)#", tmpl)
    39     def do_page(self, tmpl_fn, **map):
    68         if m:
    40         txt = file(os.path.join(self.tmpl_dir, tmpl_fn)).read()
    69             yield tmpl[:m.start(0)]
    41         while txt:
    70             v = map.get(m.group(1), "")
    42             m = re.search(r"#([a-zA-Z0-9]+)#", txt)
    71             yield callable(v) and v() or v
       
    72             tmpl = tmpl[m.end(0):]
       
    73         else:
       
    74             yield tmpl
       
    75             return
       
    76 
       
    77 class templater:
       
    78     def __init__(self, mapfile):
       
    79         self.cache = {}
       
    80         self.map = {}
       
    81         self.base = os.path.dirname(mapfile)
       
    82         
       
    83         for l in file(mapfile):
       
    84             m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
    43             if m:
    85             if m:
    44                 yield txt[:m.start(0)]
    86                 self.cache[m.group(1)] = m.group(2)
    45                 v = map.get(m.group(1), "")
    87             else:
    46                 if callable(v):
    88                 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
    47                    for y in v(**map): yield y
    89                 if m:
       
    90                     self.map[m.group(1)] = os.path.join(self.base, m.group(2))
    48                 else:
    91                 else:
    49                    yield v
    92                     raise "unknown map entry '%s'"  % l
    50                 txt = txt[m.end(0):]
    93 
       
    94     def __call__(self, t, **map):
       
    95         try:
       
    96             tmpl = self.cache[t]
       
    97         except KeyError:
       
    98             tmpl = self.cache[t] = file(self.map[t]).read()
       
    99         return template(tmpl, **map)
       
   100         
       
   101 class hgweb:
       
   102     maxchanges = 20
       
   103     maxfiles = 10
       
   104 
       
   105     def __init__(self, path, name, templatemap):
       
   106         self.reponame = name
       
   107         self.repo = repository(ui(), path)
       
   108         self.t = templater(templatemap)
       
   109 
       
   110     def date(self, cs):
       
   111         return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
       
   112 
       
   113     def listfiles(self, files, mf):
       
   114         for f in files[:self.maxfiles]:
       
   115             yield self.t("filenodelink", node = hex(mf[f]), file = f)
       
   116         if len(files) > self.maxfiles:
       
   117             yield self.t("fileellipses")
       
   118 
       
   119     def listfilediffs(self, files, changeset):
       
   120         for f in files[:self.maxfiles]:
       
   121             yield self.t("filedifflink", node = hex(changeset), file = f)
       
   122         if len(files) > self.maxfiles:
       
   123             yield self.t("fileellipses")
       
   124 
       
   125     def diff(self, node1, node2, files):
       
   126         def filterfiles(list, files):
       
   127             l = [ x for x in list if x in files ]
       
   128             
       
   129             for f in files:
       
   130                 if f[-1] != os.sep: f += os.sep
       
   131                 l += [ x for x in list if x.startswith(f) ]
       
   132             return l
       
   133 
       
   134         def prettyprint(diff):
       
   135             for l in diff.splitlines(1):
       
   136                 line = cgi.escape(l)
       
   137                 if line.startswith('+'):
       
   138                     yield self.t("difflineplus", line = line)
       
   139                 elif line.startswith('-'):
       
   140                     yield self.t("difflineminus", line = line)
       
   141                 elif line.startswith('@'):
       
   142                     yield self.t("difflineat", line = line)
       
   143                 else:
       
   144                     yield self.t("diffline", line = line)
       
   145 
       
   146         r = self.repo
       
   147         cl = r.changelog
       
   148         mf = r.manifest
       
   149         change1 = cl.read(node1)
       
   150         change2 = cl.read(node2)
       
   151         mmap1 = mf.read(change1[0])
       
   152         mmap2 = mf.read(change2[0])
       
   153         date1 = self.date(change1)
       
   154         date2 = self.date(change2)
       
   155 
       
   156         c, a, d = r.diffrevs(node1, node2)
       
   157         c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
       
   158 
       
   159         for f in c:
       
   160             to = r.file(f).read(mmap1[f])
       
   161             tn = r.file(f).read(mmap2[f])
       
   162             yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
       
   163         for f in a:
       
   164             to = ""
       
   165             tn = r.file(f).read(mmap2[f])
       
   166             yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
       
   167         for f in d:
       
   168             to = r.file(f).read(mmap1[f])
       
   169             tn = ""
       
   170             yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
       
   171 
       
   172     def changelog(self, pos=None):
       
   173         def changenav():
       
   174             def seq(factor = 1):
       
   175                 yield 1 * factor
       
   176                 yield 2 * factor
       
   177                 yield 5 * factor
       
   178                 for f in seq(factor * 10):
       
   179                     yield f
       
   180                     
       
   181             linear = range(0, count - 2, self.maxchanges)[0:8]
       
   182 
       
   183             for i in linear:
       
   184                 yield self.t("naventry", rev = max(i, 1))
       
   185 
       
   186             for s in seq():
       
   187                 if s > count - 2: break
       
   188                 if s > linear[-1]:
       
   189                     yield self.t("naventry", rev = s)
       
   190                     
       
   191             yield self.t("naventry", rev = count - 1)
       
   192 
       
   193         def changelist():
       
   194             cl = self.repo.changelog
       
   195             l = [] # build a list in forward order for efficiency
       
   196             for i in range(start, end + 1):
       
   197                 n = cl.node(i)
       
   198                 changes = cl.read(n)
       
   199                 hn = hex(n)
       
   200                 p1, p2 = cl.parents(n)
       
   201                 t = float(changes[2].split(' ')[0])
       
   202 
       
   203                 l.insert(0, self.t(
       
   204                     'changelogentry',
       
   205                     author = obfuscate(changes[1]),
       
   206                     shortdesc = cgi.escape(changes[4].splitlines()[0]),
       
   207                     age = age(t),
       
   208                     p1 = hex(p1), p2 = hex(p2),
       
   209                     p1rev = cl.rev(p1), p2rev = cl.rev(p2),
       
   210                     manifest = hex(changes[0]),
       
   211                     desc = nl2br(cgi.escape(changes[4])),
       
   212                     date = time.asctime(time.gmtime(t)),
       
   213                     files = self.listfilediffs(changes[3], n),
       
   214                     rev = i,
       
   215                     node = hn))
       
   216 
       
   217             yield l
       
   218 
       
   219         count = self.repo.changelog.count()
       
   220         pos = pos or count - 1
       
   221         end = min(pos, count - 1)
       
   222         start = max(0, pos - self.maxchanges)
       
   223         end = min(count - 1, start + self.maxchanges)
       
   224 
       
   225         yield self.t('changelog', repo = self.reponame, changenav = changenav,
       
   226                      rev = pos, changesets = count, changelist = changelist)
       
   227 
       
   228     def changeset(self, nodeid):
       
   229         n = bin(nodeid)
       
   230         cl = self.repo.changelog
       
   231         changes = cl.read(n)
       
   232         p1, p2 = cl.parents(n)
       
   233         p1rev, p2rev = cl.rev(p1), cl.rev(p2)
       
   234         t = float(changes[2].split(' ')[0])
       
   235         
       
   236         files = []
       
   237         mf = self.repo.manifest.read(changes[0])
       
   238         for f in changes[3]:
       
   239             files.append(self.t("filenodelink",
       
   240                                 filenode = hex(mf[f]), file = f))
       
   241 
       
   242         def diff():
       
   243             yield self.diff(p1, n, changes[3])
       
   244 
       
   245         yield self.t('changeset',
       
   246                      diff = diff,
       
   247                      rev = cl.rev(n),
       
   248                      node = nodeid,
       
   249                      shortdesc = cgi.escape(changes[4].splitlines()[0]),
       
   250                      p1 = hex(p1), p2 = hex(p2),
       
   251                      p1rev = cl.rev(p1), p2rev = cl.rev(p2),
       
   252                      manifest = hex(changes[0]),
       
   253                      author = obfuscate(changes[1]),
       
   254                      desc = nl2br(cgi.escape(changes[4])),
       
   255                      date = time.asctime(time.gmtime(t)),
       
   256                      files = files)
       
   257 
       
   258     def filelog(self, f, filenode):
       
   259         cl = self.repo.changelog
       
   260         fl = self.repo.file(f)
       
   261         count = fl.count()
       
   262 
       
   263         def entries():
       
   264             l = []
       
   265             for i in range(count):
       
   266 
       
   267                 n = fl.node(i)
       
   268                 lr = fl.linkrev(n)
       
   269                 cn = cl.node(lr)
       
   270                 cs = cl.read(cl.node(lr))
       
   271                 p1, p2 = fl.parents(n)
       
   272                 t = float(cs[2].split(' ')[0])
       
   273 
       
   274                 l.insert(0, self.t("filelogentry",
       
   275                                    filenode = hex(n),
       
   276                                    filerev = i,
       
   277                                    file = f,
       
   278                                    node = hex(cn),
       
   279                                    author = obfuscate(cs[1]),
       
   280                                    age = age(t),
       
   281                                    date = time.asctime(time.gmtime(t)),
       
   282                                    shortdesc = cgi.escape(cs[4].splitlines()[0]),
       
   283                                    p1 = hex(p1), p2 = hex(p2),
       
   284                                    p1rev = fl.rev(p1), p2rev = fl.rev(p2)))
       
   285 
       
   286             yield l
       
   287 
       
   288         yield self.t("filelog",
       
   289                      file = f,
       
   290                      filenode = filenode,
       
   291                      entries = entries)
       
   292 
       
   293     def filerevision(self, f, node):
       
   294         fl = self.repo.file(f)
       
   295         n = bin(node)
       
   296         text = cgi.escape(fl.read(n))
       
   297         changerev = fl.linkrev(n)
       
   298         cl = self.repo.changelog
       
   299         cn = cl.node(changerev)
       
   300         cs = cl.read(cn)
       
   301         p1, p2 = fl.parents(n)
       
   302         t = float(cs[2].split(' ')[0])
       
   303         mfn = cs[0]
       
   304         
       
   305         yield self.t("filerevision", file = f,
       
   306                      filenode = node,
       
   307                      path = up(f),
       
   308                      text = text,
       
   309                      rev = changerev,
       
   310                      node = hex(cn),
       
   311                      manifest = hex(mfn),
       
   312                      author = obfuscate(cs[1]),
       
   313                      age = age(t),
       
   314                      date = time.asctime(time.gmtime(t)),
       
   315                      shortdesc = cgi.escape(cs[4].splitlines()[0]),
       
   316                      p1 = hex(p1), p2 = hex(p2),
       
   317                      p1rev = fl.rev(p1), p2rev = fl.rev(p2))
       
   318 
       
   319 
       
   320     def fileannotate(self, f, node):
       
   321         bcache = {}
       
   322         ncache = {}
       
   323         fl = self.repo.file(f)
       
   324         n = bin(node)
       
   325         changerev = fl.linkrev(n)
       
   326 
       
   327         cl = self.repo.changelog
       
   328         cn = cl.node(changerev)
       
   329         cs = cl.read(cn)
       
   330         p1, p2 = fl.parents(n)
       
   331         t = float(cs[2].split(' ')[0])
       
   332         mfn = cs[0]
       
   333 
       
   334         def annotate():
       
   335             for r, l in fl.annotate(n):
       
   336                 try:
       
   337                     cnode = ncache[r]
       
   338                 except KeyError:
       
   339                     cnode = ncache[r] = self.repo.changelog.node(r)
       
   340                     
       
   341                 try:
       
   342                     name = bcache[r]
       
   343                 except KeyError:
       
   344                     cl = self.repo.changelog.read(cnode)
       
   345                     name = cl[1]
       
   346                     f = name.find('@')
       
   347                     if f >= 0:
       
   348                         name = name[:f]
       
   349                     bcache[r] = name
       
   350 
       
   351                 yield self.t("annotateline",
       
   352                              node = hex(cnode),
       
   353                              rev = r,
       
   354                              author = name,
       
   355                              file = f,
       
   356                              line = cgi.escape(l))
       
   357 
       
   358         yield self.t("fileannotate",
       
   359                      file = f,
       
   360                      filenode = node,
       
   361                      annotate = annotate,
       
   362                      path = up(f),
       
   363                      rev = changerev,
       
   364                      node = hex(cn),
       
   365                      manifest = hex(mfn),
       
   366                      author = obfuscate(cs[1]),
       
   367                      age = age(t),
       
   368                      date = time.asctime(time.gmtime(t)),
       
   369                      shortdesc = cgi.escape(cs[4].splitlines()[0]),
       
   370                      p1 = hex(p1), p2 = hex(p2),
       
   371                      p1rev = fl.rev(p1), p2rev = fl.rev(p2))
       
   372 
       
   373     def manifest(self, mnode, path):
       
   374         mf = self.repo.manifest.read(bin(mnode))
       
   375         rev = self.repo.manifest.rev(bin(mnode))
       
   376         node = self.repo.changelog.node(rev)
       
   377 
       
   378         dirs = {}
       
   379         files = {}
       
   380         short = {}
       
   381 
       
   382         p = path[1:]
       
   383         l = len(p)
       
   384 
       
   385         for f,n in mf.items():
       
   386             if f[:l] != p:
       
   387                 continue
       
   388             remain = f[l:]
       
   389             if "/" in remain:
       
   390                 short = remain[:remain.find("/") + 1] # bleah
       
   391                 dirs[short] = 1
    51             else:
   392             else:
    52                 yield txt
   393                 short = os.path.basename(remain)
    53                 txt = ''
   394                 files[short] = (f, n)
    54 
   395 
    55 class page:
   396         def dirlist():
    56     def __init__(self, tmpl_dir = "", type="text/html", title="Mercurial Web", 
   397             dl = dirs.keys()
    57             charset="ISO-8859-1"):
   398             dl.sort()
    58         self.tmpl = template(tmpl_dir)
   399             
    59 
   400             for d in dl:
    60         print 'Content-type: %s; charset=%s\n' % (type, charset)
   401                 yield self.t("manifestdirentry",
    61         write(self.tmpl.do_page('htmlstart.tmpl', title = title))
   402                              path = os.path.join(path, d),
    62 
   403                              manifest = mnode, basename = d[:-1])
    63     def endpage(self):
   404 
    64         print '</BODY>'
   405         def filelist():
    65         print '</HTML>'
   406             fl = files.keys()
    66 
   407             fl.sort()
    67     def show_diff(self, a, b, fn):
   408             for f in fl:
    68         a = a.splitlines(1)
   409                 full, fnode = files[f]
    69         b = b.splitlines(1)
   410                 yield self.t("manifestfileentry",
    70         l = difflib.unified_diff(a, b, fn, fn)
   411                              file = full, manifest = mnode, filenode = hex(fnode),
    71         print '<pre>'
   412                              basename = f)
    72         for line in l:
   413 
    73             line = cgi.escape(line[:-1])
   414         yield self.t("manifest",
    74             if line.startswith('+'):
   415                      manifest = mnode,
    75                 print '<span class="plusline">%s</span>' % (line, )
   416                      rev = rev,
    76             elif line.startswith('-'):
   417                      node = hex(node),
    77                 print '<span class="minusline">%s</span>' % (line, )
   418                      path = path,
    78             elif line.startswith('@'):
   419                      up = up(path),
    79                 print '<span class="atline">%s</span>' % (line, )
   420                      dirs = dirlist,
    80             else:
   421                      files = filelist)
    81                 print line
   422 
    82         print '</pre>'
   423     def filediff(self, file, changeset):
    83 
   424         n = bin(changeset)
    84 class errpage(page):
   425         cl = self.repo.changelog
    85     def __init__(self, tmpl_dir):
   426         p1 = cl.parents(n)[0]
    86         page.__init__(self, tmpl_dir, title="Mercurial Web Error Page")
   427         cs = cl.read(n)
    87 
   428         mf = self.repo.manifest.read(cs[0])
    88 class change_list(page):
   429         
    89     def __init__(self, repo, tmpl_dir, reponame, numchanges = 50):
   430         def diff():
    90         page.__init__(self, tmpl_dir)
   431             yield self.diff(p1, n, file)
    91         self.repo = repo
   432 
    92         self.numchanges = numchanges
   433         yield self.t("filediff",
    93         write(self.tmpl.do_page('changestitle.tmpl', reponame=reponame))
   434                      file = file,
    94 
   435                      filenode = hex(mf[file]),
    95     def content(self, hi=None):
   436                      node = changeset,
    96         cl = []
   437                      rev = self.repo.changelog.rev(n),
    97         count = self.repo.changelog.count()
   438                      p1 = hex(p1),
    98         if not hi:
   439                      p1rev = self.repo.changelog.rev(p1),
    99             hi = count
   440                      diff = diff)
   100         elif hi < self.numchanges:
   441                      
   101             hi = self.numchanges
   442     # header and footer, css
   102 
   443     # add tags to things
   103         start = 0
   444     # show parents
   104         if hi - self.numchanges >= 0:
   445     # diff between rev and parent in changeset and file
   105             start = hi - self.numchanges
   446     # manifest links
   106 
   447     # browse at top
   107         nav = "Displaying Revisions: %d-%d" % (start, hi-1)
   448     # tags -> list of changesets corresponding to tags
   108         if start != 0:
   449     # find tag, changeset, file
   109             nav = ('<a href="?cmd=changes;hi=%d">Previous %d</a>&nbsp;&nbsp;' \
       
   110                     % (start, self.numchanges)) + nav
       
   111         if hi != count:
       
   112             if hi + self.numchanges <= count:
       
   113                 nav += '&nbsp;&nbsp;<a href="?cmd=changes;hi=%d">Next %d</a>' \
       
   114                         % (hi + self.numchanges, self.numchanges)
       
   115             else:
       
   116                 nav += '&nbsp;&nbsp;<a href="?cmd=changes">Next %d</a>' % \
       
   117                         self.numchanges
       
   118 
       
   119         print '<center>%s</center>' % nav
       
   120 
       
   121         for i in xrange(start, hi):
       
   122             n = self.repo.changelog.node(i)
       
   123             cl.append((n, self.repo.changelog.read(n)))
       
   124         cl.reverse()
       
   125 
       
   126         print '<table summary="" width="100%" align="center">'
       
   127         for n, ch in cl:
       
   128             print '<tr><td>'
       
   129             self.change_table(n, ch)
       
   130             print '</td></tr>'
       
   131         print '</table>'
       
   132 
       
   133         print '<center>%s</center>' % nav
       
   134 
       
   135     def change_table(self, nodeid, changes):
       
   136         hn = hg.hex(nodeid)
       
   137         i = self.repo.changelog.rev(nodeid)
       
   138         (h1, h2) = [ hg.hex(x) for x in self.repo.changelog.parents(nodeid) ]
       
   139         datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0])))
       
   140         files = []
       
   141         for f in changes[3]:
       
   142             files.append('<a href="?cmd=file;cs=%s;fn=%s">%s</a>&nbsp;&nbsp;' \
       
   143                 % (hn, f, cgi.escape(f)))
       
   144         write(self.tmpl.do_page('change_table.tmpl',
       
   145                 author=obfuscate(changes[1]),
       
   146                 desc=nl2br(cgi.escape(changes[4])), date=datestr, 
       
   147                 files=' '.join(files), revnum=i, revnode=hn))
       
   148 
       
   149 class checkin(page):
       
   150     def __init__(self, repo, tmpl_dir, nodestr):
       
   151         page.__init__(self, tmpl_dir)
       
   152         self.repo = repo
       
   153         self.node = hg.bin(nodestr)
       
   154         self.nodestr = nodestr
       
   155         print '<h3>Checkin: %s</h3>' % nodestr
       
   156 
       
   157     def content(self):
       
   158         changes = self.repo.changelog.read(self.node)
       
   159         i = self.repo.changelog.rev(self.node)
       
   160         parents = self.repo.changelog.parents(self.node)
       
   161         (h1, h2) = [ hg.hex(x) for x in parents ]
       
   162         (i1, i2) = [ self.repo.changelog.rev(x) for x in parents ]
       
   163         datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0])))
       
   164         mf = self.repo.manifest.read(changes[0])
       
   165         files = []
       
   166         for f in changes[3]:
       
   167             files.append('<a href="?cmd=file;nd=%s;fn=%s">%s</a>&nbsp;&nbsp;' \
       
   168                 % (hg.hex(mf[f]), f, cgi.escape(f)))
       
   169         p2link = h2
       
   170         if i2 != -1:
       
   171             p2link = '<a href="?cmd=chkin;nd=%s">%s</a>' % (h2, h2)
       
   172 
       
   173         write(self.tmpl.do_page('checkin.tmpl', revnum=i, revnode=self.nodestr,
       
   174                 p1num=i1, p1node=h1, p2num=i2, p2node=h2, p2link=p2link,
       
   175                 mfnum=self.repo.manifest.rev(changes[0]), 
       
   176                 mfnode=hg.hex(changes[0]), author=obfuscate(changes[1]),
       
   177                 desc=nl2br(cgi.escape(changes[4])), date=datestr,
       
   178                 files=' '.join(files)))
       
   179 
       
   180         (c, a, d) = self.repo.diffrevs(parents[0], self.node)
       
   181         change = self.repo.changelog.read(parents[0])
       
   182         mf2 = self.repo.manifest.read(change[0])
       
   183         for f in c:
       
   184             self.show_diff(self.repo.file(f).read(mf2[f]), \
       
   185                     self.repo.file(f).read(mf[f]), f)
       
   186         for f in a:
       
   187             self.show_diff('', self.repo.file(f).read(mf[f]), f)
       
   188         for f in d:
       
   189             self.show_diff(self.repo.file(f).read(mf2[f]), '', f)
       
   190 
       
   191 class filepage(page):
       
   192     def __init__(self, repo, tmpl_dir, fn, node=None, cs=None):
       
   193         page.__init__(self, tmpl_dir)
       
   194         self.repo = repo
       
   195         self.fn = fn
       
   196         if cs: 
       
   197             chng = self.repo.changelog.read(hg.bin(cs))
       
   198             mf = self.repo.manifest.read(chng[0])
       
   199             self.node = mf[self.fn]
       
   200             self.nodestr = hg.hex(self.node)
       
   201         else:
       
   202             self.nodestr = node
       
   203             self.node = hg.bin(node)
       
   204         print '<div class="filename">%s (%s)</div>' % \
       
   205                 (cgi.escape(self.fn), self.nodestr, )
       
   206         print '<a href="?cmd=hist;fn=%s">history</a><br />' % self.fn
       
   207         print '<a href="?cmd=ann;fn=%s;nd=%s">annotate</a><br />' % \
       
   208                 (self.fn, self.nodestr)
       
   209 
       
   210     def content(self):
       
   211         print '<pre>'
       
   212         print cgi.escape(self.repo.file(self.fn).read(self.node))
       
   213         print '</pre>'
       
   214 
       
   215 class annpage(page):
       
   216     def __init__(self, repo, tmpl_dir, fn, node):
       
   217         page.__init__(self, tmpl_dir)
       
   218         self.repo = repo
       
   219         self.fn = fn
       
   220         self.nodestr = node
       
   221         self.node = hg.bin(node)
       
   222         print '<div class="annotation">Annotated: %s (%s)</div>' % \
       
   223                 (cgi.escape(self.fn), self.nodestr, )
       
   224 
       
   225     def content(self):
       
   226         print '<pre>'
       
   227         for n, l in self.repo.file(self.fn).annotate(self.node):
       
   228             cnode = self.repo.changelog.lookup(n)
       
   229             write(self.tmpl.do_page('annline.tmpl', cnode=hg.hex(cnode),
       
   230                     cnum='% 6s' % n, fn=self.fn, line=cgi.escape(l[:-1])))
       
   231         print '</pre>'
       
   232 
       
   233 class mfpage(page):
       
   234     def __init__(self, repo, tmpl_dir, node):
       
   235         page.__init__(self, tmpl_dir)
       
   236         self.repo = repo
       
   237         self.nodestr = node
       
   238         self.node = hg.bin(node)
       
   239 
       
   240     def content(self):
       
   241         mf = self.repo.manifest.read(self.node)
       
   242         fns = mf.keys()
       
   243         fns.sort()
       
   244         write(self.tmpl.do_page('mftitle.tmpl', node = self.nodestr))
       
   245         for f in fns:
       
   246             write(self.tmpl.do_page('mfentry.tmpl', fn=f, node=hg.hex(mf[f])))
       
   247 
       
   248 class histpage(page):
       
   249     def __init__(self, repo, tmpl_dir, fn):
       
   250         page.__init__(self, tmpl_dir)
       
   251         self.repo = repo
       
   252         self.fn = fn
       
   253 
       
   254     def content(self):
       
   255         print '<div class="filehist">File History: %s</div>' % self.fn
       
   256         r = self.repo.file(self.fn)
       
   257         print '<br />'
       
   258         print '<table summary="" width="100%" align="center">'
       
   259         for i in xrange(r.count()-1, -1, -1):
       
   260             print '<tr><td>'
       
   261             self.hist_ent(i, r)
       
   262             print '</tr></td>'
       
   263         print '</table>'
       
   264 
       
   265     def hist_ent(self, i, r):
       
   266         n = r.node(i)
       
   267         (p1, p2) = r.parents(n)
       
   268         (h, h1, h2) = map(hg.hex, (n, p1, p2))
       
   269         (i1, i2) = map(r.rev, (p1, p2))
       
   270         ci = r.linkrev(n)
       
   271         cn = self.repo.changelog.node(ci)
       
   272         cs = hg.hex(cn)
       
   273         changes = self.repo.changelog.read(cn)
       
   274         datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0])))
       
   275         p2entry = ''
       
   276         if i2 != -1:
       
   277             p2entry = '&nbsp;&nbsp;%d:<a href="?cmd=file;nd=%s;fn=%s">%s</a>' \
       
   278                     % (i2, h2, self.fn, h2 ),
       
   279         write(self.tmpl.do_page('hist_ent.tmpl', author=obfuscate(changes[1]),
       
   280                 csnode=cs, desc=nl2br(cgi.escape(changes[4])), 
       
   281                 date = datestr, fn=self.fn, revnode=h, p1num = i1,
       
   282                 p1node=h1, p2entry=p2entry))
       
   283                 
       
   284 class hgweb:
       
   285     repo_path = "."
       
   286     numchanges = 50
       
   287     tmpl_dir = "templates"
       
   288 
       
   289     def __init__(self):
       
   290         pass
       
   291 
   450 
   292     def run(self):
   451     def run(self):
   293 
       
   294         args = cgi.parse()
   452         args = cgi.parse()
   295 
   453 
   296         ui = hg.ui()
   454         if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
   297         repo = hg.repository(ui, self.repo_path)
   455             hi = self.repo.changelog.count()
   298 
   456             if args.has_key('pos'):
   299         if not args.has_key('cmd') or args['cmd'][0] == 'changes':
   457                 hi = int(args['pos'][0])
   300             page = change_list(repo, self.tmpl_dir, 'Mercurial', 
   458 
   301                     self.numchanges)
   459             write(self.changelog(hi))
   302             hi = args.get('hi', ( repo.changelog.count(), ))
       
   303             page.content(hi = int(hi[0]))
       
   304             page.endpage()
       
   305             
   460             
   306         elif args['cmd'][0] == 'chkin':
   461         elif args['cmd'][0] == 'changeset':
   307             if not args.has_key('nd'):
   462             write(self.changeset(args['node'][0]))
   308                 page = errpage(self.tmpl_dir)
   463 
   309                 print '<div class="errmsg">No Node!</div>'
   464         elif args['cmd'][0] == 'manifest':
   310             else:
   465             write(self.manifest(args['manifest'][0], args['path'][0]))
   311                 page = checkin(repo, self.tmpl_dir, args['nd'][0])
   466 
   312                 page.content()
   467         elif args['cmd'][0] == 'filediff':
   313             page.endpage()
   468             write(self.filediff(args['file'][0], args['node'][0]))
   314 
   469 
   315         elif args['cmd'][0] == 'file':
   470         elif args['cmd'][0] == 'file':
   316             if not (args.has_key('nd') and args.has_key('fn')) and \
   471             write(self.filerevision(args['file'][0], args['filenode'][0]))
   317                     not (args.has_key('cs') and args.has_key('fn')):
   472 
   318                 page = errpage(self.tmpl_dir)
   473         elif args['cmd'][0] == 'annotate':
   319                 print '<div class="errmsg">Invalid Args!</div>'
   474             write(self.fileannotate(args['file'][0], args['filenode'][0]))
   320             else:
   475 
   321                 if args.has_key('nd'):
   476         elif args['cmd'][0] == 'filelog':
   322                     page = filepage(repo, self.tmpl_dir, 
   477             write(self.filelog(args['file'][0], args['filenode'][0]))
   323                             args['fn'][0], node=args['nd'][0])
       
   324                 else:
       
   325                     page = filepage(repo, self.tmpl_dir,
       
   326                             args['fn'][0], cs=args['cs'][0])
       
   327                 page.content()
       
   328             page.endpage()
       
   329 
       
   330         elif args['cmd'][0] == 'mf':
       
   331             if not args.has_key('nd'):
       
   332                 page = errpage(self.tmpl_dir)
       
   333                 print '<div class="errmsg">No Node!</div>'
       
   334             else:
       
   335                 page = mfpage(repo, self.tmpl_dir, args['nd'][0])
       
   336                 page.content()
       
   337             page.endpage()
       
   338 
       
   339         elif args['cmd'][0] == 'hist':
       
   340             if not args.has_key('fn'):
       
   341                 page = errpage(self.tmpl_dir)
       
   342                 print '<div class="errmsg">No Filename!</div>'
       
   343             else:
       
   344                 page = histpage(repo, self.tmpl_dir, args['fn'][0])
       
   345                 page.content()
       
   346             page.endpage()
       
   347 
       
   348         elif args['cmd'][0] == 'ann':
       
   349             if not args.has_key('fn'):
       
   350                 page = errpage(self.tmpl_dir)
       
   351                 print '<div class="errmsg">No Filename!</div>'
       
   352             elif not args.has_key('nd'):
       
   353                 page = errpage(self.tmpl_dir)
       
   354                 print '<div class="errmsg">No Node!</div>'
       
   355             else:
       
   356                 page = annpage(repo, self.tmpl_dir, args['fn'][0], 
       
   357                         args['nd'][0])
       
   358                 page.content()
       
   359             page.endpage()
       
   360 
   478 
   361         elif args['cmd'][0] == 'branches':
   479         elif args['cmd'][0] == 'branches':
   362             httphdr("text/plain")
   480             httphdr("text/plain")
   363             nodes = []
   481             nodes = []
   364             if args.has_key('nodes'):
   482             if args.has_key('nodes'):
   365                 nodes = map(hg.bin, args['nodes'][0].split(" "))
   483                 nodes = map(bin, args['nodes'][0].split(" "))
   366             for b in repo.branches(nodes):
   484             for b in self.repo.branches(nodes):
   367                 print " ".join(map(hg.hex, b))
   485                 sys.stdout.write(" ".join(map(hex, b)) + "\n")
   368 
   486 
   369         elif args['cmd'][0] == 'between':
   487         elif args['cmd'][0] == 'between':
   370             httphdr("text/plain")
   488             httphdr("text/plain")
   371             nodes = []
   489             nodes = []
   372             if args.has_key('pairs'):
   490             if args.has_key('pairs'):
   373                 pairs = [ map(hg.bin, p.split("-"))
   491                 pairs = [ map(bin, p.split("-"))
   374                           for p in args['pairs'][0].split(" ") ]
   492                           for p in args['pairs'][0].split(" ") ]
   375             for b in repo.between(pairs):
   493             for b in self.repo.between(pairs):
   376                 print " ".join(map(hg.hex, b))
   494                 sys.stdout.write(" ".join(map(hex, b)) + "\n")
   377 
   495 
   378         elif args['cmd'][0] == 'changegroup':
   496         elif args['cmd'][0] == 'changegroup':
   379             httphdr("application/hg-changegroup")
   497             httphdr("application/hg-changegroup")
   380             nodes = []
   498             nodes = []
   381             if args.has_key('roots'):
   499             if args.has_key('roots'):
   382                 nodes = map(hg.bin, args['roots'][0].split(" "))
   500                 nodes = map(bin, args['roots'][0].split(" "))
   383 
   501 
   384             z = zlib.compressobj()
   502             z = zlib.compressobj()
   385             for chunk in repo.changegroup(nodes):
   503             for chunk in self.repo.changegroup(nodes):
   386                 sys.stdout.write(z.compress(chunk))
   504                 sys.stdout.write(z.compress(chunk))
   387 
   505 
   388             sys.stdout.write(z.flush())
   506             sys.stdout.write(z.flush())
   389 
   507 
   390         else:
   508         else:
   391             page = errpage(self.tmpl_dir)
   509             write(self.t("error"))
   392             print '<div class="errmsg">unknown command: %s</div>' % \
       
   393                     cgi.escape(args['cmd'][0])
       
   394             page.endpage()
       
   395 
   510 
   396 if __name__ == "__main__":
   511 if __name__ == "__main__":
   397     hgweb().run()
   512     hgweb().run()