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