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