##// END OF EJS Templates
Install the templates where they can be found by hgweb.py...
mpm@selenic.com -
r157:2653740d default
parent child Browse files
Show More
@@ -1,575 +1,582
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.hg import *
15 15
16 def templatepath():
17 for f in "templates/map", "../templates/map":
18 p = os.path.join(os.path.dirname(__file__), f)
19 if os.path.isfile(p): return p
20
16 21 def age(t):
17 22 def plural(t, c):
18 23 if c == 1: return t
19 24 return t + "s"
20 25 def fmt(t, c):
21 26 return "%d %s" % (c, plural(t, c))
22 27
23 28 now = time.time()
24 29 delta = max(1, int(now - t))
25 30
26 31 scales = [["second", 1],
27 32 ["minute", 60],
28 33 ["hour", 3600],
29 34 ["day", 3600 * 24],
30 35 ["week", 3600 * 24 * 7],
31 36 ["month", 3600 * 24 * 30],
32 37 ["year", 3600 * 24 * 365]]
33 38
34 39 scales.reverse()
35 40
36 41 for t, s in scales:
37 42 n = delta / s
38 43 if n >= 1: return fmt(t, n)
39 44
40 45 def nl2br(text):
41 46 return text.replace('\n', '<br/>')
42 47
43 48 def obfuscate(text):
44 49 return ''.join([ '&#%d' % ord(c) for c in text ])
45 50
46 51 def up(p):
47 52 if p[0] != "/": p = "/" + p
48 53 if p[-1] == "/": p = p[:-1]
49 54 up = os.path.dirname(p)
50 55 if up == "/":
51 56 return "/"
52 57 return up + "/"
53 58
54 59 def httphdr(type):
55 60 print 'Content-type: %s\n' % type
56 61
57 62 def write(*things):
58 63 for thing in things:
59 64 if hasattr(thing, "__iter__"):
60 65 for part in thing:
61 66 write(part)
62 67 else:
63 68 sys.stdout.write(str(thing))
64 69
65 70 def template(tmpl, **map):
66 71 while tmpl:
67 72 m = re.search(r"#([a-zA-Z0-9]+)#", tmpl)
68 73 if m:
69 74 yield tmpl[:m.start(0)]
70 75 v = map.get(m.group(1), "")
71 76 yield callable(v) and v() or v
72 77 tmpl = tmpl[m.end(0):]
73 78 else:
74 79 yield tmpl
75 80 return
76 81
77 82 class templater:
78 83 def __init__(self, mapfile):
79 84 self.cache = {}
80 85 self.map = {}
81 86 self.base = os.path.dirname(mapfile)
82 87
83 88 for l in file(mapfile):
84 89 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
85 90 if m:
86 91 self.cache[m.group(1)] = m.group(2)
87 92 else:
88 93 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
89 94 if m:
90 95 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
91 96 else:
92 97 raise "unknown map entry '%s'" % l
93 98
94 99 def __call__(self, t, **map):
95 100 try:
96 101 tmpl = self.cache[t]
97 102 except KeyError:
98 103 tmpl = self.cache[t] = file(self.map[t]).read()
99 104 return template(tmpl, **map)
100 105
101 106 class hgweb:
102 107 maxchanges = 20
103 108 maxfiles = 10
104 109
105 def __init__(self, path, name, templatemap):
110 def __init__(self, path, name, templatemap = ""):
111 templatemap = templatemap or templatepath()
112
106 113 self.reponame = name
107 114 self.repo = repository(ui(), path)
108 115 self.t = templater(templatemap)
109 116
110 117 def date(self, cs):
111 118 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
112 119
113 120 def listfiles(self, files, mf):
114 121 for f in files[:self.maxfiles]:
115 122 yield self.t("filenodelink", node = hex(mf[f]), file = f)
116 123 if len(files) > self.maxfiles:
117 124 yield self.t("fileellipses")
118 125
119 126 def listfilediffs(self, files, changeset):
120 127 for f in files[:self.maxfiles]:
121 128 yield self.t("filedifflink", node = hex(changeset), file = f)
122 129 if len(files) > self.maxfiles:
123 130 yield self.t("fileellipses")
124 131
125 132 def parent(self, t1, node=nullid, rev=-1, **args):
126 133 if node != hex(nullid):
127 134 yield self.t(t1, node = node, rev = rev, **args)
128 135
129 136 def diff(self, node1, node2, files):
130 137 def filterfiles(list, files):
131 138 l = [ x for x in list if x in files ]
132 139
133 140 for f in files:
134 141 if f[-1] != os.sep: f += os.sep
135 142 l += [ x for x in list if x.startswith(f) ]
136 143 return l
137 144
138 145 def prettyprint(diff):
139 146 for l in diff.splitlines(1):
140 147 line = cgi.escape(l)
141 148 if line.startswith('+'):
142 149 yield self.t("difflineplus", line = line)
143 150 elif line.startswith('-'):
144 151 yield self.t("difflineminus", line = line)
145 152 elif line.startswith('@'):
146 153 yield self.t("difflineat", line = line)
147 154 else:
148 155 yield self.t("diffline", line = line)
149 156
150 157 r = self.repo
151 158 cl = r.changelog
152 159 mf = r.manifest
153 160 change1 = cl.read(node1)
154 161 change2 = cl.read(node2)
155 162 mmap1 = mf.read(change1[0])
156 163 mmap2 = mf.read(change2[0])
157 164 date1 = self.date(change1)
158 165 date2 = self.date(change2)
159 166
160 167 c, a, d = r.diffrevs(node1, node2)
161 168 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
162 169
163 170 for f in c:
164 171 to = r.file(f).read(mmap1[f])
165 172 tn = r.file(f).read(mmap2[f])
166 173 yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
167 174 for f in a:
168 175 to = ""
169 176 tn = r.file(f).read(mmap2[f])
170 177 yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
171 178 for f in d:
172 179 to = r.file(f).read(mmap1[f])
173 180 tn = ""
174 181 yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
175 182
176 183 def header(self):
177 184 yield self.t("header", repo = self.reponame)
178 185
179 186 def footer(self):
180 187 yield self.t("footer", repo = self.reponame)
181 188
182 189 def changelog(self, pos=None):
183 190 def changenav():
184 191 def seq(factor = 1):
185 192 yield 1 * factor
186 193 yield 2 * factor
187 194 yield 5 * factor
188 195 for f in seq(factor * 10):
189 196 yield f
190 197
191 198 linear = range(0, count - 2, self.maxchanges)[0:8]
192 199
193 200 for i in linear:
194 201 yield self.t("naventry", rev = max(i, 1))
195 202
196 203 for s in seq():
197 204 if s > count - 2: break
198 205 if s > linear[-1]:
199 206 yield self.t("naventry", rev = s)
200 207
201 208 yield self.t("naventry", rev = count - 1)
202 209
203 210 def changelist():
204 211 parity = (start - end) & 1
205 212 cl = self.repo.changelog
206 213 l = [] # build a list in forward order for efficiency
207 214 for i in range(start, end + 1):
208 215 n = cl.node(i)
209 216 changes = cl.read(n)
210 217 hn = hex(n)
211 218 p1, p2 = cl.parents(n)
212 219 t = float(changes[2].split(' ')[0])
213 220
214 221 l.insert(0, self.t(
215 222 'changelogentry',
216 223 parity = parity,
217 224 author = obfuscate(changes[1]),
218 225 shortdesc = cgi.escape(changes[4].splitlines()[0]),
219 226 age = age(t),
220 227 parent1 = self.parent("changelogparent",
221 228 hex(p1), cl.rev(p1)),
222 229 parent2 = self.parent("changelogparent",
223 230 hex(p2), cl.rev(p2)),
224 231 p1 = hex(p1), p2 = hex(p2),
225 232 p1rev = cl.rev(p1), p2rev = cl.rev(p2),
226 233 manifest = hex(changes[0]),
227 234 desc = nl2br(cgi.escape(changes[4])),
228 235 date = time.asctime(time.gmtime(t)),
229 236 files = self.listfilediffs(changes[3], n),
230 237 rev = i,
231 238 node = hn))
232 239 parity = 1 - parity
233 240
234 241 yield l
235 242
236 243 count = self.repo.changelog.count()
237 244 pos = pos or count - 1
238 245 end = min(pos, count - 1)
239 246 start = max(0, pos - self.maxchanges)
240 247 end = min(count - 1, start + self.maxchanges)
241 248
242 249 yield self.t('changelog',
243 250 header = self.header(),
244 251 footer = self.footer(),
245 252 repo = self.reponame,
246 253 changenav = changenav,
247 254 rev = pos, changesets = count, entries = changelist)
248 255
249 256 def changeset(self, nodeid):
250 257 n = bin(nodeid)
251 258 cl = self.repo.changelog
252 259 changes = cl.read(n)
253 260 p1, p2 = cl.parents(n)
254 261 p1rev, p2rev = cl.rev(p1), cl.rev(p2)
255 262 t = float(changes[2].split(' ')[0])
256 263
257 264 files = []
258 265 mf = self.repo.manifest.read(changes[0])
259 266 for f in changes[3]:
260 267 files.append(self.t("filenodelink",
261 268 filenode = hex(mf[f]), file = f))
262 269
263 270 def diff():
264 271 yield self.diff(p1, n, changes[3])
265 272
266 273 yield self.t('changeset',
267 274 header = self.header(),
268 275 footer = self.footer(),
269 276 repo = self.reponame,
270 277 diff = diff,
271 278 rev = cl.rev(n),
272 279 node = nodeid,
273 280 shortdesc = cgi.escape(changes[4].splitlines()[0]),
274 281 parent1 = self.parent("changesetparent",
275 282 hex(p1), cl.rev(p1)),
276 283 parent2 = self.parent("changesetparent",
277 284 hex(p2), cl.rev(p2)),
278 285 p1 = hex(p1), p2 = hex(p2),
279 286 p1rev = cl.rev(p1), p2rev = cl.rev(p2),
280 287 manifest = hex(changes[0]),
281 288 author = obfuscate(changes[1]),
282 289 desc = nl2br(cgi.escape(changes[4])),
283 290 date = time.asctime(time.gmtime(t)),
284 291 files = files)
285 292
286 293 def filelog(self, f, filenode):
287 294 cl = self.repo.changelog
288 295 fl = self.repo.file(f)
289 296 count = fl.count()
290 297
291 298 def entries():
292 299 l = []
293 300 parity = (count - 1) & 1
294 301
295 302 for i in range(count):
296 303
297 304 n = fl.node(i)
298 305 lr = fl.linkrev(n)
299 306 cn = cl.node(lr)
300 307 cs = cl.read(cl.node(lr))
301 308 p1, p2 = fl.parents(n)
302 309 t = float(cs[2].split(' ')[0])
303 310
304 311 l.insert(0, self.t("filelogentry",
305 312 parity = parity,
306 313 filenode = hex(n),
307 314 filerev = i,
308 315 file = f,
309 316 node = hex(cn),
310 317 author = obfuscate(cs[1]),
311 318 age = age(t),
312 319 date = time.asctime(time.gmtime(t)),
313 320 shortdesc = cgi.escape(cs[4].splitlines()[0]),
314 321 p1 = hex(p1), p2 = hex(p2),
315 322 p1rev = fl.rev(p1), p2rev = fl.rev(p2)))
316 323 parity = 1 - parity
317 324
318 325 yield l
319 326
320 327 yield self.t("filelog",
321 328 header = self.header(),
322 329 footer = self.footer(),
323 330 repo = self.reponame,
324 331 file = f,
325 332 filenode = filenode,
326 333 entries = entries)
327 334
328 335 def filerevision(self, f, node):
329 336 fl = self.repo.file(f)
330 337 n = bin(node)
331 338 text = cgi.escape(fl.read(n))
332 339 changerev = fl.linkrev(n)
333 340 cl = self.repo.changelog
334 341 cn = cl.node(changerev)
335 342 cs = cl.read(cn)
336 343 p1, p2 = fl.parents(n)
337 344 t = float(cs[2].split(' ')[0])
338 345 mfn = cs[0]
339 346
340 347 def lines():
341 348 for l, t in enumerate(text.splitlines(1)):
342 349 yield self.t("fileline",
343 350 line = t,
344 351 linenumber = "% 6d" % (l + 1),
345 352 parity = l & 1)
346 353
347 354 yield self.t("filerevision", file = f,
348 355 header = self.header(),
349 356 footer = self.footer(),
350 357 repo = self.reponame,
351 358 filenode = node,
352 359 path = up(f),
353 360 text = lines(),
354 361 rev = changerev,
355 362 node = hex(cn),
356 363 manifest = hex(mfn),
357 364 author = obfuscate(cs[1]),
358 365 age = age(t),
359 366 date = time.asctime(time.gmtime(t)),
360 367 shortdesc = cgi.escape(cs[4].splitlines()[0]),
361 368 parent1 = self.parent("filerevparent",
362 369 hex(p1), fl.rev(p1), file=f),
363 370 parent2 = self.parent("filerevparent",
364 371 hex(p2), fl.rev(p2), file=f),
365 372 p1 = hex(p1), p2 = hex(p2),
366 373 p1rev = fl.rev(p1), p2rev = fl.rev(p2))
367 374
368 375
369 376 def fileannotate(self, f, node):
370 377 bcache = {}
371 378 ncache = {}
372 379 fl = self.repo.file(f)
373 380 n = bin(node)
374 381 changerev = fl.linkrev(n)
375 382
376 383 cl = self.repo.changelog
377 384 cn = cl.node(changerev)
378 385 cs = cl.read(cn)
379 386 p1, p2 = fl.parents(n)
380 387 t = float(cs[2].split(' ')[0])
381 388 mfn = cs[0]
382 389
383 390 def annotate():
384 391 parity = 1
385 392 last = None
386 393 for r, l in fl.annotate(n):
387 394 try:
388 395 cnode = ncache[r]
389 396 except KeyError:
390 397 cnode = ncache[r] = self.repo.changelog.node(r)
391 398
392 399 try:
393 400 name = bcache[r]
394 401 except KeyError:
395 402 cl = self.repo.changelog.read(cnode)
396 403 name = cl[1]
397 404 f = name.find('@')
398 405 if f >= 0:
399 406 name = name[:f]
400 407 bcache[r] = name
401 408
402 409 if last != cnode:
403 410 parity = 1 - parity
404 411 last = cnode
405 412
406 413 yield self.t("annotateline",
407 414 parity = parity,
408 415 node = hex(cnode),
409 416 rev = r,
410 417 author = name,
411 418 file = f,
412 419 line = cgi.escape(l))
413 420
414 421 yield self.t("fileannotate",
415 422 header = self.header(),
416 423 footer = self.footer(),
417 424 repo = self.reponame,
418 425 file = f,
419 426 filenode = node,
420 427 annotate = annotate,
421 428 path = up(f),
422 429 rev = changerev,
423 430 node = hex(cn),
424 431 manifest = hex(mfn),
425 432 author = obfuscate(cs[1]),
426 433 age = age(t),
427 434 date = time.asctime(time.gmtime(t)),
428 435 shortdesc = cgi.escape(cs[4].splitlines()[0]),
429 436 parent1 = self.parent("fileannotateparent",
430 437 hex(p1), fl.rev(p1), file=f),
431 438 parent2 = self.parent("fileannotateparent",
432 439 hex(p2), fl.rev(p2), file=f),
433 440 p1 = hex(p1), p2 = hex(p2),
434 441 p1rev = fl.rev(p1), p2rev = fl.rev(p2))
435 442
436 443 def manifest(self, mnode, path):
437 444 mf = self.repo.manifest.read(bin(mnode))
438 445 rev = self.repo.manifest.rev(bin(mnode))
439 446 node = self.repo.changelog.node(rev)
440 447
441 448 files = {}
442 449
443 450 p = path[1:]
444 451 l = len(p)
445 452
446 453 for f,n in mf.items():
447 454 if f[:l] != p:
448 455 continue
449 456 remain = f[l:]
450 457 if "/" in remain:
451 458 short = remain[:remain.find("/") + 1] # bleah
452 459 files[short] = (f, None)
453 460 else:
454 461 short = os.path.basename(remain)
455 462 files[short] = (f, n)
456 463
457 464 def filelist():
458 465 parity = 0
459 466 fl = files.keys()
460 467 fl.sort()
461 468 for f in fl:
462 469 full, fnode = files[f]
463 470 if fnode:
464 471 yield self.t("manifestfileentry",
465 472 file = full,
466 473 manifest = mnode,
467 474 filenode = hex(fnode),
468 475 parity = parity,
469 476 basename = f)
470 477 else:
471 478 yield self.t("manifestdirentry",
472 479 parity = parity,
473 480 path = os.path.join(path, f),
474 481 manifest = mnode, basename = f[:-1])
475 482 parity = 1 - parity
476 483
477 484 yield self.t("manifest",
478 485 header = self.header(),
479 486 footer = self.footer(),
480 487 repo = self.reponame,
481 488 manifest = mnode,
482 489 rev = rev,
483 490 node = hex(node),
484 491 path = path,
485 492 up = up(path),
486 493 entries = filelist)
487 494
488 495 def filediff(self, file, changeset):
489 496 n = bin(changeset)
490 497 cl = self.repo.changelog
491 498 p1 = cl.parents(n)[0]
492 499 cs = cl.read(n)
493 500 mf = self.repo.manifest.read(cs[0])
494 501
495 502 def diff():
496 503 yield self.diff(p1, n, file)
497 504
498 505 yield self.t("filediff",
499 506 header = self.header(),
500 507 footer = self.footer(),
501 508 repo = self.reponame,
502 509 file = file,
503 510 filenode = hex(mf[file]),
504 511 node = changeset,
505 512 rev = self.repo.changelog.rev(n),
506 513 p1 = hex(p1),
507 514 p1rev = self.repo.changelog.rev(p1),
508 515 diff = diff)
509 516
510 517 # add tags to things
511 518 # tags -> list of changesets corresponding to tags
512 519 # find tag, changeset, file
513 520
514 521 def run(self):
515 522 args = cgi.parse()
516 523
517 524 if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
518 525 hi = self.repo.changelog.count()
519 526 if args.has_key('rev'):
520 527 hi = int(args['rev'][0])
521 528
522 529 write(self.changelog(hi))
523 530
524 531 elif args['cmd'][0] == 'changeset':
525 532 write(self.changeset(args['node'][0]))
526 533
527 534 elif args['cmd'][0] == 'manifest':
528 535 write(self.manifest(args['manifest'][0], args['path'][0]))
529 536
530 537 elif args['cmd'][0] == 'filediff':
531 538 write(self.filediff(args['file'][0], args['node'][0]))
532 539
533 540 elif args['cmd'][0] == 'file':
534 541 write(self.filerevision(args['file'][0], args['filenode'][0]))
535 542
536 543 elif args['cmd'][0] == 'annotate':
537 544 write(self.fileannotate(args['file'][0], args['filenode'][0]))
538 545
539 546 elif args['cmd'][0] == 'filelog':
540 547 write(self.filelog(args['file'][0], args['filenode'][0]))
541 548
542 549 elif args['cmd'][0] == 'branches':
543 550 httphdr("text/plain")
544 551 nodes = []
545 552 if args.has_key('nodes'):
546 553 nodes = map(bin, args['nodes'][0].split(" "))
547 554 for b in self.repo.branches(nodes):
548 555 sys.stdout.write(" ".join(map(hex, b)) + "\n")
549 556
550 557 elif args['cmd'][0] == 'between':
551 558 httphdr("text/plain")
552 559 nodes = []
553 560 if args.has_key('pairs'):
554 561 pairs = [ map(bin, p.split("-"))
555 562 for p in args['pairs'][0].split(" ") ]
556 563 for b in self.repo.between(pairs):
557 564 sys.stdout.write(" ".join(map(hex, b)) + "\n")
558 565
559 566 elif args['cmd'][0] == 'changegroup':
560 567 httphdr("application/hg-changegroup")
561 568 nodes = []
562 569 if args.has_key('roots'):
563 570 nodes = map(bin, args['roots'][0].split(" "))
564 571
565 572 z = zlib.compressobj()
566 573 for chunk in self.repo.changegroup(nodes):
567 574 sys.stdout.write(z.compress(chunk))
568 575
569 576 sys.stdout.write(z.flush())
570 577
571 578 else:
572 579 write(self.t("error"))
573 580
574 581 if __name__ == "__main__":
575 582 hgweb().run()
@@ -1,19 +1,30
1 1 #!/usr/bin/env python
2 2
3 3 # This is the mercurial setup script.
4 4 #
5 5 # './setup.py install', or
6 6 # './setup.py --help' for more options
7 7
8 import glob
8 9 from distutils.core import setup, Extension
10 from distutils.command.install_data import install_data
11
12 class install_package_data(install_data):
13 def finalize_options(self):
14 self.set_undefined_options('install',
15 ('install_lib', 'install_dir'))
16 install_data.finalize_options(self)
9 17
10 18 setup(name='mercurial',
11 19 version='0.4f',
12 20 author='Matt Mackall',
13 21 author_email='mpm@selenic.com',
14 22 url='http://selenic.com/mercurial',
15 23 description='scalable distributed SCM',
16 24 license='GNU GPL',
17 25 packages=['mercurial'],
18 26 ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c'])],
27 data_files=[('mercurial/templates',
28 ['templates/map'] + glob.glob('templates/*.tmpl'))],
29 cmdclass = { 'install_data' : install_package_data },
19 30 scripts=['hg'])
General Comments 0
You need to be logged in to leave comments. Login now