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