fix: use non-paginated Gitea release attachment endpoint
This commit is contained in:
@@ -103,6 +103,13 @@ class API:
|
|||||||
raise SyncError('Attachment download failed after retries') from None
|
raise SyncError('Attachment download failed after retries') from None
|
||||||
time.sleep(2 ** attempt)
|
time.sleep(2 ** attempt)
|
||||||
|
|
||||||
|
def list_assets(self, release_id):
|
||||||
|
# Gitea's assets endpoint is NOT paginated; page/limit are ignored.
|
||||||
|
data = self.request('GET', f'/releases/{release_id}/assets')
|
||||||
|
if not isinstance(data, list):
|
||||||
|
raise SyncError('Expected attachment list')
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def digest(path):
|
def digest(path):
|
||||||
h = hashlib.sha256()
|
h = hashlib.sha256()
|
||||||
@@ -151,7 +158,7 @@ def release_body(source, source_url, hashes, source_sha):
|
|||||||
def sync_one(src, dst, source, anchor, dry_run, directory):
|
def sync_one(src, dst, source, anchor, dry_run, directory):
|
||||||
tag = source['tag_name']
|
tag = source['tag_name']
|
||||||
key = urllib.parse.quote(tag, safe='')
|
key = urllib.parse.quote(tag, safe='')
|
||||||
source_assets = src.pages(f'/releases/{source["id"]}/assets')
|
source_assets = src.list_assets(source['id'])
|
||||||
validate_names(source_assets)
|
validate_names(source_assets)
|
||||||
source_tag = src.request('GET', '/tags/' + key)
|
source_tag = src.request('GET', '/tags/' + key)
|
||||||
if not source_tag or not source_tag.get('commit', {}).get('sha'):
|
if not source_tag or not source_tag.get('commit', {}).get('sha'):
|
||||||
@@ -162,7 +169,7 @@ def sync_one(src, dst, source, anchor, dry_run, directory):
|
|||||||
p = Path(directory) / ('source-' + str(a['id']))
|
p = Path(directory) / ('source-' + str(a['id']))
|
||||||
hashes[a['name']] = src.download(a, p)
|
hashes[a['name']] = src.download(a, p)
|
||||||
files[a['name']] = p
|
files[a['name']] = p
|
||||||
fresh = src.pages(f'/releases/{source["id"]}/assets')
|
fresh = src.list_assets(source['id'])
|
||||||
identity = lambda rows: sorted((a['id'], a['name'], a['size']) for a in rows)
|
identity = lambda rows: sorted((a['id'], a['name'], a['size']) for a in rows)
|
||||||
if identity(fresh) != identity(source_assets):
|
if identity(fresh) != identity(source_assets):
|
||||||
raise SyncError('Source attachment list changed during download; retry later')
|
raise SyncError('Source attachment list changed during download; retry later')
|
||||||
@@ -175,7 +182,7 @@ def sync_one(src, dst, source, anchor, dry_run, directory):
|
|||||||
result = {'tag': tag, 'source_sha': source_sha, 'created': target is None, 'assets': [], 'metadata_changed': False}
|
result = {'tag': tag, 'source_sha': source_sha, 'created': target is None, 'assets': [], 'metadata_changed': False}
|
||||||
if target is None and not dry_run:
|
if target is None and not dry_run:
|
||||||
target = dst.request('POST', '/releases', dict(desired, tag_name=tag, target_commitish=anchor, draft=True))
|
target = dst.request('POST', '/releases', dict(desired, tag_name=tag, target_commitish=anchor, draft=True))
|
||||||
assets = dst.pages(f'/releases/{target["id"]}/assets') if target else []
|
assets = dst.list_assets(target['id']) if target else []
|
||||||
for source_asset in source_assets:
|
for source_asset in source_assets:
|
||||||
name, sha = source_asset['name'], hashes[source_asset['name']]
|
name, sha = source_asset['name'], hashes[source_asset['name']]
|
||||||
existing = next((a for a in assets if a['name'] == name), None)
|
existing = next((a for a in assets if a['name'] == name), None)
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ class Fake:
|
|||||||
return copy.deepcopy(a)
|
return copy.deepcopy(a)
|
||||||
def pages(self, path):
|
def pages(self, path):
|
||||||
return copy.deepcopy(self.assets)
|
return copy.deepcopy(self.assets)
|
||||||
|
def list_assets(self, release_id):
|
||||||
|
return copy.deepcopy(self.assets)
|
||||||
def download(self, a, p):
|
def download(self, a, p):
|
||||||
Path(p).write_bytes(self.data[a['id']])
|
Path(p).write_bytes(self.data[a['id']])
|
||||||
validate_file(p, a)
|
validate_file(p, a)
|
||||||
@@ -118,6 +120,15 @@ class Tests(unittest.TestCase):
|
|||||||
api = API('https://example.invalid', 'a/b', 'secret')
|
api = API('https://example.invalid', 'a/b', 'secret')
|
||||||
with self.assertRaises(SyncError):
|
with self.assertRaises(SyncError):
|
||||||
api.download({'browser_download_url': 'https://other.invalid/file'}, '/tmp/unused')
|
api.download({'browser_download_url': 'https://other.invalid/file'}, '/tmp/unused')
|
||||||
|
def test_assets_endpoint_is_not_paginated(self):
|
||||||
|
api = API('https://example.invalid', 'a/b', '')
|
||||||
|
calls = []
|
||||||
|
def request(method, path):
|
||||||
|
calls.append(path)
|
||||||
|
return [{'id': 1}]
|
||||||
|
api.request = request
|
||||||
|
self.assertEqual([{'id': 1}], api.list_assets(2))
|
||||||
|
self.assertEqual(['/releases/2/assets'], calls)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__': unittest.main()
|
if __name__ == '__main__': unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user