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