##// END OF EJS Templates
lfs: handle URLErrors to add additional information...
Matt Harbison -
r40697:380f5131 default
parent child Browse files
Show More
@@ -1,601 +1,640
1 # blobstore.py - local and remote (speaking Git-LFS protocol) blob storages
1 # blobstore.py - local and remote (speaking Git-LFS protocol) blob storages
2 #
2 #
3 # Copyright 2017 Facebook, Inc.
3 # Copyright 2017 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import json
12 import json
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16
16
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18
18
19 from mercurial import (
19 from mercurial import (
20 encoding,
20 error,
21 error,
21 pathutil,
22 pathutil,
22 pycompat,
23 pycompat,
23 url as urlmod,
24 url as urlmod,
24 util,
25 util,
25 vfs as vfsmod,
26 vfs as vfsmod,
26 worker,
27 worker,
27 )
28 )
28
29
30 from mercurial.utils import (
31 stringutil,
32 )
33
29 from ..largefiles import lfutil
34 from ..largefiles import lfutil
30
35
31 # 64 bytes for SHA256
36 # 64 bytes for SHA256
32 _lfsre = re.compile(br'\A[a-f0-9]{64}\Z')
37 _lfsre = re.compile(br'\A[a-f0-9]{64}\Z')
33
38
34 class lfsvfs(vfsmod.vfs):
39 class lfsvfs(vfsmod.vfs):
35 def join(self, path):
40 def join(self, path):
36 """split the path at first two characters, like: XX/XXXXX..."""
41 """split the path at first two characters, like: XX/XXXXX..."""
37 if not _lfsre.match(path):
42 if not _lfsre.match(path):
38 raise error.ProgrammingError('unexpected lfs path: %s' % path)
43 raise error.ProgrammingError('unexpected lfs path: %s' % path)
39 return super(lfsvfs, self).join(path[0:2], path[2:])
44 return super(lfsvfs, self).join(path[0:2], path[2:])
40
45
41 def walk(self, path=None, onerror=None):
46 def walk(self, path=None, onerror=None):
42 """Yield (dirpath, [], oids) tuple for blobs under path
47 """Yield (dirpath, [], oids) tuple for blobs under path
43
48
44 Oids only exist in the root of this vfs, so dirpath is always ''.
49 Oids only exist in the root of this vfs, so dirpath is always ''.
45 """
50 """
46 root = os.path.normpath(self.base)
51 root = os.path.normpath(self.base)
47 # when dirpath == root, dirpath[prefixlen:] becomes empty
52 # when dirpath == root, dirpath[prefixlen:] becomes empty
48 # because len(dirpath) < prefixlen.
53 # because len(dirpath) < prefixlen.
49 prefixlen = len(pathutil.normasprefix(root))
54 prefixlen = len(pathutil.normasprefix(root))
50 oids = []
55 oids = []
51
56
52 for dirpath, dirs, files in os.walk(self.reljoin(self.base, path or ''),
57 for dirpath, dirs, files in os.walk(self.reljoin(self.base, path or ''),
53 onerror=onerror):
58 onerror=onerror):
54 dirpath = dirpath[prefixlen:]
59 dirpath = dirpath[prefixlen:]
55
60
56 # Silently skip unexpected files and directories
61 # Silently skip unexpected files and directories
57 if len(dirpath) == 2:
62 if len(dirpath) == 2:
58 oids.extend([dirpath + f for f in files
63 oids.extend([dirpath + f for f in files
59 if _lfsre.match(dirpath + f)])
64 if _lfsre.match(dirpath + f)])
60
65
61 yield ('', [], oids)
66 yield ('', [], oids)
62
67
63 class nullvfs(lfsvfs):
68 class nullvfs(lfsvfs):
64 def __init__(self):
69 def __init__(self):
65 pass
70 pass
66
71
67 def exists(self, oid):
72 def exists(self, oid):
68 return False
73 return False
69
74
70 def read(self, oid):
75 def read(self, oid):
71 # store.read() calls into here if the blob doesn't exist in its
76 # store.read() calls into here if the blob doesn't exist in its
72 # self.vfs. Raise the same error as a normal vfs when asked to read a
77 # self.vfs. Raise the same error as a normal vfs when asked to read a
73 # file that doesn't exist. The only difference is the full file path
78 # file that doesn't exist. The only difference is the full file path
74 # isn't available in the error.
79 # isn't available in the error.
75 raise IOError(errno.ENOENT, '%s: No such file or directory' % oid)
80 raise IOError(errno.ENOENT, '%s: No such file or directory' % oid)
76
81
77 def walk(self, path=None, onerror=None):
82 def walk(self, path=None, onerror=None):
78 return ('', [], [])
83 return ('', [], [])
79
84
80 def write(self, oid, data):
85 def write(self, oid, data):
81 pass
86 pass
82
87
83 class filewithprogress(object):
88 class filewithprogress(object):
84 """a file-like object that supports __len__ and read.
89 """a file-like object that supports __len__ and read.
85
90
86 Useful to provide progress information for how many bytes are read.
91 Useful to provide progress information for how many bytes are read.
87 """
92 """
88
93
89 def __init__(self, fp, callback):
94 def __init__(self, fp, callback):
90 self._fp = fp
95 self._fp = fp
91 self._callback = callback # func(readsize)
96 self._callback = callback # func(readsize)
92 fp.seek(0, os.SEEK_END)
97 fp.seek(0, os.SEEK_END)
93 self._len = fp.tell()
98 self._len = fp.tell()
94 fp.seek(0)
99 fp.seek(0)
95
100
96 def __len__(self):
101 def __len__(self):
97 return self._len
102 return self._len
98
103
99 def read(self, size):
104 def read(self, size):
100 if self._fp is None:
105 if self._fp is None:
101 return b''
106 return b''
102 data = self._fp.read(size)
107 data = self._fp.read(size)
103 if data:
108 if data:
104 if self._callback:
109 if self._callback:
105 self._callback(len(data))
110 self._callback(len(data))
106 else:
111 else:
107 self._fp.close()
112 self._fp.close()
108 self._fp = None
113 self._fp = None
109 return data
114 return data
110
115
111 class local(object):
116 class local(object):
112 """Local blobstore for large file contents.
117 """Local blobstore for large file contents.
113
118
114 This blobstore is used both as a cache and as a staging area for large blobs
119 This blobstore is used both as a cache and as a staging area for large blobs
115 to be uploaded to the remote blobstore.
120 to be uploaded to the remote blobstore.
116 """
121 """
117
122
118 def __init__(self, repo):
123 def __init__(self, repo):
119 fullpath = repo.svfs.join('lfs/objects')
124 fullpath = repo.svfs.join('lfs/objects')
120 self.vfs = lfsvfs(fullpath)
125 self.vfs = lfsvfs(fullpath)
121
126
122 if repo.ui.configbool('experimental', 'lfs.disableusercache'):
127 if repo.ui.configbool('experimental', 'lfs.disableusercache'):
123 self.cachevfs = nullvfs()
128 self.cachevfs = nullvfs()
124 else:
129 else:
125 usercache = lfutil._usercachedir(repo.ui, 'lfs')
130 usercache = lfutil._usercachedir(repo.ui, 'lfs')
126 self.cachevfs = lfsvfs(usercache)
131 self.cachevfs = lfsvfs(usercache)
127 self.ui = repo.ui
132 self.ui = repo.ui
128
133
129 def open(self, oid):
134 def open(self, oid):
130 """Open a read-only file descriptor to the named blob, in either the
135 """Open a read-only file descriptor to the named blob, in either the
131 usercache or the local store."""
136 usercache or the local store."""
132 # The usercache is the most likely place to hold the file. Commit will
137 # The usercache is the most likely place to hold the file. Commit will
133 # write to both it and the local store, as will anything that downloads
138 # write to both it and the local store, as will anything that downloads
134 # the blobs. However, things like clone without an update won't
139 # the blobs. However, things like clone without an update won't
135 # populate the local store. For an init + push of a local clone,
140 # populate the local store. For an init + push of a local clone,
136 # the usercache is the only place it _could_ be. If not present, the
141 # the usercache is the only place it _could_ be. If not present, the
137 # missing file msg here will indicate the local repo, not the usercache.
142 # missing file msg here will indicate the local repo, not the usercache.
138 if self.cachevfs.exists(oid):
143 if self.cachevfs.exists(oid):
139 return self.cachevfs(oid, 'rb')
144 return self.cachevfs(oid, 'rb')
140
145
141 return self.vfs(oid, 'rb')
146 return self.vfs(oid, 'rb')
142
147
143 def download(self, oid, src):
148 def download(self, oid, src):
144 """Read the blob from the remote source in chunks, verify the content,
149 """Read the blob from the remote source in chunks, verify the content,
145 and write to this local blobstore."""
150 and write to this local blobstore."""
146 sha256 = hashlib.sha256()
151 sha256 = hashlib.sha256()
147
152
148 with self.vfs(oid, 'wb', atomictemp=True) as fp:
153 with self.vfs(oid, 'wb', atomictemp=True) as fp:
149 for chunk in util.filechunkiter(src, size=1048576):
154 for chunk in util.filechunkiter(src, size=1048576):
150 fp.write(chunk)
155 fp.write(chunk)
151 sha256.update(chunk)
156 sha256.update(chunk)
152
157
153 realoid = sha256.hexdigest()
158 realoid = sha256.hexdigest()
154 if realoid != oid:
159 if realoid != oid:
155 raise LfsCorruptionError(_('corrupt remote lfs object: %s')
160 raise LfsCorruptionError(_('corrupt remote lfs object: %s')
156 % oid)
161 % oid)
157
162
158 self._linktousercache(oid)
163 self._linktousercache(oid)
159
164
160 def write(self, oid, data):
165 def write(self, oid, data):
161 """Write blob to local blobstore.
166 """Write blob to local blobstore.
162
167
163 This should only be called from the filelog during a commit or similar.
168 This should only be called from the filelog during a commit or similar.
164 As such, there is no need to verify the data. Imports from a remote
169 As such, there is no need to verify the data. Imports from a remote
165 store must use ``download()`` instead."""
170 store must use ``download()`` instead."""
166 with self.vfs(oid, 'wb', atomictemp=True) as fp:
171 with self.vfs(oid, 'wb', atomictemp=True) as fp:
167 fp.write(data)
172 fp.write(data)
168
173
169 self._linktousercache(oid)
174 self._linktousercache(oid)
170
175
171 def linkfromusercache(self, oid):
176 def linkfromusercache(self, oid):
172 """Link blobs found in the user cache into this store.
177 """Link blobs found in the user cache into this store.
173
178
174 The server module needs to do this when it lets the client know not to
179 The server module needs to do this when it lets the client know not to
175 upload the blob, to ensure it is always available in this store.
180 upload the blob, to ensure it is always available in this store.
176 Normally this is done implicitly when the client reads or writes the
181 Normally this is done implicitly when the client reads or writes the
177 blob, but that doesn't happen when the server tells the client that it
182 blob, but that doesn't happen when the server tells the client that it
178 already has the blob.
183 already has the blob.
179 """
184 """
180 if (not isinstance(self.cachevfs, nullvfs)
185 if (not isinstance(self.cachevfs, nullvfs)
181 and not self.vfs.exists(oid)):
186 and not self.vfs.exists(oid)):
182 self.ui.note(_('lfs: found %s in the usercache\n') % oid)
187 self.ui.note(_('lfs: found %s in the usercache\n') % oid)
183 lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid))
188 lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid))
184
189
185 def _linktousercache(self, oid):
190 def _linktousercache(self, oid):
186 # XXX: should we verify the content of the cache, and hardlink back to
191 # XXX: should we verify the content of the cache, and hardlink back to
187 # the local store on success, but truncate, write and link on failure?
192 # the local store on success, but truncate, write and link on failure?
188 if (not self.cachevfs.exists(oid)
193 if (not self.cachevfs.exists(oid)
189 and not isinstance(self.cachevfs, nullvfs)):
194 and not isinstance(self.cachevfs, nullvfs)):
190 self.ui.note(_('lfs: adding %s to the usercache\n') % oid)
195 self.ui.note(_('lfs: adding %s to the usercache\n') % oid)
191 lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid))
196 lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid))
192
197
193 def read(self, oid, verify=True):
198 def read(self, oid, verify=True):
194 """Read blob from local blobstore."""
199 """Read blob from local blobstore."""
195 if not self.vfs.exists(oid):
200 if not self.vfs.exists(oid):
196 blob = self._read(self.cachevfs, oid, verify)
201 blob = self._read(self.cachevfs, oid, verify)
197
202
198 # Even if revlog will verify the content, it needs to be verified
203 # Even if revlog will verify the content, it needs to be verified
199 # now before making the hardlink to avoid propagating corrupt blobs.
204 # now before making the hardlink to avoid propagating corrupt blobs.
200 # Don't abort if corruption is detected, because `hg verify` will
205 # Don't abort if corruption is detected, because `hg verify` will
201 # give more useful info about the corruption- simply don't add the
206 # give more useful info about the corruption- simply don't add the
202 # hardlink.
207 # hardlink.
203 if verify or hashlib.sha256(blob).hexdigest() == oid:
208 if verify or hashlib.sha256(blob).hexdigest() == oid:
204 self.ui.note(_('lfs: found %s in the usercache\n') % oid)
209 self.ui.note(_('lfs: found %s in the usercache\n') % oid)
205 lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid))
210 lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid))
206 else:
211 else:
207 self.ui.note(_('lfs: found %s in the local lfs store\n') % oid)
212 self.ui.note(_('lfs: found %s in the local lfs store\n') % oid)
208 blob = self._read(self.vfs, oid, verify)
213 blob = self._read(self.vfs, oid, verify)
209 return blob
214 return blob
210
215
211 def _read(self, vfs, oid, verify):
216 def _read(self, vfs, oid, verify):
212 """Read blob (after verifying) from the given store"""
217 """Read blob (after verifying) from the given store"""
213 blob = vfs.read(oid)
218 blob = vfs.read(oid)
214 if verify:
219 if verify:
215 _verify(oid, blob)
220 _verify(oid, blob)
216 return blob
221 return blob
217
222
218 def verify(self, oid):
223 def verify(self, oid):
219 """Indicate whether or not the hash of the underlying file matches its
224 """Indicate whether or not the hash of the underlying file matches its
220 name."""
225 name."""
221 sha256 = hashlib.sha256()
226 sha256 = hashlib.sha256()
222
227
223 with self.open(oid) as fp:
228 with self.open(oid) as fp:
224 for chunk in util.filechunkiter(fp, size=1048576):
229 for chunk in util.filechunkiter(fp, size=1048576):
225 sha256.update(chunk)
230 sha256.update(chunk)
226
231
227 return oid == sha256.hexdigest()
232 return oid == sha256.hexdigest()
228
233
229 def has(self, oid):
234 def has(self, oid):
230 """Returns True if the local blobstore contains the requested blob,
235 """Returns True if the local blobstore contains the requested blob,
231 False otherwise."""
236 False otherwise."""
232 return self.cachevfs.exists(oid) or self.vfs.exists(oid)
237 return self.cachevfs.exists(oid) or self.vfs.exists(oid)
233
238
239 def _urlerrorreason(urlerror):
240 '''Create a friendly message for the given URLError to be used in an
241 LfsRemoteError message.
242 '''
243 inst = urlerror
244
245 if isinstance(urlerror.reason, Exception):
246 inst = urlerror.reason
247
248 if util.safehasattr(inst, 'reason'):
249 try: # usually it is in the form (errno, strerror)
250 reason = inst.reason.args[1]
251 except (AttributeError, IndexError):
252 # it might be anything, for example a string
253 reason = inst.reason
254 if isinstance(reason, pycompat.unicode):
255 # SSLError of Python 2.7.9 contains a unicode
256 reason = encoding.unitolocal(reason)
257 return reason
258 elif getattr(inst, "strerror", None):
259 return encoding.strtolocal(inst.strerror)
260 else:
261 return stringutil.forcebytestr(urlerror)
262
234 class _gitlfsremote(object):
263 class _gitlfsremote(object):
235
264
236 def __init__(self, repo, url):
265 def __init__(self, repo, url):
237 ui = repo.ui
266 ui = repo.ui
238 self.ui = ui
267 self.ui = ui
239 baseurl, authinfo = url.authinfo()
268 baseurl, authinfo = url.authinfo()
240 self.baseurl = baseurl.rstrip('/')
269 self.baseurl = baseurl.rstrip('/')
241 useragent = repo.ui.config('experimental', 'lfs.user-agent')
270 useragent = repo.ui.config('experimental', 'lfs.user-agent')
242 if not useragent:
271 if not useragent:
243 useragent = 'git-lfs/2.3.4 (Mercurial %s)' % util.version()
272 useragent = 'git-lfs/2.3.4 (Mercurial %s)' % util.version()
244 self.urlopener = urlmod.opener(ui, authinfo, useragent)
273 self.urlopener = urlmod.opener(ui, authinfo, useragent)
245 self.retry = ui.configint('lfs', 'retry')
274 self.retry = ui.configint('lfs', 'retry')
246
275
247 def writebatch(self, pointers, fromstore):
276 def writebatch(self, pointers, fromstore):
248 """Batch upload from local to remote blobstore."""
277 """Batch upload from local to remote blobstore."""
249 self._batch(_deduplicate(pointers), fromstore, 'upload')
278 self._batch(_deduplicate(pointers), fromstore, 'upload')
250
279
251 def readbatch(self, pointers, tostore):
280 def readbatch(self, pointers, tostore):
252 """Batch download from remote to local blostore."""
281 """Batch download from remote to local blostore."""
253 self._batch(_deduplicate(pointers), tostore, 'download')
282 self._batch(_deduplicate(pointers), tostore, 'download')
254
283
255 def _batchrequest(self, pointers, action):
284 def _batchrequest(self, pointers, action):
256 """Get metadata about objects pointed by pointers for given action
285 """Get metadata about objects pointed by pointers for given action
257
286
258 Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]}
287 Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]}
259 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
288 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
260 """
289 """
261 objects = [{'oid': p.oid(), 'size': p.size()} for p in pointers]
290 objects = [{'oid': p.oid(), 'size': p.size()} for p in pointers]
262 requestdata = json.dumps({
291 requestdata = json.dumps({
263 'objects': objects,
292 'objects': objects,
264 'operation': action,
293 'operation': action,
265 })
294 })
266 url = '%s/objects/batch' % self.baseurl
295 url = '%s/objects/batch' % self.baseurl
267 batchreq = util.urlreq.request(url, data=requestdata)
296 batchreq = util.urlreq.request(url, data=requestdata)
268 batchreq.add_header('Accept', 'application/vnd.git-lfs+json')
297 batchreq.add_header('Accept', 'application/vnd.git-lfs+json')
269 batchreq.add_header('Content-Type', 'application/vnd.git-lfs+json')
298 batchreq.add_header('Content-Type', 'application/vnd.git-lfs+json')
270 try:
299 try:
271 rsp = self.urlopener.open(batchreq)
300 rsp = self.urlopener.open(batchreq)
272 rawjson = rsp.read()
301 rawjson = rsp.read()
273 except util.urlerr.httperror as ex:
302 except util.urlerr.httperror as ex:
274 hints = {
303 hints = {
275 400: _('check that lfs serving is enabled on %s and "%s" is '
304 400: _('check that lfs serving is enabled on %s and "%s" is '
276 'supported') % (self.baseurl, action),
305 'supported') % (self.baseurl, action),
277 404: _('the "lfs.url" config may be used to override %s')
306 404: _('the "lfs.url" config may be used to override %s')
278 % self.baseurl,
307 % self.baseurl,
279 }
308 }
280 hint = hints.get(ex.code, _('api=%s, action=%s') % (url, action))
309 hint = hints.get(ex.code, _('api=%s, action=%s') % (url, action))
281 raise LfsRemoteError(_('LFS HTTP error: %s') % ex, hint=hint)
310 raise LfsRemoteError(_('LFS HTTP error: %s') % ex, hint=hint)
311 except util.urlerr.urlerror as ex:
312 hint = (_('the "lfs.url" config may be used to override %s')
313 % self.baseurl)
314 raise LfsRemoteError(_('LFS error: %s') % _urlerrorreason(ex),
315 hint=hint)
282 try:
316 try:
283 response = json.loads(rawjson)
317 response = json.loads(rawjson)
284 except ValueError:
318 except ValueError:
285 raise LfsRemoteError(_('LFS server returns invalid JSON: %s')
319 raise LfsRemoteError(_('LFS server returns invalid JSON: %s')
286 % rawjson)
320 % rawjson)
287
321
288 if self.ui.debugflag:
322 if self.ui.debugflag:
289 self.ui.debug('Status: %d\n' % rsp.status)
323 self.ui.debug('Status: %d\n' % rsp.status)
290 # lfs-test-server and hg serve return headers in different order
324 # lfs-test-server and hg serve return headers in different order
291 self.ui.debug('%s\n'
325 self.ui.debug('%s\n'
292 % '\n'.join(sorted(str(rsp.info()).splitlines())))
326 % '\n'.join(sorted(str(rsp.info()).splitlines())))
293
327
294 if 'objects' in response:
328 if 'objects' in response:
295 response['objects'] = sorted(response['objects'],
329 response['objects'] = sorted(response['objects'],
296 key=lambda p: p['oid'])
330 key=lambda p: p['oid'])
297 self.ui.debug('%s\n'
331 self.ui.debug('%s\n'
298 % json.dumps(response, indent=2,
332 % json.dumps(response, indent=2,
299 separators=('', ': '), sort_keys=True))
333 separators=('', ': '), sort_keys=True))
300
334
301 return response
335 return response
302
336
303 def _checkforservererror(self, pointers, responses, action):
337 def _checkforservererror(self, pointers, responses, action):
304 """Scans errors from objects
338 """Scans errors from objects
305
339
306 Raises LfsRemoteError if any objects have an error"""
340 Raises LfsRemoteError if any objects have an error"""
307 for response in responses:
341 for response in responses:
308 # The server should return 404 when objects cannot be found. Some
342 # The server should return 404 when objects cannot be found. Some
309 # server implementation (ex. lfs-test-server) does not set "error"
343 # server implementation (ex. lfs-test-server) does not set "error"
310 # but just removes "download" from "actions". Treat that case
344 # but just removes "download" from "actions". Treat that case
311 # as the same as 404 error.
345 # as the same as 404 error.
312 if 'error' not in response:
346 if 'error' not in response:
313 if (action == 'download'
347 if (action == 'download'
314 and action not in response.get('actions', [])):
348 and action not in response.get('actions', [])):
315 code = 404
349 code = 404
316 else:
350 else:
317 continue
351 continue
318 else:
352 else:
319 # An error dict without a code doesn't make much sense, so
353 # An error dict without a code doesn't make much sense, so
320 # treat as a server error.
354 # treat as a server error.
321 code = response.get('error').get('code', 500)
355 code = response.get('error').get('code', 500)
322
356
323 ptrmap = {p.oid(): p for p in pointers}
357 ptrmap = {p.oid(): p for p in pointers}
324 p = ptrmap.get(response['oid'], None)
358 p = ptrmap.get(response['oid'], None)
325 if p:
359 if p:
326 filename = getattr(p, 'filename', 'unknown')
360 filename = getattr(p, 'filename', 'unknown')
327 errors = {
361 errors = {
328 404: 'The object does not exist',
362 404: 'The object does not exist',
329 410: 'The object was removed by the owner',
363 410: 'The object was removed by the owner',
330 422: 'Validation error',
364 422: 'Validation error',
331 500: 'Internal server error',
365 500: 'Internal server error',
332 }
366 }
333 msg = errors.get(code, 'status code %d' % code)
367 msg = errors.get(code, 'status code %d' % code)
334 raise LfsRemoteError(_('LFS server error for "%s": %s')
368 raise LfsRemoteError(_('LFS server error for "%s": %s')
335 % (filename, msg))
369 % (filename, msg))
336 else:
370 else:
337 raise LfsRemoteError(
371 raise LfsRemoteError(
338 _('LFS server error. Unsolicited response for oid %s')
372 _('LFS server error. Unsolicited response for oid %s')
339 % response['oid'])
373 % response['oid'])
340
374
341 def _extractobjects(self, response, pointers, action):
375 def _extractobjects(self, response, pointers, action):
342 """extract objects from response of the batch API
376 """extract objects from response of the batch API
343
377
344 response: parsed JSON object returned by batch API
378 response: parsed JSON object returned by batch API
345 return response['objects'] filtered by action
379 return response['objects'] filtered by action
346 raise if any object has an error
380 raise if any object has an error
347 """
381 """
348 # Scan errors from objects - fail early
382 # Scan errors from objects - fail early
349 objects = response.get('objects', [])
383 objects = response.get('objects', [])
350 self._checkforservererror(pointers, objects, action)
384 self._checkforservererror(pointers, objects, action)
351
385
352 # Filter objects with given action. Practically, this skips uploading
386 # Filter objects with given action. Practically, this skips uploading
353 # objects which exist in the server.
387 # objects which exist in the server.
354 filteredobjects = [o for o in objects if action in o.get('actions', [])]
388 filteredobjects = [o for o in objects if action in o.get('actions', [])]
355
389
356 return filteredobjects
390 return filteredobjects
357
391
358 def _basictransfer(self, obj, action, localstore):
392 def _basictransfer(self, obj, action, localstore):
359 """Download or upload a single object using basic transfer protocol
393 """Download or upload a single object using basic transfer protocol
360
394
361 obj: dict, an object description returned by batch API
395 obj: dict, an object description returned by batch API
362 action: string, one of ['upload', 'download']
396 action: string, one of ['upload', 'download']
363 localstore: blobstore.local
397 localstore: blobstore.local
364
398
365 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/\
399 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/\
366 basic-transfers.md
400 basic-transfers.md
367 """
401 """
368 oid = pycompat.bytestr(obj['oid'])
402 oid = pycompat.bytestr(obj['oid'])
369
403
370 href = pycompat.bytestr(obj['actions'][action].get('href'))
404 href = pycompat.bytestr(obj['actions'][action].get('href'))
371 headers = obj['actions'][action].get('header', {}).items()
405 headers = obj['actions'][action].get('header', {}).items()
372
406
373 request = util.urlreq.request(href)
407 request = util.urlreq.request(href)
374 if action == 'upload':
408 if action == 'upload':
375 # If uploading blobs, read data from local blobstore.
409 # If uploading blobs, read data from local blobstore.
376 if not localstore.verify(oid):
410 if not localstore.verify(oid):
377 raise error.Abort(_('detected corrupt lfs object: %s') % oid,
411 raise error.Abort(_('detected corrupt lfs object: %s') % oid,
378 hint=_('run hg verify'))
412 hint=_('run hg verify'))
379 request.data = filewithprogress(localstore.open(oid), None)
413 request.data = filewithprogress(localstore.open(oid), None)
380 request.get_method = lambda: 'PUT'
414 request.get_method = lambda: 'PUT'
381 request.add_header('Content-Type', 'application/octet-stream')
415 request.add_header('Content-Type', 'application/octet-stream')
382
416
383 for k, v in headers:
417 for k, v in headers:
384 request.add_header(k, v)
418 request.add_header(k, v)
385
419
386 response = b''
420 response = b''
387 try:
421 try:
388 req = self.urlopener.open(request)
422 req = self.urlopener.open(request)
389
423
390 if self.ui.debugflag:
424 if self.ui.debugflag:
391 self.ui.debug('Status: %d\n' % req.status)
425 self.ui.debug('Status: %d\n' % req.status)
392 # lfs-test-server and hg serve return headers in different order
426 # lfs-test-server and hg serve return headers in different order
393 self.ui.debug('%s\n'
427 self.ui.debug('%s\n'
394 % '\n'.join(sorted(str(req.info()).splitlines())))
428 % '\n'.join(sorted(str(req.info()).splitlines())))
395
429
396 if action == 'download':
430 if action == 'download':
397 # If downloading blobs, store downloaded data to local blobstore
431 # If downloading blobs, store downloaded data to local blobstore
398 localstore.download(oid, req)
432 localstore.download(oid, req)
399 else:
433 else:
400 while True:
434 while True:
401 data = req.read(1048576)
435 data = req.read(1048576)
402 if not data:
436 if not data:
403 break
437 break
404 response += data
438 response += data
405 if response:
439 if response:
406 self.ui.debug('lfs %s response: %s' % (action, response))
440 self.ui.debug('lfs %s response: %s' % (action, response))
407 except util.urlerr.httperror as ex:
441 except util.urlerr.httperror as ex:
408 if self.ui.debugflag:
442 if self.ui.debugflag:
409 self.ui.debug('%s: %s\n' % (oid, ex.read()))
443 self.ui.debug('%s: %s\n' % (oid, ex.read()))
410 raise LfsRemoteError(_('HTTP error: %s (oid=%s, action=%s)')
444 raise LfsRemoteError(_('HTTP error: %s (oid=%s, action=%s)')
411 % (ex, oid, action))
445 % (ex, oid, action))
446 except util.urlerr.urlerror as ex:
447 hint = (_('attempted connection to %s')
448 % util.urllibcompat.getfullurl(request))
449 raise LfsRemoteError(_('LFS error: %s') % _urlerrorreason(ex),
450 hint=hint)
412
451
413 def _batch(self, pointers, localstore, action):
452 def _batch(self, pointers, localstore, action):
414 if action not in ['upload', 'download']:
453 if action not in ['upload', 'download']:
415 raise error.ProgrammingError('invalid Git-LFS action: %s' % action)
454 raise error.ProgrammingError('invalid Git-LFS action: %s' % action)
416
455
417 response = self._batchrequest(pointers, action)
456 response = self._batchrequest(pointers, action)
418 objects = self._extractobjects(response, pointers, action)
457 objects = self._extractobjects(response, pointers, action)
419 total = sum(x.get('size', 0) for x in objects)
458 total = sum(x.get('size', 0) for x in objects)
420 sizes = {}
459 sizes = {}
421 for obj in objects:
460 for obj in objects:
422 sizes[obj.get('oid')] = obj.get('size', 0)
461 sizes[obj.get('oid')] = obj.get('size', 0)
423 topic = {'upload': _('lfs uploading'),
462 topic = {'upload': _('lfs uploading'),
424 'download': _('lfs downloading')}[action]
463 'download': _('lfs downloading')}[action]
425 if len(objects) > 1:
464 if len(objects) > 1:
426 self.ui.note(_('lfs: need to transfer %d objects (%s)\n')
465 self.ui.note(_('lfs: need to transfer %d objects (%s)\n')
427 % (len(objects), util.bytecount(total)))
466 % (len(objects), util.bytecount(total)))
428
467
429 def transfer(chunk):
468 def transfer(chunk):
430 for obj in chunk:
469 for obj in chunk:
431 objsize = obj.get('size', 0)
470 objsize = obj.get('size', 0)
432 if self.ui.verbose:
471 if self.ui.verbose:
433 if action == 'download':
472 if action == 'download':
434 msg = _('lfs: downloading %s (%s)\n')
473 msg = _('lfs: downloading %s (%s)\n')
435 elif action == 'upload':
474 elif action == 'upload':
436 msg = _('lfs: uploading %s (%s)\n')
475 msg = _('lfs: uploading %s (%s)\n')
437 self.ui.note(msg % (obj.get('oid'),
476 self.ui.note(msg % (obj.get('oid'),
438 util.bytecount(objsize)))
477 util.bytecount(objsize)))
439 retry = self.retry
478 retry = self.retry
440 while True:
479 while True:
441 try:
480 try:
442 self._basictransfer(obj, action, localstore)
481 self._basictransfer(obj, action, localstore)
443 yield 1, obj.get('oid')
482 yield 1, obj.get('oid')
444 break
483 break
445 except socket.error as ex:
484 except socket.error as ex:
446 if retry > 0:
485 if retry > 0:
447 self.ui.note(
486 self.ui.note(
448 _('lfs: failed: %r (remaining retry %d)\n')
487 _('lfs: failed: %r (remaining retry %d)\n')
449 % (ex, retry))
488 % (ex, retry))
450 retry -= 1
489 retry -= 1
451 continue
490 continue
452 raise
491 raise
453
492
454 # Until https multiplexing gets sorted out
493 # Until https multiplexing gets sorted out
455 if self.ui.configbool('experimental', 'lfs.worker-enable'):
494 if self.ui.configbool('experimental', 'lfs.worker-enable'):
456 oids = worker.worker(self.ui, 0.1, transfer, (),
495 oids = worker.worker(self.ui, 0.1, transfer, (),
457 sorted(objects, key=lambda o: o.get('oid')))
496 sorted(objects, key=lambda o: o.get('oid')))
458 else:
497 else:
459 oids = transfer(sorted(objects, key=lambda o: o.get('oid')))
498 oids = transfer(sorted(objects, key=lambda o: o.get('oid')))
460
499
461 with self.ui.makeprogress(topic, total=total) as progress:
500 with self.ui.makeprogress(topic, total=total) as progress:
462 progress.update(0)
501 progress.update(0)
463 processed = 0
502 processed = 0
464 blobs = 0
503 blobs = 0
465 for _one, oid in oids:
504 for _one, oid in oids:
466 processed += sizes[oid]
505 processed += sizes[oid]
467 blobs += 1
506 blobs += 1
468 progress.update(processed)
507 progress.update(processed)
469 self.ui.note(_('lfs: processed: %s\n') % oid)
508 self.ui.note(_('lfs: processed: %s\n') % oid)
470
509
471 if blobs > 0:
510 if blobs > 0:
472 if action == 'upload':
511 if action == 'upload':
473 self.ui.status(_('lfs: uploaded %d files (%s)\n')
512 self.ui.status(_('lfs: uploaded %d files (%s)\n')
474 % (blobs, util.bytecount(processed)))
513 % (blobs, util.bytecount(processed)))
475 elif action == 'download':
514 elif action == 'download':
476 self.ui.status(_('lfs: downloaded %d files (%s)\n')
515 self.ui.status(_('lfs: downloaded %d files (%s)\n')
477 % (blobs, util.bytecount(processed)))
516 % (blobs, util.bytecount(processed)))
478
517
479 def __del__(self):
518 def __del__(self):
480 # copied from mercurial/httppeer.py
519 # copied from mercurial/httppeer.py
481 urlopener = getattr(self, 'urlopener', None)
520 urlopener = getattr(self, 'urlopener', None)
482 if urlopener:
521 if urlopener:
483 for h in urlopener.handlers:
522 for h in urlopener.handlers:
484 h.close()
523 h.close()
485 getattr(h, "close_all", lambda : None)()
524 getattr(h, "close_all", lambda : None)()
486
525
487 class _dummyremote(object):
526 class _dummyremote(object):
488 """Dummy store storing blobs to temp directory."""
527 """Dummy store storing blobs to temp directory."""
489
528
490 def __init__(self, repo, url):
529 def __init__(self, repo, url):
491 fullpath = repo.vfs.join('lfs', url.path)
530 fullpath = repo.vfs.join('lfs', url.path)
492 self.vfs = lfsvfs(fullpath)
531 self.vfs = lfsvfs(fullpath)
493
532
494 def writebatch(self, pointers, fromstore):
533 def writebatch(self, pointers, fromstore):
495 for p in _deduplicate(pointers):
534 for p in _deduplicate(pointers):
496 content = fromstore.read(p.oid(), verify=True)
535 content = fromstore.read(p.oid(), verify=True)
497 with self.vfs(p.oid(), 'wb', atomictemp=True) as fp:
536 with self.vfs(p.oid(), 'wb', atomictemp=True) as fp:
498 fp.write(content)
537 fp.write(content)
499
538
500 def readbatch(self, pointers, tostore):
539 def readbatch(self, pointers, tostore):
501 for p in _deduplicate(pointers):
540 for p in _deduplicate(pointers):
502 with self.vfs(p.oid(), 'rb') as fp:
541 with self.vfs(p.oid(), 'rb') as fp:
503 tostore.download(p.oid(), fp)
542 tostore.download(p.oid(), fp)
504
543
505 class _nullremote(object):
544 class _nullremote(object):
506 """Null store storing blobs to /dev/null."""
545 """Null store storing blobs to /dev/null."""
507
546
508 def __init__(self, repo, url):
547 def __init__(self, repo, url):
509 pass
548 pass
510
549
511 def writebatch(self, pointers, fromstore):
550 def writebatch(self, pointers, fromstore):
512 pass
551 pass
513
552
514 def readbatch(self, pointers, tostore):
553 def readbatch(self, pointers, tostore):
515 pass
554 pass
516
555
517 class _promptremote(object):
556 class _promptremote(object):
518 """Prompt user to set lfs.url when accessed."""
557 """Prompt user to set lfs.url when accessed."""
519
558
520 def __init__(self, repo, url):
559 def __init__(self, repo, url):
521 pass
560 pass
522
561
523 def writebatch(self, pointers, fromstore, ui=None):
562 def writebatch(self, pointers, fromstore, ui=None):
524 self._prompt()
563 self._prompt()
525
564
526 def readbatch(self, pointers, tostore, ui=None):
565 def readbatch(self, pointers, tostore, ui=None):
527 self._prompt()
566 self._prompt()
528
567
529 def _prompt(self):
568 def _prompt(self):
530 raise error.Abort(_('lfs.url needs to be configured'))
569 raise error.Abort(_('lfs.url needs to be configured'))
531
570
532 _storemap = {
571 _storemap = {
533 'https': _gitlfsremote,
572 'https': _gitlfsremote,
534 'http': _gitlfsremote,
573 'http': _gitlfsremote,
535 'file': _dummyremote,
574 'file': _dummyremote,
536 'null': _nullremote,
575 'null': _nullremote,
537 None: _promptremote,
576 None: _promptremote,
538 }
577 }
539
578
540 def _deduplicate(pointers):
579 def _deduplicate(pointers):
541 """Remove any duplicate oids that exist in the list"""
580 """Remove any duplicate oids that exist in the list"""
542 reduced = util.sortdict()
581 reduced = util.sortdict()
543 for p in pointers:
582 for p in pointers:
544 reduced[p.oid()] = p
583 reduced[p.oid()] = p
545 return reduced.values()
584 return reduced.values()
546
585
547 def _verify(oid, content):
586 def _verify(oid, content):
548 realoid = hashlib.sha256(content).hexdigest()
587 realoid = hashlib.sha256(content).hexdigest()
549 if realoid != oid:
588 if realoid != oid:
550 raise LfsCorruptionError(_('detected corrupt lfs object: %s') % oid,
589 raise LfsCorruptionError(_('detected corrupt lfs object: %s') % oid,
551 hint=_('run hg verify'))
590 hint=_('run hg verify'))
552
591
553 def remote(repo, remote=None):
592 def remote(repo, remote=None):
554 """remotestore factory. return a store in _storemap depending on config
593 """remotestore factory. return a store in _storemap depending on config
555
594
556 If ``lfs.url`` is specified, use that remote endpoint. Otherwise, try to
595 If ``lfs.url`` is specified, use that remote endpoint. Otherwise, try to
557 infer the endpoint, based on the remote repository using the same path
596 infer the endpoint, based on the remote repository using the same path
558 adjustments as git. As an extension, 'http' is supported as well so that
597 adjustments as git. As an extension, 'http' is supported as well so that
559 ``hg serve`` works out of the box.
598 ``hg serve`` works out of the box.
560
599
561 https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md
600 https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md
562 """
601 """
563 lfsurl = repo.ui.config('lfs', 'url')
602 lfsurl = repo.ui.config('lfs', 'url')
564 url = util.url(lfsurl or '')
603 url = util.url(lfsurl or '')
565 if lfsurl is None:
604 if lfsurl is None:
566 if remote:
605 if remote:
567 path = remote
606 path = remote
568 elif util.safehasattr(repo, '_subtoppath'):
607 elif util.safehasattr(repo, '_subtoppath'):
569 # The pull command sets this during the optional update phase, which
608 # The pull command sets this during the optional update phase, which
570 # tells exactly where the pull originated, whether 'paths.default'
609 # tells exactly where the pull originated, whether 'paths.default'
571 # or explicit.
610 # or explicit.
572 path = repo._subtoppath
611 path = repo._subtoppath
573 else:
612 else:
574 # TODO: investigate 'paths.remote:lfsurl' style path customization,
613 # TODO: investigate 'paths.remote:lfsurl' style path customization,
575 # and fall back to inferring from 'paths.remote' if unspecified.
614 # and fall back to inferring from 'paths.remote' if unspecified.
576 path = repo.ui.config('paths', 'default') or ''
615 path = repo.ui.config('paths', 'default') or ''
577
616
578 defaulturl = util.url(path)
617 defaulturl = util.url(path)
579
618
580 # TODO: support local paths as well.
619 # TODO: support local paths as well.
581 # TODO: consider the ssh -> https transformation that git applies
620 # TODO: consider the ssh -> https transformation that git applies
582 if defaulturl.scheme in (b'http', b'https'):
621 if defaulturl.scheme in (b'http', b'https'):
583 if defaulturl.path and defaulturl.path[:-1] != b'/':
622 if defaulturl.path and defaulturl.path[:-1] != b'/':
584 defaulturl.path += b'/'
623 defaulturl.path += b'/'
585 defaulturl.path = (defaulturl.path or b'') + b'.git/info/lfs'
624 defaulturl.path = (defaulturl.path or b'') + b'.git/info/lfs'
586
625
587 url = util.url(bytes(defaulturl))
626 url = util.url(bytes(defaulturl))
588 repo.ui.note(_('lfs: assuming remote store: %s\n') % url)
627 repo.ui.note(_('lfs: assuming remote store: %s\n') % url)
589
628
590 scheme = url.scheme
629 scheme = url.scheme
591 if scheme not in _storemap:
630 if scheme not in _storemap:
592 raise error.Abort(_('lfs: unknown url scheme: %s') % scheme)
631 raise error.Abort(_('lfs: unknown url scheme: %s') % scheme)
593 return _storemap[scheme](repo, url)
632 return _storemap[scheme](repo, url)
594
633
595 class LfsRemoteError(error.StorageError):
634 class LfsRemoteError(error.StorageError):
596 pass
635 pass
597
636
598 class LfsCorruptionError(error.Abort):
637 class LfsCorruptionError(error.Abort):
599 """Raised when a corrupt blob is detected, aborting an operation
638 """Raised when a corrupt blob is detected, aborting an operation
600
639
601 It exists to allow specialized handling on the server side."""
640 It exists to allow specialized handling on the server side."""
@@ -1,475 +1,480
1 #require serve no-reposimplestore no-chg
1 #require serve no-reposimplestore no-chg
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > lfs=
5 > lfs=
6 > [lfs]
6 > [lfs]
7 > track=all()
7 > track=all()
8 > [web]
8 > [web]
9 > push_ssl = False
9 > push_ssl = False
10 > allow-push = *
10 > allow-push = *
11 > EOF
11 > EOF
12
12
13 Serving LFS files can experimentally be turned off. The long term solution is
13 Serving LFS files can experimentally be turned off. The long term solution is
14 to support the 'verify' action in both client and server, so that the server can
14 to support the 'verify' action in both client and server, so that the server can
15 tell the client to store files elsewhere.
15 tell the client to store files elsewhere.
16
16
17 $ hg init server
17 $ hg init server
18 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
18 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
19 > --config experimental.lfs.serve=False -R server serve -d \
19 > --config experimental.lfs.serve=False -R server serve -d \
20 > -p $HGPORT --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
20 > -p $HGPORT --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
21 $ cat hg.pid >> $DAEMON_PIDS
21 $ cat hg.pid >> $DAEMON_PIDS
22
22
23 Uploads fail...
23 Uploads fail...
24
24
25 $ hg init client
25 $ hg init client
26 $ echo 'this-is-an-lfs-file' > client/lfs.bin
26 $ echo 'this-is-an-lfs-file' > client/lfs.bin
27 $ hg -R client ci -Am 'initial commit'
27 $ hg -R client ci -Am 'initial commit'
28 adding lfs.bin
28 adding lfs.bin
29 $ hg -R client push http://localhost:$HGPORT
29 $ hg -R client push http://localhost:$HGPORT
30 pushing to http://localhost:$HGPORT/
30 pushing to http://localhost:$HGPORT/
31 searching for changes
31 searching for changes
32 abort: LFS HTTP error: HTTP Error 400: no such method: .git!
32 abort: LFS HTTP error: HTTP Error 400: no such method: .git!
33 (check that lfs serving is enabled on http://localhost:$HGPORT/.git/info/lfs and "upload" is supported)
33 (check that lfs serving is enabled on http://localhost:$HGPORT/.git/info/lfs and "upload" is supported)
34 [255]
34 [255]
35
35
36 ... so do a local push to make the data available. Remove the blob from the
36 ... so do a local push to make the data available. Remove the blob from the
37 default cache, so it attempts to download.
37 default cache, so it attempts to download.
38 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
38 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
39 > --config "lfs.url=null://" \
39 > --config "lfs.url=null://" \
40 > -R client push -q server
40 > -R client push -q server
41 $ mv `hg config lfs.usercache` $TESTTMP/servercache
41 $ mv `hg config lfs.usercache` $TESTTMP/servercache
42
42
43 Downloads fail...
43 Downloads fail...
44
44
45 $ hg clone http://localhost:$HGPORT httpclone
45 $ hg clone http://localhost:$HGPORT httpclone
46 (remote is using large file support (lfs); lfs will be enabled for this repository)
46 (remote is using large file support (lfs); lfs will be enabled for this repository)
47 requesting all changes
47 requesting all changes
48 adding changesets
48 adding changesets
49 adding manifests
49 adding manifests
50 adding file changes
50 adding file changes
51 added 1 changesets with 1 changes to 1 files
51 added 1 changesets with 1 changes to 1 files
52 new changesets 525251863cad
52 new changesets 525251863cad
53 updating to branch default
53 updating to branch default
54 abort: LFS HTTP error: HTTP Error 400: no such method: .git!
54 abort: LFS HTTP error: HTTP Error 400: no such method: .git!
55 (check that lfs serving is enabled on http://localhost:$HGPORT/.git/info/lfs and "download" is supported)
55 (check that lfs serving is enabled on http://localhost:$HGPORT/.git/info/lfs and "download" is supported)
56 [255]
56 [255]
57
57
58 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
58 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
59
59
60 $ cat $TESTTMP/access.log $TESTTMP/errors.log
60 $ cat $TESTTMP/access.log $TESTTMP/errors.log
61 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
61 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
62 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D525251863cad618e55d483555f3d00a2ca99597e x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
62 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D525251863cad618e55d483555f3d00a2ca99597e x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
63 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
63 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
64 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
64 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
65 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob)
65 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob)
66 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
66 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
67 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
67 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
68 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
68 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
69 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob)
69 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob)
70
70
71 $ rm -f $TESTTMP/access.log $TESTTMP/errors.log
71 $ rm -f $TESTTMP/access.log $TESTTMP/errors.log
72 $ hg --config "lfs.usercache=$TESTTMP/servercache" -R server serve -d \
72 $ hg --config "lfs.usercache=$TESTTMP/servercache" -R server serve -d \
73 > -p $HGPORT --pid-file=hg.pid --prefix=subdir/mount/point \
73 > -p $HGPORT --pid-file=hg.pid --prefix=subdir/mount/point \
74 > -A $TESTTMP/access.log -E $TESTTMP/errors.log
74 > -A $TESTTMP/access.log -E $TESTTMP/errors.log
75 $ cat hg.pid >> $DAEMON_PIDS
75 $ cat hg.pid >> $DAEMON_PIDS
76
76
77 Reasonable hint for a misconfigured blob server
77 Reasonable hint for a misconfigured blob server
78
78
79 $ hg -R httpclone update default --config lfs.url=http://localhost:$HGPORT/missing
79 $ hg -R httpclone update default --config lfs.url=http://localhost:$HGPORT/missing
80 abort: LFS HTTP error: HTTP Error 404: Not Found!
80 abort: LFS HTTP error: HTTP Error 404: Not Found!
81 (the "lfs.url" config may be used to override http://localhost:$HGPORT/missing)
81 (the "lfs.url" config may be used to override http://localhost:$HGPORT/missing)
82 [255]
82 [255]
83
83
84 $ hg -R httpclone update default --config lfs.url=http://localhost:$HGPORT2/missing
85 abort: LFS error: *onnection *refused*! (glob)
86 (the "lfs.url" config may be used to override http://localhost:$HGPORT2/missing)
87 [255]
88
84 Blob URIs are correct when --prefix is used
89 Blob URIs are correct when --prefix is used
85
90
86 $ hg clone --debug http://localhost:$HGPORT/subdir/mount/point cloned2
91 $ hg clone --debug http://localhost:$HGPORT/subdir/mount/point cloned2
87 using http://localhost:$HGPORT/subdir/mount/point
92 using http://localhost:$HGPORT/subdir/mount/point
88 sending capabilities command
93 sending capabilities command
89 (remote is using large file support (lfs); lfs will be enabled for this repository)
94 (remote is using large file support (lfs); lfs will be enabled for this repository)
90 query 1; heads
95 query 1; heads
91 sending batch command
96 sending batch command
92 requesting all changes
97 requesting all changes
93 sending getbundle command
98 sending getbundle command
94 bundle2-input-bundle: with-transaction
99 bundle2-input-bundle: with-transaction
95 bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
100 bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
96 adding changesets
101 adding changesets
97 add changeset 525251863cad
102 add changeset 525251863cad
98 adding manifests
103 adding manifests
99 adding file changes
104 adding file changes
100 adding lfs.bin revisions
105 adding lfs.bin revisions
101 added 1 changesets with 1 changes to 1 files
106 added 1 changesets with 1 changes to 1 files
102 bundle2-input-part: total payload size 648
107 bundle2-input-part: total payload size 648
103 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
108 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
104 bundle2-input-part: "phase-heads" supported
109 bundle2-input-part: "phase-heads" supported
105 bundle2-input-part: total payload size 24
110 bundle2-input-part: total payload size 24
106 bundle2-input-part: "cache:rev-branch-cache" (advisory) supported
111 bundle2-input-part: "cache:rev-branch-cache" (advisory) supported
107 bundle2-input-part: total payload size 39
112 bundle2-input-part: total payload size 39
108 bundle2-input-bundle: 3 parts total
113 bundle2-input-bundle: 3 parts total
109 checking for updated bookmarks
114 checking for updated bookmarks
110 updating the branch cache
115 updating the branch cache
111 new changesets 525251863cad
116 new changesets 525251863cad
112 updating to branch default
117 updating to branch default
113 resolving manifests
118 resolving manifests
114 branchmerge: False, force: False, partial: False
119 branchmerge: False, force: False, partial: False
115 ancestor: 000000000000, local: 000000000000+, remote: 525251863cad
120 ancestor: 000000000000, local: 000000000000+, remote: 525251863cad
116 lfs: assuming remote store: http://localhost:$HGPORT/subdir/mount/point/.git/info/lfs
121 lfs: assuming remote store: http://localhost:$HGPORT/subdir/mount/point/.git/info/lfs
117 Status: 200
122 Status: 200
118 Content-Length: 371
123 Content-Length: 371
119 Content-Type: application/vnd.git-lfs+json
124 Content-Type: application/vnd.git-lfs+json
120 Date: $HTTP_DATE$
125 Date: $HTTP_DATE$
121 Server: testing stub value
126 Server: testing stub value
122 {
127 {
123 "objects": [
128 "objects": [
124 {
129 {
125 "actions": {
130 "actions": {
126 "download": {
131 "download": {
127 "expires_at": "$ISO_8601_DATE_TIME$"
132 "expires_at": "$ISO_8601_DATE_TIME$"
128 "header": {
133 "header": {
129 "Accept": "application/vnd.git-lfs"
134 "Accept": "application/vnd.git-lfs"
130 }
135 }
131 "href": "http://localhost:$HGPORT/subdir/mount/point/.hg/lfs/objects/f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
136 "href": "http://localhost:$HGPORT/subdir/mount/point/.hg/lfs/objects/f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
132 }
137 }
133 }
138 }
134 "oid": "f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
139 "oid": "f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
135 "size": 20
140 "size": 20
136 }
141 }
137 ]
142 ]
138 "transfer": "basic"
143 "transfer": "basic"
139 }
144 }
140 lfs: downloading f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e (20 bytes)
145 lfs: downloading f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e (20 bytes)
141 Status: 200
146 Status: 200
142 Content-Length: 20
147 Content-Length: 20
143 Content-Type: application/octet-stream
148 Content-Type: application/octet-stream
144 Date: $HTTP_DATE$
149 Date: $HTTP_DATE$
145 Server: testing stub value
150 Server: testing stub value
146 lfs: adding f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e to the usercache
151 lfs: adding f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e to the usercache
147 lfs: processed: f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e
152 lfs: processed: f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e
148 lfs: downloaded 1 files (20 bytes)
153 lfs: downloaded 1 files (20 bytes)
149 lfs.bin: remote created -> g
154 lfs.bin: remote created -> g
150 getting lfs.bin
155 getting lfs.bin
151 lfs: found f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e in the local lfs store
156 lfs: found f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e in the local lfs store
152 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
157 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
158 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
154
159
155 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
160 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
156
161
157 $ cat $TESTTMP/access.log $TESTTMP/errors.log
162 $ cat $TESTTMP/access.log $TESTTMP/errors.log
158 $LOCALIP - - [$LOGDATE$] "POST /missing/objects/batch HTTP/1.1" 404 - (glob)
163 $LOCALIP - - [$LOGDATE$] "POST /missing/objects/batch HTTP/1.1" 404 - (glob)
159 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=capabilities HTTP/1.1" 200 - (glob)
164 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=capabilities HTTP/1.1" 200 - (glob)
160 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
165 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
161 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
166 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
162 $LOCALIP - - [$LOGDATE$] "POST /subdir/mount/point/.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
167 $LOCALIP - - [$LOGDATE$] "POST /subdir/mount/point/.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
163 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point/.hg/lfs/objects/f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e HTTP/1.1" 200 - (glob)
168 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point/.hg/lfs/objects/f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e HTTP/1.1" 200 - (glob)
164
169
165 Blobs that already exist in the usercache are linked into the repo store, even
170 Blobs that already exist in the usercache are linked into the repo store, even
166 though the client doesn't send the blob.
171 though the client doesn't send the blob.
167
172
168 $ hg init server2
173 $ hg init server2
169 $ hg --config "lfs.usercache=$TESTTMP/servercache" -R server2 serve -d \
174 $ hg --config "lfs.usercache=$TESTTMP/servercache" -R server2 serve -d \
170 > -p $HGPORT --pid-file=hg.pid \
175 > -p $HGPORT --pid-file=hg.pid \
171 > -A $TESTTMP/access.log -E $TESTTMP/errors.log
176 > -A $TESTTMP/access.log -E $TESTTMP/errors.log
172 $ cat hg.pid >> $DAEMON_PIDS
177 $ cat hg.pid >> $DAEMON_PIDS
173
178
174 $ hg --config "lfs.usercache=$TESTTMP/servercache" -R cloned2 --debug \
179 $ hg --config "lfs.usercache=$TESTTMP/servercache" -R cloned2 --debug \
175 > push http://localhost:$HGPORT | grep '^[{} ]'
180 > push http://localhost:$HGPORT | grep '^[{} ]'
176 {
181 {
177 "objects": [
182 "objects": [
178 {
183 {
179 "oid": "f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
184 "oid": "f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
180 "size": 20
185 "size": 20
181 }
186 }
182 ]
187 ]
183 "transfer": "basic"
188 "transfer": "basic"
184 }
189 }
185 $ find server2/.hg/store/lfs/objects | sort
190 $ find server2/.hg/store/lfs/objects | sort
186 server2/.hg/store/lfs/objects
191 server2/.hg/store/lfs/objects
187 server2/.hg/store/lfs/objects/f0
192 server2/.hg/store/lfs/objects/f0
188 server2/.hg/store/lfs/objects/f0/3217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e
193 server2/.hg/store/lfs/objects/f0/3217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e
189 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
194 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
190 $ cat $TESTTMP/errors.log
195 $ cat $TESTTMP/errors.log
191
196
192 $ cat >> $TESTTMP/lfsstoreerror.py <<EOF
197 $ cat >> $TESTTMP/lfsstoreerror.py <<EOF
193 > import errno
198 > import errno
194 > from hgext.lfs import blobstore
199 > from hgext.lfs import blobstore
195 >
200 >
196 > _numverifies = 0
201 > _numverifies = 0
197 > _readerr = True
202 > _readerr = True
198 >
203 >
199 > def reposetup(ui, repo):
204 > def reposetup(ui, repo):
200 > # Nothing to do with a remote repo
205 > # Nothing to do with a remote repo
201 > if not repo.local():
206 > if not repo.local():
202 > return
207 > return
203 >
208 >
204 > store = repo.svfs.lfslocalblobstore
209 > store = repo.svfs.lfslocalblobstore
205 > class badstore(store.__class__):
210 > class badstore(store.__class__):
206 > def download(self, oid, src):
211 > def download(self, oid, src):
207 > '''Called in the server to handle reading from the client in a
212 > '''Called in the server to handle reading from the client in a
208 > PUT request.'''
213 > PUT request.'''
209 > origread = src.read
214 > origread = src.read
210 > def _badread(nbytes):
215 > def _badread(nbytes):
211 > # Simulate bad data/checksum failure from the client
216 > # Simulate bad data/checksum failure from the client
212 > return b'0' * len(origread(nbytes))
217 > return b'0' * len(origread(nbytes))
213 > src.read = _badread
218 > src.read = _badread
214 > super(badstore, self).download(oid, src)
219 > super(badstore, self).download(oid, src)
215 >
220 >
216 > def _read(self, vfs, oid, verify):
221 > def _read(self, vfs, oid, verify):
217 > '''Called in the server to read data for a GET request, and then
222 > '''Called in the server to read data for a GET request, and then
218 > calls self._verify() on it before returning.'''
223 > calls self._verify() on it before returning.'''
219 > global _readerr
224 > global _readerr
220 > # One time simulation of a read error
225 > # One time simulation of a read error
221 > if _readerr:
226 > if _readerr:
222 > _readerr = False
227 > _readerr = False
223 > raise IOError(errno.EIO, '%s: I/O error' % oid)
228 > raise IOError(errno.EIO, '%s: I/O error' % oid)
224 > # Simulate corrupt content on client download
229 > # Simulate corrupt content on client download
225 > blobstore._verify(oid, 'dummy content')
230 > blobstore._verify(oid, 'dummy content')
226 >
231 >
227 > def verify(self, oid):
232 > def verify(self, oid):
228 > '''Called in the server to populate the Batch API response,
233 > '''Called in the server to populate the Batch API response,
229 > letting the client re-upload if the file is corrupt.'''
234 > letting the client re-upload if the file is corrupt.'''
230 > # Fail verify in Batch API for one clone command and one push
235 > # Fail verify in Batch API for one clone command and one push
231 > # command with an IOError. Then let it through to access other
236 > # command with an IOError. Then let it through to access other
232 > # functions. Checksum failure is tested elsewhere.
237 > # functions. Checksum failure is tested elsewhere.
233 > global _numverifies
238 > global _numverifies
234 > _numverifies += 1
239 > _numverifies += 1
235 > if _numverifies <= 2:
240 > if _numverifies <= 2:
236 > raise IOError(errno.EIO, '%s: I/O error' % oid)
241 > raise IOError(errno.EIO, '%s: I/O error' % oid)
237 > return super(badstore, self).verify(oid)
242 > return super(badstore, self).verify(oid)
238 >
243 >
239 > store.__class__ = badstore
244 > store.__class__ = badstore
240 > EOF
245 > EOF
241
246
242 $ rm -rf `hg config lfs.usercache`
247 $ rm -rf `hg config lfs.usercache`
243 $ rm -f $TESTTMP/access.log $TESTTMP/errors.log
248 $ rm -f $TESTTMP/access.log $TESTTMP/errors.log
244 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
249 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
245 > --config extensions.lfsstoreerror=$TESTTMP/lfsstoreerror.py \
250 > --config extensions.lfsstoreerror=$TESTTMP/lfsstoreerror.py \
246 > -R server serve -d \
251 > -R server serve -d \
247 > -p $HGPORT1 --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
252 > -p $HGPORT1 --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
248 $ cat hg.pid >> $DAEMON_PIDS
253 $ cat hg.pid >> $DAEMON_PIDS
249
254
250 Test an I/O error in localstore.verify() (Batch API) with GET
255 Test an I/O error in localstore.verify() (Batch API) with GET
251
256
252 $ hg clone http://localhost:$HGPORT1 httpclone2
257 $ hg clone http://localhost:$HGPORT1 httpclone2
253 (remote is using large file support (lfs); lfs will be enabled for this repository)
258 (remote is using large file support (lfs); lfs will be enabled for this repository)
254 requesting all changes
259 requesting all changes
255 adding changesets
260 adding changesets
256 adding manifests
261 adding manifests
257 adding file changes
262 adding file changes
258 added 1 changesets with 1 changes to 1 files
263 added 1 changesets with 1 changes to 1 files
259 new changesets 525251863cad
264 new changesets 525251863cad
260 updating to branch default
265 updating to branch default
261 abort: LFS server error for "lfs.bin": Internal server error!
266 abort: LFS server error for "lfs.bin": Internal server error!
262 [255]
267 [255]
263
268
264 Test an I/O error in localstore.verify() (Batch API) with PUT
269 Test an I/O error in localstore.verify() (Batch API) with PUT
265
270
266 $ echo foo > client/lfs.bin
271 $ echo foo > client/lfs.bin
267 $ hg -R client ci -m 'mod lfs'
272 $ hg -R client ci -m 'mod lfs'
268 $ hg -R client push http://localhost:$HGPORT1
273 $ hg -R client push http://localhost:$HGPORT1
269 pushing to http://localhost:$HGPORT1/
274 pushing to http://localhost:$HGPORT1/
270 searching for changes
275 searching for changes
271 abort: LFS server error for "unknown": Internal server error!
276 abort: LFS server error for "unknown": Internal server error!
272 [255]
277 [255]
273 TODO: figure out how to associate the file name in the error above
278 TODO: figure out how to associate the file name in the error above
274
279
275 Test a bad checksum sent by the client in the transfer API
280 Test a bad checksum sent by the client in the transfer API
276
281
277 $ hg -R client push http://localhost:$HGPORT1
282 $ hg -R client push http://localhost:$HGPORT1
278 pushing to http://localhost:$HGPORT1/
283 pushing to http://localhost:$HGPORT1/
279 searching for changes
284 searching for changes
280 abort: HTTP error: HTTP Error 422: corrupt blob (oid=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c, action=upload)!
285 abort: HTTP error: HTTP Error 422: corrupt blob (oid=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c, action=upload)!
281 [255]
286 [255]
282
287
283 $ echo 'test lfs file' > server/lfs3.bin
288 $ echo 'test lfs file' > server/lfs3.bin
284 $ hg --config experimental.lfs.disableusercache=True \
289 $ hg --config experimental.lfs.disableusercache=True \
285 > -R server ci -Aqm 'another lfs file'
290 > -R server ci -Aqm 'another lfs file'
286 $ hg -R client pull -q http://localhost:$HGPORT1
291 $ hg -R client pull -q http://localhost:$HGPORT1
287
292
288 Test an I/O error during the processing of the GET request
293 Test an I/O error during the processing of the GET request
289
294
290 $ hg --config lfs.url=http://localhost:$HGPORT1/.git/info/lfs \
295 $ hg --config lfs.url=http://localhost:$HGPORT1/.git/info/lfs \
291 > -R client update -r tip
296 > -R client update -r tip
292 abort: HTTP error: HTTP Error 500: Internal Server Error (oid=276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d, action=download)!
297 abort: HTTP error: HTTP Error 500: Internal Server Error (oid=276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d, action=download)!
293 [255]
298 [255]
294
299
295 Test a checksum failure during the processing of the GET request
300 Test a checksum failure during the processing of the GET request
296
301
297 $ hg --config lfs.url=http://localhost:$HGPORT1/.git/info/lfs \
302 $ hg --config lfs.url=http://localhost:$HGPORT1/.git/info/lfs \
298 > -R client update -r tip
303 > -R client update -r tip
299 abort: HTTP error: HTTP Error 422: corrupt blob (oid=276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d, action=download)!
304 abort: HTTP error: HTTP Error 422: corrupt blob (oid=276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d, action=download)!
300 [255]
305 [255]
301
306
302 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
307 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
303
308
304 $ cat $TESTTMP/access.log
309 $ cat $TESTTMP/access.log
305 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
310 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
306 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
311 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
307 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
312 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
308 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
313 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
309 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
314 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
310 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D392c05922088bacf8e68a6939b480017afbf245d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
315 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D392c05922088bacf8e68a6939b480017afbf245d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
311 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
316 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
312 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
317 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
313 $LOCALIP - - [$LOGDATE$] "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
318 $LOCALIP - - [$LOGDATE$] "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
314 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
319 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
315 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
320 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
316 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
321 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
317 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D392c05922088bacf8e68a6939b480017afbf245d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
322 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D392c05922088bacf8e68a6939b480017afbf245d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
318 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
323 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
319 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
324 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
320 $LOCALIP - - [$LOGDATE$] "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
325 $LOCALIP - - [$LOGDATE$] "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
321 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
326 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
322 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
327 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
323 $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c HTTP/1.1" 422 - (glob)
328 $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c HTTP/1.1" 422 - (glob)
324 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
329 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
325 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D392c05922088bacf8e68a6939b480017afbf245d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
330 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D392c05922088bacf8e68a6939b480017afbf245d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
326 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=525251863cad618e55d483555f3d00a2ca99597e&heads=506bf3d83f78c54b89e81c6411adee19fdf02156+525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
331 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=525251863cad618e55d483555f3d00a2ca99597e&heads=506bf3d83f78c54b89e81c6411adee19fdf02156+525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
327 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
332 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
328 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d HTTP/1.1" 500 - (glob)
333 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d HTTP/1.1" 500 - (glob)
329 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
334 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
330 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d HTTP/1.1" 422 - (glob)
335 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d HTTP/1.1" 422 - (glob)
331
336
332 $ grep -v ' File "' $TESTTMP/errors.log
337 $ grep -v ' File "' $TESTTMP/errors.log
333 $LOCALIP - - [$ERRDATE$] HG error: Exception happened while processing request '/.git/info/lfs/objects/batch': (glob)
338 $LOCALIP - - [$ERRDATE$] HG error: Exception happened while processing request '/.git/info/lfs/objects/batch': (glob)
334 $LOCALIP - - [$ERRDATE$] HG error: Traceback (most recent call last): (glob)
339 $LOCALIP - - [$ERRDATE$] HG error: Traceback (most recent call last): (glob)
335 $LOCALIP - - [$ERRDATE$] HG error: verifies = store.verify(oid) (glob)
340 $LOCALIP - - [$ERRDATE$] HG error: verifies = store.verify(oid) (glob)
336 $LOCALIP - - [$ERRDATE$] HG error: raise IOError(errno.EIO, '%s: I/O error' % oid) (glob)
341 $LOCALIP - - [$ERRDATE$] HG error: raise IOError(errno.EIO, '%s: I/O error' % oid) (glob)
337 $LOCALIP - - [$ERRDATE$] HG error: IOError: [Errno 5] f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e: I/O error (glob)
342 $LOCALIP - - [$ERRDATE$] HG error: IOError: [Errno 5] f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e: I/O error (glob)
338 $LOCALIP - - [$ERRDATE$] HG error: (glob)
343 $LOCALIP - - [$ERRDATE$] HG error: (glob)
339 $LOCALIP - - [$ERRDATE$] HG error: Exception happened while processing request '/.git/info/lfs/objects/batch': (glob)
344 $LOCALIP - - [$ERRDATE$] HG error: Exception happened while processing request '/.git/info/lfs/objects/batch': (glob)
340 $LOCALIP - - [$ERRDATE$] HG error: Traceback (most recent call last): (glob)
345 $LOCALIP - - [$ERRDATE$] HG error: Traceback (most recent call last): (glob)
341 $LOCALIP - - [$ERRDATE$] HG error: verifies = store.verify(oid) (glob)
346 $LOCALIP - - [$ERRDATE$] HG error: verifies = store.verify(oid) (glob)
342 $LOCALIP - - [$ERRDATE$] HG error: raise IOError(errno.EIO, '%s: I/O error' % oid) (glob)
347 $LOCALIP - - [$ERRDATE$] HG error: raise IOError(errno.EIO, '%s: I/O error' % oid) (glob)
343 $LOCALIP - - [$ERRDATE$] HG error: IOError: [Errno 5] b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c: I/O error (glob)
348 $LOCALIP - - [$ERRDATE$] HG error: IOError: [Errno 5] b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c: I/O error (glob)
344 $LOCALIP - - [$ERRDATE$] HG error: (glob)
349 $LOCALIP - - [$ERRDATE$] HG error: (glob)
345 $LOCALIP - - [$ERRDATE$] HG error: Exception happened while processing request '/.hg/lfs/objects/b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c': (glob)
350 $LOCALIP - - [$ERRDATE$] HG error: Exception happened while processing request '/.hg/lfs/objects/b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c': (glob)
346 $LOCALIP - - [$ERRDATE$] HG error: Traceback (most recent call last): (glob)
351 $LOCALIP - - [$ERRDATE$] HG error: Traceback (most recent call last): (glob)
347 $LOCALIP - - [$ERRDATE$] HG error: localstore.download(oid, req.bodyfh) (glob)
352 $LOCALIP - - [$ERRDATE$] HG error: localstore.download(oid, req.bodyfh) (glob)
348 $LOCALIP - - [$ERRDATE$] HG error: super(badstore, self).download(oid, src) (glob)
353 $LOCALIP - - [$ERRDATE$] HG error: super(badstore, self).download(oid, src) (glob)
349 $LOCALIP - - [$ERRDATE$] HG error: % oid) (glob)
354 $LOCALIP - - [$ERRDATE$] HG error: % oid) (glob)
350 $LOCALIP - - [$ERRDATE$] HG error: LfsCorruptionError: corrupt remote lfs object: b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c (glob)
355 $LOCALIP - - [$ERRDATE$] HG error: LfsCorruptionError: corrupt remote lfs object: b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c (glob)
351 $LOCALIP - - [$ERRDATE$] HG error: (glob)
356 $LOCALIP - - [$ERRDATE$] HG error: (glob)
352 $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d': (glob)
357 $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d': (glob)
353 Traceback (most recent call last):
358 Traceback (most recent call last):
354 self.do_write()
359 self.do_write()
355 self.do_hgweb()
360 self.do_hgweb()
356 for chunk in self.server.application(env, self._start_response):
361 for chunk in self.server.application(env, self._start_response):
357 for r in self._runwsgi(req, res, repo):
362 for r in self._runwsgi(req, res, repo):
358 rctx, req, res, self.check_perm)
363 rctx, req, res, self.check_perm)
359 return func(*(args + a), **kw)
364 return func(*(args + a), **kw)
360 lambda perm:
365 lambda perm:
361 res.setbodybytes(localstore.read(oid))
366 res.setbodybytes(localstore.read(oid))
362 blob = self._read(self.vfs, oid, verify)
367 blob = self._read(self.vfs, oid, verify)
363 raise IOError(errno.EIO, '%s: I/O error' % oid)
368 raise IOError(errno.EIO, '%s: I/O error' % oid)
364 IOError: [Errno 5] 276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d: I/O error
369 IOError: [Errno 5] 276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d: I/O error
365
370
366 $LOCALIP - - [$ERRDATE$] HG error: Exception happened while processing request '/.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d': (glob)
371 $LOCALIP - - [$ERRDATE$] HG error: Exception happened while processing request '/.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d': (glob)
367 $LOCALIP - - [$ERRDATE$] HG error: Traceback (most recent call last): (glob)
372 $LOCALIP - - [$ERRDATE$] HG error: Traceback (most recent call last): (glob)
368 $LOCALIP - - [$ERRDATE$] HG error: res.setbodybytes(localstore.read(oid)) (glob)
373 $LOCALIP - - [$ERRDATE$] HG error: res.setbodybytes(localstore.read(oid)) (glob)
369 $LOCALIP - - [$ERRDATE$] HG error: blob = self._read(self.vfs, oid, verify) (glob)
374 $LOCALIP - - [$ERRDATE$] HG error: blob = self._read(self.vfs, oid, verify) (glob)
370 $LOCALIP - - [$ERRDATE$] HG error: blobstore._verify(oid, 'dummy content') (glob)
375 $LOCALIP - - [$ERRDATE$] HG error: blobstore._verify(oid, 'dummy content') (glob)
371 $LOCALIP - - [$ERRDATE$] HG error: hint=_('run hg verify')) (glob)
376 $LOCALIP - - [$ERRDATE$] HG error: hint=_('run hg verify')) (glob)
372 $LOCALIP - - [$ERRDATE$] HG error: LfsCorruptionError: detected corrupt lfs object: 276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d (glob)
377 $LOCALIP - - [$ERRDATE$] HG error: LfsCorruptionError: detected corrupt lfs object: 276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d (glob)
373 $LOCALIP - - [$ERRDATE$] HG error: (glob)
378 $LOCALIP - - [$ERRDATE$] HG error: (glob)
374
379
375 Basic Authorization headers are returned by the Batch API, and sent back with
380 Basic Authorization headers are returned by the Batch API, and sent back with
376 the GET/PUT request.
381 the GET/PUT request.
377
382
378 $ rm -f $TESTTMP/access.log $TESTTMP/errors.log
383 $ rm -f $TESTTMP/access.log $TESTTMP/errors.log
379
384
380 $ cat >> $HGRCPATH << EOF
385 $ cat >> $HGRCPATH << EOF
381 > [experimental]
386 > [experimental]
382 > lfs.disableusercache = True
387 > lfs.disableusercache = True
383 > [auth]
388 > [auth]
384 > l.schemes=http
389 > l.schemes=http
385 > l.prefix=lo
390 > l.prefix=lo
386 > l.username=user
391 > l.username=user
387 > l.password=pass
392 > l.password=pass
388 > EOF
393 > EOF
389
394
390 $ cat << EOF > userpass.py
395 $ cat << EOF > userpass.py
391 > import base64
396 > import base64
392 > from mercurial.hgweb import common
397 > from mercurial.hgweb import common
393 > def perform_authentication(hgweb, req, op):
398 > def perform_authentication(hgweb, req, op):
394 > auth = req.headers.get(b'Authorization')
399 > auth = req.headers.get(b'Authorization')
395 > if not auth:
400 > if not auth:
396 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, b'who',
401 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, b'who',
397 > [(b'WWW-Authenticate', b'Basic Realm="mercurial"')])
402 > [(b'WWW-Authenticate', b'Basic Realm="mercurial"')])
398 > if base64.b64decode(auth.split()[1]).split(b':', 1) != [b'user',
403 > if base64.b64decode(auth.split()[1]).split(b':', 1) != [b'user',
399 > b'pass']:
404 > b'pass']:
400 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, b'no')
405 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, b'no')
401 > def extsetup():
406 > def extsetup():
402 > common.permhooks.insert(0, perform_authentication)
407 > common.permhooks.insert(0, perform_authentication)
403 > EOF
408 > EOF
404
409
405 $ hg --config extensions.x=$TESTTMP/userpass.py \
410 $ hg --config extensions.x=$TESTTMP/userpass.py \
406 > -R server serve -d -p $HGPORT1 --pid-file=hg.pid \
411 > -R server serve -d -p $HGPORT1 --pid-file=hg.pid \
407 > -A $TESTTMP/access.log -E $TESTTMP/errors.log
412 > -A $TESTTMP/access.log -E $TESTTMP/errors.log
408 $ mv hg.pid $DAEMON_PIDS
413 $ mv hg.pid $DAEMON_PIDS
409
414
410 $ hg clone --debug http://localhost:$HGPORT1 auth_clone | egrep '^[{}]| '
415 $ hg clone --debug http://localhost:$HGPORT1 auth_clone | egrep '^[{}]| '
411 {
416 {
412 "objects": [
417 "objects": [
413 {
418 {
414 "actions": {
419 "actions": {
415 "download": {
420 "download": {
416 "expires_at": "$ISO_8601_DATE_TIME$"
421 "expires_at": "$ISO_8601_DATE_TIME$"
417 "header": {
422 "header": {
418 "Accept": "application/vnd.git-lfs"
423 "Accept": "application/vnd.git-lfs"
419 "Authorization": "Basic dXNlcjpwYXNz"
424 "Authorization": "Basic dXNlcjpwYXNz"
420 }
425 }
421 "href": "http://localhost:$HGPORT1/.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d"
426 "href": "http://localhost:$HGPORT1/.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d"
422 }
427 }
423 }
428 }
424 "oid": "276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d"
429 "oid": "276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d"
425 "size": 14
430 "size": 14
426 }
431 }
427 ]
432 ]
428 "transfer": "basic"
433 "transfer": "basic"
429 }
434 }
430
435
431 $ echo 'another blob' > auth_clone/lfs.blob
436 $ echo 'another blob' > auth_clone/lfs.blob
432 $ hg -R auth_clone ci -Aqm 'add blob'
437 $ hg -R auth_clone ci -Aqm 'add blob'
433 $ hg -R auth_clone --debug push | egrep '^[{}]| '
438 $ hg -R auth_clone --debug push | egrep '^[{}]| '
434 {
439 {
435 "objects": [
440 "objects": [
436 {
441 {
437 "actions": {
442 "actions": {
438 "upload": {
443 "upload": {
439 "expires_at": "$ISO_8601_DATE_TIME$"
444 "expires_at": "$ISO_8601_DATE_TIME$"
440 "header": {
445 "header": {
441 "Accept": "application/vnd.git-lfs"
446 "Accept": "application/vnd.git-lfs"
442 "Authorization": "Basic dXNlcjpwYXNz"
447 "Authorization": "Basic dXNlcjpwYXNz"
443 }
448 }
444 "href": "http://localhost:$HGPORT1/.hg/lfs/objects/df14287d8d75f076a6459e7a3703ca583ca9fb3f4918caed10c77ac8622d49b3"
449 "href": "http://localhost:$HGPORT1/.hg/lfs/objects/df14287d8d75f076a6459e7a3703ca583ca9fb3f4918caed10c77ac8622d49b3"
445 }
450 }
446 }
451 }
447 "oid": "df14287d8d75f076a6459e7a3703ca583ca9fb3f4918caed10c77ac8622d49b3"
452 "oid": "df14287d8d75f076a6459e7a3703ca583ca9fb3f4918caed10c77ac8622d49b3"
448 "size": 13
453 "size": 13
449 }
454 }
450 ]
455 ]
451 "transfer": "basic"
456 "transfer": "basic"
452 }
457 }
453
458
454 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
459 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
455
460
456 $ cat $TESTTMP/access.log $TESTTMP/errors.log
461 $ cat $TESTTMP/access.log $TESTTMP/errors.log
457 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 401 - (glob)
462 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 401 - (glob)
458 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
463 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
459 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
464 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
460 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=506bf3d83f78c54b89e81c6411adee19fdf02156+525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
465 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=506bf3d83f78c54b89e81c6411adee19fdf02156+525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
461 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 401 - (glob)
466 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 401 - (glob)
462 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
467 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
463 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d HTTP/1.1" 200 - (glob)
468 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d HTTP/1.1" 200 - (glob)
464 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 401 - (glob)
469 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 401 - (glob)
465 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
470 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
466 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D525251863cad618e55d483555f3d00a2ca99597e+4d9397055dc0c205f3132f331f36353ab1a525a3 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
471 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D525251863cad618e55d483555f3d00a2ca99597e+4d9397055dc0c205f3132f331f36353ab1a525a3 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
467 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
472 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
468 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
473 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
469 $LOCALIP - - [$LOGDATE$] "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
474 $LOCALIP - - [$LOGDATE$] "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
470 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
475 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
471 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 401 - (glob)
476 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 401 - (glob)
472 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
477 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
473 $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/df14287d8d75f076a6459e7a3703ca583ca9fb3f4918caed10c77ac8622d49b3 HTTP/1.1" 201 - (glob)
478 $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/df14287d8d75f076a6459e7a3703ca583ca9fb3f4918caed10c77ac8622d49b3 HTTP/1.1" 201 - (glob)
474 $LOCALIP - - [$LOGDATE$] "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
479 $LOCALIP - - [$LOGDATE$] "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
475 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
480 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
General Comments 0
You need to be logged in to leave comments. Login now