##// END OF EJS Templates
gitweb: Fixed-up search template...
Josef "Jeff" Sipek -
r4469:8af65bc0 default
parent child Browse files
Show More
@@ -1,1157 +1,1158
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 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, mimetypes, re, zlib, mimetools, cStringIO, sys
10 10 import tempfile, urllib, bz2
11 11 from mercurial.node import *
12 12 from mercurial.i18n import gettext as _
13 13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 14 from mercurial import revlog, templater
15 15 from common import get_mtime, staticfile, style_map, paritygen
16 16
17 17 def _up(p):
18 18 if p[0] != "/":
19 19 p = "/" + p
20 20 if p[-1] == "/":
21 21 p = p[:-1]
22 22 up = os.path.dirname(p)
23 23 if up == "/":
24 24 return "/"
25 25 return up + "/"
26 26
27 27 def revnavgen(pos, pagelen, limit, nodefunc):
28 28 def seq(factor, limit=None):
29 29 if limit:
30 30 yield limit
31 31 if limit >= 20 and limit <= 40:
32 32 yield 50
33 33 else:
34 34 yield 1 * factor
35 35 yield 3 * factor
36 36 for f in seq(factor * 10):
37 37 yield f
38 38
39 39 def nav(**map):
40 40 l = []
41 41 last = 0
42 42 for f in seq(1, pagelen):
43 43 if f < pagelen or f <= last:
44 44 continue
45 45 if f > limit:
46 46 break
47 47 last = f
48 48 if pos + f < limit:
49 49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 50 if pos - f >= 0:
51 51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 52
53 53 try:
54 54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 55
56 56 for label, node in l:
57 57 yield {"label": label, "node": node}
58 58
59 59 yield {"label": "tip", "node": "tip"}
60 60 except hg.RepoError:
61 61 pass
62 62
63 63 return nav
64 64
65 65 class hgweb(object):
66 66 def __init__(self, repo, name=None):
67 67 if type(repo) == type(""):
68 68 self.repo = hg.repository(ui.ui(report_untrusted=False), repo)
69 69 else:
70 70 self.repo = repo
71 71
72 72 self.mtime = -1
73 73 self.reponame = name
74 74 self.archives = 'zip', 'gz', 'bz2'
75 75 self.stripecount = 1
76 76 # a repo owner may set web.templates in .hg/hgrc to get any file
77 77 # readable by the user running the CGI script
78 78 self.templatepath = self.config("web", "templates",
79 79 templater.templatepath(),
80 80 untrusted=False)
81 81
82 82 # The CGI scripts are often run by a user different from the repo owner.
83 83 # Trust the settings from the .hg/hgrc files by default.
84 84 def config(self, section, name, default=None, untrusted=True):
85 85 return self.repo.ui.config(section, name, default,
86 86 untrusted=untrusted)
87 87
88 88 def configbool(self, section, name, default=False, untrusted=True):
89 89 return self.repo.ui.configbool(section, name, default,
90 90 untrusted=untrusted)
91 91
92 92 def configlist(self, section, name, default=None, untrusted=True):
93 93 return self.repo.ui.configlist(section, name, default,
94 94 untrusted=untrusted)
95 95
96 96 def refresh(self):
97 97 mtime = get_mtime(self.repo.root)
98 98 if mtime != self.mtime:
99 99 self.mtime = mtime
100 100 self.repo = hg.repository(self.repo.ui, self.repo.root)
101 101 self.maxchanges = int(self.config("web", "maxchanges", 10))
102 102 self.stripecount = int(self.config("web", "stripes", 1))
103 103 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
104 104 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 105 self.allowpull = self.configbool("web", "allowpull", True)
106 106
107 107 def archivelist(self, nodeid):
108 108 allowed = self.configlist("web", "allow_archive")
109 109 for i, spec in self.archive_specs.iteritems():
110 110 if i in allowed or self.configbool("web", "allow" + i):
111 111 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
112 112
113 113 def listfilediffs(self, files, changeset):
114 114 for f in files[:self.maxfiles]:
115 115 yield self.t("filedifflink", node=hex(changeset), file=f)
116 116 if len(files) > self.maxfiles:
117 117 yield self.t("fileellipses")
118 118
119 119 def siblings(self, siblings=[], hiderev=None, **args):
120 120 siblings = [s for s in siblings if s.node() != nullid]
121 121 if len(siblings) == 1 and siblings[0].rev() == hiderev:
122 122 return
123 123 for s in siblings:
124 124 d = {'node': hex(s.node()), 'rev': s.rev()}
125 125 if hasattr(s, 'path'):
126 126 d['file'] = s.path()
127 127 d.update(args)
128 128 yield d
129 129
130 130 def renamelink(self, fl, node):
131 131 r = fl.renamed(node)
132 132 if r:
133 133 return [dict(file=r[0], node=hex(r[1]))]
134 134 return []
135 135
136 136 def showtag(self, t1, node=nullid, **args):
137 137 for t in self.repo.nodetags(node):
138 138 yield self.t(t1, tag=t, **args)
139 139
140 140 def diff(self, node1, node2, files):
141 141 def filterfiles(filters, files):
142 142 l = [x for x in files if x in filters]
143 143
144 144 for t in filters:
145 145 if t and t[-1] != os.sep:
146 146 t += os.sep
147 147 l += [x for x in files if x.startswith(t)]
148 148 return l
149 149
150 150 parity = paritygen(self.stripecount)
151 151 def diffblock(diff, f, fn):
152 152 yield self.t("diffblock",
153 153 lines=prettyprintlines(diff),
154 154 parity=parity.next(),
155 155 file=f,
156 156 filenode=hex(fn or nullid))
157 157
158 158 def prettyprintlines(diff):
159 159 for l in diff.splitlines(1):
160 160 if l.startswith('+'):
161 161 yield self.t("difflineplus", line=l)
162 162 elif l.startswith('-'):
163 163 yield self.t("difflineminus", line=l)
164 164 elif l.startswith('@'):
165 165 yield self.t("difflineat", line=l)
166 166 else:
167 167 yield self.t("diffline", line=l)
168 168
169 169 r = self.repo
170 170 c1 = r.changectx(node1)
171 171 c2 = r.changectx(node2)
172 172 date1 = util.datestr(c1.date())
173 173 date2 = util.datestr(c2.date())
174 174
175 175 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
176 176 if files:
177 177 modified, added, removed = map(lambda x: filterfiles(files, x),
178 178 (modified, added, removed))
179 179
180 180 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
181 181 for f in modified:
182 182 to = c1.filectx(f).data()
183 183 tn = c2.filectx(f).data()
184 184 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
185 185 opts=diffopts), f, tn)
186 186 for f in added:
187 187 to = None
188 188 tn = c2.filectx(f).data()
189 189 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
190 190 opts=diffopts), f, tn)
191 191 for f in removed:
192 192 to = c1.filectx(f).data()
193 193 tn = None
194 194 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
195 195 opts=diffopts), f, tn)
196 196
197 197 def changelog(self, ctx, shortlog=False):
198 198 def changelist(**map):
199 199 cl = self.repo.changelog
200 200 l = [] # build a list in forward order for efficiency
201 201 for i in xrange(start, end):
202 202 ctx = self.repo.changectx(i)
203 203 n = ctx.node()
204 204
205 205 l.insert(0, {"parity": parity.next(),
206 206 "author": ctx.user(),
207 207 "parent": self.siblings(ctx.parents(), i - 1),
208 208 "child": self.siblings(ctx.children(), i + 1),
209 209 "changelogtag": self.showtag("changelogtag",n),
210 210 "desc": ctx.description(),
211 211 "date": ctx.date(),
212 212 "files": self.listfilediffs(ctx.files(), n),
213 213 "rev": i,
214 214 "node": hex(n)})
215 215
216 216 for e in l:
217 217 yield e
218 218
219 219 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
220 220 cl = self.repo.changelog
221 221 count = cl.count()
222 222 pos = ctx.rev()
223 223 start = max(0, pos - maxchanges + 1)
224 224 end = min(count, start + maxchanges)
225 225 pos = end - 1
226 226 parity = paritygen(self.stripecount, offset=start-end)
227 227
228 228 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
229 229
230 230 yield self.t(shortlog and 'shortlog' or 'changelog',
231 231 changenav=changenav,
232 232 node=hex(cl.tip()),
233 233 rev=pos, changesets=count, entries=changelist,
234 234 archives=self.archivelist("tip"))
235 235
236 236 def search(self, query):
237 237
238 238 def changelist(**map):
239 239 cl = self.repo.changelog
240 240 count = 0
241 241 qw = query.lower().split()
242 242
243 243 def revgen():
244 244 for i in xrange(cl.count() - 1, 0, -100):
245 245 l = []
246 246 for j in xrange(max(0, i - 100), i):
247 247 ctx = self.repo.changectx(j)
248 248 l.append(ctx)
249 249 l.reverse()
250 250 for e in l:
251 251 yield e
252 252
253 253 for ctx in revgen():
254 254 miss = 0
255 255 for q in qw:
256 256 if not (q in ctx.user().lower() or
257 257 q in ctx.description().lower() or
258 258 q in " ".join(ctx.files()).lower()):
259 259 miss = 1
260 260 break
261 261 if miss:
262 262 continue
263 263
264 264 count += 1
265 265 n = ctx.node()
266 266
267 267 yield self.t('searchentry',
268 268 parity=parity.next(),
269 269 author=ctx.user(),
270 270 parent=self.siblings(ctx.parents()),
271 271 child=self.siblings(ctx.children()),
272 272 changelogtag=self.showtag("changelogtag",n),
273 273 desc=ctx.description(),
274 274 date=ctx.date(),
275 275 files=self.listfilediffs(ctx.files(), n),
276 276 rev=ctx.rev(),
277 277 node=hex(n))
278 278
279 279 if count >= self.maxchanges:
280 280 break
281 281
282 282 cl = self.repo.changelog
283 283 parity = paritygen(self.stripecount)
284 284
285 285 yield self.t('search',
286 286 query=query,
287 287 node=hex(cl.tip()),
288 entries=changelist)
288 entries=changelist,
289 archives=self.archivelist("tip"))
289 290
290 291 def changeset(self, ctx):
291 292 n = ctx.node()
292 293 parents = ctx.parents()
293 294 p1 = parents[0].node()
294 295
295 296 files = []
296 297 parity = paritygen(self.stripecount)
297 298 for f in ctx.files():
298 299 files.append(self.t("filenodelink",
299 300 node=hex(n), file=f,
300 301 parity=parity.next()))
301 302
302 303 def diff(**map):
303 304 yield self.diff(p1, n, None)
304 305
305 306 yield self.t('changeset',
306 307 diff=diff,
307 308 rev=ctx.rev(),
308 309 node=hex(n),
309 310 parent=self.siblings(parents),
310 311 child=self.siblings(ctx.children()),
311 312 changesettag=self.showtag("changesettag",n),
312 313 author=ctx.user(),
313 314 desc=ctx.description(),
314 315 date=ctx.date(),
315 316 files=files,
316 317 archives=self.archivelist(hex(n)))
317 318
318 319 def filelog(self, fctx):
319 320 f = fctx.path()
320 321 fl = fctx.filelog()
321 322 count = fl.count()
322 323 pagelen = self.maxshortchanges
323 324 pos = fctx.filerev()
324 325 start = max(0, pos - pagelen + 1)
325 326 end = min(count, start + pagelen)
326 327 pos = end - 1
327 328 parity = paritygen(self.stripecount, offset=start-end)
328 329
329 330 def entries(**map):
330 331 l = []
331 332
332 333 for i in xrange(start, end):
333 334 ctx = fctx.filectx(i)
334 335 n = fl.node(i)
335 336
336 337 l.insert(0, {"parity": parity.next(),
337 338 "filerev": i,
338 339 "file": f,
339 340 "node": hex(ctx.node()),
340 341 "author": ctx.user(),
341 342 "date": ctx.date(),
342 343 "rename": self.renamelink(fl, n),
343 344 "parent": self.siblings(fctx.parents()),
344 345 "child": self.siblings(fctx.children()),
345 346 "desc": ctx.description()})
346 347
347 348 for e in l:
348 349 yield e
349 350
350 351 nodefunc = lambda x: fctx.filectx(fileid=x)
351 352 nav = revnavgen(pos, pagelen, count, nodefunc)
352 353 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
353 354 entries=entries)
354 355
355 356 def filerevision(self, fctx):
356 357 f = fctx.path()
357 358 text = fctx.data()
358 359 fl = fctx.filelog()
359 360 n = fctx.filenode()
360 361 parity = paritygen(self.stripecount)
361 362
362 363 mt = mimetypes.guess_type(f)[0]
363 364 rawtext = text
364 365 if util.binary(text):
365 366 mt = mt or 'application/octet-stream'
366 367 text = "(binary:%s)" % mt
367 368 mt = mt or 'text/plain'
368 369
369 370 def lines():
370 371 for l, t in enumerate(text.splitlines(1)):
371 372 yield {"line": t,
372 373 "linenumber": "% 6d" % (l + 1),
373 374 "parity": parity.next()}
374 375
375 376 yield self.t("filerevision",
376 377 file=f,
377 378 path=_up(f),
378 379 text=lines(),
379 380 raw=rawtext,
380 381 mimetype=mt,
381 382 rev=fctx.rev(),
382 383 node=hex(fctx.node()),
383 384 author=fctx.user(),
384 385 date=fctx.date(),
385 386 desc=fctx.description(),
386 387 parent=self.siblings(fctx.parents()),
387 388 child=self.siblings(fctx.children()),
388 389 rename=self.renamelink(fl, n),
389 390 permissions=fctx.manifest().execf(f))
390 391
391 392 def fileannotate(self, fctx):
392 393 f = fctx.path()
393 394 n = fctx.filenode()
394 395 fl = fctx.filelog()
395 396 parity = paritygen(self.stripecount)
396 397
397 398 def annotate(**map):
398 399 last = None
399 400 for f, l in fctx.annotate(follow=True):
400 401 fnode = f.filenode()
401 402 name = self.repo.ui.shortuser(f.user())
402 403
403 404 if last != fnode:
404 405 last = fnode
405 406
406 407 yield {"parity": parity.next(),
407 408 "node": hex(f.node()),
408 409 "rev": f.rev(),
409 410 "author": name,
410 411 "file": f.path(),
411 412 "line": l}
412 413
413 414 yield self.t("fileannotate",
414 415 file=f,
415 416 annotate=annotate,
416 417 path=_up(f),
417 418 rev=fctx.rev(),
418 419 node=hex(fctx.node()),
419 420 author=fctx.user(),
420 421 date=fctx.date(),
421 422 desc=fctx.description(),
422 423 rename=self.renamelink(fl, n),
423 424 parent=self.siblings(fctx.parents()),
424 425 child=self.siblings(fctx.children()),
425 426 permissions=fctx.manifest().execf(f))
426 427
427 428 def manifest(self, ctx, path):
428 429 mf = ctx.manifest()
429 430 node = ctx.node()
430 431
431 432 files = {}
432 433 parity = paritygen(self.stripecount)
433 434
434 435 if path and path[-1] != "/":
435 436 path += "/"
436 437 l = len(path)
437 438 abspath = "/" + path
438 439
439 440 for f, n in mf.items():
440 441 if f[:l] != path:
441 442 continue
442 443 remain = f[l:]
443 444 if "/" in remain:
444 445 short = remain[:remain.index("/") + 1] # bleah
445 446 files[short] = (f, None)
446 447 else:
447 448 short = os.path.basename(remain)
448 449 files[short] = (f, n)
449 450
450 451 def filelist(**map):
451 452 fl = files.keys()
452 453 fl.sort()
453 454 for f in fl:
454 455 full, fnode = files[f]
455 456 if not fnode:
456 457 continue
457 458
458 459 yield {"file": full,
459 460 "parity": parity.next(),
460 461 "basename": f,
461 462 "size": ctx.filectx(full).size(),
462 463 "permissions": mf.execf(full)}
463 464
464 465 def dirlist(**map):
465 466 fl = files.keys()
466 467 fl.sort()
467 468 for f in fl:
468 469 full, fnode = files[f]
469 470 if fnode:
470 471 continue
471 472
472 473 yield {"parity": parity.next(),
473 474 "path": os.path.join(abspath, f),
474 475 "basename": f[:-1]}
475 476
476 477 yield self.t("manifest",
477 478 rev=ctx.rev(),
478 479 node=hex(node),
479 480 path=abspath,
480 481 up=_up(abspath),
481 482 upparity=parity.next(),
482 483 fentries=filelist,
483 484 dentries=dirlist,
484 485 archives=self.archivelist(hex(node)))
485 486
486 487 def tags(self):
487 488 i = self.repo.tagslist()
488 489 i.reverse()
489 490 parity = paritygen(self.stripecount)
490 491
491 492 def entries(notip=False, **map):
492 493 for k, n in i:
493 494 if notip and k == "tip":
494 495 continue
495 496 yield {"parity": parity.next(),
496 497 "tag": k,
497 498 "date": self.repo.changectx(n).date(),
498 499 "node": hex(n)}
499 500
500 501 yield self.t("tags",
501 502 node=hex(self.repo.changelog.tip()),
502 503 entries=lambda **x: entries(False, **x),
503 504 entriesnotip=lambda **x: entries(True, **x))
504 505
505 506 def summary(self):
506 507 i = self.repo.tagslist()
507 508 i.reverse()
508 509
509 510 def tagentries(**map):
510 511 parity = paritygen(self.stripecount)
511 512 count = 0
512 513 for k, n in i:
513 514 if k == "tip": # skip tip
514 515 continue;
515 516
516 517 count += 1
517 518 if count > 10: # limit to 10 tags
518 519 break;
519 520
520 521 yield self.t("tagentry",
521 522 parity=parity.next(),
522 523 tag=k,
523 524 node=hex(n),
524 525 date=self.repo.changectx(n).date())
525 526
526 527
527 528 def branches(**map):
528 529 parity = paritygen(self.stripecount)
529 530
530 531 b = self.repo.branchtags()
531 532 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
532 533 l.sort()
533 534
534 535 for r,n,t in l:
535 536 ctx = self.repo.changectx(n)
536 537
537 538 yield {'parity': parity.next(),
538 539 'branch': t,
539 540 'node': hex(n),
540 541 'date': ctx.date()}
541 542
542 543 def changelist(**map):
543 544 parity = paritygen(self.stripecount, offset=start-end)
544 545 l = [] # build a list in forward order for efficiency
545 546 for i in xrange(start, end):
546 547 ctx = self.repo.changectx(i)
547 548 hn = hex(ctx.node())
548 549
549 550 l.insert(0, self.t(
550 551 'shortlogentry',
551 552 parity=parity.next(),
552 553 author=ctx.user(),
553 554 desc=ctx.description(),
554 555 date=ctx.date(),
555 556 rev=i,
556 557 node=hn))
557 558
558 559 yield l
559 560
560 561 cl = self.repo.changelog
561 562 count = cl.count()
562 563 start = max(0, count - self.maxchanges)
563 564 end = min(count, start + self.maxchanges)
564 565
565 566 yield self.t("summary",
566 567 desc=self.config("web", "description", "unknown"),
567 568 owner=(self.config("ui", "username") or # preferred
568 569 self.config("web", "contact") or # deprecated
569 570 self.config("web", "author", "unknown")), # also
570 571 lastchange=cl.read(cl.tip())[2],
571 572 tags=tagentries,
572 573 branches=branches,
573 574 shortlog=changelist,
574 575 node=hex(cl.tip()),
575 576 archives=self.archivelist("tip"))
576 577
577 578 def filediff(self, fctx):
578 579 n = fctx.node()
579 580 path = fctx.path()
580 581 parents = fctx.parents()
581 582 p1 = parents and parents[0].node() or nullid
582 583
583 584 def diff(**map):
584 585 yield self.diff(p1, n, [path])
585 586
586 587 yield self.t("filediff",
587 588 file=path,
588 589 node=hex(n),
589 590 rev=fctx.rev(),
590 591 parent=self.siblings(parents),
591 592 child=self.siblings(fctx.children()),
592 593 diff=diff)
593 594
594 595 archive_specs = {
595 596 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
596 597 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
597 598 'zip': ('application/zip', 'zip', '.zip', None),
598 599 }
599 600
600 601 def archive(self, req, id, type_):
601 602 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
602 603 cnode = self.repo.lookup(id)
603 604 arch_version = id
604 605 if cnode == id or id == 'tip':
605 606 arch_version = short(cnode)
606 607 name = "%s-%s" % (reponame, arch_version)
607 608 mimetype, artype, extension, encoding = self.archive_specs[type_]
608 609 headers = [('Content-type', mimetype),
609 610 ('Content-disposition', 'attachment; filename=%s%s' %
610 611 (name, extension))]
611 612 if encoding:
612 613 headers.append(('Content-encoding', encoding))
613 614 req.header(headers)
614 615 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
615 616
616 617 # add tags to things
617 618 # tags -> list of changesets corresponding to tags
618 619 # find tag, changeset, file
619 620
620 621 def cleanpath(self, path):
621 622 path = path.lstrip('/')
622 623 return util.canonpath(self.repo.root, '', path)
623 624
624 625 def run(self):
625 626 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
626 627 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
627 628 import mercurial.hgweb.wsgicgi as wsgicgi
628 629 from request import wsgiapplication
629 630 def make_web_app():
630 631 return self
631 632 wsgicgi.launch(wsgiapplication(make_web_app))
632 633
633 634 def run_wsgi(self, req):
634 635 def header(**map):
635 636 header_file = cStringIO.StringIO(
636 637 ''.join(self.t("header", encoding=util._encoding, **map)))
637 638 msg = mimetools.Message(header_file, 0)
638 639 req.header(msg.items())
639 640 yield header_file.read()
640 641
641 642 def rawfileheader(**map):
642 643 req.header([('Content-type', map['mimetype']),
643 644 ('Content-disposition', 'filename=%s' % map['file']),
644 645 ('Content-length', str(len(map['raw'])))])
645 646 yield ''
646 647
647 648 def footer(**map):
648 649 yield self.t("footer", **map)
649 650
650 651 def motd(**map):
651 652 yield self.config("web", "motd", "")
652 653
653 654 def expand_form(form):
654 655 shortcuts = {
655 656 'cl': [('cmd', ['changelog']), ('rev', None)],
656 657 'sl': [('cmd', ['shortlog']), ('rev', None)],
657 658 'cs': [('cmd', ['changeset']), ('node', None)],
658 659 'f': [('cmd', ['file']), ('filenode', None)],
659 660 'fl': [('cmd', ['filelog']), ('filenode', None)],
660 661 'fd': [('cmd', ['filediff']), ('node', None)],
661 662 'fa': [('cmd', ['annotate']), ('filenode', None)],
662 663 'mf': [('cmd', ['manifest']), ('manifest', None)],
663 664 'ca': [('cmd', ['archive']), ('node', None)],
664 665 'tags': [('cmd', ['tags'])],
665 666 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
666 667 'static': [('cmd', ['static']), ('file', None)]
667 668 }
668 669
669 670 for k in shortcuts.iterkeys():
670 671 if form.has_key(k):
671 672 for name, value in shortcuts[k]:
672 673 if value is None:
673 674 value = form[k]
674 675 form[name] = value
675 676 del form[k]
676 677
677 678 def rewrite_request(req):
678 679 '''translate new web interface to traditional format'''
679 680
680 681 def spliturl(req):
681 682 def firstitem(query):
682 683 return query.split('&', 1)[0].split(';', 1)[0]
683 684
684 685 def normurl(url):
685 686 inner = '/'.join([x for x in url.split('/') if x])
686 687 tl = len(url) > 1 and url.endswith('/') and '/' or ''
687 688
688 689 return '%s%s%s' % (url.startswith('/') and '/' or '',
689 690 inner, tl)
690 691
691 692 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
692 693 pi = normurl(req.env.get('PATH_INFO', ''))
693 694 if pi:
694 695 # strip leading /
695 696 pi = pi[1:]
696 697 if pi:
697 698 root = root[:root.rfind(pi)]
698 699 if req.env.has_key('REPO_NAME'):
699 700 rn = req.env['REPO_NAME'] + '/'
700 701 root += rn
701 702 query = pi[len(rn):]
702 703 else:
703 704 query = pi
704 705 else:
705 706 root += '?'
706 707 query = firstitem(req.env['QUERY_STRING'])
707 708
708 709 return (root, query)
709 710
710 711 req.url, query = spliturl(req)
711 712
712 713 if req.form.has_key('cmd'):
713 714 # old style
714 715 return
715 716
716 717 args = query.split('/', 2)
717 718 if not args or not args[0]:
718 719 return
719 720
720 721 cmd = args.pop(0)
721 722 style = cmd.rfind('-')
722 723 if style != -1:
723 724 req.form['style'] = [cmd[:style]]
724 725 cmd = cmd[style+1:]
725 726 # avoid accepting e.g. style parameter as command
726 727 if hasattr(self, 'do_' + cmd):
727 728 req.form['cmd'] = [cmd]
728 729
729 730 if args and args[0]:
730 731 node = args.pop(0)
731 732 req.form['node'] = [node]
732 733 if args:
733 734 req.form['file'] = args
734 735
735 736 if cmd == 'static':
736 737 req.form['file'] = req.form['node']
737 738 elif cmd == 'archive':
738 739 fn = req.form['node'][0]
739 740 for type_, spec in self.archive_specs.iteritems():
740 741 ext = spec[2]
741 742 if fn.endswith(ext):
742 743 req.form['node'] = [fn[:-len(ext)]]
743 744 req.form['type'] = [type_]
744 745
745 746 def sessionvars(**map):
746 747 fields = []
747 748 if req.form.has_key('style'):
748 749 style = req.form['style'][0]
749 750 if style != self.config('web', 'style', ''):
750 751 fields.append(('style', style))
751 752
752 753 separator = req.url[-1] == '?' and ';' or '?'
753 754 for name, value in fields:
754 755 yield dict(name=name, value=value, separator=separator)
755 756 separator = ';'
756 757
757 758 self.refresh()
758 759
759 760 expand_form(req.form)
760 761 rewrite_request(req)
761 762
762 763 style = self.config("web", "style", "")
763 764 if req.form.has_key('style'):
764 765 style = req.form['style'][0]
765 766 mapfile = style_map(self.templatepath, style)
766 767
767 768 port = req.env["SERVER_PORT"]
768 769 port = port != "80" and (":" + port) or ""
769 770 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
770 771 staticurl = self.config("web", "staticurl") or req.url + 'static/'
771 772 if not staticurl.endswith('/'):
772 773 staticurl += '/'
773 774
774 775 if not self.reponame:
775 776 self.reponame = (self.config("web", "name")
776 777 or req.env.get('REPO_NAME')
777 778 or req.url.strip('/') or self.repo.root)
778 779
779 780 self.t = templater.templater(mapfile, templater.common_filters,
780 781 defaults={"url": req.url,
781 782 "staticurl": staticurl,
782 783 "urlbase": urlbase,
783 784 "repo": self.reponame,
784 785 "header": header,
785 786 "footer": footer,
786 787 "motd": motd,
787 788 "rawfileheader": rawfileheader,
788 789 "sessionvars": sessionvars
789 790 })
790 791
791 792 try:
792 793 if not req.form.has_key('cmd'):
793 794 req.form['cmd'] = [self.t.cache['default']]
794 795
795 796 cmd = req.form['cmd'][0]
796 797
797 798 method = getattr(self, 'do_' + cmd, None)
798 799 if method:
799 800 try:
800 801 method(req)
801 802 except (hg.RepoError, revlog.RevlogError), inst:
802 803 req.write(self.t("error", error=str(inst)))
803 804 else:
804 805 req.write(self.t("error", error='No such method: ' + cmd))
805 806 finally:
806 807 self.t = None
807 808
808 809 def changectx(self, req):
809 810 if req.form.has_key('node'):
810 811 changeid = req.form['node'][0]
811 812 elif req.form.has_key('manifest'):
812 813 changeid = req.form['manifest'][0]
813 814 else:
814 815 changeid = self.repo.changelog.count() - 1
815 816
816 817 try:
817 818 ctx = self.repo.changectx(changeid)
818 819 except hg.RepoError:
819 820 man = self.repo.manifest
820 821 mn = man.lookup(changeid)
821 822 ctx = self.repo.changectx(man.linkrev(mn))
822 823
823 824 return ctx
824 825
825 826 def filectx(self, req):
826 827 path = self.cleanpath(req.form['file'][0])
827 828 if req.form.has_key('node'):
828 829 changeid = req.form['node'][0]
829 830 else:
830 831 changeid = req.form['filenode'][0]
831 832 try:
832 833 ctx = self.repo.changectx(changeid)
833 834 fctx = ctx.filectx(path)
834 835 except hg.RepoError:
835 836 fctx = self.repo.filectx(path, fileid=changeid)
836 837
837 838 return fctx
838 839
839 840 def do_log(self, req):
840 841 if req.form.has_key('file') and req.form['file'][0]:
841 842 self.do_filelog(req)
842 843 else:
843 844 self.do_changelog(req)
844 845
845 846 def do_rev(self, req):
846 847 self.do_changeset(req)
847 848
848 849 def do_file(self, req):
849 850 path = self.cleanpath(req.form.get('file', [''])[0])
850 851 if path:
851 852 try:
852 853 req.write(self.filerevision(self.filectx(req)))
853 854 return
854 855 except revlog.LookupError:
855 856 pass
856 857
857 858 req.write(self.manifest(self.changectx(req), path))
858 859
859 860 def do_diff(self, req):
860 861 self.do_filediff(req)
861 862
862 863 def do_changelog(self, req, shortlog = False):
863 864 if req.form.has_key('node'):
864 865 ctx = self.changectx(req)
865 866 else:
866 867 if req.form.has_key('rev'):
867 868 hi = req.form['rev'][0]
868 869 else:
869 870 hi = self.repo.changelog.count() - 1
870 871 try:
871 872 ctx = self.repo.changectx(hi)
872 873 except hg.RepoError:
873 874 req.write(self.search(hi)) # XXX redirect to 404 page?
874 875 return
875 876
876 877 req.write(self.changelog(ctx, shortlog = shortlog))
877 878
878 879 def do_shortlog(self, req):
879 880 self.do_changelog(req, shortlog = True)
880 881
881 882 def do_changeset(self, req):
882 883 req.write(self.changeset(self.changectx(req)))
883 884
884 885 def do_manifest(self, req):
885 886 req.write(self.manifest(self.changectx(req),
886 887 self.cleanpath(req.form['path'][0])))
887 888
888 889 def do_tags(self, req):
889 890 req.write(self.tags())
890 891
891 892 def do_summary(self, req):
892 893 req.write(self.summary())
893 894
894 895 def do_filediff(self, req):
895 896 req.write(self.filediff(self.filectx(req)))
896 897
897 898 def do_annotate(self, req):
898 899 req.write(self.fileannotate(self.filectx(req)))
899 900
900 901 def do_filelog(self, req):
901 902 req.write(self.filelog(self.filectx(req)))
902 903
903 904 def do_lookup(self, req):
904 905 try:
905 906 r = hex(self.repo.lookup(req.form['key'][0]))
906 907 success = 1
907 908 except Exception,inst:
908 909 r = str(inst)
909 910 success = 0
910 911 resp = "%s %s\n" % (success, r)
911 912 req.httphdr("application/mercurial-0.1", length=len(resp))
912 913 req.write(resp)
913 914
914 915 def do_heads(self, req):
915 916 resp = " ".join(map(hex, self.repo.heads())) + "\n"
916 917 req.httphdr("application/mercurial-0.1", length=len(resp))
917 918 req.write(resp)
918 919
919 920 def do_branches(self, req):
920 921 nodes = []
921 922 if req.form.has_key('nodes'):
922 923 nodes = map(bin, req.form['nodes'][0].split(" "))
923 924 resp = cStringIO.StringIO()
924 925 for b in self.repo.branches(nodes):
925 926 resp.write(" ".join(map(hex, b)) + "\n")
926 927 resp = resp.getvalue()
927 928 req.httphdr("application/mercurial-0.1", length=len(resp))
928 929 req.write(resp)
929 930
930 931 def do_between(self, req):
931 932 if req.form.has_key('pairs'):
932 933 pairs = [map(bin, p.split("-"))
933 934 for p in req.form['pairs'][0].split(" ")]
934 935 resp = cStringIO.StringIO()
935 936 for b in self.repo.between(pairs):
936 937 resp.write(" ".join(map(hex, b)) + "\n")
937 938 resp = resp.getvalue()
938 939 req.httphdr("application/mercurial-0.1", length=len(resp))
939 940 req.write(resp)
940 941
941 942 def do_changegroup(self, req):
942 943 req.httphdr("application/mercurial-0.1")
943 944 nodes = []
944 945 if not self.allowpull:
945 946 return
946 947
947 948 if req.form.has_key('roots'):
948 949 nodes = map(bin, req.form['roots'][0].split(" "))
949 950
950 951 z = zlib.compressobj()
951 952 f = self.repo.changegroup(nodes, 'serve')
952 953 while 1:
953 954 chunk = f.read(4096)
954 955 if not chunk:
955 956 break
956 957 req.write(z.compress(chunk))
957 958
958 959 req.write(z.flush())
959 960
960 961 def do_changegroupsubset(self, req):
961 962 req.httphdr("application/mercurial-0.1")
962 963 bases = []
963 964 heads = []
964 965 if not self.allowpull:
965 966 return
966 967
967 968 if req.form.has_key('bases'):
968 969 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
969 970 if req.form.has_key('heads'):
970 971 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
971 972
972 973 z = zlib.compressobj()
973 974 f = self.repo.changegroupsubset(bases, heads, 'serve')
974 975 while 1:
975 976 chunk = f.read(4096)
976 977 if not chunk:
977 978 break
978 979 req.write(z.compress(chunk))
979 980
980 981 req.write(z.flush())
981 982
982 983 def do_archive(self, req):
983 984 type_ = req.form['type'][0]
984 985 allowed = self.configlist("web", "allow_archive")
985 986 if (type_ in self.archives and (type_ in allowed or
986 987 self.configbool("web", "allow" + type_, False))):
987 988 self.archive(req, req.form['node'][0], type_)
988 989 return
989 990
990 991 req.write(self.t("error"))
991 992
992 993 def do_static(self, req):
993 994 fname = req.form['file'][0]
994 995 # a repo owner may set web.static in .hg/hgrc to get any file
995 996 # readable by the user running the CGI script
996 997 static = self.config("web", "static",
997 998 os.path.join(self.templatepath, "static"),
998 999 untrusted=False)
999 1000 req.write(staticfile(static, fname, req)
1000 1001 or self.t("error", error="%r not found" % fname))
1001 1002
1002 1003 def do_capabilities(self, req):
1003 1004 caps = ['lookup', 'changegroupsubset']
1004 1005 if self.configbool('server', 'uncompressed'):
1005 1006 caps.append('stream=%d' % self.repo.changelog.version)
1006 1007 # XXX: make configurable and/or share code with do_unbundle:
1007 1008 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1008 1009 if unbundleversions:
1009 1010 caps.append('unbundle=%s' % ','.join(unbundleversions))
1010 1011 resp = ' '.join(caps)
1011 1012 req.httphdr("application/mercurial-0.1", length=len(resp))
1012 1013 req.write(resp)
1013 1014
1014 1015 def check_perm(self, req, op, default):
1015 1016 '''check permission for operation based on user auth.
1016 1017 return true if op allowed, else false.
1017 1018 default is policy to use if no config given.'''
1018 1019
1019 1020 user = req.env.get('REMOTE_USER')
1020 1021
1021 1022 deny = self.configlist('web', 'deny_' + op)
1022 1023 if deny and (not user or deny == ['*'] or user in deny):
1023 1024 return False
1024 1025
1025 1026 allow = self.configlist('web', 'allow_' + op)
1026 1027 return (allow and (allow == ['*'] or user in allow)) or default
1027 1028
1028 1029 def do_unbundle(self, req):
1029 1030 def bail(response, headers={}):
1030 1031 length = int(req.env['CONTENT_LENGTH'])
1031 1032 for s in util.filechunkiter(req, limit=length):
1032 1033 # drain incoming bundle, else client will not see
1033 1034 # response when run outside cgi script
1034 1035 pass
1035 1036 req.httphdr("application/mercurial-0.1", headers=headers)
1036 1037 req.write('0\n')
1037 1038 req.write(response)
1038 1039
1039 1040 # require ssl by default, auth info cannot be sniffed and
1040 1041 # replayed
1041 1042 ssl_req = self.configbool('web', 'push_ssl', True)
1042 1043 if ssl_req:
1043 1044 if not req.env.get('HTTPS'):
1044 1045 bail(_('ssl required\n'))
1045 1046 return
1046 1047 proto = 'https'
1047 1048 else:
1048 1049 proto = 'http'
1049 1050
1050 1051 # do not allow push unless explicitly allowed
1051 1052 if not self.check_perm(req, 'push', False):
1052 1053 bail(_('push not authorized\n'),
1053 1054 headers={'status': '401 Unauthorized'})
1054 1055 return
1055 1056
1056 1057 their_heads = req.form['heads'][0].split(' ')
1057 1058
1058 1059 def check_heads():
1059 1060 heads = map(hex, self.repo.heads())
1060 1061 return their_heads == [hex('force')] or their_heads == heads
1061 1062
1062 1063 # fail early if possible
1063 1064 if not check_heads():
1064 1065 bail(_('unsynced changes\n'))
1065 1066 return
1066 1067
1067 1068 req.httphdr("application/mercurial-0.1")
1068 1069
1069 1070 # do not lock repo until all changegroup data is
1070 1071 # streamed. save to temporary file.
1071 1072
1072 1073 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1073 1074 fp = os.fdopen(fd, 'wb+')
1074 1075 try:
1075 1076 length = int(req.env['CONTENT_LENGTH'])
1076 1077 for s in util.filechunkiter(req, limit=length):
1077 1078 fp.write(s)
1078 1079
1079 1080 try:
1080 1081 lock = self.repo.lock()
1081 1082 try:
1082 1083 if not check_heads():
1083 1084 req.write('0\n')
1084 1085 req.write(_('unsynced changes\n'))
1085 1086 return
1086 1087
1087 1088 fp.seek(0)
1088 1089 header = fp.read(6)
1089 1090 if not header.startswith("HG"):
1090 1091 # old client with uncompressed bundle
1091 1092 def generator(f):
1092 1093 yield header
1093 1094 for chunk in f:
1094 1095 yield chunk
1095 1096 elif not header.startswith("HG10"):
1096 1097 req.write("0\n")
1097 1098 req.write(_("unknown bundle version\n"))
1098 1099 return
1099 1100 elif header == "HG10GZ":
1100 1101 def generator(f):
1101 1102 zd = zlib.decompressobj()
1102 1103 for chunk in f:
1103 1104 yield zd.decompress(chunk)
1104 1105 elif header == "HG10BZ":
1105 1106 def generator(f):
1106 1107 zd = bz2.BZ2Decompressor()
1107 1108 zd.decompress("BZ")
1108 1109 for chunk in f:
1109 1110 yield zd.decompress(chunk)
1110 1111 elif header == "HG10UN":
1111 1112 def generator(f):
1112 1113 for chunk in f:
1113 1114 yield chunk
1114 1115 else:
1115 1116 req.write("0\n")
1116 1117 req.write(_("unknown bundle compression type\n"))
1117 1118 return
1118 1119 gen = generator(util.filechunkiter(fp, 4096))
1119 1120
1120 1121 # send addchangegroup output to client
1121 1122
1122 1123 old_stdout = sys.stdout
1123 1124 sys.stdout = cStringIO.StringIO()
1124 1125
1125 1126 try:
1126 1127 url = 'remote:%s:%s' % (proto,
1127 1128 req.env.get('REMOTE_HOST', ''))
1128 1129 try:
1129 1130 ret = self.repo.addchangegroup(
1130 1131 util.chunkbuffer(gen), 'serve', url)
1131 1132 except util.Abort, inst:
1132 1133 sys.stdout.write("abort: %s\n" % inst)
1133 1134 ret = 0
1134 1135 finally:
1135 1136 val = sys.stdout.getvalue()
1136 1137 sys.stdout = old_stdout
1137 1138 req.write('%d\n' % ret)
1138 1139 req.write(val)
1139 1140 finally:
1140 1141 lock.release()
1141 1142 except (OSError, IOError), inst:
1142 1143 req.write('0\n')
1143 1144 filename = getattr(inst, 'filename', '')
1144 1145 # Don't send our filesystem layout to the client
1145 1146 if filename.startswith(self.repo.root):
1146 1147 filename = filename[len(self.repo.root)+1:]
1147 1148 else:
1148 1149 filename = ''
1149 1150 error = getattr(inst, 'strerror', 'Unknown error')
1150 1151 req.write('%s: %s\n' % (error, filename))
1151 1152 finally:
1152 1153 fp.close()
1153 1154 os.unlink(tempname)
1154 1155
1155 1156 def do_stream_out(self, req):
1156 1157 req.httphdr("application/mercurial-0.1")
1157 1158 streamclone.stream_out(self.repo, req)
@@ -1,27 +1,32
1 1 #header#
2 <title>#repo|escape#: Search</title>
3 <link rel="alternate" type="application/rss+xml"
4 href="{url}rss-log" title="RSS feed for #repo|escape#">
5 </head>
6 <body>
7
8 <div class="page_header">
9 <a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="{url}summary{sessionvars%urlparameter}">#repo|escape#</a> / search
10
11 <form action="{url}log">
12 {sessionvars%hiddenformentry}
13 <div class="search">
14 <input type="text" name="rev" value="#query|escape#" />
15 </div>
16 </form>
17 </div>
18
2 19 <div class="page_nav">
3 20 <a href="{url}summary{sessionvars%urlparameter}">summary</a> |
4 21 <a href="{url}shortlog{sessionvars%urlparameter}">shortlog</a> |
5 22 <a href="{url}log{sessionvars%urlparameter}">changelog</a> |
6 23 <a href="{url}tags{sessionvars%urlparameter}">tags</a> |
7 <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a><br/>
24 <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a>#archives%archiveentry#
25 <br/>
8 26 </div>
9 27
10 <h2>searching for #query|escape#</h2>
11
12 <form action="{url}log">
13 {sessionvars%hiddenformentry}
14 search:
15 <input name="rev" type="text" width="30" value="#query|escape#">
16 </form>
28 <div class="title">searching for #query|escape#</div>
17 29
18 30 #entries#
19 31
20 <form action="{url}log">
21 {sessionvars%hiddenformentry}
22 search:
23 <input type="hidden" name="style" value="gitweb">
24 <input name="rev" type="text" width="30">
25 </form>
26
27 32 #footer#
General Comments 0
You need to be logged in to leave comments. Login now