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