##// END OF EJS Templates
http: query server for capabilities
Vadim Gelfer -
r2442:c660691f default
parent child Browse files
Show More
@@ -1,835 +1,841 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 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 cStringIO")
14 14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
15 15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
16 16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
17 17 from mercurial.node import *
18 18 from mercurial.i18n import gettext as _
19 19
20 20 def _up(p):
21 21 if p[0] != "/":
22 22 p = "/" + p
23 23 if p[-1] == "/":
24 24 p = p[:-1]
25 25 up = os.path.dirname(p)
26 26 if up == "/":
27 27 return "/"
28 28 return up + "/"
29 29
30 30 class hgweb(object):
31 31 def __init__(self, repo, name=None):
32 32 if type(repo) == type(""):
33 33 self.repo = hg.repository(ui.ui(), repo)
34 34 else:
35 35 self.repo = repo
36 36
37 37 self.mtime = -1
38 38 self.reponame = name
39 39 self.archives = 'zip', 'gz', 'bz2'
40 40 self.templatepath = self.repo.ui.config("web", "templates",
41 41 templater.templatepath())
42 42
43 43 def refresh(self):
44 44 mtime = get_mtime(self.repo.root)
45 45 if mtime != self.mtime:
46 46 self.mtime = mtime
47 47 self.repo = hg.repository(self.repo.ui, self.repo.root)
48 48 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
49 49 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
50 50 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
51 51
52 52 def archivelist(self, nodeid):
53 53 allowed = (self.repo.ui.config("web", "allow_archive", "")
54 54 .replace(",", " ").split())
55 55 for i in self.archives:
56 56 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
57 57 yield {"type" : i, "node" : nodeid, "url": ""}
58 58
59 59 def listfiles(self, files, mf):
60 60 for f in files[:self.maxfiles]:
61 61 yield self.t("filenodelink", node=hex(mf[f]), file=f)
62 62 if len(files) > self.maxfiles:
63 63 yield self.t("fileellipses")
64 64
65 65 def listfilediffs(self, files, changeset):
66 66 for f in files[:self.maxfiles]:
67 67 yield self.t("filedifflink", node=hex(changeset), file=f)
68 68 if len(files) > self.maxfiles:
69 69 yield self.t("fileellipses")
70 70
71 71 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
72 72 if not rev:
73 73 rev = lambda x: ""
74 74 siblings = [s for s in siblings if s != nullid]
75 75 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
76 76 return
77 77 for s in siblings:
78 78 yield dict(node=hex(s), rev=rev(s), **args)
79 79
80 80 def renamelink(self, fl, node):
81 81 r = fl.renamed(node)
82 82 if r:
83 83 return [dict(file=r[0], node=hex(r[1]))]
84 84 return []
85 85
86 86 def showtag(self, t1, node=nullid, **args):
87 87 for t in self.repo.nodetags(node):
88 88 yield self.t(t1, tag=t, **args)
89 89
90 90 def diff(self, node1, node2, files):
91 91 def filterfiles(filters, files):
92 92 l = [x for x in files if x in filters]
93 93
94 94 for t in filters:
95 95 if t and t[-1] != os.sep:
96 96 t += os.sep
97 97 l += [x for x in files if x.startswith(t)]
98 98 return l
99 99
100 100 parity = [0]
101 101 def diffblock(diff, f, fn):
102 102 yield self.t("diffblock",
103 103 lines=prettyprintlines(diff),
104 104 parity=parity[0],
105 105 file=f,
106 106 filenode=hex(fn or nullid))
107 107 parity[0] = 1 - parity[0]
108 108
109 109 def prettyprintlines(diff):
110 110 for l in diff.splitlines(1):
111 111 if l.startswith('+'):
112 112 yield self.t("difflineplus", line=l)
113 113 elif l.startswith('-'):
114 114 yield self.t("difflineminus", line=l)
115 115 elif l.startswith('@'):
116 116 yield self.t("difflineat", line=l)
117 117 else:
118 118 yield self.t("diffline", line=l)
119 119
120 120 r = self.repo
121 121 cl = r.changelog
122 122 mf = r.manifest
123 123 change1 = cl.read(node1)
124 124 change2 = cl.read(node2)
125 125 mmap1 = mf.read(change1[0])
126 126 mmap2 = mf.read(change2[0])
127 127 date1 = util.datestr(change1[2])
128 128 date2 = util.datestr(change2[2])
129 129
130 130 modified, added, removed, deleted, unknown = r.changes(node1, node2)
131 131 if files:
132 132 modified, added, removed = map(lambda x: filterfiles(files, x),
133 133 (modified, added, removed))
134 134
135 135 diffopts = self.repo.ui.diffopts()
136 136 showfunc = diffopts['showfunc']
137 137 ignorews = diffopts['ignorews']
138 138 for f in modified:
139 139 to = r.file(f).read(mmap1[f])
140 140 tn = r.file(f).read(mmap2[f])
141 141 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
142 142 showfunc=showfunc, ignorews=ignorews), f, tn)
143 143 for f in added:
144 144 to = None
145 145 tn = r.file(f).read(mmap2[f])
146 146 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
147 147 showfunc=showfunc, ignorews=ignorews), f, tn)
148 148 for f in removed:
149 149 to = r.file(f).read(mmap1[f])
150 150 tn = None
151 151 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
152 152 showfunc=showfunc, ignorews=ignorews), f, tn)
153 153
154 154 def changelog(self, pos):
155 155 def changenav(**map):
156 156 def seq(factor, maxchanges=None):
157 157 if maxchanges:
158 158 yield maxchanges
159 159 if maxchanges >= 20 and maxchanges <= 40:
160 160 yield 50
161 161 else:
162 162 yield 1 * factor
163 163 yield 3 * factor
164 164 for f in seq(factor * 10):
165 165 yield f
166 166
167 167 l = []
168 168 last = 0
169 169 for f in seq(1, self.maxchanges):
170 170 if f < self.maxchanges or f <= last:
171 171 continue
172 172 if f > count:
173 173 break
174 174 last = f
175 175 r = "%d" % f
176 176 if pos + f < count:
177 177 l.append(("+" + r, pos + f))
178 178 if pos - f >= 0:
179 179 l.insert(0, ("-" + r, pos - f))
180 180
181 181 yield {"rev": 0, "label": "(0)"}
182 182
183 183 for label, rev in l:
184 184 yield {"label": label, "rev": rev}
185 185
186 186 yield {"label": "tip", "rev": "tip"}
187 187
188 188 def changelist(**map):
189 189 parity = (start - end) & 1
190 190 cl = self.repo.changelog
191 191 l = [] # build a list in forward order for efficiency
192 192 for i in range(start, end):
193 193 n = cl.node(i)
194 194 changes = cl.read(n)
195 195 hn = hex(n)
196 196
197 197 l.insert(0, {"parity": parity,
198 198 "author": changes[1],
199 199 "parent": self.siblings(cl.parents(n), cl.rev,
200 200 cl.rev(n) - 1),
201 201 "child": self.siblings(cl.children(n), cl.rev,
202 202 cl.rev(n) + 1),
203 203 "changelogtag": self.showtag("changelogtag",n),
204 204 "manifest": hex(changes[0]),
205 205 "desc": changes[4],
206 206 "date": changes[2],
207 207 "files": self.listfilediffs(changes[3], n),
208 208 "rev": i,
209 209 "node": hn})
210 210 parity = 1 - parity
211 211
212 212 for e in l:
213 213 yield e
214 214
215 215 cl = self.repo.changelog
216 216 mf = cl.read(cl.tip())[0]
217 217 count = cl.count()
218 218 start = max(0, pos - self.maxchanges + 1)
219 219 end = min(count, start + self.maxchanges)
220 220 pos = end - 1
221 221
222 222 yield self.t('changelog',
223 223 changenav=changenav,
224 224 manifest=hex(mf),
225 225 rev=pos, changesets=count, entries=changelist,
226 226 archives=self.archivelist("tip"))
227 227
228 228 def search(self, query):
229 229
230 230 def changelist(**map):
231 231 cl = self.repo.changelog
232 232 count = 0
233 233 qw = query.lower().split()
234 234
235 235 def revgen():
236 236 for i in range(cl.count() - 1, 0, -100):
237 237 l = []
238 238 for j in range(max(0, i - 100), i):
239 239 n = cl.node(j)
240 240 changes = cl.read(n)
241 241 l.append((n, j, changes))
242 242 l.reverse()
243 243 for e in l:
244 244 yield e
245 245
246 246 for n, i, changes in revgen():
247 247 miss = 0
248 248 for q in qw:
249 249 if not (q in changes[1].lower() or
250 250 q in changes[4].lower() or
251 251 q in " ".join(changes[3][:20]).lower()):
252 252 miss = 1
253 253 break
254 254 if miss:
255 255 continue
256 256
257 257 count += 1
258 258 hn = hex(n)
259 259
260 260 yield self.t('searchentry',
261 261 parity=count & 1,
262 262 author=changes[1],
263 263 parent=self.siblings(cl.parents(n), cl.rev),
264 264 child=self.siblings(cl.children(n), cl.rev),
265 265 changelogtag=self.showtag("changelogtag",n),
266 266 manifest=hex(changes[0]),
267 267 desc=changes[4],
268 268 date=changes[2],
269 269 files=self.listfilediffs(changes[3], n),
270 270 rev=i,
271 271 node=hn)
272 272
273 273 if count >= self.maxchanges:
274 274 break
275 275
276 276 cl = self.repo.changelog
277 277 mf = cl.read(cl.tip())[0]
278 278
279 279 yield self.t('search',
280 280 query=query,
281 281 manifest=hex(mf),
282 282 entries=changelist)
283 283
284 284 def changeset(self, nodeid):
285 285 cl = self.repo.changelog
286 286 n = self.repo.lookup(nodeid)
287 287 nodeid = hex(n)
288 288 changes = cl.read(n)
289 289 p1 = cl.parents(n)[0]
290 290
291 291 files = []
292 292 mf = self.repo.manifest.read(changes[0])
293 293 for f in changes[3]:
294 294 files.append(self.t("filenodelink",
295 295 filenode=hex(mf.get(f, nullid)), file=f))
296 296
297 297 def diff(**map):
298 298 yield self.diff(p1, n, None)
299 299
300 300 yield self.t('changeset',
301 301 diff=diff,
302 302 rev=cl.rev(n),
303 303 node=nodeid,
304 304 parent=self.siblings(cl.parents(n), cl.rev),
305 305 child=self.siblings(cl.children(n), cl.rev),
306 306 changesettag=self.showtag("changesettag",n),
307 307 manifest=hex(changes[0]),
308 308 author=changes[1],
309 309 desc=changes[4],
310 310 date=changes[2],
311 311 files=files,
312 312 archives=self.archivelist(nodeid))
313 313
314 314 def filelog(self, f, filenode):
315 315 cl = self.repo.changelog
316 316 fl = self.repo.file(f)
317 317 filenode = hex(fl.lookup(filenode))
318 318 count = fl.count()
319 319
320 320 def entries(**map):
321 321 l = []
322 322 parity = (count - 1) & 1
323 323
324 324 for i in range(count):
325 325 n = fl.node(i)
326 326 lr = fl.linkrev(n)
327 327 cn = cl.node(lr)
328 328 cs = cl.read(cl.node(lr))
329 329
330 330 l.insert(0, {"parity": parity,
331 331 "filenode": hex(n),
332 332 "filerev": i,
333 333 "file": f,
334 334 "node": hex(cn),
335 335 "author": cs[1],
336 336 "date": cs[2],
337 337 "rename": self.renamelink(fl, n),
338 338 "parent": self.siblings(fl.parents(n),
339 339 fl.rev, file=f),
340 340 "child": self.siblings(fl.children(n),
341 341 fl.rev, file=f),
342 342 "desc": cs[4]})
343 343 parity = 1 - parity
344 344
345 345 for e in l:
346 346 yield e
347 347
348 348 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
349 349
350 350 def filerevision(self, f, node):
351 351 fl = self.repo.file(f)
352 352 n = fl.lookup(node)
353 353 node = hex(n)
354 354 text = fl.read(n)
355 355 changerev = fl.linkrev(n)
356 356 cl = self.repo.changelog
357 357 cn = cl.node(changerev)
358 358 cs = cl.read(cn)
359 359 mfn = cs[0]
360 360
361 361 mt = mimetypes.guess_type(f)[0]
362 362 rawtext = text
363 363 if util.binary(text):
364 364 mt = mt or 'application/octet-stream'
365 365 text = "(binary:%s)" % mt
366 366 mt = mt or 'text/plain'
367 367
368 368 def lines():
369 369 for l, t in enumerate(text.splitlines(1)):
370 370 yield {"line": t,
371 371 "linenumber": "% 6d" % (l + 1),
372 372 "parity": l & 1}
373 373
374 374 yield self.t("filerevision",
375 375 file=f,
376 376 filenode=node,
377 377 path=_up(f),
378 378 text=lines(),
379 379 raw=rawtext,
380 380 mimetype=mt,
381 381 rev=changerev,
382 382 node=hex(cn),
383 383 manifest=hex(mfn),
384 384 author=cs[1],
385 385 date=cs[2],
386 386 parent=self.siblings(fl.parents(n), fl.rev, file=f),
387 387 child=self.siblings(fl.children(n), fl.rev, file=f),
388 388 rename=self.renamelink(fl, n),
389 389 permissions=self.repo.manifest.readflags(mfn)[f])
390 390
391 391 def fileannotate(self, f, node):
392 392 bcache = {}
393 393 ncache = {}
394 394 fl = self.repo.file(f)
395 395 n = fl.lookup(node)
396 396 node = hex(n)
397 397 changerev = fl.linkrev(n)
398 398
399 399 cl = self.repo.changelog
400 400 cn = cl.node(changerev)
401 401 cs = cl.read(cn)
402 402 mfn = cs[0]
403 403
404 404 def annotate(**map):
405 405 parity = 1
406 406 last = None
407 407 for r, l in fl.annotate(n):
408 408 try:
409 409 cnode = ncache[r]
410 410 except KeyError:
411 411 cnode = ncache[r] = self.repo.changelog.node(r)
412 412
413 413 try:
414 414 name = bcache[r]
415 415 except KeyError:
416 416 cl = self.repo.changelog.read(cnode)
417 417 bcache[r] = name = self.repo.ui.shortuser(cl[1])
418 418
419 419 if last != cnode:
420 420 parity = 1 - parity
421 421 last = cnode
422 422
423 423 yield {"parity": parity,
424 424 "node": hex(cnode),
425 425 "rev": r,
426 426 "author": name,
427 427 "file": f,
428 428 "line": l}
429 429
430 430 yield self.t("fileannotate",
431 431 file=f,
432 432 filenode=node,
433 433 annotate=annotate,
434 434 path=_up(f),
435 435 rev=changerev,
436 436 node=hex(cn),
437 437 manifest=hex(mfn),
438 438 author=cs[1],
439 439 date=cs[2],
440 440 rename=self.renamelink(fl, n),
441 441 parent=self.siblings(fl.parents(n), fl.rev, file=f),
442 442 child=self.siblings(fl.children(n), fl.rev, file=f),
443 443 permissions=self.repo.manifest.readflags(mfn)[f])
444 444
445 445 def manifest(self, mnode, path):
446 446 man = self.repo.manifest
447 447 mn = man.lookup(mnode)
448 448 mnode = hex(mn)
449 449 mf = man.read(mn)
450 450 rev = man.rev(mn)
451 451 changerev = man.linkrev(mn)
452 452 node = self.repo.changelog.node(changerev)
453 453 mff = man.readflags(mn)
454 454
455 455 files = {}
456 456
457 457 p = path[1:]
458 458 if p and p[-1] != "/":
459 459 p += "/"
460 460 l = len(p)
461 461
462 462 for f,n in mf.items():
463 463 if f[:l] != p:
464 464 continue
465 465 remain = f[l:]
466 466 if "/" in remain:
467 467 short = remain[:remain.find("/") + 1] # bleah
468 468 files[short] = (f, None)
469 469 else:
470 470 short = os.path.basename(remain)
471 471 files[short] = (f, n)
472 472
473 473 def filelist(**map):
474 474 parity = 0
475 475 fl = files.keys()
476 476 fl.sort()
477 477 for f in fl:
478 478 full, fnode = files[f]
479 479 if not fnode:
480 480 continue
481 481
482 482 yield {"file": full,
483 483 "manifest": mnode,
484 484 "filenode": hex(fnode),
485 485 "parity": parity,
486 486 "basename": f,
487 487 "permissions": mff[full]}
488 488 parity = 1 - parity
489 489
490 490 def dirlist(**map):
491 491 parity = 0
492 492 fl = files.keys()
493 493 fl.sort()
494 494 for f in fl:
495 495 full, fnode = files[f]
496 496 if fnode:
497 497 continue
498 498
499 499 yield {"parity": parity,
500 500 "path": os.path.join(path, f),
501 501 "manifest": mnode,
502 502 "basename": f[:-1]}
503 503 parity = 1 - parity
504 504
505 505 yield self.t("manifest",
506 506 manifest=mnode,
507 507 rev=rev,
508 508 node=hex(node),
509 509 path=path,
510 510 up=_up(path),
511 511 fentries=filelist,
512 512 dentries=dirlist,
513 513 archives=self.archivelist(hex(node)))
514 514
515 515 def tags(self):
516 516 cl = self.repo.changelog
517 517 mf = cl.read(cl.tip())[0]
518 518
519 519 i = self.repo.tagslist()
520 520 i.reverse()
521 521
522 522 def entries(notip=False, **map):
523 523 parity = 0
524 524 for k,n in i:
525 525 if notip and k == "tip": continue
526 526 yield {"parity": parity,
527 527 "tag": k,
528 528 "tagmanifest": hex(cl.read(n)[0]),
529 529 "date": cl.read(n)[2],
530 530 "node": hex(n)}
531 531 parity = 1 - parity
532 532
533 533 yield self.t("tags",
534 534 manifest=hex(mf),
535 535 entries=lambda **x: entries(False, **x),
536 536 entriesnotip=lambda **x: entries(True, **x))
537 537
538 538 def summary(self):
539 539 cl = self.repo.changelog
540 540 mf = cl.read(cl.tip())[0]
541 541
542 542 i = self.repo.tagslist()
543 543 i.reverse()
544 544
545 545 def tagentries(**map):
546 546 parity = 0
547 547 count = 0
548 548 for k,n in i:
549 549 if k == "tip": # skip tip
550 550 continue;
551 551
552 552 count += 1
553 553 if count > 10: # limit to 10 tags
554 554 break;
555 555
556 556 c = cl.read(n)
557 557 m = c[0]
558 558 t = c[2]
559 559
560 560 yield self.t("tagentry",
561 561 parity = parity,
562 562 tag = k,
563 563 node = hex(n),
564 564 date = t,
565 565 tagmanifest = hex(m))
566 566 parity = 1 - parity
567 567
568 568 def changelist(**map):
569 569 parity = 0
570 570 cl = self.repo.changelog
571 571 l = [] # build a list in forward order for efficiency
572 572 for i in range(start, end):
573 573 n = cl.node(i)
574 574 changes = cl.read(n)
575 575 hn = hex(n)
576 576 t = changes[2]
577 577
578 578 l.insert(0, self.t(
579 579 'shortlogentry',
580 580 parity = parity,
581 581 author = changes[1],
582 582 manifest = hex(changes[0]),
583 583 desc = changes[4],
584 584 date = t,
585 585 rev = i,
586 586 node = hn))
587 587 parity = 1 - parity
588 588
589 589 yield l
590 590
591 591 cl = self.repo.changelog
592 592 mf = cl.read(cl.tip())[0]
593 593 count = cl.count()
594 594 start = max(0, count - self.maxchanges)
595 595 end = min(count, start + self.maxchanges)
596 596
597 597 yield self.t("summary",
598 598 desc = self.repo.ui.config("web", "description", "unknown"),
599 599 owner = (self.repo.ui.config("ui", "username") or # preferred
600 600 self.repo.ui.config("web", "contact") or # deprecated
601 601 self.repo.ui.config("web", "author", "unknown")), # also
602 602 lastchange = (0, 0), # FIXME
603 603 manifest = hex(mf),
604 604 tags = tagentries,
605 605 shortlog = changelist)
606 606
607 607 def filediff(self, file, changeset):
608 608 cl = self.repo.changelog
609 609 n = self.repo.lookup(changeset)
610 610 changeset = hex(n)
611 611 p1 = cl.parents(n)[0]
612 612 cs = cl.read(n)
613 613 mf = self.repo.manifest.read(cs[0])
614 614
615 615 def diff(**map):
616 616 yield self.diff(p1, n, [file])
617 617
618 618 yield self.t("filediff",
619 619 file=file,
620 620 filenode=hex(mf.get(file, nullid)),
621 621 node=changeset,
622 622 rev=self.repo.changelog.rev(n),
623 623 parent=self.siblings(cl.parents(n), cl.rev),
624 624 child=self.siblings(cl.children(n), cl.rev),
625 625 diff=diff)
626 626
627 627 archive_specs = {
628 628 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
629 629 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
630 630 'zip': ('application/zip', 'zip', '.zip', None),
631 631 }
632 632
633 633 def archive(self, req, cnode, type_):
634 634 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
635 635 name = "%s-%s" % (reponame, short(cnode))
636 636 mimetype, artype, extension, encoding = self.archive_specs[type_]
637 637 headers = [('Content-type', mimetype),
638 638 ('Content-disposition', 'attachment; filename=%s%s' %
639 639 (name, extension))]
640 640 if encoding:
641 641 headers.append(('Content-encoding', encoding))
642 642 req.header(headers)
643 643 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
644 644
645 645 # add tags to things
646 646 # tags -> list of changesets corresponding to tags
647 647 # find tag, changeset, file
648 648
649 649 def cleanpath(self, path):
650 650 p = util.normpath(path)
651 651 if p[:2] == "..":
652 652 raise Exception("suspicious path")
653 653 return p
654 654
655 655 def run(self, req=hgrequest()):
656 656 def header(**map):
657 657 yield self.t("header", **map)
658 658
659 659 def footer(**map):
660 660 yield self.t("footer",
661 661 motd=self.repo.ui.config("web", "motd", ""),
662 662 **map)
663 663
664 664 def expand_form(form):
665 665 shortcuts = {
666 666 'cl': [('cmd', ['changelog']), ('rev', None)],
667 667 'cs': [('cmd', ['changeset']), ('node', None)],
668 668 'f': [('cmd', ['file']), ('filenode', None)],
669 669 'fl': [('cmd', ['filelog']), ('filenode', None)],
670 670 'fd': [('cmd', ['filediff']), ('node', None)],
671 671 'fa': [('cmd', ['annotate']), ('filenode', None)],
672 672 'mf': [('cmd', ['manifest']), ('manifest', None)],
673 673 'ca': [('cmd', ['archive']), ('node', None)],
674 674 'tags': [('cmd', ['tags'])],
675 675 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
676 676 'static': [('cmd', ['static']), ('file', None)]
677 677 }
678 678
679 679 for k in shortcuts.iterkeys():
680 680 if form.has_key(k):
681 681 for name, value in shortcuts[k]:
682 682 if value is None:
683 683 value = form[k]
684 684 form[name] = value
685 685 del form[k]
686 686
687 687 self.refresh()
688 688
689 689 expand_form(req.form)
690 690
691 691 m = os.path.join(self.templatepath, "map")
692 692 style = self.repo.ui.config("web", "style", "")
693 693 if req.form.has_key('style'):
694 694 style = req.form['style'][0]
695 695 if style:
696 696 b = os.path.basename("map-" + style)
697 697 p = os.path.join(self.templatepath, b)
698 698 if os.path.isfile(p):
699 699 m = p
700 700
701 701 port = req.env["SERVER_PORT"]
702 702 port = port != "80" and (":" + port) or ""
703 703 uri = req.env["REQUEST_URI"]
704 704 if "?" in uri:
705 705 uri = uri.split("?")[0]
706 706 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
707 707 if not self.reponame:
708 708 self.reponame = (self.repo.ui.config("web", "name")
709 709 or uri.strip('/') or self.repo.root)
710 710
711 711 self.t = templater.templater(m, templater.common_filters,
712 712 defaults={"url": url,
713 713 "repo": self.reponame,
714 714 "header": header,
715 715 "footer": footer,
716 716 })
717 717
718 718 if not req.form.has_key('cmd'):
719 719 req.form['cmd'] = [self.t.cache['default'],]
720 720
721 721 cmd = req.form['cmd'][0]
722 722
723 723 method = getattr(self, 'do_' + cmd, None)
724 724 if method:
725 725 method(req)
726 726 else:
727 727 req.write(self.t("error"))
728 728 req.done()
729 729
730 730 def do_changelog(self, req):
731 731 hi = self.repo.changelog.count() - 1
732 732 if req.form.has_key('rev'):
733 733 hi = req.form['rev'][0]
734 734 try:
735 735 hi = self.repo.changelog.rev(self.repo.lookup(hi))
736 736 except hg.RepoError:
737 737 req.write(self.search(hi)) # XXX redirect to 404 page?
738 738 return
739 739
740 740 req.write(self.changelog(hi))
741 741
742 742 def do_changeset(self, req):
743 743 req.write(self.changeset(req.form['node'][0]))
744 744
745 745 def do_manifest(self, req):
746 746 req.write(self.manifest(req.form['manifest'][0],
747 747 self.cleanpath(req.form['path'][0])))
748 748
749 749 def do_tags(self, req):
750 750 req.write(self.tags())
751 751
752 752 def do_summary(self, req):
753 753 req.write(self.summary())
754 754
755 755 def do_filediff(self, req):
756 756 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
757 757 req.form['node'][0]))
758 758
759 759 def do_file(self, req):
760 760 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
761 761 req.form['filenode'][0]))
762 762
763 763 def do_annotate(self, req):
764 764 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
765 765 req.form['filenode'][0]))
766 766
767 767 def do_filelog(self, req):
768 768 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
769 769 req.form['filenode'][0]))
770 770
771 771 def do_heads(self, req):
772 772 resp = " ".join(map(hex, self.repo.heads())) + "\n"
773 773 req.httphdr("application/mercurial-0.1", length=len(resp))
774 774 req.write(resp)
775 775
776 776 def do_branches(self, req):
777 777 nodes = []
778 778 if req.form.has_key('nodes'):
779 779 nodes = map(bin, req.form['nodes'][0].split(" "))
780 780 resp = cStringIO.StringIO()
781 781 for b in self.repo.branches(nodes):
782 782 resp.write(" ".join(map(hex, b)) + "\n")
783 783 resp = resp.getvalue()
784 784 req.httphdr("application/mercurial-0.1", length=len(resp))
785 785 req.write(resp)
786 786
787 787 def do_between(self, req):
788 788 nodes = []
789 789 if req.form.has_key('pairs'):
790 790 pairs = [map(bin, p.split("-"))
791 791 for p in req.form['pairs'][0].split(" ")]
792 792 resp = cStringIO.StringIO()
793 793 for b in self.repo.between(pairs):
794 794 resp.write(" ".join(map(hex, b)) + "\n")
795 795 resp = resp.getvalue()
796 796 req.httphdr("application/mercurial-0.1", length=len(resp))
797 797 req.write(resp)
798 798
799 799 def do_changegroup(self, req):
800 800 req.httphdr("application/mercurial-0.1")
801 801 nodes = []
802 802 if not self.allowpull:
803 803 return
804 804
805 805 if req.form.has_key('roots'):
806 806 nodes = map(bin, req.form['roots'][0].split(" "))
807 807
808 808 z = zlib.compressobj()
809 809 f = self.repo.changegroup(nodes, 'serve')
810 810 while 1:
811 811 chunk = f.read(4096)
812 812 if not chunk:
813 813 break
814 814 req.write(z.compress(chunk))
815 815
816 816 req.write(z.flush())
817 817
818 818 def do_archive(self, req):
819 819 changeset = self.repo.lookup(req.form['node'][0])
820 820 type_ = req.form['type'][0]
821 821 allowed = self.repo.ui.config("web", "allow_archive", "").split()
822 822 if (type_ in self.archives and (type_ in allowed or
823 823 self.repo.ui.configbool("web", "allow" + type_, False))):
824 824 self.archive(req, changeset, type_)
825 825 return
826 826
827 827 req.write(self.t("error"))
828 828
829 829 def do_static(self, req):
830 830 fname = req.form['file'][0]
831 831 static = self.repo.ui.config("web", "static",
832 832 os.path.join(self.templatepath,
833 833 "static"))
834 834 req.write(staticfile(static, fname)
835 835 or self.t("error", error="%r not found" % fname))
836
837 def do_capabilities(self, req):
838 resp = ''
839 req.httphdr("application/mercurial-0.1", length=len(resp))
840 req.write(resp)
841
@@ -1,242 +1,254 b''
1 1 # httprepo.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from remoterepo import *
10 10 from i18n import gettext as _
11 11 from demandload import *
12 12 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
13 13 demandload(globals(), "keepalive")
14 14
15 15 class passwordmgr(urllib2.HTTPPasswordMgr):
16 16 def __init__(self, ui):
17 17 urllib2.HTTPPasswordMgr.__init__(self)
18 18 self.ui = ui
19 19
20 20 def find_user_password(self, realm, authuri):
21 21 authinfo = urllib2.HTTPPasswordMgr.find_user_password(
22 22 self, realm, authuri)
23 23 if authinfo != (None, None):
24 24 return authinfo
25 25
26 26 if not ui.interactive:
27 27 raise util.Abort(_('http authorization required'))
28 28
29 29 self.ui.write(_("http authorization required\n"))
30 30 self.ui.status(_("realm: %s\n") % realm)
31 31 user = self.ui.prompt(_("user:"), default=None)
32 32 passwd = self.ui.getpass()
33 33
34 34 self.add_password(realm, authuri, user, passwd)
35 35 return (user, passwd)
36 36
37 37 def netlocsplit(netloc):
38 38 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
39 39
40 40 a = netloc.find('@')
41 41 if a == -1:
42 42 user, passwd = None, None
43 43 else:
44 44 userpass, netloc = netloc[:a], netloc[a+1:]
45 45 c = userpass.find(':')
46 46 if c == -1:
47 47 user, passwd = urllib.unquote(userpass), None
48 48 else:
49 49 user = urllib.unquote(userpass[:c])
50 50 passwd = urllib.unquote(userpass[c+1:])
51 51 c = netloc.find(':')
52 52 if c == -1:
53 53 host, port = netloc, None
54 54 else:
55 55 host, port = netloc[:c], netloc[c+1:]
56 56 return host, port, user, passwd
57 57
58 58 def netlocunsplit(host, port, user=None, passwd=None):
59 59 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
60 60 if port:
61 61 hostport = host + ':' + port
62 62 else:
63 63 hostport = host
64 64 if user:
65 65 if passwd:
66 66 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
67 67 else:
68 68 userpass = urllib.quote(user)
69 69 return userpass + '@' + hostport
70 70 return hostport
71 71
72 72 class httprepository(remoterepository):
73 73 def __init__(self, ui, path):
74 self.capabilities = ()
74 self.caps = None
75 75 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
76 76 if query or frag:
77 77 raise util.Abort(_('unsupported URL component: "%s"') %
78 78 (query or frag))
79 79 if not urlpath: urlpath = '/'
80 80 host, port, user, passwd = netlocsplit(netloc)
81 81
82 82 # urllib cannot handle URLs with embedded user or passwd
83 83 self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
84 84 urlpath, '', ''))
85 85 self.ui = ui
86 86
87 87 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
88 88 proxyauthinfo = None
89 89 handler = keepalive.HTTPHandler()
90 90
91 91 if proxyurl:
92 92 # proxy can be proper url or host[:port]
93 93 if not (proxyurl.startswith('http:') or
94 94 proxyurl.startswith('https:')):
95 95 proxyurl = 'http://' + proxyurl + '/'
96 96 snpqf = urlparse.urlsplit(proxyurl)
97 97 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
98 98 hpup = netlocsplit(proxynetloc)
99 99
100 100 proxyhost, proxyport, proxyuser, proxypasswd = hpup
101 101 if not proxyuser:
102 102 proxyuser = ui.config("http_proxy", "user")
103 103 proxypasswd = ui.config("http_proxy", "passwd")
104 104
105 105 # see if we should use a proxy for this url
106 106 no_list = [ "localhost", "127.0.0.1" ]
107 107 no_list.extend([p.strip().lower() for
108 108 p in ui.config("http_proxy", "no", '').split(',')
109 109 if p.strip()])
110 110 no_list.extend([p.strip().lower() for
111 111 p in os.getenv("no_proxy", '').split(',')
112 112 if p.strip()])
113 113 # "http_proxy.always" config is for running tests on localhost
114 114 if (not ui.configbool("http_proxy", "always") and
115 115 host.lower() in no_list):
116 116 ui.debug(_('disabling proxy for %s\n') % host)
117 117 else:
118 118 proxyurl = urlparse.urlunsplit((
119 119 proxyscheme, netlocunsplit(proxyhost, proxyport,
120 120 proxyuser, proxypasswd or ''),
121 121 proxypath, proxyquery, proxyfrag))
122 122 handler = urllib2.ProxyHandler({scheme: proxyurl})
123 123 ui.debug(_('proxying through %s\n') % proxyurl)
124 124
125 125 # urllib2 takes proxy values from the environment and those
126 126 # will take precedence if found, so drop them
127 127 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
128 128 try:
129 129 if os.environ.has_key(env):
130 130 del os.environ[env]
131 131 except OSError:
132 132 pass
133 133
134 134 passmgr = passwordmgr(ui)
135 135 if user:
136 136 ui.debug(_('will use user %s for http auth\n') % user)
137 137 passmgr.add_password(None, host, user, passwd or '')
138 138
139 139 opener = urllib2.build_opener(
140 140 handler,
141 141 urllib2.HTTPBasicAuthHandler(passmgr),
142 142 urllib2.HTTPDigestAuthHandler(passmgr))
143 143
144 144 # 1.0 here is the _protocol_ version
145 145 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
146 146 urllib2.install_opener(opener)
147 147
148 # look up capabilities only when needed
149
150 def get_caps(self):
151 if self.caps is None:
152 try:
153 self.caps = self.do_read('capabilities').split()
154 except hg.RepoError:
155 self.caps = ()
156 return self.caps
157
158 capabilities = property(get_caps)
159
148 160 def dev(self):
149 161 return -1
150 162
151 163 def lock(self):
152 164 raise util.Abort(_('operation not supported over http'))
153 165
154 166 def do_cmd(self, cmd, **args):
155 167 self.ui.debug(_("sending %s command\n") % cmd)
156 168 q = {"cmd": cmd}
157 169 q.update(args)
158 170 qs = urllib.urlencode(q)
159 171 cu = "%s?%s" % (self.url, qs)
160 172 try:
161 173 resp = urllib2.urlopen(cu)
162 174 except httplib.HTTPException, inst:
163 175 self.ui.debug(_('http error while sending %s command\n') % cmd)
164 176 self.ui.print_exc()
165 177 raise IOError(None, inst)
166 178 try:
167 179 proto = resp.getheader('content-type')
168 180 except AttributeError:
169 181 proto = resp.headers['content-type']
170 182
171 183 # accept old "text/plain" and "application/hg-changegroup" for now
172 184 if not proto.startswith('application/mercurial') and \
173 185 not proto.startswith('text/plain') and \
174 186 not proto.startswith('application/hg-changegroup'):
175 187 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
176 188 self.url)
177 189
178 190 if proto.startswith('application/mercurial'):
179 191 version = proto[22:]
180 192 if float(version) > 0.1:
181 193 raise hg.RepoError(_("'%s' uses newer protocol %s") %
182 194 (self.url, version))
183 195
184 196 return resp
185 197
186 198 def do_read(self, cmd, **args):
187 199 fp = self.do_cmd(cmd, **args)
188 200 try:
189 201 return fp.read()
190 202 finally:
191 203 # if using keepalive, allow connection to be reused
192 204 fp.close()
193 205
194 206 def heads(self):
195 207 d = self.do_read("heads")
196 208 try:
197 209 return map(bin, d[:-1].split(" "))
198 210 except:
199 211 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
200 212 raise
201 213
202 214 def branches(self, nodes):
203 215 n = " ".join(map(hex, nodes))
204 216 d = self.do_read("branches", nodes=n)
205 217 try:
206 218 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
207 219 return br
208 220 except:
209 221 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
210 222 raise
211 223
212 224 def between(self, pairs):
213 225 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
214 226 d = self.do_read("between", pairs=n)
215 227 try:
216 228 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
217 229 return p
218 230 except:
219 231 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
220 232 raise
221 233
222 234 def changegroup(self, nodes, kind):
223 235 n = " ".join(map(hex, nodes))
224 236 f = self.do_cmd("changegroup", roots=n)
225 237 bytes = 0
226 238
227 239 def zgenerator(f):
228 240 zd = zlib.decompressobj()
229 241 try:
230 242 for chnk in f:
231 243 yield zd.decompress(chnk)
232 244 except httplib.HTTPException, inst:
233 245 raise IOError(None, _('connection ended unexpectedly'))
234 246 yield zd.flush()
235 247
236 248 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
237 249
238 250 def unbundle(self, cg, heads, source):
239 251 raise util.Abort(_('operation not supported over http'))
240 252
241 253 class httpsrepository(httprepository):
242 254 pass
General Comments 0
You need to be logged in to leave comments. Login now