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