##// END OF EJS Templates
hgweb: add strip and rstrip filters to handle summary
TK Soh -
r1445:56281e08 default
parent child Browse files
Show More
@@ -1,1021 +1,1023
1 1 # hgweb.py - web interface to a mercurial repository
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
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 import os, cgi, sys
10 10 from demandload import demandload
11 11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 13 demandload(globals(), "mimetypes")
14 14 from node import *
15 15 from i18n import gettext as _
16 16
17 17 def templatepath():
18 18 for f in "templates", "../templates":
19 19 p = os.path.join(os.path.dirname(__file__), f)
20 20 if os.path.isdir(p):
21 21 return p
22 22
23 23 def age(x):
24 24 def plural(t, c):
25 25 if c == 1:
26 26 return t
27 27 return t + "s"
28 28 def fmt(t, c):
29 29 return "%d %s" % (c, plural(t, c))
30 30
31 31 now = time.time()
32 32 then = x[0]
33 33 delta = max(1, int(now - then))
34 34
35 35 scales = [["second", 1],
36 36 ["minute", 60],
37 37 ["hour", 3600],
38 38 ["day", 3600 * 24],
39 39 ["week", 3600 * 24 * 7],
40 40 ["month", 3600 * 24 * 30],
41 41 ["year", 3600 * 24 * 365]]
42 42
43 43 scales.reverse()
44 44
45 45 for t, s in scales:
46 46 n = delta / s
47 47 if n >= 2 or s == 1:
48 48 return fmt(t, n)
49 49
50 50 def nl2br(text):
51 51 return text.replace('\n', '<br/>\n')
52 52
53 53 def obfuscate(text):
54 54 return ''.join(['&#%d;' % ord(c) for c in text])
55 55
56 56 def up(p):
57 57 if p[0] != "/":
58 58 p = "/" + p
59 59 if p[-1] == "/":
60 60 p = p[:-1]
61 61 up = os.path.dirname(p)
62 62 if up == "/":
63 63 return "/"
64 64 return up + "/"
65 65
66 66 def get_mtime(repo_path):
67 67 hg_path = os.path.join(repo_path, ".hg")
68 68 cl_path = os.path.join(hg_path, "00changelog.i")
69 69 if os.path.exists(os.path.join(cl_path)):
70 70 return os.stat(cl_path).st_mtime
71 71 else:
72 72 return os.stat(hg_path).st_mtime
73 73
74 74 class hgrequest:
75 75 def __init__(self, inp=None, out=None, env=None):
76 76 self.inp = inp or sys.stdin
77 77 self.out = out or sys.stdout
78 78 self.env = env or os.environ
79 79 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
80 80
81 81 def write(self, *things):
82 82 for thing in things:
83 83 if hasattr(thing, "__iter__"):
84 84 for part in thing:
85 85 self.write(part)
86 86 else:
87 87 try:
88 88 self.out.write(str(thing))
89 89 except socket.error, inst:
90 90 if inst[0] != errno.ECONNRESET:
91 91 raise
92 92
93 93 def header(self, headers=[('Content-type','text/html')]):
94 94 for header in headers:
95 95 self.out.write("%s: %s\r\n" % header)
96 96 self.out.write("\r\n")
97 97
98 98 def httphdr(self, type, file="", size=0):
99 99
100 100 headers = [('Content-type', type)]
101 101 if file:
102 102 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
103 103 if size > 0:
104 104 headers.append(('Content-length', str(size)))
105 105 self.header(headers)
106 106
107 107 class templater:
108 108 def __init__(self, mapfile, filters={}, defaults={}):
109 109 self.cache = {}
110 110 self.map = {}
111 111 self.base = os.path.dirname(mapfile)
112 112 self.filters = filters
113 113 self.defaults = defaults
114 114
115 115 for l in file(mapfile):
116 116 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
117 117 if m:
118 118 self.cache[m.group(1)] = m.group(2)
119 119 else:
120 120 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
121 121 if m:
122 122 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
123 123 else:
124 124 raise LookupError(_("unknown map entry '%s'") % l)
125 125
126 126 def __call__(self, t, **map):
127 127 m = self.defaults.copy()
128 128 m.update(map)
129 129 try:
130 130 tmpl = self.cache[t]
131 131 except KeyError:
132 132 tmpl = self.cache[t] = file(self.map[t]).read()
133 133 return self.template(tmpl, self.filters, **m)
134 134
135 135 def template(self, tmpl, filters={}, **map):
136 136 while tmpl:
137 137 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
138 138 if m:
139 139 yield tmpl[:m.start(0)]
140 140 v = map.get(m.group(1), "")
141 141 v = callable(v) and v(**map) or v
142 142
143 143 format = m.group(2)
144 144 fl = m.group(4)
145 145
146 146 if format:
147 147 q = v.__iter__
148 148 for i in q():
149 149 lm = map.copy()
150 150 lm.update(i)
151 151 yield self(format[1:], **lm)
152 152
153 153 v = ""
154 154
155 155 elif fl:
156 156 for f in fl.split("|")[1:]:
157 157 v = filters[f](v)
158 158
159 159 yield v
160 160 tmpl = tmpl[m.end(0):]
161 161 else:
162 162 yield tmpl
163 163 return
164 164
165 165 common_filters = {
166 166 "escape": cgi.escape,
167 "strip": lambda x: x.strip(),
168 "rstrip": lambda x: x.rstrip(),
167 169 "age": age,
168 170 "date": lambda x: util.datestr(x),
169 171 "addbreaks": nl2br,
170 172 "obfuscate": obfuscate,
171 173 "short": (lambda x: x[:12]),
172 174 "firstline": (lambda x: x.splitlines(1)[0]),
173 175 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
174 176 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
175 177 }
176 178
177 179 class hgweb:
178 180 def __init__(self, repo, name=None):
179 181 if type(repo) == type(""):
180 182 self.repo = hg.repository(ui.ui(), repo)
181 183 else:
182 184 self.repo = repo
183 185
184 186 self.mtime = -1
185 187 self.reponame = name
186 188 self.archives = 'zip', 'gz', 'bz2'
187 189
188 190 def refresh(self):
189 191 mtime = get_mtime(self.repo.root)
190 192 if mtime != self.mtime:
191 193 self.mtime = mtime
192 194 self.repo = hg.repository(self.repo.ui, self.repo.root)
193 195 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
194 196 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
195 197 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
196 198
197 199 def listfiles(self, files, mf):
198 200 for f in files[:self.maxfiles]:
199 201 yield self.t("filenodelink", node=hex(mf[f]), file=f)
200 202 if len(files) > self.maxfiles:
201 203 yield self.t("fileellipses")
202 204
203 205 def listfilediffs(self, files, changeset):
204 206 for f in files[:self.maxfiles]:
205 207 yield self.t("filedifflink", node=hex(changeset), file=f)
206 208 if len(files) > self.maxfiles:
207 209 yield self.t("fileellipses")
208 210
209 211 def parents(self, node, parents=[], rev=None, hide=False, **args):
210 212 if not rev:
211 213 rev = lambda x: ""
212 214 parents = [p for p in parents if p != nullid]
213 215 if hide and len(parents) == 1 and rev(parents[0]) == rev(node) - 1:
214 216 return
215 217 for p in parents:
216 218 yield dict(node=hex(p), rev=rev(p), **args)
217 219
218 220 def showtag(self, t1, node=nullid, **args):
219 221 for t in self.repo.nodetags(node):
220 222 yield self.t(t1, tag=t, **args)
221 223
222 224 def diff(self, node1, node2, files):
223 225 def filterfiles(list, files):
224 226 l = [x for x in list if x in files]
225 227
226 228 for f in files:
227 229 if f[-1] != os.sep:
228 230 f += os.sep
229 231 l += [x for x in list if x.startswith(f)]
230 232 return l
231 233
232 234 parity = [0]
233 235 def diffblock(diff, f, fn):
234 236 yield self.t("diffblock",
235 237 lines=prettyprintlines(diff),
236 238 parity=parity[0],
237 239 file=f,
238 240 filenode=hex(fn or nullid))
239 241 parity[0] = 1 - parity[0]
240 242
241 243 def prettyprintlines(diff):
242 244 for l in diff.splitlines(1):
243 245 if l.startswith('+'):
244 246 yield self.t("difflineplus", line=l)
245 247 elif l.startswith('-'):
246 248 yield self.t("difflineminus", line=l)
247 249 elif l.startswith('@'):
248 250 yield self.t("difflineat", line=l)
249 251 else:
250 252 yield self.t("diffline", line=l)
251 253
252 254 r = self.repo
253 255 cl = r.changelog
254 256 mf = r.manifest
255 257 change1 = cl.read(node1)
256 258 change2 = cl.read(node2)
257 259 mmap1 = mf.read(change1[0])
258 260 mmap2 = mf.read(change2[0])
259 261 date1 = util.datestr(change1[2])
260 262 date2 = util.datestr(change2[2])
261 263
262 264 c, a, d, u = r.changes(node1, node2)
263 265 if files:
264 266 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
265 267
266 268 for f in c:
267 269 to = r.file(f).read(mmap1[f])
268 270 tn = r.file(f).read(mmap2[f])
269 271 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
270 272 for f in a:
271 273 to = None
272 274 tn = r.file(f).read(mmap2[f])
273 275 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
274 276 for f in d:
275 277 to = r.file(f).read(mmap1[f])
276 278 tn = None
277 279 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
278 280
279 281 def changelog(self, pos):
280 282 def changenav(**map):
281 283 def seq(factor=1):
282 284 yield 1 * factor
283 285 yield 3 * factor
284 286 #yield 5 * factor
285 287 for f in seq(factor * 10):
286 288 yield f
287 289
288 290 l = []
289 291 for f in seq():
290 292 if f < self.maxchanges / 2:
291 293 continue
292 294 if f > count:
293 295 break
294 296 r = "%d" % f
295 297 if pos + f < count:
296 298 l.append(("+" + r, pos + f))
297 299 if pos - f >= 0:
298 300 l.insert(0, ("-" + r, pos - f))
299 301
300 302 yield {"rev": 0, "label": "(0)"}
301 303
302 304 for label, rev in l:
303 305 yield {"label": label, "rev": rev}
304 306
305 307 yield {"label": "tip", "rev": "tip"}
306 308
307 309 def changelist(**map):
308 310 parity = (start - end) & 1
309 311 cl = self.repo.changelog
310 312 l = [] # build a list in forward order for efficiency
311 313 for i in range(start, end):
312 314 n = cl.node(i)
313 315 changes = cl.read(n)
314 316 hn = hex(n)
315 317
316 318 l.insert(0, {"parity": parity,
317 319 "author": changes[1],
318 320 "parent": self.parents(n, cl.parents(n), cl.rev,
319 321 hide=True),
320 322 "changelogtag": self.showtag("changelogtag",n),
321 323 "manifest": hex(changes[0]),
322 324 "desc": changes[4],
323 325 "date": changes[2],
324 326 "files": self.listfilediffs(changes[3], n),
325 327 "rev": i,
326 328 "node": hn})
327 329 parity = 1 - parity
328 330
329 331 for e in l:
330 332 yield e
331 333
332 334 cl = self.repo.changelog
333 335 mf = cl.read(cl.tip())[0]
334 336 count = cl.count()
335 337 start = max(0, pos - self.maxchanges + 1)
336 338 end = min(count, start + self.maxchanges)
337 339 pos = end - 1
338 340
339 341 yield self.t('changelog',
340 342 changenav=changenav,
341 343 manifest=hex(mf),
342 344 rev=pos, changesets=count, entries=changelist)
343 345
344 346 def search(self, query):
345 347
346 348 def changelist(**map):
347 349 cl = self.repo.changelog
348 350 count = 0
349 351 qw = query.lower().split()
350 352
351 353 def revgen():
352 354 for i in range(cl.count() - 1, 0, -100):
353 355 l = []
354 356 for j in range(max(0, i - 100), i):
355 357 n = cl.node(j)
356 358 changes = cl.read(n)
357 359 l.append((n, j, changes))
358 360 l.reverse()
359 361 for e in l:
360 362 yield e
361 363
362 364 for n, i, changes in revgen():
363 365 miss = 0
364 366 for q in qw:
365 367 if not (q in changes[1].lower() or
366 368 q in changes[4].lower() or
367 369 q in " ".join(changes[3][:20]).lower()):
368 370 miss = 1
369 371 break
370 372 if miss:
371 373 continue
372 374
373 375 count += 1
374 376 hn = hex(n)
375 377
376 378 yield self.t('searchentry',
377 379 parity=count & 1,
378 380 author=changes[1],
379 381 parent=self.parents(n, cl.parents(n), cl.rev),
380 382 changelogtag=self.showtag("changelogtag",n),
381 383 manifest=hex(changes[0]),
382 384 desc=changes[4],
383 385 date=changes[2],
384 386 files=self.listfilediffs(changes[3], n),
385 387 rev=i,
386 388 node=hn)
387 389
388 390 if count >= self.maxchanges:
389 391 break
390 392
391 393 cl = self.repo.changelog
392 394 mf = cl.read(cl.tip())[0]
393 395
394 396 yield self.t('search',
395 397 query=query,
396 398 manifest=hex(mf),
397 399 entries=changelist)
398 400
399 401 def changeset(self, nodeid):
400 402 cl = self.repo.changelog
401 403 n = self.repo.lookup(nodeid)
402 404 nodeid = hex(n)
403 405 changes = cl.read(n)
404 406 p1 = cl.parents(n)[0]
405 407
406 408 files = []
407 409 mf = self.repo.manifest.read(changes[0])
408 410 for f in changes[3]:
409 411 files.append(self.t("filenodelink",
410 412 filenode=hex(mf.get(f, nullid)), file=f))
411 413
412 414 def diff(**map):
413 415 yield self.diff(p1, n, None)
414 416
415 417 def archivelist():
416 418 for i in self.archives:
417 419 if self.repo.ui.configbool("web", "allow" + i, False):
418 420 yield {"type" : i, "node" : nodeid}
419 421
420 422 yield self.t('changeset',
421 423 diff=diff,
422 424 rev=cl.rev(n),
423 425 node=nodeid,
424 426 parent=self.parents(n, cl.parents(n), cl.rev),
425 427 changesettag=self.showtag("changesettag",n),
426 428 manifest=hex(changes[0]),
427 429 author=changes[1],
428 430 desc=changes[4],
429 431 date=changes[2],
430 432 files=files,
431 433 archives=archivelist())
432 434
433 435 def filelog(self, f, filenode):
434 436 cl = self.repo.changelog
435 437 fl = self.repo.file(f)
436 438 filenode = hex(fl.lookup(filenode))
437 439 count = fl.count()
438 440
439 441 def entries(**map):
440 442 l = []
441 443 parity = (count - 1) & 1
442 444
443 445 for i in range(count):
444 446 n = fl.node(i)
445 447 lr = fl.linkrev(n)
446 448 cn = cl.node(lr)
447 449 cs = cl.read(cl.node(lr))
448 450
449 451 l.insert(0, {"parity": parity,
450 452 "filenode": hex(n),
451 453 "filerev": i,
452 454 "file": f,
453 455 "node": hex(cn),
454 456 "author": cs[1],
455 457 "date": cs[2],
456 458 "parent": self.parents(n, fl.parents(n),
457 459 fl.rev, file=f),
458 460 "desc": cs[4]})
459 461 parity = 1 - parity
460 462
461 463 for e in l:
462 464 yield e
463 465
464 466 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
465 467
466 468 def filerevision(self, f, node):
467 469 fl = self.repo.file(f)
468 470 n = fl.lookup(node)
469 471 node = hex(n)
470 472 text = fl.read(n)
471 473 changerev = fl.linkrev(n)
472 474 cl = self.repo.changelog
473 475 cn = cl.node(changerev)
474 476 cs = cl.read(cn)
475 477 mfn = cs[0]
476 478
477 479 mt = mimetypes.guess_type(f)[0]
478 480 rawtext = text
479 481 if util.binary(text):
480 482 text = "(binary:%s)" % mt
481 483
482 484 def lines():
483 485 for l, t in enumerate(text.splitlines(1)):
484 486 yield {"line": t,
485 487 "linenumber": "% 6d" % (l + 1),
486 488 "parity": l & 1}
487 489
488 490 yield self.t("filerevision",
489 491 file=f,
490 492 filenode=node,
491 493 path=up(f),
492 494 text=lines(),
493 495 raw=rawtext,
494 496 mimetype=mt,
495 497 rev=changerev,
496 498 node=hex(cn),
497 499 manifest=hex(mfn),
498 500 author=cs[1],
499 501 date=cs[2],
500 502 parent=self.parents(n, fl.parents(n), fl.rev, file=f),
501 503 permissions=self.repo.manifest.readflags(mfn)[f])
502 504
503 505 def fileannotate(self, f, node):
504 506 bcache = {}
505 507 ncache = {}
506 508 fl = self.repo.file(f)
507 509 n = fl.lookup(node)
508 510 node = hex(n)
509 511 changerev = fl.linkrev(n)
510 512
511 513 cl = self.repo.changelog
512 514 cn = cl.node(changerev)
513 515 cs = cl.read(cn)
514 516 mfn = cs[0]
515 517
516 518 def annotate(**map):
517 519 parity = 1
518 520 last = None
519 521 for r, l in fl.annotate(n):
520 522 try:
521 523 cnode = ncache[r]
522 524 except KeyError:
523 525 cnode = ncache[r] = self.repo.changelog.node(r)
524 526
525 527 try:
526 528 name = bcache[r]
527 529 except KeyError:
528 530 cl = self.repo.changelog.read(cnode)
529 531 bcache[r] = name = self.repo.ui.shortuser(cl[1])
530 532
531 533 if last != cnode:
532 534 parity = 1 - parity
533 535 last = cnode
534 536
535 537 yield {"parity": parity,
536 538 "node": hex(cnode),
537 539 "rev": r,
538 540 "author": name,
539 541 "file": f,
540 542 "line": l}
541 543
542 544 yield self.t("fileannotate",
543 545 file=f,
544 546 filenode=node,
545 547 annotate=annotate,
546 548 path=up(f),
547 549 rev=changerev,
548 550 node=hex(cn),
549 551 manifest=hex(mfn),
550 552 author=cs[1],
551 553 date=cs[2],
552 554 parent=self.parents(n, fl.parents(n), fl.rev, file=f),
553 555 permissions=self.repo.manifest.readflags(mfn)[f])
554 556
555 557 def manifest(self, mnode, path):
556 558 man = self.repo.manifest
557 559 mn = man.lookup(mnode)
558 560 mnode = hex(mn)
559 561 mf = man.read(mn)
560 562 rev = man.rev(mn)
561 563 node = self.repo.changelog.node(rev)
562 564 mff = man.readflags(mn)
563 565
564 566 files = {}
565 567
566 568 p = path[1:]
567 569 l = len(p)
568 570
569 571 for f,n in mf.items():
570 572 if f[:l] != p:
571 573 continue
572 574 remain = f[l:]
573 575 if "/" in remain:
574 576 short = remain[:remain.find("/") + 1] # bleah
575 577 files[short] = (f, None)
576 578 else:
577 579 short = os.path.basename(remain)
578 580 files[short] = (f, n)
579 581
580 582 def filelist(**map):
581 583 parity = 0
582 584 fl = files.keys()
583 585 fl.sort()
584 586 for f in fl:
585 587 full, fnode = files[f]
586 588 if not fnode:
587 589 continue
588 590
589 591 yield {"file": full,
590 592 "manifest": mnode,
591 593 "filenode": hex(fnode),
592 594 "parity": parity,
593 595 "basename": f,
594 596 "permissions": mff[full]}
595 597 parity = 1 - parity
596 598
597 599 def dirlist(**map):
598 600 parity = 0
599 601 fl = files.keys()
600 602 fl.sort()
601 603 for f in fl:
602 604 full, fnode = files[f]
603 605 if fnode:
604 606 continue
605 607
606 608 yield {"parity": parity,
607 609 "path": os.path.join(path, f),
608 610 "manifest": mnode,
609 611 "basename": f[:-1]}
610 612 parity = 1 - parity
611 613
612 614 yield self.t("manifest",
613 615 manifest=mnode,
614 616 rev=rev,
615 617 node=hex(node),
616 618 path=path,
617 619 up=up(path),
618 620 fentries=filelist,
619 621 dentries=dirlist)
620 622
621 623 def tags(self):
622 624 cl = self.repo.changelog
623 625 mf = cl.read(cl.tip())[0]
624 626
625 627 i = self.repo.tagslist()
626 628 i.reverse()
627 629
628 630 def entries(**map):
629 631 parity = 0
630 632 for k,n in i:
631 633 yield {"parity": parity,
632 634 "tag": k,
633 635 "node": hex(n)}
634 636 parity = 1 - parity
635 637
636 638 yield self.t("tags",
637 639 manifest=hex(mf),
638 640 entries=entries)
639 641
640 642 def filediff(self, file, changeset):
641 643 cl = self.repo.changelog
642 644 n = self.repo.lookup(changeset)
643 645 changeset = hex(n)
644 646 p1 = cl.parents(n)[0]
645 647 cs = cl.read(n)
646 648 mf = self.repo.manifest.read(cs[0])
647 649
648 650 def diff(**map):
649 651 yield self.diff(p1, n, file)
650 652
651 653 yield self.t("filediff",
652 654 file=file,
653 655 filenode=hex(mf.get(file, nullid)),
654 656 node=changeset,
655 657 rev=self.repo.changelog.rev(n),
656 658 parent=self.parents(n, cl.parents(n), cl.rev),
657 659 diff=diff)
658 660
659 661 def archive(self, req, cnode, type):
660 662 cs = self.repo.changelog.read(cnode)
661 663 mnode = cs[0]
662 664 mf = self.repo.manifest.read(mnode)
663 665 rev = self.repo.manifest.rev(mnode)
664 666 reponame = re.sub(r"\W+", "-", self.reponame)
665 667 name = "%s-%s/" % (reponame, short(cnode))
666 668
667 669 files = mf.keys()
668 670 files.sort()
669 671
670 672 if type == 'zip':
671 673 tmp = tempfile.mkstemp()[1]
672 674 try:
673 675 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
674 676
675 677 for f in files:
676 678 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
677 679 zf.close()
678 680
679 681 f = open(tmp, 'r')
680 682 req.httphdr('application/zip', name[:-1] + '.zip',
681 683 os.path.getsize(tmp))
682 684 req.write(f.read())
683 685 f.close()
684 686 finally:
685 687 os.unlink(tmp)
686 688
687 689 else:
688 690 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
689 691 mff = self.repo.manifest.readflags(mnode)
690 692 mtime = int(time.time())
691 693
692 694 if type == "gz":
693 695 encoding = "gzip"
694 696 else:
695 697 encoding = "x-bzip2"
696 698 req.header([('Content-type', 'application/x-tar'),
697 699 ('Content-disposition', 'attachment; filename=%s%s%s' %
698 700 (name[:-1], '.tar.', type)),
699 701 ('Content-encoding', encoding)])
700 702 for fname in files:
701 703 rcont = self.repo.file(fname).read(mf[fname])
702 704 finfo = tarfile.TarInfo(name + fname)
703 705 finfo.mtime = mtime
704 706 finfo.size = len(rcont)
705 707 finfo.mode = mff[fname] and 0755 or 0644
706 708 tf.addfile(finfo, StringIO.StringIO(rcont))
707 709 tf.close()
708 710
709 711 # add tags to things
710 712 # tags -> list of changesets corresponding to tags
711 713 # find tag, changeset, file
712 714
713 715 def run(self, req=hgrequest()):
714 716 def header(**map):
715 717 yield self.t("header", **map)
716 718
717 719 def footer(**map):
718 720 yield self.t("footer", **map)
719 721
720 722 def expand_form(form):
721 723 shortcuts = {
722 724 'cl': [('cmd', ['changelog']), ('rev', None)],
723 725 'cs': [('cmd', ['changeset']), ('node', None)],
724 726 'f': [('cmd', ['file']), ('filenode', None)],
725 727 'fl': [('cmd', ['filelog']), ('filenode', None)],
726 728 'fd': [('cmd', ['filediff']), ('node', None)],
727 729 'fa': [('cmd', ['annotate']), ('filenode', None)],
728 730 'mf': [('cmd', ['manifest']), ('manifest', None)],
729 731 'ca': [('cmd', ['archive']), ('node', None)],
730 732 'tags': [('cmd', ['tags'])],
731 733 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
732 734 }
733 735
734 736 for k in shortcuts.iterkeys():
735 737 if form.has_key(k):
736 738 for name, value in shortcuts[k]:
737 739 if value is None:
738 740 value = form[k]
739 741 form[name] = value
740 742 del form[k]
741 743
742 744 self.refresh()
743 745
744 746 expand_form(req.form)
745 747
746 748 t = self.repo.ui.config("web", "templates", templatepath())
747 749 m = os.path.join(t, "map")
748 750 style = self.repo.ui.config("web", "style", "")
749 751 if req.form.has_key('style'):
750 752 style = req.form['style'][0]
751 753 if style:
752 754 b = os.path.basename("map-" + style)
753 755 p = os.path.join(t, b)
754 756 if os.path.isfile(p):
755 757 m = p
756 758
757 759 port = req.env["SERVER_PORT"]
758 760 port = port != "80" and (":" + port) or ""
759 761 uri = req.env["REQUEST_URI"]
760 762 if "?" in uri:
761 763 uri = uri.split("?")[0]
762 764 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
763 765 if not self.reponame:
764 766 self.reponame = (self.repo.ui.config("web", "name")
765 767 or uri.strip('/') or self.repo.root)
766 768
767 769 self.t = templater(m, common_filters,
768 770 {"url": url,
769 771 "repo": self.reponame,
770 772 "header": header,
771 773 "footer": footer,
772 774 })
773 775
774 776 if not req.form.has_key('cmd'):
775 777 req.form['cmd'] = [self.t.cache['default'],]
776 778
777 779 if req.form['cmd'][0] == 'changelog':
778 780 c = self.repo.changelog.count() - 1
779 781 hi = c
780 782 if req.form.has_key('rev'):
781 783 hi = req.form['rev'][0]
782 784 try:
783 785 hi = self.repo.changelog.rev(self.repo.lookup(hi))
784 786 except hg.RepoError:
785 787 req.write(self.search(hi))
786 788 return
787 789
788 790 req.write(self.changelog(hi))
789 791
790 792 elif req.form['cmd'][0] == 'changeset':
791 793 req.write(self.changeset(req.form['node'][0]))
792 794
793 795 elif req.form['cmd'][0] == 'manifest':
794 796 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
795 797
796 798 elif req.form['cmd'][0] == 'tags':
797 799 req.write(self.tags())
798 800
799 801 elif req.form['cmd'][0] == 'filediff':
800 802 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
801 803
802 804 elif req.form['cmd'][0] == 'file':
803 805 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
804 806
805 807 elif req.form['cmd'][0] == 'annotate':
806 808 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
807 809
808 810 elif req.form['cmd'][0] == 'filelog':
809 811 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
810 812
811 813 elif req.form['cmd'][0] == 'heads':
812 814 req.httphdr("application/mercurial-0.1")
813 815 h = self.repo.heads()
814 816 req.write(" ".join(map(hex, h)) + "\n")
815 817
816 818 elif req.form['cmd'][0] == 'branches':
817 819 req.httphdr("application/mercurial-0.1")
818 820 nodes = []
819 821 if req.form.has_key('nodes'):
820 822 nodes = map(bin, req.form['nodes'][0].split(" "))
821 823 for b in self.repo.branches(nodes):
822 824 req.write(" ".join(map(hex, b)) + "\n")
823 825
824 826 elif req.form['cmd'][0] == 'between':
825 827 req.httphdr("application/mercurial-0.1")
826 828 nodes = []
827 829 if req.form.has_key('pairs'):
828 830 pairs = [map(bin, p.split("-"))
829 831 for p in req.form['pairs'][0].split(" ")]
830 832 for b in self.repo.between(pairs):
831 833 req.write(" ".join(map(hex, b)) + "\n")
832 834
833 835 elif req.form['cmd'][0] == 'changegroup':
834 836 req.httphdr("application/mercurial-0.1")
835 837 nodes = []
836 838 if not self.allowpull:
837 839 return
838 840
839 841 if req.form.has_key('roots'):
840 842 nodes = map(bin, req.form['roots'][0].split(" "))
841 843
842 844 z = zlib.compressobj()
843 845 f = self.repo.changegroup(nodes)
844 846 while 1:
845 847 chunk = f.read(4096)
846 848 if not chunk:
847 849 break
848 850 req.write(z.compress(chunk))
849 851
850 852 req.write(z.flush())
851 853
852 854 elif req.form['cmd'][0] == 'archive':
853 855 changeset = self.repo.lookup(req.form['node'][0])
854 856 type = req.form['type'][0]
855 857 if (type in self.archives and
856 858 self.repo.ui.configbool("web", "allow" + type, False)):
857 859 self.archive(req, changeset, type)
858 860 return
859 861
860 862 req.write(self.t("error"))
861 863
862 864 else:
863 865 req.write(self.t("error"))
864 866
865 867 def create_server(repo):
866 868
867 869 def openlog(opt, default):
868 870 if opt and opt != '-':
869 871 return open(opt, 'w')
870 872 return default
871 873
872 874 address = repo.ui.config("web", "address", "")
873 875 port = int(repo.ui.config("web", "port", 8000))
874 876 use_ipv6 = repo.ui.configbool("web", "ipv6")
875 877 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
876 878 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
877 879
878 880 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
879 881 address_family = getattr(socket, 'AF_INET6', None)
880 882
881 883 def __init__(self, *args, **kwargs):
882 884 if self.address_family is None:
883 885 raise hg.RepoError(_('IPv6 not available on this system'))
884 886 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
885 887
886 888 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
887 889 def log_error(self, format, *args):
888 890 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
889 891 self.log_date_time_string(),
890 892 format % args))
891 893
892 894 def log_message(self, format, *args):
893 895 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
894 896 self.log_date_time_string(),
895 897 format % args))
896 898
897 899 def do_POST(self):
898 900 try:
899 901 self.do_hgweb()
900 902 except socket.error, inst:
901 903 if inst[0] != errno.EPIPE:
902 904 raise
903 905
904 906 def do_GET(self):
905 907 self.do_POST()
906 908
907 909 def do_hgweb(self):
908 910 query = ""
909 911 p = self.path.find("?")
910 912 if p:
911 913 query = self.path[p + 1:]
912 914 query = query.replace('+', ' ')
913 915
914 916 env = {}
915 917 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
916 918 env['REQUEST_METHOD'] = self.command
917 919 env['SERVER_NAME'] = self.server.server_name
918 920 env['SERVER_PORT'] = str(self.server.server_port)
919 921 env['REQUEST_URI'] = "/"
920 922 if query:
921 923 env['QUERY_STRING'] = query
922 924 host = self.address_string()
923 925 if host != self.client_address[0]:
924 926 env['REMOTE_HOST'] = host
925 927 env['REMOTE_ADDR'] = self.client_address[0]
926 928
927 929 if self.headers.typeheader is None:
928 930 env['CONTENT_TYPE'] = self.headers.type
929 931 else:
930 932 env['CONTENT_TYPE'] = self.headers.typeheader
931 933 length = self.headers.getheader('content-length')
932 934 if length:
933 935 env['CONTENT_LENGTH'] = length
934 936 accept = []
935 937 for line in self.headers.getallmatchingheaders('accept'):
936 938 if line[:1] in "\t\n\r ":
937 939 accept.append(line.strip())
938 940 else:
939 941 accept = accept + line[7:].split(',')
940 942 env['HTTP_ACCEPT'] = ','.join(accept)
941 943
942 944 req = hgrequest(self.rfile, self.wfile, env)
943 945 self.send_response(200, "Script output follows")
944 946 hg.run(req)
945 947
946 948 hg = hgweb(repo)
947 949 if use_ipv6:
948 950 return IPv6HTTPServer((address, port), hgwebhandler)
949 951 else:
950 952 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
951 953
952 954 def server(path, name, templates, address, port, use_ipv6=False,
953 955 accesslog=sys.stdout, errorlog=sys.stderr):
954 956 httpd = create_server(path, name, templates, address, port, use_ipv6,
955 957 accesslog, errorlog)
956 958 httpd.serve_forever()
957 959
958 960 # This is a stopgap
959 961 class hgwebdir:
960 962 def __init__(self, config):
961 963 def cleannames(items):
962 964 return [(name.strip('/'), path) for name, path in items]
963 965
964 966 if type(config) == type([]):
965 967 self.repos = cleannames(config)
966 968 elif type(config) == type({}):
967 969 self.repos = cleannames(config.items())
968 970 self.repos.sort()
969 971 else:
970 972 cp = ConfigParser.SafeConfigParser()
971 973 cp.read(config)
972 974 self.repos = cleannames(cp.items("paths"))
973 975 self.repos.sort()
974 976
975 977 def run(self, req=hgrequest()):
976 978 def header(**map):
977 979 yield tmpl("header", **map)
978 980
979 981 def footer(**map):
980 982 yield tmpl("footer", **map)
981 983
982 984 m = os.path.join(templatepath(), "map")
983 985 tmpl = templater(m, common_filters,
984 986 {"header": header, "footer": footer})
985 987
986 988 def entries(**map):
987 989 parity = 0
988 990 for name, path in self.repos:
989 991 u = ui.ui()
990 992 try:
991 993 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
992 994 except IOError:
993 995 pass
994 996 get = u.config
995 997
996 998 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
997 999 .replace("//", "/"))
998 1000
999 1001 # update time with local timezone
1000 1002 d = (get_mtime(path), util.makedate()[1])
1001 1003
1002 1004 yield dict(contact=(get("ui", "username") or # preferred
1003 1005 get("web", "contact") or # deprecated
1004 1006 get("web", "author", "unknown")), # also
1005 1007 name=get("web", "name", name),
1006 1008 url=url,
1007 1009 parity=parity,
1008 1010 shortdesc=get("web", "description", "unknown"),
1009 1011 lastupdate=d)
1010 1012
1011 1013 parity = 1 - parity
1012 1014
1013 1015 virtual = req.env.get("PATH_INFO", "").strip('/')
1014 1016 if virtual:
1015 1017 real = dict(self.repos).get(virtual)
1016 1018 if real:
1017 1019 hgweb(real).run(req)
1018 1020 else:
1019 1021 req.write(tmpl("notfound", repo=virtual))
1020 1022 else:
1021 1023 req.write(tmpl("index", entries=entries))
@@ -1,7 +1,7
1 1 <item>
2 <title>#desc|firstline|escape#</title>
2 <title>#desc|strip|firstline|rstrip|escape#</title>
3 3 <link>#url#?cs=#node|short#</link>
4 <description><![CDATA[#desc|escape|addbreaks#]]></description>
4 <description><![CDATA[#desc|strip|escape|addbreaks#]]></description>
5 5 <author>#author|obfuscate#</author>
6 6 <pubDate>#date|rfc822date#</pubDate>
7 7 </item>
@@ -1,24 +1,24
1 1 <table class="changelogEntry parity#parity#">
2 2 <tr>
3 3 <th class="age" width="15%">#date|age# ago:</th>
4 <th class="firstline">#desc|firstline|escape#</th>
4 <th class="firstline">#desc|strip|firstline|escape#</th>
5 5 </tr>
6 6 <tr>
7 7 <th class="changesetRev">changeset #rev#:</th>
8 8 <td class="changesetNode"><a href="?cs=#node|short#">#node|short#</a></td>
9 9 </tr>
10 10 #parent%changelogparent#
11 11 #changelogtag#
12 12 <tr>
13 13 <th class="author">author:</th>
14 14 <td class="author">#author|obfuscate#</td>
15 15 </tr>
16 16 <tr>
17 17 <th class="date">date:</th>
18 18 <td class="date">#date|date#</td>
19 19 </tr>
20 20 <tr>
21 21 <th class="files"><a href="?mf=#manifest|short#;path=/">files</a>:</th>
22 22 <td class="files">#files#</td>
23 23 </tr>
24 24 </table>
@@ -1,45 +1,45
1 1 #header#
2 2 <title>#repo|escape#: changeset #node|short#</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="buttons">
7 7 <a href="?cl=#rev#">changelog</a>
8 8 <a href="?cmd=tags">tags</a>
9 9 <a href="?mf=#manifest|short#;path=/">manifest</a>
10 10 <a href="?cs=#node|short#;style=raw">raw</a>
11 11 #archives%archiveentry#
12 12 </div>
13 13
14 <h2>changeset: #desc|escape|firstline#</h2>
14 <h2>changeset: #desc|strip|escape|firstline#</h2>
15 15
16 16 <table id="changesetEntry">
17 17 <tr>
18 18 <th class="changeset">changeset #rev#:</th>
19 19 <td class="changeset"><a href="?cs=#node|short#">#node|short#</a></td>
20 20 </tr>
21 21 #parent%changesetparent#
22 22 #changesettag#
23 23 <tr>
24 24 <th class="author">author:</th>
25 25 <td class="author">#author|obfuscate#</td>
26 26 </tr>
27 27 <tr>
28 28 <th class="date">date:</th>
29 29 <td class="date">#date|date# (#date|age# ago)</td></tr>
30 30 <tr>
31 31 <th class="files">files:</th>
32 32 <td class="files">#files#</td></tr>
33 33 <tr>
34 34 <th class="description">description:</th>
35 <td class="description">#desc|escape|addbreaks#</td>
35 <td class="description">#desc|strip|escape|addbreaks#</td>
36 36 </tr>
37 37 </table>
38 38
39 39 <div id="changesetDiff">
40 40 #diff#
41 41 </div>
42 42
43 43 #footer#
44 44
45 45
@@ -1,7 +1,7
1 1 <item>
2 <title>#desc|firstline|escape#</title>
2 <title>#desc|strip|firstline|rstrip|escape#</title>
3 3 <link>#url#?f=#filenode|short#;file=#file#</link>
4 <description><![CDATA[#desc|escape|addbreaks#]]></description>
4 <description><![CDATA[#desc|strip|escape|addbreaks#]]></description>
5 5 <author>#author|obfuscate#</author>
6 6 <pubDate>#date|rfc822date#</pubDate>>
7 7 </item>
@@ -1,19 +1,19
1 1 <table class="parity#parity#" width="100%" cellspacing="0" cellpadding="0">
2 2 <tr>
3 3 <td align="right" width="15%"><b>#date|age# ago:&nbsp;</b></td>
4 <td><b><a href="?cs=#node|short#">#desc|firstline|escape#</a></b></td></tr>
4 <td><b><a href="?cs=#node|short#">#desc|strip|firstline|escape#</a></b></td></tr>
5 5 <tr>
6 6 <td align="right">revision #filerev#:&nbsp;</td>
7 7 <td><a href="?f=#filenode|short#;file=#file#">#filenode|short#</a>
8 8 <a href="?fd=#node|short#;file=#file#">(diff)</a>
9 9 <a href="?fa=#filenode|short#;file=#file#">(annotate)</a>
10 10 </td></tr>
11 11 <tr>
12 12 <td align="right">author:&nbsp;</td>
13 13 <td>#author|obfuscate#</td></tr>
14 14 <tr>
15 15 <td align="right">date:&nbsp;</td>
16 16 <td>#date|date# (#date|age# ago)</td></tr>
17 17 </table>
18 18
19 19
General Comments 0
You need to be logged in to leave comments. Login now