diff --git a/aider_gitea/__init__.py b/aider_gitea/__init__.py index 9fe3f23..9d907d8 100644 --- a/aider_gitea/__init__.py +++ b/aider_gitea/__init__.py @@ -368,11 +368,9 @@ class ClaudeCodeSolver(CodeSolverStrategy): 'claude', '-p', '--output-format', - 'stream-json', - #'--max-turns', '100', - '--debug', - '--verbose', - '--dangerously-skip-permissions', + 'json', + '--max-turns', + '10', ] if CODE_MODEL: diff --git a/aider_gitea/gitea_client.py b/aider_gitea/gitea_client.py index bf90768..6c125ec 100644 --- a/aider_gitea/gitea_client.py +++ b/aider_gitea/gitea_client.py @@ -15,10 +15,12 @@ class GiteaClient: Read more about the Gitea API here: https://gitea.com/api/swagger Attributes: - gitea_url (str): The base URL for the Gitea API endpoints. + ROOT_URL (str): The base URL for the Gitea API endpoints. session (requests.Session): HTTP session for making API requests. """ + ROOT_URL = None + def __init__(self, gitea_url: str, token: str) -> None: """Initialize a new Gitea API client. @@ -30,7 +32,7 @@ class GiteaClient: AssertionError: If gitea_url ends with '/api/v1'. """ assert not gitea_url.endswith('/api/v1') - self.gitea_url = gitea_url + '/api/v1' + self.ROOT_URL = gitea_url + '/api/v1' self.session = requests.Session() self.session.headers['Content-Type'] = 'application/json' if token: @@ -50,7 +52,7 @@ class GiteaClient: Raises: requests.HTTPError: If the API request fails. """ - url = f'{self.gitea_url}/repos/{owner}/{repo}/branches/{branch}' + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/branches/{branch}' response = self.session.get(url) response.raise_for_status() data = response.json() @@ -71,7 +73,7 @@ class GiteaClient: Raises: requests.HTTPError: If the API request fails for reasons other than branch already existing. """ - url = f'{self.gitea_url}/repos/{owner}/{repo}/git/refs' + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/git/refs' json_data = {'ref': f'refs/heads/{new_branch}', 'sha': sha} response = self.session.post(url, json=json_data) if response.status_code == 422: @@ -93,7 +95,7 @@ class GiteaClient: Raises: requests.HTTPError: If the API request fails. """ - url = f'{self.gitea_url}/repos/{owner}/{repo}/issues' + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/issues' response = self.session.get(url) response.raise_for_status() issues = response.json() @@ -119,7 +121,7 @@ class GiteaClient: Returns: Iterator[str]: An iterator of repository names. """ - url = f'{self.gitea_url}/user/repos' + url = f'{self.ROOT_URL}/user/repos' response = self.session.get(url) response.raise_for_status() @@ -157,7 +159,7 @@ class GiteaClient: Raises: requests.HTTPError: If the API request fails. """ - url = f'{self.gitea_url}/repos/{owner}/{repo}/pulls' + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/pulls' json_data = { 'title': title, 'body': body, @@ -187,7 +189,7 @@ class GiteaClient: def get_failed_pipelines(self, owner: str, repo: str, pr_number: str) -> list[int]: """Fetch pipeline runs for a PR and return IDs of failed runs.""" - url = f'{self.gitea_url}/repos/{owner}/{repo}/actions/runs' + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/actions/runs' response = self.session.get(url) response.raise_for_status() runs = response.json().get('workflow_runs', []) @@ -203,7 +205,7 @@ class GiteaClient: def get_pipeline_log(self, owner: str, repo: str, run_id: int) -> str: """Download the logs for a pipeline run.""" - url = f'{self.gitea_url}/repos/{owner}/{repo}/actions/runs/{run_id}/logs' + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/actions/runs/{run_id}/logs' response = self.session.get(url) response.raise_for_status() return response.text @@ -215,7 +217,112 @@ class GiteaClient: state: str = 'open', ) -> list[dict]: """Fetch pull requests for a repository.""" - url = f'{self.gitea_url}/repos/{owner}/{repo}/pulls?state={state}' + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/pulls?state={state}' response = self.session.get(url) response.raise_for_status() return response.json() + + def get_pull_request_reviews( + self, + owner: str, + repo: str, + pr_number: int, + ) -> list[dict]: + """Download all reviews of a pull request. + + Args: + owner (str): Owner of the repository. + repo (str): Name of the repository. + pr_number (int): Pull request number. + + Returns: + list[dict]: A list of review dictionaries. + + Raises: + requests.HTTPError: If the API request fails. + """ + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/pulls/{pr_number}/reviews' + response = self.session.get(url) + response.raise_for_status() + return response.json() + + def get_review_comments( + self, + owner: str, + repo: str, + pr_number: int, + review_id: int, + ) -> list[dict]: + """Download all comments for a specific review. + + Args: + owner (str): Owner of the repository. + repo (str): Name of the repository. + pr_number (int): Pull request number. + review_id (int): Review ID. + + Returns: + list[dict]: A list of review comment dictionaries. + + Raises: + requests.HTTPError: If the API request fails. + """ + url = f'{self.ROOT_URL}/repos/{owner}/{repo}/pulls/{pr_number}/reviews/{review_id}/comments' + response = self.session.get(url) + response.raise_for_status() + return response.json() + + def get_pull_request_comments( + self, + owner: str, + repo: str, + pr_number: int, + ) -> list[dict]: + """Download all review comments for a pull request by iterating through all reviews. + + This method implements the proper flow: + 1. Download all reviews of the pull request + 2. Download all comments for each review + 3. Return all comments combined + + Args: + owner (str): Owner of the repository. + repo (str): Name of the repository. + pr_number (int): Pull request number. + + Returns: + list[dict]: A list of all review comment dictionaries. + + Raises: + requests.HTTPError: If the API request fails. + """ + all_comments = [] + + # Step 1: Download all reviews of the pull request + reviews = self.get_pull_request_reviews(owner, repo, pr_number) + + # Step 2: Download all comments for each review + for review in reviews: + review_id = review.get('id') + if review_id: + try: + comments = self.get_review_comments( + owner, + repo, + pr_number, + review_id, + ) + # Add review context to each comment + for comment in comments: + comment['review_id'] = review_id + comment['review_state'] = review.get('state') + all_comments.extend(comments) + except Exception as e: + logger.warning( + 'Failed to get comments for review %s: %s', + review_id, + e, + ) + continue + + return all_comments