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