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