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