##// END OF EJS Templates
Adding changegroupsubset and lookup to web protocol so pull -r and...
Eric Hopper -
r3444:3505fcd5 default
parent child Browse files
Show More
@@ -1,1055 +1,1082 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 import os.path
11 11 import mimetypes
12 12 from mercurial.demandload import demandload
13 13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 14 demandload(globals(), 'urllib')
15 15 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
16 16 demandload(globals(), "mercurial: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 def do_lookup(self, req):
887 resp = hex(self.repo.lookup(req.form['key'][0])) + "\n"
888 req.httphdr("application/mercurial-0.1", length=len(resp))
889 req.write(resp)
890
886 891 def do_heads(self, req):
887 892 resp = " ".join(map(hex, self.repo.heads())) + "\n"
888 893 req.httphdr("application/mercurial-0.1", length=len(resp))
889 894 req.write(resp)
890 895
891 896 def do_branches(self, req):
892 897 nodes = []
893 898 if req.form.has_key('nodes'):
894 899 nodes = map(bin, req.form['nodes'][0].split(" "))
895 900 resp = cStringIO.StringIO()
896 901 for b in self.repo.branches(nodes):
897 902 resp.write(" ".join(map(hex, b)) + "\n")
898 903 resp = resp.getvalue()
899 904 req.httphdr("application/mercurial-0.1", length=len(resp))
900 905 req.write(resp)
901 906
902 907 def do_between(self, req):
903 908 if req.form.has_key('pairs'):
904 909 pairs = [map(bin, p.split("-"))
905 910 for p in req.form['pairs'][0].split(" ")]
906 911 resp = cStringIO.StringIO()
907 912 for b in self.repo.between(pairs):
908 913 resp.write(" ".join(map(hex, b)) + "\n")
909 914 resp = resp.getvalue()
910 915 req.httphdr("application/mercurial-0.1", length=len(resp))
911 916 req.write(resp)
912 917
913 918 def do_changegroup(self, req):
914 919 req.httphdr("application/mercurial-0.1")
915 920 nodes = []
916 921 if not self.allowpull:
917 922 return
918 923
919 924 if req.form.has_key('roots'):
920 925 nodes = map(bin, req.form['roots'][0].split(" "))
921 926
922 927 z = zlib.compressobj()
923 928 f = self.repo.changegroup(nodes, 'serve')
924 929 while 1:
925 930 chunk = f.read(4096)
926 931 if not chunk:
927 932 break
928 933 req.write(z.compress(chunk))
929 934
930 935 req.write(z.flush())
931 936
937 def do_changegroupsubset(self, req):
938 req.httphdr("application/mercurial-0.1")
939 bases = []
940 heads = []
941 if not self.allowpull:
942 return
943
944 if req.form.has_key('bases'):
945 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
946 if req.form.has_key('heads'):
947 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
948
949 z = zlib.compressobj()
950 f = self.repo.changegroupsubset(bases, heads, 'serve')
951 while 1:
952 chunk = f.read(4096)
953 if not chunk:
954 break
955 req.write(z.compress(chunk))
956
957 req.write(z.flush())
958
932 959 def do_archive(self, req):
933 960 changeset = self.repo.lookup(req.form['node'][0])
934 961 type_ = req.form['type'][0]
935 962 allowed = self.repo.ui.configlist("web", "allow_archive")
936 963 if (type_ in self.archives and (type_ in allowed or
937 964 self.repo.ui.configbool("web", "allow" + type_, False))):
938 965 self.archive(req, changeset, type_)
939 966 return
940 967
941 968 req.write(self.t("error"))
942 969
943 970 def do_static(self, req):
944 971 fname = req.form['file'][0]
945 972 static = self.repo.ui.config("web", "static",
946 973 os.path.join(self.templatepath,
947 974 "static"))
948 975 req.write(staticfile(static, fname, req)
949 976 or self.t("error", error="%r not found" % fname))
950 977
951 978 def do_capabilities(self, req):
952 caps = ['unbundle']
979 caps = ['unbundle', 'lookup', 'changegroupsubset']
953 980 if self.repo.ui.configbool('server', 'uncompressed'):
954 981 caps.append('stream=%d' % self.repo.revlogversion)
955 982 resp = ' '.join(caps)
956 983 req.httphdr("application/mercurial-0.1", length=len(resp))
957 984 req.write(resp)
958 985
959 986 def check_perm(self, req, op, default):
960 987 '''check permission for operation based on user auth.
961 988 return true if op allowed, else false.
962 989 default is policy to use if no config given.'''
963 990
964 991 user = req.env.get('REMOTE_USER')
965 992
966 993 deny = self.repo.ui.configlist('web', 'deny_' + op)
967 994 if deny and (not user or deny == ['*'] or user in deny):
968 995 return False
969 996
970 997 allow = self.repo.ui.configlist('web', 'allow_' + op)
971 998 return (allow and (allow == ['*'] or user in allow)) or default
972 999
973 1000 def do_unbundle(self, req):
974 1001 def bail(response, headers={}):
975 1002 length = int(req.env['CONTENT_LENGTH'])
976 1003 for s in util.filechunkiter(req, limit=length):
977 1004 # drain incoming bundle, else client will not see
978 1005 # response when run outside cgi script
979 1006 pass
980 1007 req.httphdr("application/mercurial-0.1", headers=headers)
981 1008 req.write('0\n')
982 1009 req.write(response)
983 1010
984 1011 # require ssl by default, auth info cannot be sniffed and
985 1012 # replayed
986 1013 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
987 1014 if ssl_req:
988 1015 if not req.env.get('HTTPS'):
989 1016 bail(_('ssl required\n'))
990 1017 return
991 1018 proto = 'https'
992 1019 else:
993 1020 proto = 'http'
994 1021
995 1022 # do not allow push unless explicitly allowed
996 1023 if not self.check_perm(req, 'push', False):
997 1024 bail(_('push not authorized\n'),
998 1025 headers={'status': '401 Unauthorized'})
999 1026 return
1000 1027
1001 1028 req.httphdr("application/mercurial-0.1")
1002 1029
1003 1030 their_heads = req.form['heads'][0].split(' ')
1004 1031
1005 1032 def check_heads():
1006 1033 heads = map(hex, self.repo.heads())
1007 1034 return their_heads == [hex('force')] or their_heads == heads
1008 1035
1009 1036 # fail early if possible
1010 1037 if not check_heads():
1011 1038 bail(_('unsynced changes\n'))
1012 1039 return
1013 1040
1014 1041 # do not lock repo until all changegroup data is
1015 1042 # streamed. save to temporary file.
1016 1043
1017 1044 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1018 1045 fp = os.fdopen(fd, 'wb+')
1019 1046 try:
1020 1047 length = int(req.env['CONTENT_LENGTH'])
1021 1048 for s in util.filechunkiter(req, limit=length):
1022 1049 fp.write(s)
1023 1050
1024 1051 lock = self.repo.lock()
1025 1052 try:
1026 1053 if not check_heads():
1027 1054 req.write('0\n')
1028 1055 req.write(_('unsynced changes\n'))
1029 1056 return
1030 1057
1031 1058 fp.seek(0)
1032 1059
1033 1060 # send addchangegroup output to client
1034 1061
1035 1062 old_stdout = sys.stdout
1036 1063 sys.stdout = cStringIO.StringIO()
1037 1064
1038 1065 try:
1039 1066 url = 'remote:%s:%s' % (proto,
1040 1067 req.env.get('REMOTE_HOST', ''))
1041 1068 ret = self.repo.addchangegroup(fp, 'serve', url)
1042 1069 finally:
1043 1070 val = sys.stdout.getvalue()
1044 1071 sys.stdout = old_stdout
1045 1072 req.write('%d\n' % ret)
1046 1073 req.write(val)
1047 1074 finally:
1048 1075 lock.release()
1049 1076 finally:
1050 1077 fp.close()
1051 1078 os.unlink(tempname)
1052 1079
1053 1080 def do_stream_out(self, req):
1054 1081 req.httphdr("application/mercurial-0.1")
1055 1082 streamclone.stream_out(self.repo, req)
@@ -1,355 +1,379 b''
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 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
271
264 272 def heads(self):
265 273 d = self.do_read("heads")
266 274 try:
267 275 return map(bin, d[:-1].split(" "))
268 276 except:
269 277 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
270 278 raise
271 279
272 280 def branches(self, nodes):
273 281 n = " ".join(map(hex, nodes))
274 282 d = self.do_read("branches", nodes=n)
275 283 try:
276 284 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
277 285 return br
278 286 except:
279 287 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
280 288 raise
281 289
282 290 def between(self, pairs):
283 291 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
284 292 d = self.do_read("between", pairs=n)
285 293 try:
286 294 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
287 295 return p
288 296 except:
289 297 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
290 298 raise
291 299
292 300 def changegroup(self, nodes, kind):
293 301 n = " ".join(map(hex, nodes))
294 302 f = self.do_cmd("changegroup", roots=n)
295 303
296 304 def zgenerator(f):
297 305 zd = zlib.decompressobj()
298 306 try:
299 307 for chnk in f:
300 308 yield zd.decompress(chnk)
309 except httplib.HTTPException, inst:
310 raise IOError(None, _('connection ended unexpectedly'))
311 yield zd.flush()
312
313 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
314
315 def changegroupsubset(self, bases, heads, source):
316 baselst = " ".join([hex(n) for n in bases])
317 headlst = " ".join([hex(n) for n in heads])
318 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
319
320 def zgenerator(f):
321 zd = zlib.decompressobj()
322 try:
323 for chnk in f:
324 yield zd.decompress(chnk)
301 325 except httplib.HTTPException:
302 326 raise IOError(None, _('connection ended unexpectedly'))
303 327 yield zd.flush()
304 328
305 329 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
306 330
307 331 def unbundle(self, cg, heads, source):
308 332 # have to stream bundle to a temp file because we do not have
309 333 # http 1.1 chunked transfer.
310 334
311 335 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
312 336 fp = os.fdopen(fd, 'wb+')
313 337 try:
314 338 for chunk in util.filechunkiter(cg):
315 339 fp.write(chunk)
316 340 length = fp.tell()
317 341 try:
318 342 rfp = self.do_cmd(
319 343 'unbundle', data=fp,
320 344 headers={'content-length': length,
321 345 'content-type': 'application/octet-stream'},
322 346 heads=' '.join(map(hex, heads)))
323 347 try:
324 348 ret = int(rfp.readline())
325 349 self.ui.write(rfp.read())
326 350 return ret
327 351 finally:
328 352 rfp.close()
329 353 except socket.error, err:
330 354 if err[0] in (errno.ECONNRESET, errno.EPIPE):
331 355 raise util.Abort(_('push failed: %s') % err[1])
332 356 raise util.Abort(err[1])
333 357 finally:
334 358 fp.close()
335 359 os.unlink(tempname)
336 360
337 361 def stream_out(self):
338 362 return self.do_cmd('stream_out')
339 363
340 364 class httpsrepository(httprepository):
341 365 def __init__(self, ui, path):
342 366 if not has_https:
343 367 raise util.Abort(_('Python support for SSL and HTTPS '
344 368 'is not installed'))
345 369 httprepository.__init__(self, ui, path)
346 370
347 371 def instance(ui, path, create):
348 372 if create:
349 373 raise util.Abort(_('cannot create new http repository'))
350 374 if path.startswith('hg:'):
351 375 ui.warn(_("hg:// syntax is deprecated, please use http:// instead\n"))
352 376 path = 'http:' + path[3:]
353 377 if path.startswith('https:'):
354 378 return httpsrepository(ui, path)
355 379 return httprepository(ui, path)
General Comments 0
You need to be logged in to leave comments. Login now