refactor: update remote tag fetching behaviour so it uses api instead of shallow clone
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import re
|
||||
|
||||
import giturlparse
|
||||
import requests
|
||||
|
||||
@@ -72,6 +74,12 @@ class RemoteAPI(ABC):
|
||||
viejo), sin depender del historial local."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_tags(self, suffix: str = "") -> list[str]:
|
||||
"""Nombres de los tags del repositorio, sin orden garantizado. `suffix` es solo una pista
|
||||
para que la API filtre; el filtrado definitivo se hace en `get_latest_tag`."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_endpoint(self, resource: str) -> str:
|
||||
pass
|
||||
@@ -91,13 +99,33 @@ class RemoteAPI(ABC):
|
||||
|
||||
return response
|
||||
|
||||
def get_all_pages(self, url: str, page_size: int = 50) -> list:
|
||||
def get_latest_tag(self, suffix: str = "") -> str | None:
|
||||
"""Tag SemVer más alto (vX.Y.Z + suffix exacto), o None si no hay ninguno."""
|
||||
pattern = re.compile(rf"^v(\d+)\.(\d+)\.(\d+){re.escape(suffix)}$")
|
||||
versions = {}
|
||||
|
||||
for name in self.list_tags(suffix):
|
||||
match = pattern.match(name)
|
||||
|
||||
if match:
|
||||
versions[tuple(map(int, match.groups()))] = name
|
||||
|
||||
return versions[max(versions)] if versions else None
|
||||
|
||||
def get_all_pages(
|
||||
self,
|
||||
url: str,
|
||||
page_size: int = 50,
|
||||
params: dict | None = None,
|
||||
size_param: str = "per_page",
|
||||
) -> list:
|
||||
"""Recorre un endpoint paginado por número de página (page / per_page)."""
|
||||
items: list = []
|
||||
page = 1
|
||||
|
||||
while True:
|
||||
batch = self.get_json(url, {"page": page, "per_page": page_size}).json()
|
||||
query = {**(params or {}), "page": page, size_param: page_size}
|
||||
batch = self.get_json(url, query).json()
|
||||
items += batch
|
||||
|
||||
if len(batch) < page_size:
|
||||
|
||||
@@ -77,5 +77,21 @@ class BitbucketRemoteAPI(RemoteAPI):
|
||||
|
||||
return commits
|
||||
|
||||
def list_tags(self, suffix: str = "") -> list[str]:
|
||||
names: list[str] = []
|
||||
url: str | None = self.get_endpoint("refs/tags")
|
||||
params: dict[str, int|str] | None = {"pagelen": 100}
|
||||
|
||||
if suffix:
|
||||
params["q"] = f'name ~ "{suffix}"'
|
||||
|
||||
while url:
|
||||
page = self.get_json(url, params).json()
|
||||
names += [t["name"] for t in page["values"]]
|
||||
url = page.get("next")
|
||||
params = None # "next" ya incluye la query
|
||||
|
||||
return names
|
||||
|
||||
def get_endpoint(self, resource: str) -> str:
|
||||
return f"{BitbucketRemoteAPI.API_ENDPOINT}/{self.repository}/{resource}"
|
||||
|
||||
@@ -74,6 +74,11 @@ class GiteaRemoteAPI(RemoteAPI):
|
||||
for c in reversed(items)
|
||||
]
|
||||
|
||||
def list_tags(self, suffix: str = "") -> list[str]:
|
||||
tags = self.get_all_pages(self.get_endpoint("/tags"), 50, size_param="limit")
|
||||
|
||||
return [t["name"] for t in tags]
|
||||
|
||||
def get_endpoint(self, resource: str) -> str:
|
||||
return f"{self.base_url}/api/v1/repos/{self.repository}{resource}"
|
||||
|
||||
|
||||
@@ -63,5 +63,8 @@ class GithubRemoteAPI(RemoteAPI):
|
||||
for c in reversed(items)
|
||||
]
|
||||
|
||||
def list_tags(self, suffix: str = "") -> list[str]:
|
||||
return [t["name"] for t in self.get_all_pages(self.get_endpoint("/tags"), 100)]
|
||||
|
||||
def get_endpoint(self, resource: str) -> str:
|
||||
return f"{GithubRemoteAPI.API_ENDPOINT}/{self.repository}{resource}"
|
||||
|
||||
@@ -70,6 +70,13 @@ class GitlabRemoteAPI(RemoteAPI):
|
||||
for c in items
|
||||
]
|
||||
|
||||
def list_tags(self, suffix: str = "") -> list[str]:
|
||||
# "search" soporta "$" para anclar al final del nombre
|
||||
params = {"search": suffix + "$"} if suffix else None
|
||||
tags = self.get_all_pages(self.get_endpoint("/repository/tags"), 100, params)
|
||||
|
||||
return [t["name"] for t in tags]
|
||||
|
||||
def get_endpoint(self, resource: str) -> str:
|
||||
project_id = quote(self.repository, safe="")
|
||||
return f"{self.base_url}/api/v4/projects/{project_id}{resource}"
|
||||
|
||||
Reference in New Issue
Block a user