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