diff -r 84f2becbd106 -r 0ee0a3f6a990 hgext/lfs/blobstore.py --- a/hgext/lfs/blobstore.py Tue Jan 14 18:02:20 2020 -0500 +++ b/hgext/lfs/blobstore.py Tue Jan 14 19:42:24 2020 -0500 @@ -155,15 +155,29 @@ return self.vfs(oid, b'rb') - def download(self, oid, src): + def download(self, oid, src, content_length): """Read the blob from the remote source in chunks, verify the content, and write to this local blobstore.""" sha256 = hashlib.sha256() + size = 0 with self.vfs(oid, b'wb', atomictemp=True) as fp: for chunk in util.filechunkiter(src, size=1048576): fp.write(chunk) sha256.update(chunk) + size += len(chunk) + + # If the server advertised a length longer than what we actually + # received, then we should expect that the server crashed while + # producing the response (but the server has no way of telling us + # that), and we really don't need to try to write the response to + # the localstore, because it's not going to match the expected. + if content_length is not None and int(content_length) != size: + msg = ( + b"Response length (%s) does not match Content-Length " + b"header (%d): likely server-side crash" + ) + raise LfsRemoteError(_(msg) % (size, int(content_length))) realoid = node.hex(sha256.digest()) if realoid != oid: @@ -492,6 +506,7 @@ response = b'' try: with contextlib.closing(self.urlopener.open(request)) as res: + contentlength = res.info().get(b"content-length") ui = self.ui # Shorten debug lines if self.ui.debugflag: ui.debug(b'Status: %d\n' % res.status) @@ -503,7 +518,7 @@ if action == b'download': # If downloading blobs, store downloaded data to local # blobstore - localstore.download(oid, res) + localstore.download(oid, res, contentlength) else: while True: data = res.read(1048576) @@ -637,7 +652,7 @@ def readbatch(self, pointers, tostore): for p in _deduplicate(pointers): with self.vfs(p.oid(), b'rb') as fp: - tostore.download(p.oid(), fp) + tostore.download(p.oid(), fp, None) class _nullremote(object):