statichttprepo.py
265 lines
| 7.7 KiB
| text/x-python
|
PythonLexer
/ mercurial / statichttprepo.py
|
r1101 | # statichttprepo.py - simple http repository class for mercurial | ||
# | ||||
# This provides read-only repo access to repositories exported via static http | ||||
# | ||||
|
r47575 | # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com> | ||
|
r1101 | # | ||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r1101 | |||
|
r25978 | from __future__ import absolute_import | ||
import errno | ||||
from .i18n import _ | ||||
|
r47538 | from .node import sha1nodeconstants | ||
|
r25978 | from . import ( | ||
|
r41764 | branchmap, | ||
|
r25978 | changelog, | ||
error, | ||||
localrepo, | ||||
manifest, | ||||
namespaces, | ||||
|
r34944 | pathutil, | ||
|
r41452 | pycompat, | ||
|
r25978 | url, | ||
util, | ||||
|
r31241 | vfs as vfsmod, | ||
|
r25978 | ) | ||
r47669 | from .utils import ( | |||
urlutil, | ||||
) | ||||
|
r1325 | |||
|
r28883 | urlerr = util.urlerr | ||
urlreq = util.urlreq | ||||
|
r43346 | |||
|
r7274 | class httprangereader(object): | ||
def __init__(self, url, opener): | ||||
# we assume opener has HTTPRangeHandler | ||||
self.url = url | ||||
self.pos = 0 | ||||
self.opener = opener | ||||
|
r11066 | self.name = url | ||
|
r27705 | |||
def __enter__(self): | ||||
return self | ||||
def __exit__(self, exc_type, exc_value, traceback): | ||||
self.close() | ||||
|
r7274 | def seek(self, pos): | ||
self.pos = pos | ||||
|
r43346 | |||
|
r7274 | def read(self, bytes=None): | ||
|
r41452 | req = urlreq.request(pycompat.strurl(self.url)) | ||
|
r43347 | end = b'' | ||
|
r7274 | if bytes: | ||
end = self.pos + bytes - 1 | ||||
|
r16882 | if self.pos or end: | ||
|
r43906 | req.add_header('Range', 'bytes=%d-%s' % (self.pos, end)) | ||
|
r7274 | |||
|
r1325 | try: | ||
|
r7274 | f = self.opener.open(req) | ||
data = f.read() | ||||
|
r25196 | code = f.code | ||
|
r28883 | except urlerr.httperror as inst: | ||
|
r6028 | num = inst.code == 404 and errno.ENOENT or None | ||
|
r46818 | # Explicitly convert the exception to str as Py3 will try | ||
# convert it to local encoding and with as the HTTPResponse | ||||
# instance doesn't support encode. | ||||
raise IOError(num, str(inst)) | ||||
|
r28883 | except urlerr.urlerror as inst: | ||
|
r41451 | raise IOError(None, inst.reason) | ||
|
r1101 | |||
|
r8612 | if code == 200: | ||
# HTTPRangeHandler does nothing if remote does not support | ||||
# Range headers and returns the full entity. Let's slice it. | ||||
if bytes: | ||||
|
r43346 | data = data[self.pos : self.pos + bytes] | ||
|
r8612 | else: | ||
|
r43346 | data = data[self.pos :] | ||
|
r8612 | elif bytes: | ||
|
r7274 | data = data[:bytes] | ||
|
r8612 | self.pos += len(data) | ||
|
r7274 | return data | ||
|
r43346 | |||
|
r20055 | def readlines(self): | ||
return self.read().splitlines(True) | ||||
|
r43346 | |||
|
r11066 | def __iter__(self): | ||
|
r20055 | return iter(self.readlines()) | ||
|
r43346 | |||
|
r11066 | def close(self): | ||
pass | ||||
|
r7274 | |||
|
r43346 | |||
|
r36443 | # _RangeError and _HTTPRangeHandler were originally in byterange.py, | ||
# which was itself extracted from urlgrabber. See the last version of | ||||
# byterange.py from history if you need more information. | ||||
class _RangeError(IOError): | ||||
"""Error raised when an unsatisfiable range is requested.""" | ||||
|
r43346 | |||
|
r36443 | class _HTTPRangeHandler(urlreq.basehandler): | ||
"""Handler that enables HTTP Range headers. | ||||
This was extremely simple. The Range header is a HTTP feature to | ||||
begin with so all this class does is tell urllib2 that the | ||||
"206 Partial Content" response from the HTTP server is what we | ||||
expected. | ||||
""" | ||||
def http_error_206(self, req, fp, code, msg, hdrs): | ||||
# 206 Partial Content Response | ||||
r = urlreq.addinfourl(fp, hdrs, req.get_full_url()) | ||||
r.code = code | ||||
r.msg = msg | ||||
return r | ||||
def http_error_416(self, req, fp, code, msg, hdrs): | ||||
# HTTP's Range Not Satisfiable error | ||||
|
r43347 | raise _RangeError(b'Requested Range Not Satisfiable') | ||
|
r36443 | |||
|
r43346 | |||
|
r7274 | def build_opener(ui, authinfo): | ||
# urllib cannot handle URLs with embedded user or passwd | ||||
urlopener = url.opener(ui, authinfo) | ||||
|
r36443 | urlopener.add_handler(_HTTPRangeHandler()) | ||
|
r7274 | |||
|
r31241 | class statichttpvfs(vfsmod.abstractvfs): | ||
|
r14091 | def __init__(self, base): | ||
self.base = base | ||||
r43295 | self.options = {} | |||
|
r14091 | |||
|
r43347 | def __call__(self, path, mode=b'r', *args, **kw): | ||
if mode not in (b'r', b'rb'): | ||||
raise IOError(b'Permission denied') | ||||
f = b"/".join((self.base, urlreq.quote(path))) | ||||
|
r7274 | return httprangereader(f, urlopener) | ||
|
r17725 | def join(self, path): | ||
if path: | ||||
|
r34944 | return pathutil.join(self.base, path) | ||
|
r17725 | else: | ||
return self.base | ||||
|
r17649 | return statichttpvfs | ||
|
r1101 | |||
|
r43346 | |||
|
r17192 | class statichttppeer(localrepo.localpeer): | ||
def local(self): | ||||
return None | ||||
|
r43346 | |||
|
r17193 | def canpush(self): | ||
return False | ||||
|
r17192 | |||
|
r43346 | |||
class statichttprepository( | ||||
localrepo.localrepository, localrepo.revlogfilestorage | ||||
): | ||||
|
r19778 | supported = localrepo.localrepository._basesupported | ||
|
r1101 | def __init__(self, ui, path): | ||
|
r2673 | self._url = path | ||
|
r1101 | self.ui = ui | ||
|
r3853 | |||
|
r11066 | self.root = path | ||
r47669 | u = urlutil.url(path.rstrip(b'/') + b"/.hg") | |||
|
r13819 | self.path, authinfo = u.authinfo() | ||
|
r7274 | |||
|
r31147 | vfsclass = build_opener(ui, authinfo) | ||
self.vfs = vfsclass(self.path) | ||||
|
r43347 | self.cachevfs = vfsclass(self.vfs.join(b'cache')) | ||
|
r15922 | self._phasedefaults = [] | ||
|
r6028 | |||
|
r23561 | self.names = namespaces.namespaces() | ||
|
r32730 | self.filtername = None | ||
r42417 | self._extrafilterid = None | |||
|
r47447 | self._wanted_sidedata = set() | ||
|
r23558 | |||
|
r3851 | try: | ||
|
r39730 | requirements = set(self.vfs.read(b'requires').splitlines()) | ||
|
r25660 | except IOError as inst: | ||
|
r7178 | if inst.errno != errno.ENOENT: | ||
raise | ||||
|
r14482 | requirements = set() | ||
|
r7178 | # check if it is a non-empty old-style repository | ||
try: | ||||
|
r43347 | fp = self.vfs(b"00changelog.i") | ||
|
r13400 | fp.read(1) | ||
fp.close() | ||||
|
r25660 | except IOError as inst: | ||
|
r7178 | if inst.errno != errno.ENOENT: | ||
raise | ||||
# we do not care about empty old-style repositories here | ||||
|
r43347 | msg = _(b"'%s' does not appear to be an hg repository") % path | ||
|
r7637 | raise error.RepoError(msg) | ||
|
r3851 | |||
|
r39730 | supportedrequirements = localrepo.gathersupportedrequirements(ui) | ||
|
r43346 | localrepo.ensurerequirementsrecognized( | ||
requirements, supportedrequirements | ||||
) | ||||
|
r39731 | localrepo.ensurerequirementscompatible(ui, requirements) | ||
|
r47538 | self.nodeconstants = sha1nodeconstants | ||
self.nullid = self.nodeconstants.nullid | ||||
|
r39730 | |||
|
r3851 | # setup store | ||
|
r39734 | self.store = localrepo.makestore(requirements, self.path, vfsclass) | ||
|
r6897 | self.spath = self.store.path | ||
|
r23878 | self.svfs = self.store.opener | ||
|
r6897 | self.sjoin = self.store.join | ||
|
r16115 | self._filecache = {} | ||
|
r17192 | self.requirements = requirements | ||
|
r3851 | |||
|
r47538 | rootmanifest = manifest.manifestrevlog(self.nodeconstants, self.svfs) | ||
|
r43346 | self.manifestlog = manifest.manifestlog( | ||
self.svfs, self, rootmanifest, self.narrowmatch() | ||||
) | ||||
|
r23878 | self.changelog = changelog.changelog(self.svfs) | ||
|
r9146 | self._tags = None | ||
|
r1101 | self.nodetagscache = None | ||
|
r41764 | self._branchcaches = branchmap.BranchMapCache() | ||
|
r24373 | self._revbranchcache = None | ||
|
r1598 | self.encodepats = None | ||
self.decodepats = None | ||||
|
r24377 | self._transref = None | ||
|
r17192 | |||
def _restrictcapabilities(self, caps): | ||||
|
r20962 | caps = super(statichttprepository, self)._restrictcapabilities(caps) | ||
|
r43347 | return caps.difference([b"pushkey"]) | ||
|
r1101 | |||
|
r2673 | def url(self): | ||
|
r7211 | return self._url | ||
|
r2673 | |||
|
r1101 | def local(self): | ||
return False | ||||
|
r2740 | |||
|
r17192 | def peer(self): | ||
return statichttppeer(self) | ||||
|
r33605 | def wlock(self, wait=True): | ||
|
r43346 | raise error.LockUnavailable( | ||
0, | ||||
|
r43347 | _(b'lock not available'), | ||
b'lock', | ||||
_(b'cannot lock static-http repository'), | ||||
|
r43346 | ) | ||
|
r33605 | |||
|
r7005 | def lock(self, wait=True): | ||
|
r46004 | raise error.LockUnavailable( | ||
0, | ||||
_(b'lock not available'), | ||||
b'lock', | ||||
_(b'cannot lock static-http repository'), | ||||
) | ||||
|
r7005 | |||
|
r29738 | def _writecaches(self): | ||
|
r43346 | pass # statichttprepository are read only | ||
|
r29738 | |||
|
r39585 | def instance(ui, path, create, intents=None, createopts=None): | ||
|
r2740 | if create: | ||
|
r43347 | raise error.Abort(_(b'cannot create new static-http repository')) | ||
|
r4853 | return statichttprepository(ui, path[7:]) | ||