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