289 """Get metadata about objects pointed by pointers for given action |
289 """Get metadata about objects pointed by pointers for given action |
290 |
290 |
291 Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]} |
291 Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]} |
292 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md |
292 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md |
293 """ |
293 """ |
294 objects = [{'oid': p.oid(), 'size': p.size()} for p in pointers] |
294 objects = [{r'oid': pycompat.strurl(p.oid()), |
295 requestdata = json.dumps({ |
295 r'size': p.size()} for p in pointers] |
296 'objects': objects, |
296 requestdata = pycompat.bytesurl(json.dumps({ |
297 'operation': action, |
297 r'objects': objects, |
298 }) |
298 r'operation': pycompat.strurl(action), |
|
299 })) |
299 url = b'%s/objects/batch' % self.baseurl |
300 url = b'%s/objects/batch' % self.baseurl |
300 batchreq = util.urlreq.request(pycompat.strurl(url), data=requestdata) |
301 batchreq = util.urlreq.request(pycompat.strurl(url), data=requestdata) |
301 batchreq.add_header('Accept', 'application/vnd.git-lfs+json') |
302 batchreq.add_header(r'Accept', r'application/vnd.git-lfs+json') |
302 batchreq.add_header('Content-Type', 'application/vnd.git-lfs+json') |
303 batchreq.add_header(r'Content-Type', r'application/vnd.git-lfs+json') |
303 try: |
304 try: |
304 with contextlib.closing(self.urlopener.open(batchreq)) as rsp: |
305 with contextlib.closing(self.urlopener.open(batchreq)) as rsp: |
305 rawjson = rsp.read() |
306 rawjson = rsp.read() |
306 except util.urlerr.httperror as ex: |
307 except util.urlerr.httperror as ex: |
307 hints = { |
308 hints = { |
330 # lfs-test-server and hg serve return headers in different order |
331 # lfs-test-server and hg serve return headers in different order |
331 headers = pycompat.bytestr(rsp.info()) |
332 headers = pycompat.bytestr(rsp.info()) |
332 self.ui.debug(b'%s\n' |
333 self.ui.debug(b'%s\n' |
333 % b'\n'.join(sorted(headers.splitlines()))) |
334 % b'\n'.join(sorted(headers.splitlines()))) |
334 |
335 |
335 if 'objects' in response: |
336 if r'objects' in response: |
336 response['objects'] = sorted(response['objects'], |
337 response[r'objects'] = sorted(response[r'objects'], |
337 key=lambda p: p['oid']) |
338 key=lambda p: p[r'oid']) |
338 self.ui.debug('%s\n' |
339 self.ui.debug(b'%s\n' |
339 % json.dumps(response, indent=2, |
340 % pycompat.bytesurl( |
340 separators=('', ': '), sort_keys=True)) |
341 json.dumps(response, indent=2, |
|
342 separators=(r'', r': '), |
|
343 sort_keys=True))) |
341 |
344 |
342 return response |
345 return response |
343 |
346 |
344 def _checkforservererror(self, pointers, responses, action): |
347 def _checkforservererror(self, pointers, responses, action): |
345 """Scans errors from objects |
348 """Scans errors from objects |
417 # If uploading blobs, read data from local blobstore. |
420 # If uploading blobs, read data from local blobstore. |
418 if not localstore.verify(oid): |
421 if not localstore.verify(oid): |
419 raise error.Abort(_(b'detected corrupt lfs object: %s') % oid, |
422 raise error.Abort(_(b'detected corrupt lfs object: %s') % oid, |
420 hint=_(b'run hg verify')) |
423 hint=_(b'run hg verify')) |
421 request.data = filewithprogress(localstore.open(oid), None) |
424 request.data = filewithprogress(localstore.open(oid), None) |
422 request.get_method = lambda: 'PUT' |
425 request.get_method = lambda: r'PUT' |
423 request.add_header('Content-Type', 'application/octet-stream') |
426 request.add_header(r'Content-Type', r'application/octet-stream') |
424 |
427 |
425 for k, v in headers: |
428 for k, v in headers: |
426 request.add_header(k, v) |
429 request.add_header(k, v) |
427 |
430 |
428 response = b'' |
431 response = b'' |