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