Mercurial > public > mercurial-scm > hg-stable
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> ' \ | |
110 % (start, self.numchanges)) + nav | |
111 if hi != count: | |
112 if hi + self.numchanges <= count: | |
113 nav += ' <a href="?cmd=changes;hi=%d">Next %d</a>' \ | |
114 % (hi + self.numchanges, self.numchanges) | |
115 else: | |
116 nav += ' <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> ' \ | |
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> ' \ | |
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 = ' %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() |