##// END OF EJS Templates
statichttprepo: stop shadowing the `bytes` builtin...
Matt Harbison -
r52793:77a9c7d8 default
parent child Browse files
Show More
@@ -1,281 +1,281
1 # statichttprepo.py - simple http repository class for mercurial
1 # statichttprepo.py - simple http repository class for mercurial
2 #
2 #
3 # This provides read-only repo access to repositories exported via static http
3 # This provides read-only repo access to repositories exported via static http
4 #
4 #
5 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import annotations
10 from __future__ import annotations
11
11
12 import errno
12 import errno
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import sha1nodeconstants
15 from .node import sha1nodeconstants
16 from . import (
16 from . import (
17 branchmap,
17 branchmap,
18 changelog,
18 changelog,
19 error,
19 error,
20 localrepo,
20 localrepo,
21 manifest,
21 manifest,
22 namespaces,
22 namespaces,
23 pathutil,
23 pathutil,
24 pycompat,
24 pycompat,
25 requirements as requirementsmod,
25 requirements as requirementsmod,
26 url,
26 url,
27 util,
27 util,
28 vfs as vfsmod,
28 vfs as vfsmod,
29 )
29 )
30 from .utils import (
30 from .utils import (
31 urlutil,
31 urlutil,
32 )
32 )
33
33
34 urlerr = util.urlerr
34 urlerr = util.urlerr
35 urlreq = util.urlreq
35 urlreq = util.urlreq
36
36
37
37
38 class httprangereader:
38 class httprangereader:
39 def __init__(self, url, opener):
39 def __init__(self, url, opener):
40 # we assume opener has HTTPRangeHandler
40 # we assume opener has HTTPRangeHandler
41 self.url = url
41 self.url = url
42 self.pos = 0
42 self.pos = 0
43 self.opener = opener
43 self.opener = opener
44 self.name = url
44 self.name = url
45
45
46 def __enter__(self):
46 def __enter__(self):
47 return self
47 return self
48
48
49 def __exit__(self, exc_type, exc_value, traceback):
49 def __exit__(self, exc_type, exc_value, traceback):
50 self.close()
50 self.close()
51
51
52 def seek(self, pos):
52 def seek(self, pos):
53 self.pos = pos
53 self.pos = pos
54
54
55 def read(self, bytes: int = -1):
55 def read(self, n: int = -1):
56 req = urlreq.request(pycompat.strurl(self.url))
56 req = urlreq.request(pycompat.strurl(self.url))
57 end = ''
57 end = ''
58
58
59 if bytes == 0:
59 if n == 0:
60 return b''
60 return b''
61 elif bytes > 0:
61 elif n > 0:
62 end = "%d" % (self.pos + bytes - 1)
62 end = "%d" % (self.pos + n - 1)
63 if self.pos or end:
63 if self.pos or end:
64 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
64 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
65
65
66 try:
66 try:
67 f = self.opener.open(req)
67 f = self.opener.open(req)
68 data = f.read()
68 data = f.read()
69 code = f.code
69 code = f.code
70 except urlerr.httperror as inst:
70 except urlerr.httperror as inst:
71 num = inst.code == 404 and errno.ENOENT or None
71 num = inst.code == 404 and errno.ENOENT or None
72 # Explicitly convert the exception to str as Py3 will try
72 # Explicitly convert the exception to str as Py3 will try
73 # convert it to local encoding and with as the HTTPResponse
73 # convert it to local encoding and with as the HTTPResponse
74 # instance doesn't support encode.
74 # instance doesn't support encode.
75 raise IOError(num, str(inst))
75 raise IOError(num, str(inst))
76 except urlerr.urlerror as inst:
76 except urlerr.urlerror as inst:
77 raise IOError(None, inst.reason)
77 raise IOError(None, inst.reason)
78
78
79 if code == 200:
79 if code == 200:
80 # HTTPRangeHandler does nothing if remote does not support
80 # HTTPRangeHandler does nothing if remote does not support
81 # Range headers and returns the full entity. Let's slice it.
81 # Range headers and returns the full entity. Let's slice it.
82 if bytes > 0 and (self.pos + bytes) < len(data):
82 if n > 0 and (self.pos + n) < len(data):
83 data = data[self.pos : self.pos + bytes]
83 data = data[self.pos : self.pos + n]
84 elif self.pos < len(data):
84 elif self.pos < len(data):
85 data = data[self.pos :]
85 data = data[self.pos :]
86 else:
86 else:
87 data = b''
87 data = b''
88 elif 0 < bytes < len(data):
88 elif 0 < n < len(data):
89 data = data[:bytes]
89 data = data[:n]
90 self.pos += len(data)
90 self.pos += len(data)
91 return data
91 return data
92
92
93 def readlines(self):
93 def readlines(self):
94 return self.read().splitlines(True)
94 return self.read().splitlines(True)
95
95
96 def __iter__(self):
96 def __iter__(self):
97 return iter(self.readlines())
97 return iter(self.readlines())
98
98
99 def close(self):
99 def close(self):
100 pass
100 pass
101
101
102
102
103 # _RangeError and _HTTPRangeHandler were originally in byterange.py,
103 # _RangeError and _HTTPRangeHandler were originally in byterange.py,
104 # which was itself extracted from urlgrabber. See the last version of
104 # which was itself extracted from urlgrabber. See the last version of
105 # byterange.py from history if you need more information.
105 # byterange.py from history if you need more information.
106 class _RangeError(IOError):
106 class _RangeError(IOError):
107 """Error raised when an unsatisfiable range is requested."""
107 """Error raised when an unsatisfiable range is requested."""
108
108
109
109
110 class _HTTPRangeHandler(urlreq.basehandler):
110 class _HTTPRangeHandler(urlreq.basehandler):
111 """Handler that enables HTTP Range headers.
111 """Handler that enables HTTP Range headers.
112
112
113 This was extremely simple. The Range header is a HTTP feature to
113 This was extremely simple. The Range header is a HTTP feature to
114 begin with so all this class does is tell urllib2 that the
114 begin with so all this class does is tell urllib2 that the
115 "206 Partial Content" response from the HTTP server is what we
115 "206 Partial Content" response from the HTTP server is what we
116 expected.
116 expected.
117 """
117 """
118
118
119 def http_error_206(self, req, fp, code, msg, hdrs):
119 def http_error_206(self, req, fp, code, msg, hdrs):
120 # 206 Partial Content Response
120 # 206 Partial Content Response
121 r = urlreq.addinfourl(fp, hdrs, req.get_full_url())
121 r = urlreq.addinfourl(fp, hdrs, req.get_full_url())
122 r.code = code
122 r.code = code
123 r.msg = msg
123 r.msg = msg
124 return r
124 return r
125
125
126 def http_error_416(self, req, fp, code, msg, hdrs):
126 def http_error_416(self, req, fp, code, msg, hdrs):
127 # HTTP's Range Not Satisfiable error
127 # HTTP's Range Not Satisfiable error
128 raise _RangeError('Requested Range Not Satisfiable')
128 raise _RangeError('Requested Range Not Satisfiable')
129
129
130
130
131 def build_opener(ui, authinfo):
131 def build_opener(ui, authinfo):
132 # urllib cannot handle URLs with embedded user or passwd
132 # urllib cannot handle URLs with embedded user or passwd
133 urlopener = url.opener(ui, authinfo)
133 urlopener = url.opener(ui, authinfo)
134 urlopener.add_handler(_HTTPRangeHandler())
134 urlopener.add_handler(_HTTPRangeHandler())
135
135
136 class statichttpvfs(vfsmod.abstractvfs):
136 class statichttpvfs(vfsmod.abstractvfs):
137 def __init__(self, base):
137 def __init__(self, base):
138 self.base = base
138 self.base = base
139 self.options = {}
139 self.options = {}
140
140
141 def __call__(self, path, mode=b'r', *args, **kw):
141 def __call__(self, path, mode=b'r', *args, **kw):
142 if mode not in (b'r', b'rb'):
142 if mode not in (b'r', b'rb'):
143 raise IOError('Permission denied')
143 raise IOError('Permission denied')
144 f = b"/".join((self.base, urlreq.quote(path)))
144 f = b"/".join((self.base, urlreq.quote(path)))
145 return httprangereader(f, urlopener)
145 return httprangereader(f, urlopener)
146
146
147 def _auditpath(self, path: bytes, mode: bytes) -> None:
147 def _auditpath(self, path: bytes, mode: bytes) -> None:
148 raise NotImplementedError
148 raise NotImplementedError
149
149
150 def join(self, path, *insidef):
150 def join(self, path, *insidef):
151 if path:
151 if path:
152 return pathutil.join(self.base, path, *insidef)
152 return pathutil.join(self.base, path, *insidef)
153 else:
153 else:
154 return self.base
154 return self.base
155
155
156 return statichttpvfs
156 return statichttpvfs
157
157
158
158
159 class statichttppeer(localrepo.localpeer):
159 class statichttppeer(localrepo.localpeer):
160 def local(self):
160 def local(self):
161 return None
161 return None
162
162
163 def canpush(self):
163 def canpush(self):
164 return False
164 return False
165
165
166
166
167 class statichttprepository(
167 class statichttprepository(
168 localrepo.localrepository, localrepo.revlogfilestorage
168 localrepo.localrepository, localrepo.revlogfilestorage
169 ):
169 ):
170 supported = localrepo.localrepository._basesupported
170 supported = localrepo.localrepository._basesupported
171
171
172 manifestlog: manifest.ManifestLog
172 manifestlog: manifest.ManifestLog
173
173
174 def __init__(self, ui, path):
174 def __init__(self, ui, path):
175 self._url = path
175 self._url = path
176 self.ui = ui
176 self.ui = ui
177
177
178 self.root = path
178 self.root = path
179 u = urlutil.url(path.rstrip(b'/') + b"/.hg")
179 u = urlutil.url(path.rstrip(b'/') + b"/.hg")
180 self.path, authinfo = u.authinfo()
180 self.path, authinfo = u.authinfo()
181
181
182 vfsclass = build_opener(ui, authinfo)
182 vfsclass = build_opener(ui, authinfo)
183 self.vfs = vfsclass(self.path)
183 self.vfs = vfsclass(self.path)
184 self.cachevfs = vfsclass(self.vfs.join(b'cache'))
184 self.cachevfs = vfsclass(self.vfs.join(b'cache'))
185 self._phasedefaults = []
185 self._phasedefaults = []
186
186
187 self.names = namespaces.namespaces()
187 self.names = namespaces.namespaces()
188 self.filtername = None
188 self.filtername = None
189 self._extrafilterid = None
189 self._extrafilterid = None
190 self._wanted_sidedata = set()
190 self._wanted_sidedata = set()
191 self.features = set()
191 self.features = set()
192
192
193 try:
193 try:
194 requirements = set(self.vfs.read(b'requires').splitlines())
194 requirements = set(self.vfs.read(b'requires').splitlines())
195 except FileNotFoundError:
195 except FileNotFoundError:
196 requirements = set()
196 requirements = set()
197
197
198 # check if it is a non-empty old-style repository
198 # check if it is a non-empty old-style repository
199 try:
199 try:
200 with self.vfs(b"00changelog.i") as fp:
200 with self.vfs(b"00changelog.i") as fp:
201 fp.read(1)
201 fp.read(1)
202 except FileNotFoundError:
202 except FileNotFoundError:
203 # we do not care about empty old-style repositories here
203 # we do not care about empty old-style repositories here
204 msg = _(b"'%s' does not appear to be an hg repository") % path
204 msg = _(b"'%s' does not appear to be an hg repository") % path
205 raise error.RepoError(msg)
205 raise error.RepoError(msg)
206 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
206 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
207 storevfs = vfsclass(self.vfs.join(b'store'))
207 storevfs = vfsclass(self.vfs.join(b'store'))
208 requirements |= set(storevfs.read(b'requires').splitlines())
208 requirements |= set(storevfs.read(b'requires').splitlines())
209
209
210 supportedrequirements = localrepo.gathersupportedrequirements(ui)
210 supportedrequirements = localrepo.gathersupportedrequirements(ui)
211 localrepo.ensurerequirementsrecognized(
211 localrepo.ensurerequirementsrecognized(
212 requirements, supportedrequirements
212 requirements, supportedrequirements
213 )
213 )
214 localrepo.ensurerequirementscompatible(ui, requirements)
214 localrepo.ensurerequirementscompatible(ui, requirements)
215 self.nodeconstants = sha1nodeconstants
215 self.nodeconstants = sha1nodeconstants
216 self.nullid = self.nodeconstants.nullid
216 self.nullid = self.nodeconstants.nullid
217
217
218 # setup store
218 # setup store
219 self.store = localrepo.makestore(requirements, self.path, vfsclass)
219 self.store = localrepo.makestore(requirements, self.path, vfsclass)
220 self.spath = self.store.path
220 self.spath = self.store.path
221 self.svfs = self.store.opener
221 self.svfs = self.store.opener
222 self.sjoin = self.store.join
222 self.sjoin = self.store.join
223 self._filecache = {}
223 self._filecache = {}
224 self.requirements = requirements
224 self.requirements = requirements
225
225
226 rootmanifest = manifest.manifestrevlog(self.nodeconstants, self.svfs)
226 rootmanifest = manifest.manifestrevlog(self.nodeconstants, self.svfs)
227 self.manifestlog = manifest.manifestlog(
227 self.manifestlog = manifest.manifestlog(
228 self.svfs, self, rootmanifest, self.narrowmatch()
228 self.svfs, self, rootmanifest, self.narrowmatch()
229 )
229 )
230 self.changelog = changelog.changelog(self.svfs)
230 self.changelog = changelog.changelog(self.svfs)
231 self._tags = None
231 self._tags = None
232 self.nodetagscache = None
232 self.nodetagscache = None
233 self._branchcaches = branchmap.BranchMapCache()
233 self._branchcaches = branchmap.BranchMapCache()
234 self._revbranchcache = None
234 self._revbranchcache = None
235 self.encodepats = None
235 self.encodepats = None
236 self.decodepats = None
236 self.decodepats = None
237 self._transref = None
237 self._transref = None
238 self._dirstate = None
238 self._dirstate = None
239
239
240 def _restrictcapabilities(self, caps):
240 def _restrictcapabilities(self, caps):
241 caps = super(statichttprepository, self)._restrictcapabilities(caps)
241 caps = super(statichttprepository, self)._restrictcapabilities(caps)
242 return caps.difference([b"pushkey"])
242 return caps.difference([b"pushkey"])
243
243
244 def url(self):
244 def url(self):
245 return self._url
245 return self._url
246
246
247 def local(self):
247 def local(self):
248 return False
248 return False
249
249
250 def peer(self, path=None, remotehidden=False):
250 def peer(self, path=None, remotehidden=False):
251 return statichttppeer(self, path=path, remotehidden=remotehidden)
251 return statichttppeer(self, path=path, remotehidden=remotehidden)
252
252
253 def wlock(self, wait=True):
253 def wlock(self, wait=True):
254 raise error.LockUnavailable(
254 raise error.LockUnavailable(
255 0,
255 0,
256 pycompat.sysstr(_(b'lock not available')),
256 pycompat.sysstr(_(b'lock not available')),
257 b'lock',
257 b'lock',
258 _(b'cannot lock static-http repository'),
258 _(b'cannot lock static-http repository'),
259 )
259 )
260
260
261 def lock(self, wait=True):
261 def lock(self, wait=True):
262 raise error.LockUnavailable(
262 raise error.LockUnavailable(
263 0,
263 0,
264 pycompat.sysstr(_(b'lock not available')),
264 pycompat.sysstr(_(b'lock not available')),
265 b'lock',
265 b'lock',
266 _(b'cannot lock static-http repository'),
266 _(b'cannot lock static-http repository'),
267 )
267 )
268
268
269 def _writecaches(self):
269 def _writecaches(self):
270 pass # statichttprepository are read only
270 pass # statichttprepository are read only
271
271
272
272
273 def make_peer(
273 def make_peer(
274 ui, path, create, intents=None, createopts=None, remotehidden=False
274 ui, path, create, intents=None, createopts=None, remotehidden=False
275 ):
275 ):
276 if create:
276 if create:
277 raise error.Abort(_(b'cannot create new static-http repository'))
277 raise error.Abort(_(b'cannot create new static-http repository'))
278 url = path.loc[7:]
278 url = path.loc[7:]
279 return statichttprepository(ui, url).peer(
279 return statichttprepository(ui, url).peer(
280 path=path, remotehidden=remotehidden
280 path=path, remotehidden=remotehidden
281 )
281 )
General Comments 0
You need to be logged in to leave comments. Login now