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