comparison mercurial/hgweb.py @ 137:b45b1b00fc9e

Merge from hgweb
author mpm@selenic.com
date Sun, 22 May 2005 08:13:38 -0800
parents 0e8d60d2bb2b
children c77a679e9cfa
comparison
equal deleted inserted replaced
130:e6678a1beb6a 137:b45b1b00fc9e
1 #!/usr/bin/env python
2 #
3 # hgweb.py - 0.2 - 21 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 def nl2br(text):
17 return re.sub('\n', '<br />', text)
18
19 def obfuscate(text):
20 l = []
21 for c in text:
22 l.append('&#%d;' % ord(c))
23 return ''.join(l)
24
25 def httphdr(type):
26 print 'Content-type: %s\n' % type
27
28 def write(*things):
29 for thing in things:
30 if hasattr(thing, "__iter__"):
31 for part in thing:
32 write(part)
33 else:
34 sys.stdout.write(str(thing))
35
36 class template:
37 def __init__(self, tmpl_dir):
38 self.tmpl_dir = tmpl_dir
39 def do_page(self, tmpl_fn, **map):
40 txt = file(os.path.join(self.tmpl_dir, tmpl_fn)).read()
41 while txt:
42 m = re.search(r"#([a-zA-Z0-9]+)#", txt)
43 if m:
44 yield txt[:m.start(0)]
45 v = map.get(m.group(1), "")
46 if callable(v):
47 for y in v(**map): yield y
48 else:
49 yield v
50 txt = txt[m.end(0):]
51 else:
52 yield txt
53 txt = ''
54
55 class page:
56 def __init__(self, tmpl_dir = "", type="text/html", title="Mercurial Web",
57 charset="ISO-8859-1"):
58 self.tmpl = template(tmpl_dir)
59
60 print 'Content-type: %s; charset=%s\n' % (type, charset)
61 write(self.tmpl.do_page('htmlstart.tmpl', title = title))
62
63 def endpage(self):
64 print '</BODY>'
65 print '</HTML>'
66
67 def show_diff(self, a, b, fn):
68 a = a.splitlines(1)
69 b = b.splitlines(1)
70 l = difflib.unified_diff(a, b, fn, fn)
71 print '<pre>'
72 for line in l:
73 line = cgi.escape(line[:-1])
74 if line.startswith('+'):
75 print '<span class="plusline">%s</span>' % (line, )
76 elif line.startswith('-'):
77 print '<span class="minusline">%s</span>' % (line, )
78 elif line.startswith('@'):
79 print '<span class="atline">%s</span>' % (line, )
80 else:
81 print line
82 print '</pre>'
83
84 class errpage(page):
85 def __init__(self, tmpl_dir):
86 page.__init__(self, tmpl_dir, title="Mercurial Web Error Page")
87
88 class change_list(page):
89 def __init__(self, repo, tmpl_dir, reponame, numchanges = 50):
90 page.__init__(self, tmpl_dir)
91 self.repo = repo
92 self.numchanges = numchanges
93 write(self.tmpl.do_page('changestitle.tmpl', reponame=reponame))
94
95 def content(self, hi=None):
96 cl = []
97 count = self.repo.changelog.count()
98 if not hi:
99 hi = count
100 elif hi < self.numchanges:
101 hi = self.numchanges
102
103 start = 0
104 if hi - self.numchanges >= 0:
105 start = hi - self.numchanges
106
107 nav = "Displaying Revisions: %d-%d" % (start, hi-1)
108 if start != 0:
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
292 def run(self):
293
294 args = cgi.parse()
295
296 ui = hg.ui()
297 repo = hg.repository(ui, self.repo_path)
298
299 if not args.has_key('cmd') or args['cmd'][0] == 'changes':
300 page = change_list(repo, self.tmpl_dir, 'Mercurial',
301 self.numchanges)
302 hi = args.get('hi', ( repo.changelog.count(), ))
303 page.content(hi = int(hi[0]))
304 page.endpage()
305
306 elif args['cmd'][0] == 'chkin':
307 if not args.has_key('nd'):
308 page = errpage(self.tmpl_dir)
309 print '<div class="errmsg">No Node!</div>'
310 else:
311 page = checkin(repo, self.tmpl_dir, args['nd'][0])
312 page.content()
313 page.endpage()
314
315 elif args['cmd'][0] == 'file':
316 if not (args.has_key('nd') and args.has_key('fn')) and \
317 not (args.has_key('cs') and args.has_key('fn')):
318 page = errpage(self.tmpl_dir)
319 print '<div class="errmsg">Invalid Args!</div>'
320 else:
321 if args.has_key('nd'):
322 page = filepage(repo, self.tmpl_dir,
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
361 elif args['cmd'][0] == 'branches':
362 httphdr("text/plain")
363 nodes = []
364 if args.has_key('nodes'):
365 nodes = map(hg.bin, args['nodes'][0].split(" "))
366 for b in repo.branches(nodes):
367 print " ".join(map(hg.hex, b))
368
369 elif args['cmd'][0] == 'between':
370 httphdr("text/plain")
371 nodes = []
372 if args.has_key('pairs'):
373 pairs = [ map(hg.bin, p.split("-"))
374 for p in args['pairs'][0].split(" ") ]
375 for b in repo.between(pairs):
376 print " ".join(map(hg.hex, b))
377
378 elif args['cmd'][0] == 'changegroup':
379 httphdr("application/hg-changegroup")
380 nodes = []
381 if args.has_key('roots'):
382 nodes = map(hg.bin, args['roots'][0].split(" "))
383
384 z = zlib.compressobj()
385 for chunk in repo.changegroup(nodes):
386 sys.stdout.write(z.compress(chunk))
387
388 sys.stdout.write(z.flush())
389
390 else:
391 page = errpage(self.tmpl_dir)
392 print '<div class="errmsg">unknown command: %s</div>' % \
393 cgi.escape(args['cmd'][0])
394 page.endpage()
395
396 if __name__ == "__main__":
397 hgweb().run()