##// END OF EJS Templates
NWI base URL detection fixes
Brendan Cully -
r3327:e6353b7b default
parent child Browse files
Show More
@@ -1,1049 +1,1042 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 root = req.env.get('REQUEST_URI', '').split('?', 1)[0]
651 pi = req.env.get('PATH_INFO', '')
652 if pi:
653 root = root[:-len(pi)]
654
655 if req.env.has_key('REPO_NAME'):
656 base = '/' + req.env['REPO_NAME']
657 else:
658 base = root
650 def normurl(url):
651 inner = '/'.join([x for x in url.split('/') if x])
652 tl = len(url) > 1 and url.endswith('/') and '/' or ''
659 653
654 return '%s%s%s' % (url.startswith('/') and '/' or '',
655 inner, tl)
656
657 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
658 pi = normurl(req.env.get('PATH_INFO', ''))
660 659 if pi:
661 while pi.startswith('//'):
662 pi = pi[1:]
663 if pi.startswith(base):
664 if len(pi) > len(base):
665 base += '/'
666 query = pi[len(base):]
667 else:
668 if req.env.has_key('REPO_NAME'):
669 # We are using hgwebdir
670 base += '/'
671 else:
672 base += '?'
673 query = firstitem(req.env['QUERY_STRING'])
660 # strip leading /
661 pi = pi[1:]
662 if pi:
663 root = root[:-len(pi)]
664 if req.env.has_key('REPO_NAME'):
665 rn = req.env['REPO_NAME'] + '/'
666 root += rn
667 query = pi[len(rn):]
674 668 else:
675 base += '/'
676 query = pi[1:]
669 query = pi
677 670 else:
678 base += '?'
671 root += '?'
679 672 query = firstitem(req.env['QUERY_STRING'])
680 673
681 return (root + base, query)
674 return (root, query)
682 675
683 676 req.url, query = spliturl(req)
684 677
685 678 if req.form.has_key('cmd'):
686 679 # old style
687 680 return
688 681
689 682 args = query.split('/', 2)
690 683 if not args or not args[0]:
691 684 return
692 685
693 686 cmd = args.pop(0)
694 687 style = cmd.rfind('-')
695 688 if style != -1:
696 689 req.form['style'] = [cmd[:style]]
697 690 cmd = cmd[style+1:]
698 691 # avoid accepting e.g. style parameter as command
699 692 if hasattr(self, 'do_' + cmd):
700 693 req.form['cmd'] = [cmd]
701 694
702 695 if args and args[0]:
703 696 node = args.pop(0)
704 697 req.form['node'] = [node]
705 698 if args:
706 699 req.form['file'] = args
707 700
708 701 if cmd == 'static':
709 702 req.form['file'] = req.form['node']
710 703 elif cmd == 'archive':
711 704 fn = req.form['node'][0]
712 705 for type_, spec in self.archive_specs.iteritems():
713 706 ext = spec[2]
714 707 if fn.endswith(ext):
715 708 req.form['node'] = [fn[:-len(ext)]]
716 709 req.form['type'] = [type_]
717 710
718 711 def queryprefix(**map):
719 712 return req.url[-1] == '?' and ';' or '?'
720 713
721 714 def getentries(**map):
722 715 fields = {}
723 716 if req.form.has_key('style'):
724 717 style = req.form['style'][0]
725 718 if style != self.repo.ui.config('web', 'style', ''):
726 719 fields['style'] = style
727 720
728 721 if fields:
729 722 fields = ['%s=%s' % (k, urllib.quote(v))
730 723 for k, v in fields.iteritems()]
731 724 yield '%s%s' % (queryprefix(), ';'.join(fields))
732 725 else:
733 726 yield ''
734 727
735 728 self.refresh()
736 729
737 730 expand_form(req.form)
738 731 rewrite_request(req)
739 732
740 733 style = self.repo.ui.config("web", "style", "")
741 734 if req.form.has_key('style'):
742 735 style = req.form['style'][0]
743 736 mapfile = style_map(self.templatepath, style)
744 737
745 738 if not req.url:
746 739 port = req.env["SERVER_PORT"]
747 740 port = port != "80" and (":" + port) or ""
748 741 uri = req.env["REQUEST_URI"]
749 742 if "?" in uri:
750 743 uri = uri.split("?")[0]
751 744 req.url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
752 745
753 746 if not self.reponame:
754 747 self.reponame = (self.repo.ui.config("web", "name")
755 748 or req.env.get('REPO_NAME')
756 749 or req.url.strip('/') or self.repo.root)
757 750
758 751 self.t = templater.templater(mapfile, templater.common_filters,
759 752 defaults={"url": req.url,
760 753 "repo": self.reponame,
761 754 "header": header,
762 755 "footer": footer,
763 756 "rawfileheader": rawfileheader,
764 757 "queryprefix": queryprefix,
765 758 "getentries": getentries
766 759 })
767 760
768 761 if not req.form.has_key('cmd'):
769 762 req.form['cmd'] = [self.t.cache['default'],]
770 763
771 764 cmd = req.form['cmd'][0]
772 765
773 766 method = getattr(self, 'do_' + cmd, None)
774 767 if method:
775 768 method(req)
776 769 else:
777 770 req.write(self.t("error"))
778 771
779 772 def changectx(self, req):
780 773 if req.form.has_key('node'):
781 774 changeid = req.form['node'][0]
782 775 else:
783 776 changeid = req.form['manifest'][0]
784 777 try:
785 778 ctx = self.repo.changectx(changeid)
786 779 except hg.RepoError:
787 780 man = self.repo.manifest
788 781 mn = man.lookup(changeid)
789 782 ctx = self.repo.changectx(man.linkrev(mn))
790 783
791 784 return ctx
792 785
793 786 def filectx(self, req):
794 787 path = self.cleanpath(req.form['file'][0])
795 788 if req.form.has_key('node'):
796 789 changeid = req.form['node'][0]
797 790 else:
798 791 changeid = req.form['filenode'][0]
799 792 try:
800 793 ctx = self.repo.changectx(changeid)
801 794 fctx = ctx.filectx(path)
802 795 except hg.RepoError:
803 796 fctx = self.repo.filectx(path, fileid=changeid)
804 797
805 798 return fctx
806 799
807 800 def stripes(self, parity):
808 801 "make horizontal stripes for easier reading"
809 802 if self.stripecount:
810 803 return (1 + parity / self.stripecount) & 1
811 804 else:
812 805 return 0
813 806
814 807 def do_log(self, req):
815 808 if req.form.has_key('file') and req.form['file'][0]:
816 809 self.do_filelog(req)
817 810 else:
818 811 self.do_changelog(req)
819 812
820 813 def do_rev(self, req):
821 814 self.do_changeset(req)
822 815
823 816 def do_file(self, req):
824 817 path = req.form.get('file', [''])[0]
825 818 if path:
826 819 try:
827 820 req.write(self.filerevision(self.filectx(req)))
828 821 return
829 822 except hg.RepoError:
830 823 pass
831 824 path = self.cleanpath(path)
832 825
833 826 req.write(self.manifest(self.changectx(req), '/' + path))
834 827
835 828 def do_diff(self, req):
836 829 self.do_filediff(req)
837 830
838 831 def do_changelog(self, req, shortlog = False):
839 832 if req.form.has_key('node'):
840 833 ctx = self.changectx(req)
841 834 else:
842 835 if req.form.has_key('rev'):
843 836 hi = req.form['rev'][0]
844 837 else:
845 838 hi = self.repo.changelog.count() - 1
846 839 try:
847 840 ctx = self.repo.changectx(hi)
848 841 except hg.RepoError:
849 842 req.write(self.search(hi)) # XXX redirect to 404 page?
850 843 return
851 844
852 845 req.write(self.changelog(ctx, shortlog = shortlog))
853 846
854 847 def do_shortlog(self, req):
855 848 self.do_changelog(req, shortlog = True)
856 849
857 850 def do_changeset(self, req):
858 851 ctx = self.repo.changectx(req.form['node'][0])
859 852 req.write(self.changeset(ctx))
860 853
861 854 def do_manifest(self, req):
862 855 req.write(self.manifest(self.changectx(req),
863 856 self.cleanpath(req.form['path'][0])))
864 857
865 858 def do_tags(self, req):
866 859 req.write(self.tags())
867 860
868 861 def do_summary(self, req):
869 862 req.write(self.summary())
870 863
871 864 def do_filediff(self, req):
872 865 req.write(self.filediff(self.filectx(req)))
873 866
874 867 def do_annotate(self, req):
875 868 req.write(self.fileannotate(self.filectx(req)))
876 869
877 870 def do_filelog(self, req):
878 871 req.write(self.filelog(self.filectx(req)))
879 872
880 873 def do_heads(self, req):
881 874 resp = " ".join(map(hex, self.repo.heads())) + "\n"
882 875 req.httphdr("application/mercurial-0.1", length=len(resp))
883 876 req.write(resp)
884 877
885 878 def do_branches(self, req):
886 879 nodes = []
887 880 if req.form.has_key('nodes'):
888 881 nodes = map(bin, req.form['nodes'][0].split(" "))
889 882 resp = cStringIO.StringIO()
890 883 for b in self.repo.branches(nodes):
891 884 resp.write(" ".join(map(hex, b)) + "\n")
892 885 resp = resp.getvalue()
893 886 req.httphdr("application/mercurial-0.1", length=len(resp))
894 887 req.write(resp)
895 888
896 889 def do_between(self, req):
897 890 if req.form.has_key('pairs'):
898 891 pairs = [map(bin, p.split("-"))
899 892 for p in req.form['pairs'][0].split(" ")]
900 893 resp = cStringIO.StringIO()
901 894 for b in self.repo.between(pairs):
902 895 resp.write(" ".join(map(hex, b)) + "\n")
903 896 resp = resp.getvalue()
904 897 req.httphdr("application/mercurial-0.1", length=len(resp))
905 898 req.write(resp)
906 899
907 900 def do_changegroup(self, req):
908 901 req.httphdr("application/mercurial-0.1")
909 902 nodes = []
910 903 if not self.allowpull:
911 904 return
912 905
913 906 if req.form.has_key('roots'):
914 907 nodes = map(bin, req.form['roots'][0].split(" "))
915 908
916 909 z = zlib.compressobj()
917 910 f = self.repo.changegroup(nodes, 'serve')
918 911 while 1:
919 912 chunk = f.read(4096)
920 913 if not chunk:
921 914 break
922 915 req.write(z.compress(chunk))
923 916
924 917 req.write(z.flush())
925 918
926 919 def do_archive(self, req):
927 920 changeset = self.repo.lookup(req.form['node'][0])
928 921 type_ = req.form['type'][0]
929 922 allowed = self.repo.ui.configlist("web", "allow_archive")
930 923 if (type_ in self.archives and (type_ in allowed or
931 924 self.repo.ui.configbool("web", "allow" + type_, False))):
932 925 self.archive(req, changeset, type_)
933 926 return
934 927
935 928 req.write(self.t("error"))
936 929
937 930 def do_static(self, req):
938 931 fname = req.form['file'][0]
939 932 static = self.repo.ui.config("web", "static",
940 933 os.path.join(self.templatepath,
941 934 "static"))
942 935 req.write(staticfile(static, fname, req)
943 936 or self.t("error", error="%r not found" % fname))
944 937
945 938 def do_capabilities(self, req):
946 939 caps = ['unbundle']
947 940 if self.repo.ui.configbool('server', 'uncompressed'):
948 941 caps.append('stream=%d' % self.repo.revlogversion)
949 942 resp = ' '.join(caps)
950 943 req.httphdr("application/mercurial-0.1", length=len(resp))
951 944 req.write(resp)
952 945
953 946 def check_perm(self, req, op, default):
954 947 '''check permission for operation based on user auth.
955 948 return true if op allowed, else false.
956 949 default is policy to use if no config given.'''
957 950
958 951 user = req.env.get('REMOTE_USER')
959 952
960 953 deny = self.repo.ui.configlist('web', 'deny_' + op)
961 954 if deny and (not user or deny == ['*'] or user in deny):
962 955 return False
963 956
964 957 allow = self.repo.ui.configlist('web', 'allow_' + op)
965 958 return (allow and (allow == ['*'] or user in allow)) or default
966 959
967 960 def do_unbundle(self, req):
968 961 def bail(response, headers={}):
969 962 length = int(req.env['CONTENT_LENGTH'])
970 963 for s in util.filechunkiter(req, limit=length):
971 964 # drain incoming bundle, else client will not see
972 965 # response when run outside cgi script
973 966 pass
974 967 req.httphdr("application/mercurial-0.1", headers=headers)
975 968 req.write('0\n')
976 969 req.write(response)
977 970
978 971 # require ssl by default, auth info cannot be sniffed and
979 972 # replayed
980 973 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
981 974 if ssl_req:
982 975 if not req.env.get('HTTPS'):
983 976 bail(_('ssl required\n'))
984 977 return
985 978 proto = 'https'
986 979 else:
987 980 proto = 'http'
988 981
989 982 # do not allow push unless explicitly allowed
990 983 if not self.check_perm(req, 'push', False):
991 984 bail(_('push not authorized\n'),
992 985 headers={'status': '401 Unauthorized'})
993 986 return
994 987
995 988 req.httphdr("application/mercurial-0.1")
996 989
997 990 their_heads = req.form['heads'][0].split(' ')
998 991
999 992 def check_heads():
1000 993 heads = map(hex, self.repo.heads())
1001 994 return their_heads == [hex('force')] or their_heads == heads
1002 995
1003 996 # fail early if possible
1004 997 if not check_heads():
1005 998 bail(_('unsynced changes\n'))
1006 999 return
1007 1000
1008 1001 # do not lock repo until all changegroup data is
1009 1002 # streamed. save to temporary file.
1010 1003
1011 1004 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1012 1005 fp = os.fdopen(fd, 'wb+')
1013 1006 try:
1014 1007 length = int(req.env['CONTENT_LENGTH'])
1015 1008 for s in util.filechunkiter(req, limit=length):
1016 1009 fp.write(s)
1017 1010
1018 1011 lock = self.repo.lock()
1019 1012 try:
1020 1013 if not check_heads():
1021 1014 req.write('0\n')
1022 1015 req.write(_('unsynced changes\n'))
1023 1016 return
1024 1017
1025 1018 fp.seek(0)
1026 1019
1027 1020 # send addchangegroup output to client
1028 1021
1029 1022 old_stdout = sys.stdout
1030 1023 sys.stdout = cStringIO.StringIO()
1031 1024
1032 1025 try:
1033 1026 url = 'remote:%s:%s' % (proto,
1034 1027 req.env.get('REMOTE_HOST', ''))
1035 1028 ret = self.repo.addchangegroup(fp, 'serve', url)
1036 1029 finally:
1037 1030 val = sys.stdout.getvalue()
1038 1031 sys.stdout = old_stdout
1039 1032 req.write('%d\n' % ret)
1040 1033 req.write(val)
1041 1034 finally:
1042 1035 lock.release()
1043 1036 finally:
1044 1037 fp.close()
1045 1038 os.unlink(tempname)
1046 1039
1047 1040 def do_stream_out(self, req):
1048 1041 req.httphdr("application/mercurial-0.1")
1049 1042 streamclone.stream_out(self.repo, req)
General Comments 0
You need to be logged in to leave comments. Login now