#!/usr/bin/env python3 """Baixa os objetos Git LFS de uma arvore (pelo endpoint batch do LFS, sem o git-lfs instalado). python3 -I lfs_fetch.py Cada ponteiro (version/oid/size) vira o arquivo real; o sha256 do objeto e conferido contra o oid do ponteiro. Os objetos ficam no cache, com o oid no nome. """ import hashlib import json import os import sys import urllib.request tree, base, cache = sys.argv[1:4] os.makedirs(cache, exist_ok=True) MAGIC = b'version https://git-lfs.github.com/spec/v1' ptrs = {} for dp, dn, fn in os.walk(tree): dn[:] = [d for d in dn if d != '.git'] for f in sorted(fn): p = os.path.join(dp, f) if os.path.islink(p) or os.path.getsize(p) > 200: continue with open(p, 'rb') as fh: t = fh.read() if t.startswith(MAGIC): d = dict(l.split(' ', 1) for l in t.decode().strip().split('\n')[1:]) ptrs.setdefault(d['oid'].split(':')[1], (int(d['size']), []))[1].append(p) need = [(o, v) for o, v in sorted(ptrs.items()) if not os.path.exists(os.path.join(cache, 'lfs-' + o))] print(len(ptrs), 'objetos LFS,', len(need), 'a baixar') for i in range(0, len(need), 50): chunk = need[i:i + 50] body = json.dumps({'operation': 'download', 'transfers': ['basic'], 'objects': [{'oid': o, 'size': v[0]} for o, v in chunk]}).encode() req = urllib.request.Request(base + '/info/lfs/objects/batch', data=body, headers={ 'Accept': 'application/vnd.git-lfs+json', 'Content-Type': 'application/vnd.git-lfs+json', 'User-Agent': 'git-lfs/3.4'}) r = json.load(urllib.request.urlopen(req)) for ob in r['objects']: a = ob.get('actions', {}).get('download') if not a: sys.exit('sem download: %s %s' % (ob['oid'], ob.get('error'))) rq = urllib.request.Request(a['href'], headers=a.get('header', {})) data = urllib.request.urlopen(rq).read() if hashlib.sha256(data).hexdigest() != ob['oid']: sys.exit('sha256 diferente: ' + ob['oid']) with open(os.path.join(cache, 'lfs-' + ob['oid'] + '.partial'), 'wb') as fh: fh.write(data) os.rename(os.path.join(cache, 'lfs-' + ob['oid'] + '.partial'), os.path.join(cache, 'lfs-' + ob['oid'])) for o, (size, paths) in ptrs.items(): src = os.path.join(cache, 'lfs-' + o) data = open(src, 'rb').read() if len(data) != size or hashlib.sha256(data).hexdigest() != o: sys.exit('cache corrompido: ' + o) for p in paths: with open(p, 'wb') as fh: fh.write(data) print('ok')