"""Pull Request Handler - manages Gitea pull requests, reviews, and merges""" import json from typing import Any from plugins.gitea.client import GiteaClient def get_tool_specifications() -> list[dict[str, Any]]: """Return tool specifications for ToolGenerator""" return [ # === PULL REQUESTS === { "name": "list_pull_requests", "method_name": "list_pull_requests", "description": "List pull requests in a Gitea repository with filters.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "state": { "type": "string", "description": "Filter by state", "enum": ["open", "closed", "all"], "default": "open", }, "sort": { "type": "string", "description": "Sort by", "enum": ["created", "updated", "comments", "recentupdate"], "default": "created", }, "labels": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated label IDs", }, "milestone": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Milestone name", }, "page": { "type": "integer", "description": "Page number", "default": 1, "minimum": 1, }, "limit": { "type": "integer", "description": "Items per page (1-100)", "default": 30, "minimum": 1, "maximum": 100, }, }, "required": ["owner", "repo"], }, "scope": "read", }, { "name": "get_pull_request", "method_name": "get_pull_request", "description": "Get details of a specific pull request by number.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "read", }, { "name": "create_pull_request", "method_name": "create_pull_request", "description": "Create a new pull request in a Gitea repository.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "title": { "type": "string", "description": "Pull request title", "minLength": 1, "maxLength": 255, }, "head": { "type": "string", "description": "Source branch (head branch)", "minLength": 1, }, "base": { "type": "string", "description": "Target branch (base branch)", "minLength": 1, }, "body": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Pull request description (supports Markdown)", }, "assignee": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Assignee username", }, "assignees": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "List of assignee usernames", }, "labels": { "anyOf": [ {"type": "array", "items": {"type": "integer"}}, {"type": "null"}, ], "description": "List of label IDs", }, "milestone": { "anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Milestone ID", }, }, "required": ["owner", "repo", "title", "head", "base"], }, "scope": "write", }, { "name": "update_pull_request", "method_name": "update_pull_request", "description": "Update an existing pull request. Can modify title, body, state, assignees, labels, and milestone.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, "title": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Pull request title", }, "body": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Pull request description", }, "state": { "anyOf": [{"type": "string", "enum": ["open", "closed"]}, {"type": "null"}], "description": "Pull request state", }, "assignee": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Assignee username", }, "assignees": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "List of assignee usernames", }, "labels": { "anyOf": [ {"type": "array", "items": {"type": "integer"}}, {"type": "null"}, ], "description": "List of label IDs", }, "milestone": { "anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Milestone ID", }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "write", }, { "name": "merge_pull_request", "method_name": "merge_pull_request", "description": "Merge a pull request using specified merge method.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, "method": { "type": "string", "description": "Merge method", "enum": ["merge", "rebase", "rebase-merge", "squash"], "default": "merge", }, "title": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Merge commit title", }, "message": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Merge commit message", }, "delete_branch_after_merge": { "type": "boolean", "description": "Delete source branch after merge", "default": False, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "write", }, { "name": "close_pull_request", "method_name": "close_pull_request", "description": "Close a pull request without merging.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "write", }, { "name": "reopen_pull_request", "method_name": "reopen_pull_request", "description": "Reopen a closed pull request.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "write", }, # === PR DETAILS === { "name": "list_pr_commits", "method_name": "list_pr_commits", "description": "List all commits in a pull request.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "read", }, { "name": "list_pr_files", "method_name": "list_pr_files", "description": "List all changed files in a pull request with additions/deletions count.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "read", }, { "name": "get_pr_diff", "method_name": "get_pr_diff", "description": "Get the unified diff of a pull request.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "read", }, { "name": "list_pr_comments", "method_name": "list_pr_comments", "description": "List all comments on a pull request.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "read", }, { "name": "create_pr_comment", "method_name": "create_pr_comment", "description": "Add a comment to a pull request.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, "body": { "type": "string", "description": "Comment body (supports Markdown)", "minLength": 1, }, }, "required": ["owner", "repo", "pr_number", "body"], }, "scope": "write", }, # === PR REVIEWS === { "name": "list_pr_reviews", "method_name": "list_pr_reviews", "description": "List all reviews on a pull request.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, }, "required": ["owner", "repo", "pr_number"], }, "scope": "read", }, { "name": "create_pr_review", "method_name": "create_pr_review", "description": "Create a review for a pull request (approve, request changes, or comment).", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, "event": { "type": "string", "description": "Review event type", "enum": ["APPROVED", "REQUEST_CHANGES", "COMMENT"], }, "body": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Review comment (optional for APPROVED)", }, }, "required": ["owner", "repo", "pr_number", "event"], }, "scope": "write", }, { "name": "request_pr_reviewers", "method_name": "request_pr_reviewers", "description": "Request reviewers for a pull request.", "schema": { "type": "object", "properties": { "owner": {"type": "string", "description": "Repository owner", "minLength": 1}, "repo": {"type": "string", "description": "Repository name", "minLength": 1}, "pr_number": { "type": "integer", "description": "Pull request number", "minimum": 1, }, "reviewers": { "type": "array", "items": {"type": "string"}, "description": "List of reviewer usernames", "minItems": 1, }, }, "required": ["owner", "repo", "pr_number", "reviewers"], }, "scope": "write", }, ] async def list_pull_requests( client: GiteaClient, owner: str, repo: str, state: str = "open", sort: str = "created", labels: str | None = None, milestone: str | None = None, page: int = 1, limit: int = 30, ) -> str: """List repository pull requests""" params = { "state": state, "sort": sort, "labels": labels, "milestone": milestone, "page": page, "limit": limit, } prs = await client.list_pull_requests(owner, repo, params) result = {"success": True, "count": len(prs), "pull_requests": prs} return json.dumps(result, indent=2) async def get_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str: """Get pull request details""" pr = await client.get_pull_request(owner, repo, pr_number) result = {"success": True, "pull_request": pr} return json.dumps(result, indent=2) async def create_pull_request( client: GiteaClient, owner: str, repo: str, title: str, head: str, base: str, body: str | None = None, assignee: str | None = None, assignees: list[str] | None = None, labels: list[int] | None = None, milestone: int | None = None, ) -> str: """Create a new pull request""" data = { "title": title, "head": head, "base": base, "body": body, "assignee": assignee, "assignees": assignees, "labels": labels, "milestone": milestone, } pr = await client.create_pull_request(owner, repo, data) result = { "success": True, "message": f"Pull request #{pr['number']} created successfully", "pull_request": pr, } return json.dumps(result, indent=2) async def update_pull_request( client: GiteaClient, owner: str, repo: str, pr_number: int, **kwargs ) -> str: """Update a pull request""" # Build update data from kwargs data = { k: v for k, v in kwargs.items() if v is not None and k not in ["owner", "repo", "pr_number"] } pr = await client.update_pull_request(owner, repo, pr_number, data) result = { "success": True, "message": f"Pull request #{pr_number} updated successfully", "pull_request": pr, } return json.dumps(result, indent=2) async def merge_pull_request( client: GiteaClient, owner: str, repo: str, pr_number: int, method: str = "merge", title: str | None = None, message: str | None = None, delete_branch_after_merge: bool = False, ) -> str: """Merge a pull request""" data: dict = {"Do": method} if title is not None: data["MergeTitleField"] = title if message is not None: data["MergeMessageField"] = message if delete_branch_after_merge: data["delete_branch_after_merge"] = True merge_result = await client.merge_pull_request(owner, repo, pr_number, data) result = { "success": True, "message": f"Pull request #{pr_number} merged successfully using {method}", "result": merge_result, } return json.dumps(result, indent=2) async def close_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str: """Close a pull request""" data = {"state": "closed"} pr = await client.update_pull_request(owner, repo, pr_number, data) result = { "success": True, "message": f"Pull request #{pr_number} closed successfully", "pull_request": pr, } return json.dumps(result, indent=2) async def reopen_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str: """Reopen a pull request""" data = {"state": "open"} pr = await client.update_pull_request(owner, repo, pr_number, data) result = { "success": True, "message": f"Pull request #{pr_number} reopened successfully", "pull_request": pr, } return json.dumps(result, indent=2) # PR Details async def list_pr_commits(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str: """List pull request commits""" commits = await client.list_pr_commits(owner, repo, pr_number) result = {"success": True, "count": len(commits), "commits": commits} return json.dumps(result, indent=2) async def list_pr_files(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str: """List pull request files""" files = await client.list_pr_files(owner, repo, pr_number) result = {"success": True, "count": len(files), "files": files} return json.dumps(result, indent=2) async def get_pr_diff(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str: """Get pull request diff""" diff = await client.get_pr_diff(owner, repo, pr_number) result = {"success": True, "diff": diff} return json.dumps(result, indent=2) async def list_pr_comments(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str: """List pull request comments""" # PR comments are same as issue comments in Gitea API comments = await client.list_issue_comments(owner, repo, pr_number) result = {"success": True, "count": len(comments), "comments": comments} return json.dumps(result, indent=2) async def create_pr_comment( client: GiteaClient, owner: str, repo: str, pr_number: int, body: str ) -> str: """Create pull request comment""" data = {"body": body} comment = await client.create_issue_comment(owner, repo, pr_number, data) result = { "success": True, "message": f"Comment added to pull request #{pr_number}", "comment": comment, } return json.dumps(result, indent=2) # PR Reviews async def list_pr_reviews(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str: """List pull request reviews""" reviews = await client.list_pr_reviews(owner, repo, pr_number) result = {"success": True, "count": len(reviews), "reviews": reviews} return json.dumps(result, indent=2) async def create_pr_review( client: GiteaClient, owner: str, repo: str, pr_number: int, event: str, body: str | None = None ) -> str: """Create pull request review""" data = {"event": event, "body": body} review = await client.create_pr_review(owner, repo, pr_number, data) result = { "success": True, "message": f"Review {event} added to pull request #{pr_number}", "review": review, } return json.dumps(result, indent=2) async def request_pr_reviewers( client: GiteaClient, owner: str, repo: str, pr_number: int, reviewers: list[str] ) -> str: """Request pull request reviewers""" data = {"reviewers": reviewers} reviewers_result = await client.request_pr_reviewers(owner, repo, pr_number, data) result = { "success": True, "message": f"Reviewers requested for pull request #{pr_number}", "result": reviewers_result, } return json.dumps(result, indent=2)