mercurial/hgweb.py
changeset 131 c9d51742471c
child 132 210eeb6f5197
equal deleted inserted replaced
127:44538462d3c8 131:c9d51742471c
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # hgweb.py - 0.1 - 9 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
       
     4 #    - web interface to a mercurial repository
       
     5 #
       
     6 # This software may be used and distributed according to the terms
       
     7 # of the GNU General Public License, incorporated herein by reference.
       
     8 
       
     9 # useful for debugging
       
    10 import cgitb
       
    11 cgitb.enable()
       
    12 
       
    13 import os, cgi, time, re, difflib, sys, zlib
       
    14 from mercurial import hg, mdiff
       
    15 
       
    16 repo_path = "."  # change as needed
       
    17 
       
    18 def nl2br(text):
       
    19     return re.sub('\n', '<br />', text)
       
    20 
       
    21 def obfuscate(text):
       
    22     l = []
       
    23     for c in text:
       
    24         l.append('&#%d;' % ord(c))
       
    25     return ''.join(l)
       
    26 
       
    27 def httphdr(type):
       
    28     print 'Content-type: %s\n' % type
       
    29 
       
    30 class page:
       
    31     def __init__(self, type="text/html", title="Mercurial Web", 
       
    32             charset="ISO-8859-1"):
       
    33         print 'Content-type: %s; charset=%s\n' % (type, charset)
       
    34         print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'
       
    35         print '<HTML>'
       
    36         print '<!-- created by hgweb 0.1 - jake@edge2.net -->'
       
    37         print '<HEAD><TITLE>%s</TITLE>' % title
       
    38         print '<style type="text/css">'
       
    39         print 'body { font-family: sans-serif; font-size: 12px; }'
       
    40         print 'table { font-size: 12px; }'
       
    41         print '.errmsg { font-size: 200%; color: red; }'
       
    42         print '.filename { font-size: 150%; color: purple; }'
       
    43         print '.manifest { font-size: 150%; color: purple; }'
       
    44         print '.filehist { font-size: 150%; color: purple; }'
       
    45         print '.plusline { color: green; }'
       
    46         print '.minusline { color: red; }'
       
    47         print '.atline { color: purple; }'
       
    48         print '</style>'
       
    49         print '</HEAD>'
       
    50         print '<BODY>'
       
    51 
       
    52     def endpage(self):
       
    53         print '</BODY>'
       
    54         print '</HTML>'
       
    55 
       
    56     def show_diff(self, a, b, fn):
       
    57         a = a.splitlines(1)
       
    58         b = b.splitlines(1)
       
    59         l = difflib.unified_diff(a, b, fn, fn)
       
    60         print '<pre>'
       
    61         for line in l:
       
    62             line = cgi.escape(line[:-1])
       
    63             if line.startswith('+'):
       
    64                 print '<span class="plusline">%s</span>' % (line, )
       
    65             elif line.startswith('-'):
       
    66                 print '<span class="minusline">%s</span>' % (line, )
       
    67             elif line.startswith('@'):
       
    68                 print '<span class="atline">%s</span>' % (line, )
       
    69             else:
       
    70                 print line
       
    71         print '</pre>'
       
    72 
       
    73 class errpage(page):
       
    74     def __init__(self):
       
    75         page.__init__(self, title="Mercurial Web Error Page")
       
    76 
       
    77 class change_list(page):
       
    78 
       
    79     numchanges = 50   # number of changes to show
       
    80 
       
    81     def __init__(self, repo, reponame):
       
    82         page.__init__(self)
       
    83         self.repo = repo
       
    84         print '<h3>Changes For: %s</h3>' % reponame
       
    85 
       
    86     def content(self, hi=None):
       
    87         cl = []
       
    88         count = self.repo.changelog.count()
       
    89         if not hi:
       
    90             hi = count
       
    91         elif hi < self.numchanges:
       
    92             hi = self.numchanges
       
    93 
       
    94         start = 0
       
    95         if hi - self.numchanges >= 0:
       
    96             start = hi - self.numchanges
       
    97 
       
    98         nav = "Displaying Revisions: %d-%d" % (start, hi-1)
       
    99         if start != 0:
       
   100             nav = ('<a href="?cmd=changes;hi=%d">Previous %d</a>&nbsp;&nbsp;' \
       
   101                     % (start, self.numchanges)) + nav
       
   102         if hi != count:
       
   103             if hi + self.numchanges <= count:
       
   104                 nav += '&nbsp;&nbsp;<a href="?cmd=changes;hi=%d">Next %d</a>' \
       
   105                         % (hi + self.numchanges, self.numchanges)
       
   106             else:
       
   107                 nav += '&nbsp;&nbsp;<a href="?cmd=changes">Next %d</a>' % \
       
   108                         self.numchanges
       
   109 
       
   110         print '<center>%s</center>' % nav
       
   111 
       
   112         for i in xrange(start, hi):
       
   113             n = self.repo.changelog.node(i)
       
   114             cl.append((n, self.repo.changelog.read(n)))
       
   115         cl.reverse()
       
   116 
       
   117         print '<table summary="" width="100%" align="center">'
       
   118         for n, ch in cl:
       
   119             print '<tr><td>'
       
   120             self.change_table(n, ch)
       
   121             print '</td></tr>'
       
   122         print '</table>'
       
   123 
       
   124         print '<center>%s</center>' % nav
       
   125 
       
   126     def change_table(self, nodeid, changes):
       
   127         hn = hg.hex(nodeid)
       
   128         i = self.repo.changelog.rev(nodeid)
       
   129         (h1, h2) = [ hg.hex(x) for x in self.repo.changelog.parents(nodeid) ]
       
   130         datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0])))
       
   131         print '<table summary="" width="100%" border="1">'
       
   132         print '\t<tr><td valign="top" width="10%">author:</td>' + \
       
   133                 '<td valign="top" width="20%%">%s</td>' % \
       
   134                 (obfuscate(changes[1]), )
       
   135         print '\t\t<td valign="top" width="10%">description:</td>' + \
       
   136                 '<td width="60%">' + \
       
   137                 '<a href="?cmd=chkin;nd=%s">%s</a></td></tr>' % \
       
   138                 (hn, nl2br(cgi.escape(changes[4])), )
       
   139         print '\t<tr><td>date:</td><td>%s UTC</td>' % (datestr, )
       
   140         print '\t\t<td valign="top">files:</td><td valign="top">'
       
   141         for f in changes[3]:
       
   142             print '\t\t<a href="?cmd=file;cs=%s;fn=%s">%s</a>&nbsp;&nbsp;' % \
       
   143                     (hn, f, cgi.escape(f), )
       
   144         print '\t</td></tr>'
       
   145         print '\t<tr><td>revision:</td><td colspan="3">%d:<a ' % (i, ) + \
       
   146                 'href="?cmd=chkin;nd=%s">%s</a></td></tr>' % (hn, hn, )
       
   147         print '</table><br />'
       
   148 
       
   149 class checkin(page):
       
   150     def __init__(self, repo, nodestr):
       
   151         page.__init__(self)
       
   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         print '<table summary="" width="100%" border="1">'
       
   166         print '\t<tr><td>revision:</td><td colspan="3">%d:' % (i, ),
       
   167         print '<a href="?cmd=chkin;nd=%s">%s</a></td></tr>' % \
       
   168                 (self.nodestr, self.nodestr, )
       
   169         print '\t<tr><td>parent(s):</td><td colspan="3">%d:' % (i1, )
       
   170         print '<a href="?cmd=chkin;nd=%s">%s</a>' % (h1, h1, ),
       
   171         if i2 != -1:
       
   172             print '&nbsp;&nbsp;%d:<a href="?cmd=chkin;nd=%s">%s</a>' % \
       
   173                     (i2, h2, h2, ),
       
   174         else:
       
   175             print '&nbsp;&nbsp;%d:%s' % (i2, h2, ),
       
   176         print '</td></tr>'
       
   177         print '\t<tr><td>manifest:</td><td colspan="3">%d:' % \
       
   178                 (self.repo.manifest.rev(changes[0]), ),
       
   179         print '<a href="?cmd=mf;nd=%s">%s</a></td></tr>' % \
       
   180                 (hg.hex(changes[0]), hg.hex(changes[0]), )
       
   181         print '\t<tr><td valign="top" width="10%">author:</td>' + \
       
   182                 '<td valign="top" width="20%%">%s</td>' % \
       
   183                 (obfuscate(changes[1]), )
       
   184         print '\t\t<td valign="top" width="10%">description:</td>' + \
       
   185                 '<td width="60%">' + \
       
   186                 '<a href="?cmd=chkin;nd=%s">%s</a></td></tr>' % \
       
   187                 (self.nodestr, nl2br(cgi.escape(changes[4])), )
       
   188         print '\t<tr><td>date:</td><td>%s UTC</td>' % (datestr, )
       
   189         print '\t\t<td valign="top">files:</td><td valign="top">'
       
   190         for f in changes[3]:
       
   191             print '\t\t<a href="?cmd=file;nd=%s&fn=%s">%s</a>' % \
       
   192                     (hg.hex(mf[f]), f, cgi.escape(f), ),
       
   193             print '&nbsp;&nbsp;'
       
   194         print '\t</td></tr>'
       
   195         print '</table><br />'
       
   196 
       
   197         (c, a, d) = self.repo.diffrevs(parents[0], self.node)
       
   198         change = self.repo.changelog.read(parents[0])
       
   199         mf2 = self.repo.manifest.read(change[0])
       
   200         for f in c:
       
   201             self.show_diff(self.repo.file(f).read(mf2[f]), \
       
   202                     self.repo.file(f).read(mf[f]), f)
       
   203         for f in a:
       
   204             self.show_diff('', self.repo.file(f).read(mf[f]), f)
       
   205         for f in d:
       
   206             self.show_diff(self.repo.file(f).read(mf2[f]), '', f)
       
   207 
       
   208 class filepage(page):
       
   209     def __init__(self, repo, fn, node=None, cs=None):
       
   210         page.__init__(self)
       
   211         self.repo = repo
       
   212         self.fn = fn
       
   213         if cs: 
       
   214             chng = self.repo.changelog.read(hg.bin(cs))
       
   215             mf = self.repo.manifest.read(chng[0])
       
   216             self.node = mf[self.fn]
       
   217             self.nodestr = hg.hex(self.node)
       
   218         else:
       
   219             self.nodestr = node
       
   220             self.node = hg.bin(node)
       
   221         print '<div class="filename">%s (%s)</div>' % \
       
   222                 (cgi.escape(self.fn), self.nodestr, )
       
   223         print '<a href="?cmd=hist;fn=%s">history</a><br />' % self.fn
       
   224 
       
   225     def content(self):
       
   226         print '<pre>'
       
   227         print cgi.escape(self.repo.file(self.fn).read(self.node))
       
   228         print '</pre>'
       
   229 
       
   230 class mfpage(page):
       
   231     def __init__(self, repo, node):
       
   232         page.__init__(self)
       
   233         self.repo = repo
       
   234         self.nodestr = node
       
   235         self.node = hg.bin(node)
       
   236 
       
   237     def content(self):
       
   238         mf = self.repo.manifest.read(self.node)
       
   239         fns = mf.keys()
       
   240         fns.sort()
       
   241         print '<div class="manifest">Manifest (%s)</div>' % self.nodestr
       
   242         for f in fns:
       
   243             print '<a href="?cmd=file;fn=%s;nd=%s">%s</a><br />' % \
       
   244                     (f, hg.hex(mf[f]), f)
       
   245 
       
   246 class histpage(page):
       
   247     def __init__(self, repo, fn):
       
   248         page.__init__(self)
       
   249         self.repo = repo
       
   250         self.fn = fn
       
   251 
       
   252     def content(self):
       
   253         print '<div class="filehist">File History: %s</div>' % self.fn
       
   254         r = self.repo.file(self.fn)
       
   255         print '<br />'
       
   256         print '<table summary="" width="100%" align="center">'
       
   257         for i in xrange(r.count()-1, -1, -1):
       
   258             n = r.node(i)
       
   259             (p1, p2) = r.parents(n)
       
   260             (h, h1, h2) = map(hg.hex, (n, p1, p2))
       
   261             (i1, i2) = map(r.rev, (p1, p2))
       
   262             ci = r.linkrev(n)
       
   263             cn = self.repo.changelog.node(ci)
       
   264             cs = hg.hex(cn)
       
   265             changes = self.repo.changelog.read(cn)
       
   266             print '<tr><td>'
       
   267             self.hist_ent(i, h, i1, h1, i2, h2, ci, cs, changes)
       
   268             print '</tr></td>'
       
   269         print '</table>'
       
   270 
       
   271     def hist_ent(self, revi, revs, p1i, p1s, p2i, p2s, ci, cs, changes):
       
   272         datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0])))
       
   273         print '<table summary="" width="100%" border="1">'
       
   274         print '\t<tr><td valign="top" width="10%">author:</td>' + \
       
   275                 '<td valign="top" width="20%%">%s</td>' % \
       
   276                 (obfuscate(changes[1]), )
       
   277         print '\t\t<td valign="top" width="10%">description:</td>' + \
       
   278                 '<td width="60%">' + \
       
   279                 '<a href="?cmd=chkin;nd=%s">%s</a></td></tr>' % \
       
   280                 (cs, nl2br(cgi.escape(changes[4])), )
       
   281         print '\t<tr><td>date:</td><td>%s UTC</td>' % (datestr, )
       
   282         print '\t\t<td>revision:</td><td>%d:<a ' % (revi, ) + \
       
   283                 'href="?cmd=file;cs=%s;fn=%s">%s</a></td></tr>' % \
       
   284                 (cs, self.fn, revs )
       
   285         print '\t<tr><td>parent(s):</td><td colspan="3">%d:' % (p1i, )
       
   286         print '<a href="?cmd=file;nd=%s;fn=%s">%s</a>' % (p1s, self.fn, p1s, ),
       
   287         if p2i != -1:
       
   288             print '&nbsp;&nbsp;%d:<a href="?cmd=file;nd=%s;fn=%s">%s</a>' % \
       
   289                     (p2i, p2s, self.fn, p2s ),
       
   290         print '</td></tr>'
       
   291         print '</table><br />'
       
   292 
       
   293 args = cgi.parse()
       
   294 
       
   295 ui = hg.ui()
       
   296 repo = hg.repository(ui, repo_path)
       
   297 
       
   298 if not args.has_key('cmd') or args['cmd'][0] == 'changes':
       
   299     page = change_list(repo, 'Mercurial')
       
   300     hi = args.get('hi', ( repo.changelog.count(), ))
       
   301     page.content(hi = int(hi[0]))
       
   302     page.endpage()
       
   303     
       
   304 elif args['cmd'][0] == 'chkin':
       
   305     if not args.has_key('nd'):
       
   306         page = errpage()
       
   307         print '<div class="errmsg">No Node!</div>'
       
   308     else:
       
   309         page = checkin(repo, args['nd'][0])
       
   310         page.content()
       
   311     page.endpage()
       
   312 
       
   313 elif args['cmd'][0] == 'file':
       
   314     if not (args.has_key('nd') and args.has_key('fn')) and \
       
   315             not (args.has_key('cs') and args.has_key('fn')):
       
   316         page = errpage()
       
   317         print '<div class="errmsg">Invalid Args!</div>'
       
   318     else:
       
   319         if args.has_key('nd'):
       
   320             page = filepage(repo, args['fn'][0], node=args['nd'][0])
       
   321         else:
       
   322             page = filepage(repo, args['fn'][0], cs=args['cs'][0])
       
   323         page.content()
       
   324     page.endpage()
       
   325 
       
   326 elif args['cmd'][0] == 'mf':
       
   327     if not args.has_key('nd'):
       
   328         page = errpage()
       
   329         print '<div class="errmsg">No Node!</div>'
       
   330     else:
       
   331         page = mfpage(repo, args['nd'][0])
       
   332         page.content()
       
   333     page.endpage()
       
   334 
       
   335 elif args['cmd'][0] == 'hist':
       
   336     if not args.has_key('fn'):
       
   337         page = errpage()
       
   338         print '<div class="errmsg">No Filename!</div>'
       
   339     else:
       
   340         page = histpage(repo, args['fn'][0])
       
   341         page.content()
       
   342     page.endpage()
       
   343 
       
   344 elif args['cmd'][0] == 'branches':
       
   345     httphdr("text/plain")
       
   346     nodes = []
       
   347     if args.has_key('nodes'):
       
   348         nodes = map(hg.bin, args['nodes'][0].split(" "))
       
   349     for b in repo.branches(nodes):
       
   350         print " ".join(map(hg.hex, b))
       
   351 
       
   352 elif args['cmd'][0] == 'between':
       
   353     httphdr("text/plain")
       
   354     nodes = []
       
   355     if args.has_key('pairs'):
       
   356         pairs = [ map(hg.bin, p.split("-"))
       
   357                   for p in args['pairs'][0].split(" ") ]
       
   358     for b in repo.between(pairs):
       
   359         print " ".join(map(hg.hex, b))
       
   360 
       
   361 elif args['cmd'][0] == 'changegroup':
       
   362     httphdr("application/hg-changegroup")
       
   363     nodes = []
       
   364     if args.has_key('roots'):
       
   365         nodes = map(hg.bin, args['roots'][0].split(" "))
       
   366 
       
   367     z = zlib.compressobj()
       
   368     for chunk in repo.changegroup(nodes):
       
   369         sys.stdout.write(z.compress(chunk))
       
   370 
       
   371     sys.stdout.write(z.flush())
       
   372 
       
   373 else:
       
   374     page = errpage()
       
   375     print '<div class="errmsg">unknown command: %s</div>' % \
       
   376             cgi.escape(args['cmd'][0])
       
   377     page.endpage()