Mercurial > public > mercurial-scm > hg
diff hgext/lfs/blobstore.py @ 43076:2372284d9457
formatting: blacken the codebase
This is using my patch to black
(https://github.com/psf/black/pull/826) so we don't un-wrap collection
literals.
Done with:
hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S
# skip-blame mass-reformatting only
# no-check-commit reformats foo_bar functions
Differential Revision: https://phab.mercurial-scm.org/D6971
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 06 Oct 2019 09:45:02 -0400 |
parents | 698667eb7523 |
children | 687b865b95ad |
line wrap: on
line diff
--- a/hgext/lfs/blobstore.py Sat Oct 05 10:29:34 2019 -0400 +++ b/hgext/lfs/blobstore.py Sun Oct 06 09:45:02 2019 -0400 @@ -29,15 +29,14 @@ worker, ) -from mercurial.utils import ( - stringutil, -) +from mercurial.utils import stringutil from ..largefiles import lfutil # 64 bytes for SHA256 _lfsre = re.compile(br'\A[a-f0-9]{64}\Z') + class lfsvfs(vfsmod.vfs): def join(self, path): """split the path at first two characters, like: XX/XXXXX...""" @@ -56,18 +55,20 @@ prefixlen = len(pathutil.normasprefix(root)) oids = [] - for dirpath, dirs, files in os.walk(self.reljoin(self.base, path - or b''), - onerror=onerror): + for dirpath, dirs, files in os.walk( + self.reljoin(self.base, path or b''), onerror=onerror + ): dirpath = dirpath[prefixlen:] # Silently skip unexpected files and directories if len(dirpath) == 2: - oids.extend([dirpath + f for f in files - if _lfsre.match(dirpath + f)]) + oids.extend( + [dirpath + f for f in files if _lfsre.match(dirpath + f)] + ) yield ('', [], oids) + class nullvfs(lfsvfs): def __init__(self): pass @@ -80,8 +81,10 @@ # self.vfs. Raise the same error as a normal vfs when asked to read a # file that doesn't exist. The only difference is the full file path # isn't available in the error. - raise IOError(errno.ENOENT, - pycompat.sysstr(b'%s: No such file or directory' % oid)) + raise IOError( + errno.ENOENT, + pycompat.sysstr(b'%s: No such file or directory' % oid), + ) def walk(self, path=None, onerror=None): return (b'', [], []) @@ -89,6 +92,7 @@ def write(self, oid, data): pass + class filewithprogress(object): """a file-like object that supports __len__ and read. @@ -97,7 +101,7 @@ def __init__(self, fp, callback): self._fp = fp - self._callback = callback # func(readsize) + self._callback = callback # func(readsize) fp.seek(0, os.SEEK_END) self._len = fp.tell() fp.seek(0) @@ -117,6 +121,7 @@ self._fp = None return data + class local(object): """Local blobstore for large file contents. @@ -161,8 +166,9 @@ realoid = node.hex(sha256.digest()) if realoid != oid: - raise LfsCorruptionError(_(b'corrupt remote lfs object: %s') - % oid) + raise LfsCorruptionError( + _(b'corrupt remote lfs object: %s') % oid + ) self._linktousercache(oid) @@ -186,16 +192,16 @@ blob, but that doesn't happen when the server tells the client that it already has the blob. """ - if (not isinstance(self.cachevfs, nullvfs) - and not self.vfs.exists(oid)): + if not isinstance(self.cachevfs, nullvfs) and not self.vfs.exists(oid): self.ui.note(_(b'lfs: found %s in the usercache\n') % oid) lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid)) def _linktousercache(self, oid): # XXX: should we verify the content of the cache, and hardlink back to # the local store on success, but truncate, write and link on failure? - if (not self.cachevfs.exists(oid) - and not isinstance(self.cachevfs, nullvfs)): + if not self.cachevfs.exists(oid) and not isinstance( + self.cachevfs, nullvfs + ): self.ui.note(_(b'lfs: adding %s to the usercache\n') % oid) lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid)) @@ -240,6 +246,7 @@ False otherwise.""" return self.cachevfs.exists(oid) or self.vfs.exists(oid) + def _urlerrorreason(urlerror): '''Create a friendly message for the given URLError to be used in an LfsRemoteError message. @@ -250,7 +257,7 @@ inst = urlerror.reason if util.safehasattr(inst, 'reason'): - try: # usually it is in the form (errno, strerror) + try: # usually it is in the form (errno, strerror) reason = inst.reason.args[1] except (AttributeError, IndexError): # it might be anything, for example a string @@ -264,6 +271,7 @@ else: return stringutil.forcebytestr(urlerror) + class lfsauthhandler(util.urlreq.basehandler): handler_order = 480 # Before HTTPDigestAuthHandler (== 490) @@ -277,13 +285,17 @@ if scheme.lower() != r'basic': msg = _(b'the server must support Basic Authentication') - raise util.urlerr.httperror(req.get_full_url(), code, - encoding.strfromlocal(msg), headers, - fp) + raise util.urlerr.httperror( + req.get_full_url(), + code, + encoding.strfromlocal(msg), + headers, + fp, + ) return None + class _gitlfsremote(object): - def __init__(self, repo, url): ui = repo.ui self.ui = ui @@ -310,12 +322,15 @@ Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]} See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md """ - objects = [{r'oid': pycompat.strurl(p.oid()), - r'size': p.size()} for p in pointers] - requestdata = pycompat.bytesurl(json.dumps({ - r'objects': objects, - r'operation': pycompat.strurl(action), - })) + objects = [ + {r'oid': pycompat.strurl(p.oid()), r'size': p.size()} + for p in pointers + ] + requestdata = pycompat.bytesurl( + json.dumps( + {r'objects': objects, r'operation': pycompat.strurl(action),} + ) + ) url = b'%s/objects/batch' % self.baseurl batchreq = util.urlreq.request(pycompat.strurl(url), data=requestdata) batchreq.add_header(r'Accept', r'application/vnd.git-lfs+json') @@ -325,41 +340,56 @@ rawjson = rsp.read() except util.urlerr.httperror as ex: hints = { - 400: _(b'check that lfs serving is enabled on %s and "%s" is ' - b'supported') % (self.baseurl, action), + 400: _( + b'check that lfs serving is enabled on %s and "%s" is ' + b'supported' + ) + % (self.baseurl, action), 404: _(b'the "lfs.url" config may be used to override %s') - % self.baseurl, + % self.baseurl, } hint = hints.get(ex.code, _(b'api=%s, action=%s') % (url, action)) raise LfsRemoteError( _(b'LFS HTTP error: %s') % stringutil.forcebytestr(ex), - hint=hint) + hint=hint, + ) except util.urlerr.urlerror as ex: - hint = (_(b'the "lfs.url" config may be used to override %s') - % self.baseurl) - raise LfsRemoteError(_(b'LFS error: %s') % _urlerrorreason(ex), - hint=hint) + hint = ( + _(b'the "lfs.url" config may be used to override %s') + % self.baseurl + ) + raise LfsRemoteError( + _(b'LFS error: %s') % _urlerrorreason(ex), hint=hint + ) try: response = json.loads(rawjson) except ValueError: - raise LfsRemoteError(_(b'LFS server returns invalid JSON: %s') - % rawjson.encode("utf-8")) + raise LfsRemoteError( + _(b'LFS server returns invalid JSON: %s') + % rawjson.encode("utf-8") + ) if self.ui.debugflag: self.ui.debug(b'Status: %d\n' % rsp.status) # lfs-test-server and hg serve return headers in different order headers = pycompat.bytestr(rsp.info()).strip() - self.ui.debug(b'%s\n' - % b'\n'.join(sorted(headers.splitlines()))) + self.ui.debug(b'%s\n' % b'\n'.join(sorted(headers.splitlines()))) if r'objects' in response: - response[r'objects'] = sorted(response[r'objects'], - key=lambda p: p[r'oid']) - self.ui.debug(b'%s\n' - % pycompat.bytesurl( - json.dumps(response, indent=2, - separators=(r'', r': '), - sort_keys=True))) + response[r'objects'] = sorted( + response[r'objects'], key=lambda p: p[r'oid'] + ) + self.ui.debug( + b'%s\n' + % pycompat.bytesurl( + json.dumps( + response, + indent=2, + separators=(r'', r': '), + sort_keys=True, + ) + ) + ) def encodestr(x): if isinstance(x, pycompat.unicode): @@ -378,8 +408,9 @@ # but just removes "download" from "actions". Treat that case # as the same as 404 error. if b'error' not in response: - if (action == b'download' - and action not in response.get(b'actions', [])): + if action == b'download' and action not in response.get( + b'actions', [] + ): code = 404 else: continue @@ -399,12 +430,14 @@ 500: b'Internal server error', } msg = errors.get(code, b'status code %d' % code) - raise LfsRemoteError(_(b'LFS server error for "%s": %s') - % (filename, msg)) + raise LfsRemoteError( + _(b'LFS server error for "%s": %s') % (filename, msg) + ) else: raise LfsRemoteError( _(b'LFS server error. Unsolicited response for oid %s') - % response[b'oid']) + % response[b'oid'] + ) def _extractobjects(self, response, pointers, action): """extract objects from response of the batch API @@ -419,8 +452,9 @@ # Filter objects with given action. Practically, this skips uploading # objects which exist in the server. - filteredobjects = [o for o in objects - if action in o.get(b'actions', [])] + filteredobjects = [ + o for o in objects if action in o.get(b'actions', []) + ] return filteredobjects @@ -442,8 +476,10 @@ if action == b'upload': # If uploading blobs, read data from local blobstore. if not localstore.verify(oid): - raise error.Abort(_(b'detected corrupt lfs object: %s') % oid, - hint=_(b'run hg verify')) + raise error.Abort( + _(b'detected corrupt lfs object: %s') % oid, + hint=_(b'run hg verify'), + ) request.data = filewithprogress(localstore.open(oid), None) request.get_method = lambda: r'PUT' request.add_header(r'Content-Type', r'application/octet-stream') @@ -461,8 +497,7 @@ # lfs-test-server and hg serve return headers in different # order headers = pycompat.bytestr(req.info()).strip() - ui.debug(b'%s\n' - % b'\n'.join(sorted(headers.splitlines()))) + ui.debug(b'%s\n' % b'\n'.join(sorted(headers.splitlines()))) if action == b'download': # If downloading blobs, store downloaded data to local @@ -478,14 +513,20 @@ ui.debug(b'lfs %s response: %s' % (action, response)) except util.urlerr.httperror as ex: if self.ui.debugflag: - self.ui.debug(b'%s: %s\n' % (oid, ex.read())) # XXX: also bytes? - raise LfsRemoteError(_(b'LFS HTTP error: %s (oid=%s, action=%s)') - % (stringutil.forcebytestr(ex), oid, action)) + self.ui.debug( + b'%s: %s\n' % (oid, ex.read()) + ) # XXX: also bytes? + raise LfsRemoteError( + _(b'LFS HTTP error: %s (oid=%s, action=%s)') + % (stringutil.forcebytestr(ex), oid, action) + ) except util.urlerr.urlerror as ex: - hint = (_(b'attempted connection to %s') - % pycompat.bytesurl(util.urllibcompat.getfullurl(request))) - raise LfsRemoteError(_(b'LFS error: %s') % _urlerrorreason(ex), - hint=hint) + hint = _(b'attempted connection to %s') % pycompat.bytesurl( + util.urllibcompat.getfullurl(request) + ) + raise LfsRemoteError( + _(b'LFS error: %s') % _urlerrorreason(ex), hint=hint + ) def _batch(self, pointers, localstore, action): if action not in [b'upload', b'download']: @@ -497,11 +538,15 @@ sizes = {} for obj in objects: sizes[obj.get(b'oid')] = obj.get(b'size', 0) - topic = {b'upload': _(b'lfs uploading'), - b'download': _(b'lfs downloading')}[action] + topic = { + b'upload': _(b'lfs uploading'), + b'download': _(b'lfs downloading'), + }[action] if len(objects) > 1: - self.ui.note(_(b'lfs: need to transfer %d objects (%s)\n') - % (len(objects), util.bytecount(total))) + self.ui.note( + _(b'lfs: need to transfer %d objects (%s)\n') + % (len(objects), util.bytecount(total)) + ) def transfer(chunk): for obj in chunk: @@ -511,8 +556,9 @@ msg = _(b'lfs: downloading %s (%s)\n') elif action == b'upload': msg = _(b'lfs: uploading %s (%s)\n') - self.ui.note(msg % (obj.get(b'oid'), - util.bytecount(objsize))) + self.ui.note( + msg % (obj.get(b'oid'), util.bytecount(objsize)) + ) retry = self.retry while True: try: @@ -523,15 +569,21 @@ if retry > 0: self.ui.note( _(b'lfs: failed: %r (remaining retry %d)\n') - % (stringutil.forcebytestr(ex), retry)) + % (stringutil.forcebytestr(ex), retry) + ) retry -= 1 continue raise # Until https multiplexing gets sorted out if self.ui.configbool(b'experimental', b'lfs.worker-enable'): - oids = worker.worker(self.ui, 0.1, transfer, (), - sorted(objects, key=lambda o: o.get(b'oid'))) + oids = worker.worker( + self.ui, + 0.1, + transfer, + (), + sorted(objects, key=lambda o: o.get(b'oid')), + ) else: oids = transfer(sorted(objects, key=lambda o: o.get(b'oid'))) @@ -547,11 +599,15 @@ if blobs > 0: if action == b'upload': - self.ui.status(_(b'lfs: uploaded %d files (%s)\n') - % (blobs, util.bytecount(processed))) + self.ui.status( + _(b'lfs: uploaded %d files (%s)\n') + % (blobs, util.bytecount(processed)) + ) elif action == b'download': - self.ui.status(_(b'lfs: downloaded %d files (%s)\n') - % (blobs, util.bytecount(processed))) + self.ui.status( + _(b'lfs: downloaded %d files (%s)\n') + % (blobs, util.bytecount(processed)) + ) def __del__(self): # copied from mercurial/httppeer.py @@ -559,7 +615,8 @@ if urlopener: for h in urlopener.handlers: h.close() - getattr(h, "close_all", lambda : None)() + getattr(h, "close_all", lambda: None)() + class _dummyremote(object): """Dummy store storing blobs to temp directory.""" @@ -579,6 +636,7 @@ with self.vfs(p.oid(), b'rb') as fp: tostore.download(p.oid(), fp) + class _nullremote(object): """Null store storing blobs to /dev/null.""" @@ -591,6 +649,7 @@ def readbatch(self, pointers, tostore): pass + class _promptremote(object): """Prompt user to set lfs.url when accessed.""" @@ -606,6 +665,7 @@ def _prompt(self): raise error.Abort(_(b'lfs.url needs to be configured')) + _storemap = { b'https': _gitlfsremote, b'http': _gitlfsremote, @@ -614,6 +674,7 @@ None: _promptremote, } + def _deduplicate(pointers): """Remove any duplicate oids that exist in the list""" reduced = util.sortdict() @@ -621,11 +682,15 @@ reduced[p.oid()] = p return reduced.values() + def _verify(oid, content): realoid = node.hex(hashlib.sha256(content).digest()) if realoid != oid: - raise LfsCorruptionError(_(b'detected corrupt lfs object: %s') % oid, - hint=_(b'run hg verify')) + raise LfsCorruptionError( + _(b'detected corrupt lfs object: %s') % oid, + hint=_(b'run hg verify'), + ) + def remote(repo, remote=None): """remotestore factory. return a store in _storemap depending on config @@ -669,9 +734,11 @@ raise error.Abort(_(b'lfs: unknown url scheme: %s') % scheme) return _storemap[scheme](repo, url) + class LfsRemoteError(error.StorageError): pass + class LfsCorruptionError(error.Abort): """Raised when a corrupt blob is detected, aborting an operation