##// END OF EJS Templates
Remove tabs, and trailing whitespace from hgweb.py
Josef "Jeff" Sipek -
r1579:85803ec2 default
parent child Browse files
Show More
@@ -1,1100 +1,1100 b''
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(object):
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(object):
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 167 "strip": lambda x: x.strip(),
168 168 "age": age,
169 169 "date": lambda x: util.datestr(x),
170 170 "addbreaks": nl2br,
171 171 "obfuscate": obfuscate,
172 172 "short": (lambda x: x[:12]),
173 173 "firstline": (lambda x: x.splitlines(1)[0]),
174 174 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
175 175 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
176 176 }
177 177
178 178 class hgweb(object):
179 179 def __init__(self, repo, name=None):
180 180 if type(repo) == type(""):
181 181 self.repo = hg.repository(ui.ui(), repo)
182 182 else:
183 183 self.repo = repo
184 184
185 185 self.mtime = -1
186 186 self.reponame = name
187 187 self.archives = 'zip', 'gz', 'bz2'
188 188
189 189 def refresh(self):
190 190 mtime = get_mtime(self.repo.root)
191 191 if mtime != self.mtime:
192 192 self.mtime = mtime
193 193 self.repo = hg.repository(self.repo.ui, self.repo.root)
194 194 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
195 195 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
196 196 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
197 197
198 198 def archivelist(self, nodeid):
199 199 for i in self.archives:
200 200 if self.repo.ui.configbool("web", "allow" + i, False):
201 201 yield {"type" : i, "node" : nodeid}
202 202
203 203 def listfiles(self, files, mf):
204 204 for f in files[:self.maxfiles]:
205 205 yield self.t("filenodelink", node=hex(mf[f]), file=f)
206 206 if len(files) > self.maxfiles:
207 207 yield self.t("fileellipses")
208 208
209 209 def listfilediffs(self, files, changeset):
210 210 for f in files[:self.maxfiles]:
211 211 yield self.t("filedifflink", node=hex(changeset), file=f)
212 212 if len(files) > self.maxfiles:
213 213 yield self.t("fileellipses")
214 214
215 215 def parents(self, node, parents=[], rev=None, hide=False, **args):
216 216 if not rev:
217 217 rev = lambda x: ""
218 218 parents = [p for p in parents if p != nullid]
219 219 if hide and len(parents) == 1 and rev(parents[0]) == rev(node) - 1:
220 220 return
221 221 for p in parents:
222 222 yield dict(node=hex(p), rev=rev(p), **args)
223 223
224 224 def showtag(self, t1, node=nullid, **args):
225 225 for t in self.repo.nodetags(node):
226 226 yield self.t(t1, tag=t, **args)
227 227
228 228 def diff(self, node1, node2, files):
229 229 def filterfiles(list, files):
230 230 l = [x for x in list if x in files]
231 231
232 232 for f in files:
233 233 if f[-1] != os.sep:
234 234 f += os.sep
235 235 l += [x for x in list if x.startswith(f)]
236 236 return l
237 237
238 238 parity = [0]
239 239 def diffblock(diff, f, fn):
240 240 yield self.t("diffblock",
241 241 lines=prettyprintlines(diff),
242 242 parity=parity[0],
243 243 file=f,
244 244 filenode=hex(fn or nullid))
245 245 parity[0] = 1 - parity[0]
246 246
247 247 def prettyprintlines(diff):
248 248 for l in diff.splitlines(1):
249 249 if l.startswith('+'):
250 250 yield self.t("difflineplus", line=l)
251 251 elif l.startswith('-'):
252 252 yield self.t("difflineminus", line=l)
253 253 elif l.startswith('@'):
254 254 yield self.t("difflineat", line=l)
255 255 else:
256 256 yield self.t("diffline", line=l)
257 257
258 258 r = self.repo
259 259 cl = r.changelog
260 260 mf = r.manifest
261 261 change1 = cl.read(node1)
262 262 change2 = cl.read(node2)
263 263 mmap1 = mf.read(change1[0])
264 264 mmap2 = mf.read(change2[0])
265 265 date1 = util.datestr(change1[2])
266 266 date2 = util.datestr(change2[2])
267 267
268 268 c, a, d, u = r.changes(node1, node2)
269 269 if files:
270 270 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
271 271
272 272 for f in c:
273 273 to = r.file(f).read(mmap1[f])
274 274 tn = r.file(f).read(mmap2[f])
275 275 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
276 276 for f in a:
277 277 to = None
278 278 tn = r.file(f).read(mmap2[f])
279 279 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
280 280 for f in d:
281 281 to = r.file(f).read(mmap1[f])
282 282 tn = None
283 283 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
284 284
285 285 def changelog(self, pos):
286 286 def changenav(**map):
287 287 def seq(factor=1):
288 288 yield 1 * factor
289 289 yield 3 * factor
290 290 #yield 5 * factor
291 291 for f in seq(factor * 10):
292 292 yield f
293 293
294 294 l = []
295 295 for f in seq():
296 296 if f < self.maxchanges / 2:
297 297 continue
298 298 if f > count:
299 299 break
300 300 r = "%d" % f
301 301 if pos + f < count:
302 302 l.append(("+" + r, pos + f))
303 303 if pos - f >= 0:
304 304 l.insert(0, ("-" + r, pos - f))
305 305
306 306 yield {"rev": 0, "label": "(0)"}
307 307
308 308 for label, rev in l:
309 309 yield {"label": label, "rev": rev}
310 310
311 311 yield {"label": "tip", "rev": "tip"}
312 312
313 313 def changelist(**map):
314 314 parity = (start - end) & 1
315 315 cl = self.repo.changelog
316 316 l = [] # build a list in forward order for efficiency
317 317 for i in range(start, end):
318 318 n = cl.node(i)
319 319 changes = cl.read(n)
320 320 hn = hex(n)
321 321
322 322 l.insert(0, {"parity": parity,
323 323 "author": changes[1],
324 324 "parent": self.parents(n, cl.parents(n), cl.rev,
325 325 hide=True),
326 326 "changelogtag": self.showtag("changelogtag",n),
327 327 "manifest": hex(changes[0]),
328 328 "desc": changes[4],
329 329 "date": changes[2],
330 330 "files": self.listfilediffs(changes[3], n),
331 331 "rev": i,
332 332 "node": hn})
333 333 parity = 1 - parity
334 334
335 335 for e in l:
336 336 yield e
337 337
338 338 cl = self.repo.changelog
339 339 mf = cl.read(cl.tip())[0]
340 340 count = cl.count()
341 341 start = max(0, pos - self.maxchanges + 1)
342 342 end = min(count, start + self.maxchanges)
343 343 pos = end - 1
344 344
345 345 yield self.t('changelog',
346 346 changenav=changenav,
347 347 manifest=hex(mf),
348 348 rev=pos, changesets=count, entries=changelist)
349 349
350 350 def search(self, query):
351 351
352 352 def changelist(**map):
353 353 cl = self.repo.changelog
354 354 count = 0
355 355 qw = query.lower().split()
356 356
357 357 def revgen():
358 358 for i in range(cl.count() - 1, 0, -100):
359 359 l = []
360 360 for j in range(max(0, i - 100), i):
361 361 n = cl.node(j)
362 362 changes = cl.read(n)
363 363 l.append((n, j, changes))
364 364 l.reverse()
365 365 for e in l:
366 366 yield e
367 367
368 368 for n, i, changes in revgen():
369 369 miss = 0
370 370 for q in qw:
371 371 if not (q in changes[1].lower() or
372 372 q in changes[4].lower() or
373 373 q in " ".join(changes[3][:20]).lower()):
374 374 miss = 1
375 375 break
376 376 if miss:
377 377 continue
378 378
379 379 count += 1
380 380 hn = hex(n)
381 381
382 382 yield self.t('searchentry',
383 383 parity=count & 1,
384 384 author=changes[1],
385 385 parent=self.parents(n, cl.parents(n), cl.rev),
386 386 changelogtag=self.showtag("changelogtag",n),
387 387 manifest=hex(changes[0]),
388 388 desc=changes[4],
389 389 date=changes[2],
390 390 files=self.listfilediffs(changes[3], n),
391 391 rev=i,
392 392 node=hn)
393 393
394 394 if count >= self.maxchanges:
395 395 break
396 396
397 397 cl = self.repo.changelog
398 398 mf = cl.read(cl.tip())[0]
399 399
400 400 yield self.t('search',
401 401 query=query,
402 402 manifest=hex(mf),
403 403 entries=changelist)
404 404
405 405 def changeset(self, nodeid):
406 406 cl = self.repo.changelog
407 407 n = self.repo.lookup(nodeid)
408 408 nodeid = hex(n)
409 409 changes = cl.read(n)
410 410 p1 = cl.parents(n)[0]
411 411
412 412 files = []
413 413 mf = self.repo.manifest.read(changes[0])
414 414 for f in changes[3]:
415 415 files.append(self.t("filenodelink",
416 416 filenode=hex(mf.get(f, nullid)), file=f))
417 417
418 418 def diff(**map):
419 419 yield self.diff(p1, n, None)
420 420
421 421 yield self.t('changeset',
422 422 diff=diff,
423 423 rev=cl.rev(n),
424 424 node=nodeid,
425 425 parent=self.parents(n, cl.parents(n), cl.rev),
426 426 changesettag=self.showtag("changesettag",n),
427 427 manifest=hex(changes[0]),
428 428 author=changes[1],
429 429 desc=changes[4],
430 430 date=changes[2],
431 431 files=files,
432 432 archives=self.archivelist(nodeid))
433 433
434 434 def filelog(self, f, filenode):
435 435 cl = self.repo.changelog
436 436 fl = self.repo.file(f)
437 437 filenode = hex(fl.lookup(filenode))
438 438 count = fl.count()
439 439
440 440 def entries(**map):
441 441 l = []
442 442 parity = (count - 1) & 1
443 443
444 444 for i in range(count):
445 445 n = fl.node(i)
446 446 lr = fl.linkrev(n)
447 447 cn = cl.node(lr)
448 448 cs = cl.read(cl.node(lr))
449 449
450 450 l.insert(0, {"parity": parity,
451 451 "filenode": hex(n),
452 452 "filerev": i,
453 453 "file": f,
454 454 "node": hex(cn),
455 455 "author": cs[1],
456 456 "date": cs[2],
457 457 "parent": self.parents(n, fl.parents(n),
458 458 fl.rev, file=f),
459 459 "desc": cs[4]})
460 460 parity = 1 - parity
461 461
462 462 for e in l:
463 463 yield e
464 464
465 465 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
466 466
467 467 def filerevision(self, f, node):
468 468 fl = self.repo.file(f)
469 469 n = fl.lookup(node)
470 470 node = hex(n)
471 471 text = fl.read(n)
472 472 changerev = fl.linkrev(n)
473 473 cl = self.repo.changelog
474 474 cn = cl.node(changerev)
475 475 cs = cl.read(cn)
476 476 mfn = cs[0]
477 477
478 478 mt = mimetypes.guess_type(f)[0]
479 479 rawtext = text
480 480 if util.binary(text):
481 481 text = "(binary:%s)" % mt
482 482
483 483 def lines():
484 484 for l, t in enumerate(text.splitlines(1)):
485 485 yield {"line": t,
486 486 "linenumber": "% 6d" % (l + 1),
487 487 "parity": l & 1}
488 488
489 489 yield self.t("filerevision",
490 490 file=f,
491 491 filenode=node,
492 492 path=up(f),
493 493 text=lines(),
494 494 raw=rawtext,
495 495 mimetype=mt,
496 496 rev=changerev,
497 497 node=hex(cn),
498 498 manifest=hex(mfn),
499 499 author=cs[1],
500 500 date=cs[2],
501 501 parent=self.parents(n, fl.parents(n), fl.rev, file=f),
502 502 permissions=self.repo.manifest.readflags(mfn)[f])
503 503
504 504 def fileannotate(self, f, node):
505 505 bcache = {}
506 506 ncache = {}
507 507 fl = self.repo.file(f)
508 508 n = fl.lookup(node)
509 509 node = hex(n)
510 510 changerev = fl.linkrev(n)
511 511
512 512 cl = self.repo.changelog
513 513 cn = cl.node(changerev)
514 514 cs = cl.read(cn)
515 515 mfn = cs[0]
516 516
517 517 def annotate(**map):
518 518 parity = 1
519 519 last = None
520 520 for r, l in fl.annotate(n):
521 521 try:
522 522 cnode = ncache[r]
523 523 except KeyError:
524 524 cnode = ncache[r] = self.repo.changelog.node(r)
525 525
526 526 try:
527 527 name = bcache[r]
528 528 except KeyError:
529 529 cl = self.repo.changelog.read(cnode)
530 530 bcache[r] = name = self.repo.ui.shortuser(cl[1])
531 531
532 532 if last != cnode:
533 533 parity = 1 - parity
534 534 last = cnode
535 535
536 536 yield {"parity": parity,
537 537 "node": hex(cnode),
538 538 "rev": r,
539 539 "author": name,
540 540 "file": f,
541 541 "line": l}
542 542
543 543 yield self.t("fileannotate",
544 544 file=f,
545 545 filenode=node,
546 546 annotate=annotate,
547 547 path=up(f),
548 548 rev=changerev,
549 549 node=hex(cn),
550 550 manifest=hex(mfn),
551 551 author=cs[1],
552 552 date=cs[2],
553 553 parent=self.parents(n, fl.parents(n), fl.rev, file=f),
554 554 permissions=self.repo.manifest.readflags(mfn)[f])
555 555
556 556 def manifest(self, mnode, path):
557 557 man = self.repo.manifest
558 558 mn = man.lookup(mnode)
559 559 mnode = hex(mn)
560 560 mf = man.read(mn)
561 561 rev = man.rev(mn)
562 562 node = self.repo.changelog.node(rev)
563 563 mff = man.readflags(mn)
564 564
565 565 files = {}
566 566
567 567 p = path[1:]
568 568 l = len(p)
569 569
570 570 for f,n in mf.items():
571 571 if f[:l] != p:
572 572 continue
573 573 remain = f[l:]
574 574 if "/" in remain:
575 575 short = remain[:remain.find("/") + 1] # bleah
576 576 files[short] = (f, None)
577 577 else:
578 578 short = os.path.basename(remain)
579 579 files[short] = (f, n)
580 580
581 581 def filelist(**map):
582 582 parity = 0
583 583 fl = files.keys()
584 584 fl.sort()
585 585 for f in fl:
586 586 full, fnode = files[f]
587 587 if not fnode:
588 588 continue
589 589
590 590 yield {"file": full,
591 591 "manifest": mnode,
592 592 "filenode": hex(fnode),
593 593 "parity": parity,
594 594 "basename": f,
595 595 "permissions": mff[full]}
596 596 parity = 1 - parity
597 597
598 598 def dirlist(**map):
599 599 parity = 0
600 600 fl = files.keys()
601 601 fl.sort()
602 602 for f in fl:
603 603 full, fnode = files[f]
604 604 if fnode:
605 605 continue
606 606
607 607 yield {"parity": parity,
608 608 "path": os.path.join(path, f),
609 609 "manifest": mnode,
610 610 "basename": f[:-1]}
611 611 parity = 1 - parity
612 612
613 613 yield self.t("manifest",
614 614 manifest=mnode,
615 615 rev=rev,
616 616 node=hex(node),
617 617 path=path,
618 618 up=up(path),
619 619 fentries=filelist,
620 620 dentries=dirlist,
621 621 archives=self.archivelist(hex(node)))
622 622
623 623 def tags(self):
624 624 cl = self.repo.changelog
625 625 mf = cl.read(cl.tip())[0]
626 626
627 627 i = self.repo.tagslist()
628 628 i.reverse()
629 629
630 630 def entries(**map):
631 631 parity = 0
632 632 for k,n in i:
633 633 yield {"parity": parity,
634 634 "tag": k,
635 "tagmanifest": hex(cl.read(n)[0]),
636 "date": cl.read(n)[2],
635 "tagmanifest": hex(cl.read(n)[0]),
636 "date": cl.read(n)[2],
637 637 "node": hex(n)}
638 638 parity = 1 - parity
639 639
640 640 yield self.t("tags",
641 641 manifest=hex(mf),
642 642 entries=entries)
643 643
644 644 def summary(self):
645 645 cl = self.repo.changelog
646 646 mf = cl.read(cl.tip())[0]
647 647
648 648 i = self.repo.tagslist()
649 649 i.reverse()
650 650
651 651 def tagentries(**map):
652 652 parity = 0
653 count = 0
653 count = 0
654 654 for k,n in i:
655 if k == "tip": # skip tip
655 if k == "tip": # skip tip
656 656 continue;
657 657
658 658 count += 1
659 if count > 10: # limit to 10 tags
659 if count > 10: # limit to 10 tags
660 660 break;
661 661
662 662 c = cl.read(n)
663 663 m = c[0]
664 664 t = c[2]
665
665
666 666 yield self.t("tagentry",
667 667 parity = parity,
668 668 tag = k,
669 669 node = hex(n),
670 670 date = t,
671 671 tagmanifest = hex(m))
672 672 parity = 1 - parity
673
673
674 674 def changelist(**map):
675 675 parity = 0
676 676 cl = self.repo.changelog
677 677 l = [] # build a list in forward order for efficiency
678 678 for i in range(start, end):
679 679 n = cl.node(i)
680 680 changes = cl.read(n)
681 681 hn = hex(n)
682 682 t = changes[2]
683 683
684 684 l.insert(0, self.t(
685 685 'shortlogentry',
686 686 parity = parity,
687 687 author = changes[1],
688 688 manifest = hex(changes[0]),
689 689 desc = changes[4],
690 690 date = t,
691 691 rev = i,
692 692 node = hn))
693 693 parity = 1 - parity
694 694
695 695 yield l
696 696
697 697 cl = self.repo.changelog
698 698 mf = cl.read(cl.tip())[0]
699 699 count = cl.count()
700 700 start = max(0, count - self.maxchanges)
701 701 end = min(count, start + self.maxchanges)
702 702 pos = end - 1
703 703
704 704 yield self.t("summary",
705 desc = self.repo.ui.config("web", "description", "unknown"),
706 owner = (self.repo.ui.config("ui", "username") or # preferred
705 desc = self.repo.ui.config("web", "description", "unknown"),
706 owner = (self.repo.ui.config("ui", "username") or # preferred
707 707 self.repo.ui.config("web", "contact") or # deprecated
708 708 self.repo.ui.config("web", "author", "unknown")), # also
709 lastchange = (0, 0), # FIXME
709 lastchange = (0, 0), # FIXME
710 710 manifest = hex(mf),
711 711 tags = tagentries,
712 712 shortlog = changelist)
713
713
714 714 def filediff(self, file, changeset):
715 715 cl = self.repo.changelog
716 716 n = self.repo.lookup(changeset)
717 717 changeset = hex(n)
718 718 p1 = cl.parents(n)[0]
719 719 cs = cl.read(n)
720 720 mf = self.repo.manifest.read(cs[0])
721 721
722 722 def diff(**map):
723 723 yield self.diff(p1, n, file)
724 724
725 725 yield self.t("filediff",
726 726 file=file,
727 727 filenode=hex(mf.get(file, nullid)),
728 728 node=changeset,
729 729 rev=self.repo.changelog.rev(n),
730 730 parent=self.parents(n, cl.parents(n), cl.rev),
731 731 diff=diff)
732 732
733 733 def archive(self, req, cnode, type):
734 734 cs = self.repo.changelog.read(cnode)
735 735 mnode = cs[0]
736 736 mf = self.repo.manifest.read(mnode)
737 737 rev = self.repo.manifest.rev(mnode)
738 738 reponame = re.sub(r"\W+", "-", self.reponame)
739 739 name = "%s-%s/" % (reponame, short(cnode))
740 740
741 741 files = mf.keys()
742 742 files.sort()
743 743
744 744 if type == 'zip':
745 745 tmp = tempfile.mkstemp()[1]
746 746 try:
747 747 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
748 748
749 749 for f in files:
750 750 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
751 751 zf.close()
752 752
753 753 f = open(tmp, 'r')
754 754 req.httphdr('application/zip', name[:-1] + '.zip',
755 755 os.path.getsize(tmp))
756 756 req.write(f.read())
757 757 f.close()
758 758 finally:
759 759 os.unlink(tmp)
760 760
761 761 else:
762 762 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
763 763 mff = self.repo.manifest.readflags(mnode)
764 764 mtime = int(time.time())
765 765
766 766 if type == "gz":
767 767 encoding = "gzip"
768 768 else:
769 769 encoding = "x-bzip2"
770 770 req.header([('Content-type', 'application/x-tar'),
771 771 ('Content-disposition', 'attachment; filename=%s%s%s' %
772 772 (name[:-1], '.tar.', type)),
773 773 ('Content-encoding', encoding)])
774 774 for fname in files:
775 775 rcont = self.repo.file(fname).read(mf[fname])
776 776 finfo = tarfile.TarInfo(name + fname)
777 777 finfo.mtime = mtime
778 778 finfo.size = len(rcont)
779 779 finfo.mode = mff[fname] and 0755 or 0644
780 780 tf.addfile(finfo, StringIO.StringIO(rcont))
781 781 tf.close()
782 782
783 783 # add tags to things
784 784 # tags -> list of changesets corresponding to tags
785 785 # find tag, changeset, file
786 786
787 787 def run(self, req=hgrequest()):
788 788 def header(**map):
789 789 yield self.t("header", **map)
790 790
791 791 def footer(**map):
792 792 yield self.t("footer", **map)
793 793
794 794 def expand_form(form):
795 795 shortcuts = {
796 796 'cl': [('cmd', ['changelog']), ('rev', None)],
797 797 'cs': [('cmd', ['changeset']), ('node', None)],
798 798 'f': [('cmd', ['file']), ('filenode', None)],
799 799 'fl': [('cmd', ['filelog']), ('filenode', None)],
800 800 'fd': [('cmd', ['filediff']), ('node', None)],
801 801 'fa': [('cmd', ['annotate']), ('filenode', None)],
802 802 'mf': [('cmd', ['manifest']), ('manifest', None)],
803 803 'ca': [('cmd', ['archive']), ('node', None)],
804 804 'tags': [('cmd', ['tags'])],
805 805 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
806 806 }
807 807
808 808 for k in shortcuts.iterkeys():
809 809 if form.has_key(k):
810 810 for name, value in shortcuts[k]:
811 811 if value is None:
812 812 value = form[k]
813 813 form[name] = value
814 814 del form[k]
815 815
816 816 self.refresh()
817 817
818 818 expand_form(req.form)
819 819
820 820 t = self.repo.ui.config("web", "templates", templatepath())
821 821 m = os.path.join(t, "map")
822 822 style = self.repo.ui.config("web", "style", "")
823 823 if req.form.has_key('style'):
824 824 style = req.form['style'][0]
825 825 if style:
826 826 b = os.path.basename("map-" + style)
827 827 p = os.path.join(t, b)
828 828 if os.path.isfile(p):
829 829 m = p
830 830
831 831 port = req.env["SERVER_PORT"]
832 832 port = port != "80" and (":" + port) or ""
833 833 uri = req.env["REQUEST_URI"]
834 834 if "?" in uri:
835 835 uri = uri.split("?")[0]
836 836 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
837 837 if not self.reponame:
838 838 self.reponame = (self.repo.ui.config("web", "name")
839 839 or uri.strip('/') or self.repo.root)
840 840
841 841 self.t = templater(m, common_filters,
842 842 {"url": url,
843 843 "repo": self.reponame,
844 844 "header": header,
845 845 "footer": footer,
846 846 })
847 847
848 848 if not req.form.has_key('cmd'):
849 849 req.form['cmd'] = [self.t.cache['default'],]
850 850
851 851 if req.form['cmd'][0] == 'changelog':
852 852 c = self.repo.changelog.count() - 1
853 853 hi = c
854 854 if req.form.has_key('rev'):
855 855 hi = req.form['rev'][0]
856 856 try:
857 857 hi = self.repo.changelog.rev(self.repo.lookup(hi))
858 858 except hg.RepoError:
859 859 req.write(self.search(hi))
860 860 return
861 861
862 862 req.write(self.changelog(hi))
863 863
864 864 elif req.form['cmd'][0] == 'changeset':
865 865 req.write(self.changeset(req.form['node'][0]))
866 866
867 867 elif req.form['cmd'][0] == 'manifest':
868 868 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
869 869
870 870 elif req.form['cmd'][0] == 'tags':
871 871 req.write(self.tags())
872 872
873 873 elif req.form['cmd'][0] == 'summary':
874 874 req.write(self.summary())
875
875
876 876 elif req.form['cmd'][0] == 'filediff':
877 877 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
878 878
879 879 elif req.form['cmd'][0] == 'file':
880 880 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
881 881
882 882 elif req.form['cmd'][0] == 'annotate':
883 883 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
884 884
885 885 elif req.form['cmd'][0] == 'filelog':
886 886 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
887 887
888 888 elif req.form['cmd'][0] == 'heads':
889 889 req.httphdr("application/mercurial-0.1")
890 890 h = self.repo.heads()
891 891 req.write(" ".join(map(hex, h)) + "\n")
892 892
893 893 elif req.form['cmd'][0] == 'branches':
894 894 req.httphdr("application/mercurial-0.1")
895 895 nodes = []
896 896 if req.form.has_key('nodes'):
897 897 nodes = map(bin, req.form['nodes'][0].split(" "))
898 898 for b in self.repo.branches(nodes):
899 899 req.write(" ".join(map(hex, b)) + "\n")
900 900
901 901 elif req.form['cmd'][0] == 'between':
902 902 req.httphdr("application/mercurial-0.1")
903 903 nodes = []
904 904 if req.form.has_key('pairs'):
905 905 pairs = [map(bin, p.split("-"))
906 906 for p in req.form['pairs'][0].split(" ")]
907 907 for b in self.repo.between(pairs):
908 908 req.write(" ".join(map(hex, b)) + "\n")
909 909
910 910 elif req.form['cmd'][0] == 'changegroup':
911 911 req.httphdr("application/mercurial-0.1")
912 912 nodes = []
913 913 if not self.allowpull:
914 914 return
915 915
916 916 if req.form.has_key('roots'):
917 917 nodes = map(bin, req.form['roots'][0].split(" "))
918 918
919 919 z = zlib.compressobj()
920 920 f = self.repo.changegroup(nodes)
921 921 while 1:
922 922 chunk = f.read(4096)
923 923 if not chunk:
924 924 break
925 925 req.write(z.compress(chunk))
926 926
927 927 req.write(z.flush())
928 928
929 929 elif req.form['cmd'][0] == 'archive':
930 930 changeset = self.repo.lookup(req.form['node'][0])
931 931 type = req.form['type'][0]
932 932 if (type in self.archives and
933 933 self.repo.ui.configbool("web", "allow" + type, False)):
934 934 self.archive(req, changeset, type)
935 935 return
936 936
937 937 req.write(self.t("error"))
938 938
939 939 else:
940 940 req.write(self.t("error"))
941 941
942 942 def create_server(repo):
943 943
944 944 def openlog(opt, default):
945 945 if opt and opt != '-':
946 946 return open(opt, 'w')
947 947 return default
948 948
949 949 address = repo.ui.config("web", "address", "")
950 950 port = int(repo.ui.config("web", "port", 8000))
951 951 use_ipv6 = repo.ui.configbool("web", "ipv6")
952 952 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
953 953 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
954 954
955 955 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
956 956 address_family = getattr(socket, 'AF_INET6', None)
957 957
958 958 def __init__(self, *args, **kwargs):
959 959 if self.address_family is None:
960 960 raise hg.RepoError(_('IPv6 not available on this system'))
961 961 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
962 962
963 963 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
964 964 def log_error(self, format, *args):
965 965 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
966 966 self.log_date_time_string(),
967 967 format % args))
968 968
969 969 def log_message(self, format, *args):
970 970 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
971 971 self.log_date_time_string(),
972 972 format % args))
973 973
974 974 def do_POST(self):
975 975 try:
976 976 self.do_hgweb()
977 977 except socket.error, inst:
978 978 if inst[0] != errno.EPIPE:
979 979 raise
980 980
981 981 def do_GET(self):
982 982 self.do_POST()
983 983
984 984 def do_hgweb(self):
985 985 query = ""
986 986 p = self.path.find("?")
987 987 if p:
988 988 query = self.path[p + 1:]
989 989 query = query.replace('+', ' ')
990 990
991 991 env = {}
992 992 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
993 993 env['REQUEST_METHOD'] = self.command
994 994 env['SERVER_NAME'] = self.server.server_name
995 995 env['SERVER_PORT'] = str(self.server.server_port)
996 996 env['REQUEST_URI'] = "/"
997 997 if query:
998 998 env['QUERY_STRING'] = query
999 999 host = self.address_string()
1000 1000 if host != self.client_address[0]:
1001 1001 env['REMOTE_HOST'] = host
1002 1002 env['REMOTE_ADDR'] = self.client_address[0]
1003 1003
1004 1004 if self.headers.typeheader is None:
1005 1005 env['CONTENT_TYPE'] = self.headers.type
1006 1006 else:
1007 1007 env['CONTENT_TYPE'] = self.headers.typeheader
1008 1008 length = self.headers.getheader('content-length')
1009 1009 if length:
1010 1010 env['CONTENT_LENGTH'] = length
1011 1011 accept = []
1012 1012 for line in self.headers.getallmatchingheaders('accept'):
1013 1013 if line[:1] in "\t\n\r ":
1014 1014 accept.append(line.strip())
1015 1015 else:
1016 1016 accept = accept + line[7:].split(',')
1017 1017 env['HTTP_ACCEPT'] = ','.join(accept)
1018 1018
1019 1019 req = hgrequest(self.rfile, self.wfile, env)
1020 1020 self.send_response(200, "Script output follows")
1021 1021 hg.run(req)
1022 1022
1023 1023 hg = hgweb(repo)
1024 1024 if use_ipv6:
1025 1025 return IPv6HTTPServer((address, port), hgwebhandler)
1026 1026 else:
1027 1027 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1028 1028
1029 1029 # This is a stopgap
1030 1030 class hgwebdir(object):
1031 1031 def __init__(self, config):
1032 1032 def cleannames(items):
1033 1033 return [(name.strip('/'), path) for name, path in items]
1034 1034
1035 1035 if type(config) == type([]):
1036 1036 self.repos = cleannames(config)
1037 1037 elif type(config) == type({}):
1038 1038 self.repos = cleannames(config.items())
1039 1039 self.repos.sort()
1040 1040 else:
1041 1041 cp = ConfigParser.SafeConfigParser()
1042 1042 cp.read(config)
1043 1043 self.repos = cleannames(cp.items("paths"))
1044 1044 self.repos.sort()
1045 1045
1046 1046 def run(self, req=hgrequest()):
1047 1047 def header(**map):
1048 1048 yield tmpl("header", **map)
1049 1049
1050 1050 def footer(**map):
1051 1051 yield tmpl("footer", **map)
1052 1052
1053 1053 m = os.path.join(templatepath(), "map")
1054 1054 tmpl = templater(m, common_filters,
1055 1055 {"header": header, "footer": footer})
1056 1056
1057 1057 def entries(**map):
1058 1058 parity = 0
1059 1059 for name, path in self.repos:
1060 1060 u = ui.ui()
1061 1061 try:
1062 1062 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1063 1063 except IOError:
1064 1064 pass
1065 1065 get = u.config
1066 1066
1067 1067 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1068 1068 .replace("//", "/"))
1069 1069
1070 1070 # update time with local timezone
1071 1071 try:
1072 1072 d = (get_mtime(path), util.makedate()[1])
1073 1073 except OSError:
1074 1074 continue
1075 1075
1076 1076 yield dict(contact=(get("ui", "username") or # preferred
1077 1077 get("web", "contact") or # deprecated
1078 1078 get("web", "author", "unknown")), # also
1079 1079 name=get("web", "name", name),
1080 1080 url=url,
1081 1081 parity=parity,
1082 1082 shortdesc=get("web", "description", "unknown"),
1083 1083 lastupdate=d)
1084 1084
1085 1085 parity = 1 - parity
1086 1086
1087 1087 virtual = req.env.get("PATH_INFO", "").strip('/')
1088 1088 if virtual:
1089 1089 real = dict(self.repos).get(virtual)
1090 1090 if real:
1091 1091 try:
1092 1092 hgweb(real).run(req)
1093 1093 except IOError, inst:
1094 1094 req.write(tmpl("error", error=inst.strerror))
1095 1095 except hg.RepoError, inst:
1096 1096 req.write(tmpl("error", error=str(inst)))
1097 1097 else:
1098 1098 req.write(tmpl("notfound", repo=virtual))
1099 1099 else:
1100 1100 req.write(tmpl("index", entries=entries))
General Comments 0
You need to be logged in to leave comments. Login now