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