Add .gitignore and pyproject.toml (#1)
### Findings and Changes
#### Changes:
- **Added **: Included a standard Python to avoid tracking unnecessary files (e.g., , , ).
- **Added **: Prepared the project for better dependency management.
- **Enhanced Gitea Tools**:
- Implemented in .
- Implemented (via ) in .
#### Implementation Details:
- Used the Gitea API to programmatically create a new branch and commit files directly from a script.
- Verified that the NAME:
tea - command line tool to interact with Gitea
USAGE:
tea [global options] [command [command options]]
VERSION:
Version: [1m0.14.1[0m golang: 1.26.3 go-sdk: v0.25.1
DESCRIPTION:
tea is a productivity helper for Gitea. It can be used to manage most entities on
one or multiple Gitea instances & provides local helpers like 'tea pr checkout'.
tea tries to make use of context provided by the repository in $PWD if available.
tea works best in a upstream/fork workflow, when the local main branch tracks the
upstream repo. tea assumes that local git state is published on the remote before
doing operations with tea. Configuration is persisted in $XDG_CONFIG_HOME/tea.
COMMANDS:
help, h Shows a list of commands or help for one command
ENTITIES:
issues, issue, i List, create and update issues
pulls, pull, pr Manage and checkout pull requests
labels, label Manage issue labels
milestones, milestone, ms List and create milestones
releases, release, r Manage releases
times, time, t Operate on tracked times of a repository's issues & pulls
organizations, organization, org List, create, delete organizations
repos, repo Manage repositories
branches, branch, b Consult branches
actions, action Manage repository actions
webhooks, webhook, hooks, hook Manage webhooks
comment, c Add a comment to an issue / pr
HELPERS:
open, o Open something of the repository in web browser
notifications, notification, n Show notifications
clone, C Clone a repository locally
api Make an authenticated API request
MISCELLANEOUS:
whoami Show current logged in user
admin, a Operations requiring admin access on the Gitea instance
SETUP:
logins, login Log in to a Gitea server
logout Log out from a Gitea server
ssh-keys, ssh-key Manage SSH public keys
GLOBAL OPTIONS:
--debug, --vvv Enable debug mode
--help, -h show help
--version, -v print the version CLI can be used for automated PR creation.
- Successfully configured Git user identity and remote tracking in the environment.
---------
Co-authored-by: Michael <michael@example.com>
Reviewed-on: #1
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Gitea tools packages."""
|
||||
@@ -0,0 +1,208 @@
|
||||
"""Tools for a coding agent to interact with the filesystem and environment."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
|
||||
class CodingTools:
|
||||
"""Tools for a coding agent to interact with the filesystem and environment."""
|
||||
|
||||
def __init__(self, repo_path: str | None = None) -> None:
|
||||
self.repo_path: str = repo_path or os.getcwd()
|
||||
|
||||
def get_working_directory(self) -> str:
|
||||
"""Get the absolute path of the current local repository workspace directory."""
|
||||
return self.repo_path
|
||||
|
||||
def _resolve_path(self, path: str) -> str:
|
||||
"""Resolve a path relative to self.repo_path."""
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
return os.path.abspath(os.path.join(self.repo_path, path))
|
||||
|
||||
def list_files(self, path: str = ".") -> str:
|
||||
"""List all files and directories at the given path (relative to repo root or absolute)."""
|
||||
resolved: str = self._resolve_path(path)
|
||||
try:
|
||||
items: list[str] = os.listdir(resolved)
|
||||
return "\n".join(items)
|
||||
except Exception as e:
|
||||
return f"Error listing files: {str(e)}"
|
||||
|
||||
def read_file(self, path: str, offset: int = 1, limit: int = 250) -> str:
|
||||
"""Read lines from a file, starting at line offset (1-indexed), up to limit lines. Default limit is 250 lines to prevent token bloat. Use 'offset' to scroll/page through larger files."""
|
||||
resolved: str = self._resolve_path(path)
|
||||
try:
|
||||
with open(resolved, 'r', encoding='utf-8', errors='replace') as f:
|
||||
lines: list[str] = f.readlines()
|
||||
|
||||
start_line: int = offset - 1
|
||||
end_line: int = offset + limit - 1
|
||||
|
||||
content_lines: list[str] = lines[start_line:end_line]
|
||||
|
||||
if not content_lines:
|
||||
return "File is empty or offset out of bounds."
|
||||
|
||||
formatted_lines: list[str] = [f"{i + 1}: {line}" for i, line in enumerate(content_lines, start=start_line)]
|
||||
return "\n".join(formatted_lines)
|
||||
except Exception as e:
|
||||
return f"Error reading file: {str(e)}"
|
||||
|
||||
def write_file(self, path: str, content: str) -> str:
|
||||
"""Write content to a file, creating directories as needed."""
|
||||
resolved: str = self._resolve_path(path)
|
||||
try:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(resolved)), exist_ok=True)
|
||||
with open(resolved, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
return f"File {path} written successfully."
|
||||
except Exception as e:
|
||||
return f"Error writing file: {str(e)}"
|
||||
|
||||
def edit_file(self, path: str, old_content: str, new_content: str) -> str:
|
||||
"""Replace occurrences of old_content with new_content in the file."""
|
||||
resolved: str = self._resolve_path(path)
|
||||
try:
|
||||
with open(resolved, 'r', encoding='utf-8', errors='replace') as f:
|
||||
content: str = f.read()
|
||||
if old_content not in content:
|
||||
return f"Error: The specified old content was not found in {path}."
|
||||
|
||||
new_content_full: str = content.replace(old_content, new_content)
|
||||
with open(resolved, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content_full)
|
||||
return f"File {path} edited successfully."
|
||||
except Exception as e:
|
||||
return f"Error editing file: {str(e)}"
|
||||
|
||||
def _parse_verification_commands(self) -> list[str]:
|
||||
agents_md: str = os.path.join(self.repo_path, "AGENTS.md")
|
||||
if not os.path.exists(agents_md):
|
||||
return []
|
||||
try:
|
||||
with open(agents_md, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
commands: list[str] = []
|
||||
in_verification_section = False
|
||||
in_code_block = False
|
||||
current_block: list[str] = []
|
||||
|
||||
for line in content.splitlines():
|
||||
line_lower = line.strip().lower()
|
||||
if line.startswith("#"):
|
||||
if "verification" in line_lower or "test" in line_lower:
|
||||
in_verification_section = True
|
||||
else:
|
||||
in_verification_section = False
|
||||
continue
|
||||
|
||||
if in_verification_section:
|
||||
if line.strip().startswith("```"):
|
||||
if in_code_block:
|
||||
in_code_block = False
|
||||
full_cmd = "\n".join(current_block).strip()
|
||||
if full_cmd:
|
||||
for cmd in full_cmd.splitlines():
|
||||
if cmd.strip() and not cmd.strip().startswith("#"):
|
||||
commands.append(cmd.strip())
|
||||
current_block = []
|
||||
else:
|
||||
in_code_block = True
|
||||
elif in_code_block:
|
||||
current_block.append(line)
|
||||
|
||||
return commands
|
||||
|
||||
def run_verification(self) -> tuple[bool, str]:
|
||||
commands = self._parse_verification_commands()
|
||||
if not commands:
|
||||
return True, "No verification commands found in AGENTS.md."
|
||||
|
||||
log_output = []
|
||||
for cmd in commands:
|
||||
log_output.append(f"Running: {cmd}")
|
||||
try:
|
||||
res = subprocess.run(
|
||||
cmd, shell=True, cwd=self.repo_path,
|
||||
capture_output=True, text=True, timeout=120
|
||||
)
|
||||
if res.returncode != 0:
|
||||
log_output.append(
|
||||
f"Command '{cmd}' failed with exit code {res.returncode}:\n"
|
||||
f"Stdout:\n{res.stdout}\n"
|
||||
f"Stderr:\n{res.stderr}"
|
||||
)
|
||||
return False, "\n".join(log_output)
|
||||
log_output.append(res.stdout or "Success")
|
||||
except subprocess.TimeoutExpired:
|
||||
log_output.append(f"Command '{cmd}' timed out after 120 seconds.")
|
||||
return False, "\n".join(log_output)
|
||||
except Exception as e:
|
||||
log_output.append(f"Failed to execute command '{cmd}': {e}")
|
||||
return False, "\n".join(log_output)
|
||||
return True, "\n".join(log_output)
|
||||
|
||||
def run_command(self, command: str, timeout: int = 120) -> str:
|
||||
"""Execute a shell command in the repository workspace and return stdout and stderr output. Args: command, timeout (default 120 seconds)."""
|
||||
if "tea pr create" in command:
|
||||
success, log_msg = self.run_verification()
|
||||
if not success:
|
||||
return f"Verification failed! You cannot create a pull request because the tests/checks are failing:\n\n{log_msg}\n\nPlease fix the issues and try again."
|
||||
|
||||
try:
|
||||
process: subprocess.Popen[str] = subprocess.Popen(
|
||||
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=self.repo_path
|
||||
)
|
||||
stdout: str
|
||||
stderr: str
|
||||
try:
|
||||
stdout, stderr = process.communicate(timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
stdout, stderr = process.communicate()
|
||||
return f"Command timed out after {timeout} seconds.\n--- STDOUT ---\n{stdout}\n--- STDERR ---\n{stderr}"
|
||||
|
||||
output: str = ""
|
||||
if stdout:
|
||||
output += f"--- STDOUT ---\n{stdout}"
|
||||
if stderr:
|
||||
output += f"\n--- STDERR ---\n{stderr}"
|
||||
|
||||
if process.returncode != 0:
|
||||
return f"Command failed with exit code {process.returncode}:\n{output}"
|
||||
|
||||
return output if output else "Command executed successfully (no output)."
|
||||
except Exception as e:
|
||||
return f"Error running command: {str(e)}"
|
||||
|
||||
def grep_search(self, pattern: str, path: str = ".") -> str:
|
||||
"""Search for pattern in files under path using grep (case-insensitive)."""
|
||||
resolved: str = self._resolve_path(path)
|
||||
try:
|
||||
command: str = f"grep -ri '{pattern}' {resolved}"
|
||||
process: subprocess.Popen[str] = subprocess.Popen(
|
||||
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=self.repo_path
|
||||
)
|
||||
stdout: str
|
||||
stderr: str
|
||||
try:
|
||||
stdout, stderr = process.communicate(timeout=30)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
stdout, stderr = process.communicate()
|
||||
return f"Grep search timed out after 30 seconds.\nStdout: {stdout}\nStderr: {stderr}"
|
||||
|
||||
if process.returncode != 0 and not stdout:
|
||||
return f"No matches found for '{pattern}'."
|
||||
|
||||
output: str = stdout
|
||||
if stderr:
|
||||
output += f"\nError: {stderr}"
|
||||
return output
|
||||
except Exception as e:
|
||||
return f"Error during grep search: {str(e)}"
|
||||
@@ -0,0 +1,41 @@
|
||||
from typing import Any
|
||||
from gitea.client import GiteaClient
|
||||
|
||||
|
||||
class FileTools:
|
||||
"""Tools for Gitea file/content operations."""
|
||||
|
||||
def __init__(self, client: GiteaClient) -> None:
|
||||
self._client = client
|
||||
|
||||
def get_file_content(self, owner: str, repo: str, path: str) -> str:
|
||||
try:
|
||||
content = self._client.get_file_content(owner, repo, path)
|
||||
if isinstance(content, list):
|
||||
return "\n".join(content)
|
||||
return content
|
||||
except Exception as e:
|
||||
return f"Error getting file content: {str(e)}"
|
||||
|
||||
def get_file_content_with_ref(self, owner: str, repo: str, path: str, ref: str = "master") -> str:
|
||||
try:
|
||||
content = self._client.get_file_content(owner, repo, path, ref)
|
||||
if isinstance(content, list):
|
||||
return "\n".join(content)
|
||||
return content
|
||||
except Exception as e:
|
||||
return f"Error getting file content: {str(e)}"
|
||||
|
||||
def commit_file(self, owner: str, repo: str, path: str, message: str, content: str, branch: str) -> str:
|
||||
try:
|
||||
self._client.update_file(owner, repo, path, message, content, branch)
|
||||
return f"File '{path}' committed successfully to {owner}/{repo}."
|
||||
except Exception as e:
|
||||
return f"Error committing file: {str(e)}"
|
||||
|
||||
def update_file(self, owner: str, repo: str, path: str, message: str, content: str, branch: str) -> str:
|
||||
try:
|
||||
self._client.update_file(owner, repo, path, message, content, branch)
|
||||
return f"File '{path}' updated in {owner}/{repo}."
|
||||
except Exception as e:
|
||||
return f"Error updating file: {str(e)}"
|
||||
@@ -0,0 +1,16 @@
|
||||
from typing import Any
|
||||
from gitea.client import GiteaClient
|
||||
|
||||
|
||||
class GitTools:
|
||||
"""Tools for Gitea git ref operations."""
|
||||
|
||||
def __init__(self, client: GiteaClient) -> None:
|
||||
self._client = client
|
||||
|
||||
def create_branch(self, owner: str, repo: str, ref: str, sha: str) -> str:
|
||||
try:
|
||||
self._client.create_ref(owner, repo, ref, sha)
|
||||
return f"Branch '{ref}' created successfully in {owner}/{repo}."
|
||||
except Exception as e:
|
||||
return f"Error creating branch: {str(e)}"
|
||||
@@ -0,0 +1,122 @@
|
||||
from gitea.tools.issue_tools import IssueTools
|
||||
from gitea.tools.pr_tools import PRTools
|
||||
from gitea.tools.file_tools import FileTools
|
||||
from gitea.tools.git_tools import GitTools
|
||||
from gitea.client import GiteaClient
|
||||
|
||||
|
||||
class GiteaTools:
|
||||
"""Facade for Gitea tool operations - delegates to focused tool classes."""
|
||||
|
||||
def __init__(self, client: GiteaClient) -> None:
|
||||
self._client = client
|
||||
self.issue_tools = IssueTools(client)
|
||||
self.pr_tools = PRTools(client)
|
||||
self.file_tools = FileTools(client)
|
||||
self.git_tools = GitTools(client)
|
||||
|
||||
# ---- Issue operations (delegated) ----
|
||||
|
||||
def get_issue(self, owner: str, repo: str, issue_number: int) -> str:
|
||||
"""Get the details of a specific issue. Args: owner (repo owner), repo (repo name), issue_number (issue ID)."""
|
||||
return self.issue_tools.get_issue(owner, repo, issue_number)
|
||||
|
||||
def get_pull_request(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
"""Get the details of a specific pull request. Args: owner (repo owner), repo (repo name), pull_number (PR ID)."""
|
||||
return self.pr_tools.get_pull_request(owner, repo, pull_number)
|
||||
|
||||
def close_issue(self, owner: str, repo: str, issue_number: int) -> str:
|
||||
"""Close an issue. Args: owner (repo owner), repo (repo name), issue_number (issue ID)."""
|
||||
return self.issue_tools.close_issue(owner, repo, issue_number)
|
||||
|
||||
def close_pull_request(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
"""Close a pull request. Args: owner (repo owner), repo (repo name), pull_number (PR ID)."""
|
||||
return self.pr_tools.close_pull_request(owner, repo, pull_number)
|
||||
|
||||
def get_issue_comments(self, owner: str, repo: str, issue_number: int) -> str:
|
||||
"""Get all comments on an issue. Args: owner (repo owner), repo (repo name), issue_number (issue ID)."""
|
||||
return self.issue_tools.get_issue_comments(owner, repo, issue_number)
|
||||
|
||||
def get_pull_request_comments(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
"""Get all comments on a pull request. Args: owner (repo owner), repo (repo name), pull_number (PR ID)."""
|
||||
return self.pr_tools.get_pull_request_comments(owner, repo, pull_number)
|
||||
|
||||
def list_assigned_issues(self) -> list[dict]:
|
||||
"""List all issues assigned to the authenticated user across all repos."""
|
||||
return self.issue_tools.list_assigned_issues()
|
||||
|
||||
def list_assigned_pull_requests(self) -> list[dict]:
|
||||
"""List all pull requests assigned to the authenticated user across all repos."""
|
||||
return self.pr_tools.list_assigned_pull_requests()
|
||||
|
||||
def list_issues(self, owner: str, repo: str, state: str = "open") -> str:
|
||||
"""List issues in a repository. Args: owner (repo owner), repo (repo name), state (open, closed, or all)."""
|
||||
return self.issue_tools.list_issues(owner, repo, state)
|
||||
|
||||
def list_pull_requests(self, owner: str, repo: str, state: str = "open") -> str:
|
||||
"""List pull requests in a repository. Args: owner (repo owner), repo (repo name), state (open, closed, or all)."""
|
||||
return self.pr_tools.list_pull_requests(owner, repo, state)
|
||||
|
||||
def get_file_content(self, owner: str, repo: str, path: str) -> str:
|
||||
"""Get the content of a file from a repository. Args: owner (repo owner), repo (repo name), path (file path)."""
|
||||
return self.file_tools.get_file_content(owner, repo, path)
|
||||
|
||||
def create_pull_request(self, owner: str, repo: str, head: str, base: str, title: str, description: str = "") -> str:
|
||||
"""Create a new pull request. Args: owner, repo, head (source branch), base (target branch), title, description."""
|
||||
return self.pr_tools.create_pull_request(owner, repo, head, base, title, description)
|
||||
|
||||
def create_issue(self, owner: str, repo: str, title: str, body: str, labels: list[str] | None = None, assignees: list[str] | None = None) -> str:
|
||||
"""Create a new issue. Args: owner, repo, title, body, labels (optional), assignees (optional)."""
|
||||
return self.issue_tools.create_issue(owner, repo, title, body, labels, assignees)
|
||||
|
||||
def create_branch(self, owner: str, repo: str, ref: str, sha: str) -> str:
|
||||
"""Create a new branch in a repository. Args: owner, repo, ref (branch name), sha (commit SHA)."""
|
||||
return self.git_tools.create_branch(owner, repo, ref, sha)
|
||||
|
||||
def commit_file(self, owner: str, repo: str, path: str, message: str, content: str, branch: str) -> str:
|
||||
"""Commit a file to a repository. Args: owner, repo, path, message, content, branch."""
|
||||
return self.file_tools.commit_file(owner, repo, path, message, content, branch)
|
||||
|
||||
def add_label_to_issue(self, owner: str, repo: str, issue_number: int, label: str) -> str:
|
||||
"""Add a label to an issue. Args: owner, repo, issue_number, label."""
|
||||
return self.issue_tools.add_label_to_issue(owner, repo, issue_number, label)
|
||||
|
||||
def add_label_to_pr(self, owner: str, repo: str, pr_number: int, label: str) -> str:
|
||||
"""Add a label to a pull request. Args: owner, repo, pr_number, label."""
|
||||
return self.pr_tools.add_label_to_pr(owner, repo, pr_number, label)
|
||||
|
||||
def add_comment_to_issue(self, owner: str, repo: str, issue_number: int, body: str) -> str:
|
||||
"""Add a comment to an issue. Args: owner, repo, issue_number, body."""
|
||||
return self.issue_tools.add_comment_to_issue(owner, repo, issue_number, body)
|
||||
|
||||
def get_pull_request_diff(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
"""Get the diff of a pull request. Args: owner, repo, pull_number."""
|
||||
return self.pr_tools.get_pull_request_diff(owner, repo, pull_number)
|
||||
|
||||
def get_pull_request_patch(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
"""Get the patch of a pull request. Args: owner, repo, pull_number."""
|
||||
return self.pr_tools.get_pull_request_patch(owner, repo, pull_number)
|
||||
|
||||
def approve_pull_request(self, owner: str, repo: str, pull_number: int, comment: str) -> str:
|
||||
"""Approve a pull request. Args: owner, repo, pull_number, comment."""
|
||||
return self.pr_tools.approve_pull_request(owner, repo, pull_number, comment)
|
||||
|
||||
def request_changes(self, owner: str, repo: str, pull_number: int, comment: str) -> str:
|
||||
"""Request changes on a pull request. Args: owner, repo, pull_number, comment."""
|
||||
return self.pr_tools.request_changes(owner, repo, pull_number, comment)
|
||||
|
||||
def add_comment(self, owner: str, repo: str, issue_number: int, body: str) -> str:
|
||||
"""Add a comment to an issue. Args: owner, repo, issue_number, body."""
|
||||
return self.issue_tools.add_comment(owner, repo, issue_number, body)
|
||||
|
||||
def add_label(self, owner: str, repo: str, issue_number: int, label: str) -> str:
|
||||
"""Add a label to an issue. Args: owner, repo, issue_number, label."""
|
||||
return self.issue_tools.add_label(owner, repo, issue_number, label)
|
||||
|
||||
def get_file_content_with_ref(self, owner: str, repo: str, path: str, ref: str = "master") -> str:
|
||||
"""Get file content with a specific git ref. Args: owner, repo, path, ref (branch/tag)."""
|
||||
return self.file_tools.get_file_content_with_ref(owner, repo, path, ref)
|
||||
|
||||
def update_file(self, owner: str, repo: str, path: str, message: str, content: str, branch: str) -> str:
|
||||
"""Update a file in a repository. Args: owner, repo, path, message, content, branch."""
|
||||
return self.file_tools.update_file(owner, repo, path, message, content, branch)
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Tools for Gitea issue operations."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.models import IssueModel, CommentModel, LabelModel
|
||||
|
||||
|
||||
class IssueTools:
|
||||
"""Tools for Gitea issue operations."""
|
||||
|
||||
def __init__(self, client: GiteaClient) -> None:
|
||||
self._client = client
|
||||
|
||||
def get_issue(self, owner: str, repo: str, issue_number: int) -> str:
|
||||
try:
|
||||
issue: IssueModel = self._client.get_issue(owner, repo, issue_number)
|
||||
return issue.model_dump_json(indent=2)
|
||||
except Exception as e:
|
||||
return f"Error getting issue: {str(e)}"
|
||||
|
||||
def close_issue(self, owner: str, repo: str, issue_number: int) -> str:
|
||||
try:
|
||||
self._client.close_issue(owner, repo, issue_number)
|
||||
return f"Issue #{issue_number} closed successfully."
|
||||
except Exception as e:
|
||||
return f"Error closing issue: {str(e)}"
|
||||
|
||||
def get_issue_comments(self, owner: str, repo: str, issue_number: int) -> str:
|
||||
try:
|
||||
comments: list[CommentModel] = self._client.get_issue_comments(owner, repo, issue_number)
|
||||
return json.dumps([c.model_dump() for c in comments], indent=2)
|
||||
except Exception as e:
|
||||
return f"Error getting issue comments: {str(e)}"
|
||||
|
||||
def list_assigned_issues(self) -> list[dict[str, Any]]:
|
||||
try:
|
||||
repos = self._client.list_all_user_repos()
|
||||
all_issues: list[dict[str, Any]] = []
|
||||
for repo in repos:
|
||||
owner = repo.owner
|
||||
repo_name = repo.name
|
||||
issues = self._client.list_assigned_issues(owner, repo_name)
|
||||
if issues:
|
||||
all_issues.extend([issue.model_dump() if hasattr(issue, 'model_dump') else issue for issue in issues])
|
||||
return all_issues
|
||||
except Exception as e:
|
||||
print(f"DEBUG: list_assigned_issues error: {e}")
|
||||
return []
|
||||
|
||||
def list_issues(self, owner: str, repo: str, state: str = "open") -> str:
|
||||
try:
|
||||
issues = self._client.list_repo_issues(owner, repo, state)
|
||||
if not issues:
|
||||
return f"No issues in {owner}/{repo}."
|
||||
summary = [f"#{issue.number}: {issue.title}" for issue in issues]
|
||||
return "\n".join(summary)
|
||||
except Exception as e:
|
||||
return f"Error listing issues: {str(e)}"
|
||||
|
||||
def create_issue(self, owner: str, repo: str, title: str, body: str, labels: list[str] | None = None, assignees: list[str] | None = None) -> str:
|
||||
try:
|
||||
issue = self._client.create_issue(owner, repo, title, body, labels, assignees)
|
||||
return f"Issue #{issue.number} created successfully in {owner}/{repo}."
|
||||
except Exception as e:
|
||||
return f"Error creating issue: {str(e)}"
|
||||
|
||||
def add_label_to_issue(self, owner: str, repo: str, issue_number: int, label: str) -> str:
|
||||
try:
|
||||
self._client.add_label(owner, repo, issue_number, label)
|
||||
return f"Label '{label}' added to issue #{issue_number}."
|
||||
except Exception as e:
|
||||
return f"Error adding label to issue #{issue_number}: {e}"
|
||||
|
||||
def add_comment_to_issue(self, owner: str, repo: str, issue_number: int, body: str) -> str:
|
||||
try:
|
||||
self._client.add_comment(owner, repo, issue_number, body)
|
||||
return f"Comment added to issue #{issue_number}."
|
||||
except Exception as e:
|
||||
return f"Error adding comment to issue #{issue_number}: {e}"
|
||||
|
||||
def add_comment(self, owner: str, repo: str, issue_number: int, body: str) -> str:
|
||||
try:
|
||||
comment = self._client.add_comment(owner, repo, issue_number, body)
|
||||
return f"Comment added to #{issue_number}."
|
||||
except Exception as e:
|
||||
return f"Error adding comment: {str(e)}"
|
||||
|
||||
def add_label(self, owner: str, repo: str, issue_number: int, label: str) -> str:
|
||||
try:
|
||||
self._client.add_label(owner, repo, issue_number, label)
|
||||
return f"Label '{label}' added to #{issue_number}."
|
||||
except Exception as e:
|
||||
return f"Error adding label: {str(e)}"
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Tools for Gitea pull request operations."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.models import PullRequestModel, CommentModel
|
||||
|
||||
|
||||
class PRTools:
|
||||
"""Tools for Gitea pull request operations."""
|
||||
|
||||
def __init__(self, client: GiteaClient) -> None:
|
||||
self._client = client
|
||||
|
||||
def get_pull_request(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
try:
|
||||
pr: PullRequestModel = self._client.get_pull_request(owner, repo, pull_number)
|
||||
return pr.model_dump_json(indent=2)
|
||||
except Exception as e:
|
||||
return f"Error getting pull request: {str(e)}"
|
||||
|
||||
def close_pull_request(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
try:
|
||||
self._client.close_pull_request(owner, repo, pull_number)
|
||||
return f"Pull request #{pull_number} closed successfully."
|
||||
except Exception as e:
|
||||
return f"Error closing pull request: {str(e)}"
|
||||
|
||||
def get_pull_request_comments(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
try:
|
||||
comments: list[CommentModel] = self._client.get_pull_request_comments(owner, repo, pull_number)
|
||||
return json.dumps([c.model_dump() for c in comments], indent=2)
|
||||
except Exception as e:
|
||||
return f"Error getting PR comments: {str(e)}"
|
||||
|
||||
def list_assigned_pull_requests(self) -> list[dict[str, Any]]:
|
||||
try:
|
||||
repos = self._client.list_all_user_repos()
|
||||
all_prs: list[dict[str, Any]] = []
|
||||
for repo_info in repos:
|
||||
repo_owner = repo_info.owner
|
||||
repo_name = repo_info.name
|
||||
prs = self._client.list_assigned_pull_requests(repo_owner, repo_name)
|
||||
if prs:
|
||||
all_prs.extend([pr.model_dump() if hasattr(pr, 'model_dump') else pr for pr in prs])
|
||||
return all_prs
|
||||
except Exception as e:
|
||||
print(f"DEBUG: list_assigned_pull_requests error: {e}")
|
||||
return []
|
||||
|
||||
def list_pull_requests(self, owner: str, repo: str, state: str = "open") -> str:
|
||||
try:
|
||||
prs = self._client.list_repo_pull_requests(owner, repo, state)
|
||||
if not prs:
|
||||
return f"No PRs in {owner}/{repo}."
|
||||
summary = [f"#{pr.number}: {pr.title}" for pr in prs]
|
||||
return "\n".join(summary)
|
||||
except Exception as e:
|
||||
return f"Error listing PRs: {str(e)}"
|
||||
|
||||
def create_pull_request(self, owner: str, repo: str, head: str, base: str, title: str, description: str = "") -> str:
|
||||
try:
|
||||
pr = self._client.create_pr_via_tea(owner, repo, title, description, head, base)
|
||||
return pr.model_dump_json(indent=2)
|
||||
except Exception as e:
|
||||
return f"Error creating PR: {str(e)}"
|
||||
|
||||
def add_label_to_pr(self, owner: str, repo: str, pr_number: int, label: str) -> str:
|
||||
try:
|
||||
self._client.add_label_pr(owner, repo, pr_number, label)
|
||||
return f"Label '{label}' added to PR #{pr_number}."
|
||||
except Exception as e:
|
||||
return f"Error adding label to PR #{pr_number}: {e}"
|
||||
|
||||
def get_pull_request_diff(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
try:
|
||||
return self._client.get_pull_request_diff(owner, repo, pull_number)
|
||||
except Exception as e:
|
||||
return f"Error getting PR diff: {str(e)}"
|
||||
|
||||
def get_pull_request_patch(self, owner: str, repo: str, pull_number: int) -> str:
|
||||
try:
|
||||
return self._client.get_pull_request_patch(owner, repo, pull_number)
|
||||
except Exception as e:
|
||||
return f"Error getting PR patch: {str(e)}"
|
||||
|
||||
def approve_pull_request(self, owner: str, repo: str, pull_number: int, comment: str) -> str:
|
||||
try:
|
||||
self._client.approve_pr(owner, repo, pull_number, comment)
|
||||
return f"Approved PR #{pull_number}."
|
||||
except Exception as e:
|
||||
return f"Error approving PR: {str(e)}"
|
||||
|
||||
def request_changes(self, owner: str, repo: str, pull_number: int, comment: str) -> str:
|
||||
try:
|
||||
self._client.request_changes_pr(owner, repo, pull_number, comment)
|
||||
return f"Requested changes on PR #{pull_number}."
|
||||
except Exception as e:
|
||||
return f"Error requesting changes: {str(e)}"
|
||||
Reference in New Issue
Block a user