SXXXXXXX_RepoSync/reposync/core/gitea_client.py
2025-07-07 11:09:32 +02:00

189 lines
6.5 KiB
Python

# RepoSync/core/gitea_client.py
"""
Gitea-specific implementation of the BaseVCSClient.
This module handles all interactions with a Gitea instance via its REST API.
"""
import logging
import requests
from typing import List, Dict, Optional, Any
from .base_vcs_client import BaseVCSClient
class GiteaAPIError(Exception):
"""Custom exception for Gitea API errors."""
pass
class GiteaClient(BaseVCSClient):
"""
A client for interacting with the Gitea API.
Implements the abstract methods defined in BaseVCSClient.
"""
def __init__(self, api_url: str, token: str, logger: logging.Logger):
"""
Initializes the GiteaClient.
Args:
api_url: The base URL of the Gitea instance (e.g., 'https://gitea.com').
token: The personal access token for authentication.
logger: A configured logger instance.
"""
super().__init__(api_url, token)
self.logger = logger
self.logger.debug(f"GiteaClient initialized for URL: {self.api_url}")
def _api_request(
self,
method: str,
endpoint: str,
params: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
) -> Any:
"""
A helper method to make requests to the Gitea API.
"""
url = f"{self.api_url}/api/v1{endpoint}"
self.logger.debug(f"Making API call: {method} {url}")
try:
response = requests.request(
method=method,
url=url,
headers=self.headers,
params=params,
json=json_data,
timeout=30,
)
response.raise_for_status()
if response.status_code == 204:
return None
return response.json()
except requests.exceptions.RequestException as e:
self.logger.error(f"Gitea API request failed: {e}")
raise GiteaAPIError(f"Failed to communicate with Gitea API: {e}") from e
def get_repositories(self) -> List[Dict[str, Any]]:
"""
Retrieves a list of all repositories accessible by the user, handling pagination.
Now includes repository size.
"""
self.logger.info("Fetching list of repositories from Gitea...")
all_repos = []
page = 1
limit = 50
while True:
self.logger.debug(f"Fetching page {page} of repositories...")
params = {"page": page, "limit": limit}
repos_page = self._api_request("GET", "/user/repos", params=params)
if not repos_page:
break
for repo in repos_page:
standardized_repo = {
"name": repo.get("name"),
"owner": repo.get("owner", {}).get("login"),
"clone_url": repo.get("clone_url"),
"description": repo.get("description"),
"private": repo.get("private"),
"size_kb": repo.get("size", 0), # Get size in KB
}
all_repos.append(standardized_repo)
page += 1
self.logger.info(f"Found {len(all_repos)} repositories.")
return all_repos
def get_repository(self, repo_name: str) -> Optional[Dict[str, Any]]:
"""
Retrieves a single repository by its name using the search endpoint.
Now includes repository size.
"""
self.logger.info(f"Searching for repository '{repo_name}'...")
params = {"q": repo_name, "limit": 10}
try:
search_result = self._api_request("GET", "/repos/search", params=params)
except GiteaAPIError as e:
self.logger.warning(
f"Repo search failed: {e}. This may be a permissions issue."
)
return None
if not search_result or not search_result.get("data"):
self.logger.info(f"Repository '{repo_name}' not found via search.")
return None
for repo in search_result["data"]:
if repo.get("name").lower() == repo_name.lower():
self.logger.info(f"Found exact match for repository '{repo_name}'.")
return {
"name": repo.get("name"),
"owner": repo.get("owner", {}).get("login"),
"clone_url": repo.get("clone_url"),
"description": repo.get("description"),
"private": repo.get("private"),
"size_kb": repo.get("size", 0), # Get size in KB
}
self.logger.info(
f"No exact match for repository '{repo_name}' found in search results."
)
return None
def create_repository(
self, name: str, description: str = "", private: bool = True
) -> Dict[str, Any]:
"""
Creates a new repository on the Gitea platform.
"""
self.logger.info(f"Creating new repository '{name}'...")
payload = {"name": name, "description": description, "private": private}
new_repo = self._api_request("POST", "/user/repos", json_data=payload)
self.logger.info(f"Successfully created repository '{name}'.")
return {
"name": new_repo.get("name"),
"owner": new_repo.get("owner", {}).get("login"),
"clone_url": new_repo.get("clone_url"),
"description": new_repo.get("description"),
"private": new_repo.get("private"),
"size_kb": new_repo.get("size", 0),
}
def get_repository_branches(self, owner: str, repo_name: str) -> Dict[str, str]:
"""
Retrieves a dictionary of branches and their head commit hashes for a
specific repository on Gitea.
"""
self.logger.info(f"Fetching branches for '{owner}/{repo_name}' from Gitea...")
endpoint = f"/repos/{owner}/{repo_name}/branches"
try:
branches_data = self._api_request("GET", endpoint)
branches = {}
if branches_data:
for branch in branches_data:
branch_name = branch.get("name")
commit_hash = branch.get("commit", {}).get("id")
if branch_name and commit_hash:
branches[branch_name] = commit_hash
self.logger.debug(
f"Found branches for '{repo_name}': {list(branches.keys())}"
)
return branches
except GiteaAPIError as e:
self.logger.error(f"Failed to fetch branches for '{repo_name}': {e}")
return {}