mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-21 04:50:39 +08:00
Merge pull request #1811 from loolootech/feature/gitea-implement
[Feature] - Gitea implement
This commit is contained in:
@ -58,6 +58,9 @@ def filter_ignored(files, platform = 'github'):
|
||||
files = files_o
|
||||
elif platform == 'azure':
|
||||
files = [f for f in files if not r.match(f)]
|
||||
elif platform == 'gitea':
|
||||
files = [f for f in files if not r.match(f.get("filename", ""))]
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Could not filter file list: {e}")
|
||||
|
@ -8,6 +8,7 @@ from pr_agent.git_providers.bitbucket_server_provider import \
|
||||
from pr_agent.git_providers.codecommit_provider import CodeCommitProvider
|
||||
from pr_agent.git_providers.gerrit_provider import GerritProvider
|
||||
from pr_agent.git_providers.git_provider import GitProvider
|
||||
from pr_agent.git_providers.gitea_provider import GiteaProvider
|
||||
from pr_agent.git_providers.github_provider import GithubProvider
|
||||
from pr_agent.git_providers.gitlab_provider import GitLabProvider
|
||||
from pr_agent.git_providers.local_git_provider import LocalGitProvider
|
||||
@ -22,7 +23,7 @@ _GIT_PROVIDERS = {
|
||||
'codecommit': CodeCommitProvider,
|
||||
'local': LocalGitProvider,
|
||||
'gerrit': GerritProvider,
|
||||
'gitea': GiteaProvider,
|
||||
'gitea': GiteaProvider
|
||||
}
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
128
pr_agent/servers/gitea_app.py
Normal file
128
pr_agent/servers/gitea_app.py
Normal file
@ -0,0 +1,128 @@
|
||||
import asyncio
|
||||
import copy
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
from fastapi import APIRouter, FastAPI, HTTPException, Request, Response
|
||||
from starlette.background import BackgroundTasks
|
||||
from starlette.middleware import Middleware
|
||||
from starlette_context import context
|
||||
from starlette_context.middleware import RawContextMiddleware
|
||||
|
||||
from pr_agent.agent.pr_agent import PRAgent
|
||||
from pr_agent.config_loader import get_settings, global_settings
|
||||
from pr_agent.log import LoggingFormat, get_logger, setup_logger
|
||||
from pr_agent.servers.utils import verify_signature
|
||||
|
||||
# Setup logging and router
|
||||
setup_logger(fmt=LoggingFormat.JSON, level=get_settings().get("CONFIG.LOG_LEVEL", "DEBUG"))
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/api/v1/gitea_webhooks")
|
||||
async def handle_gitea_webhooks(background_tasks: BackgroundTasks, request: Request, response: Response):
|
||||
"""Handle incoming Gitea webhook requests"""
|
||||
get_logger().debug("Received a Gitea webhook")
|
||||
|
||||
body = await get_body(request)
|
||||
|
||||
# Set context for the request
|
||||
context["settings"] = copy.deepcopy(global_settings)
|
||||
context["git_provider"] = {}
|
||||
|
||||
# Handle the webhook in background
|
||||
background_tasks.add_task(handle_request, body, event=request.headers.get("X-Gitea-Event", None))
|
||||
return {}
|
||||
|
||||
async def get_body(request: Request):
|
||||
"""Parse and verify webhook request body"""
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception as e:
|
||||
get_logger().error("Error parsing request body", artifact={'error': e})
|
||||
raise HTTPException(status_code=400, detail="Error parsing request body") from e
|
||||
|
||||
|
||||
# Verify webhook signature
|
||||
webhook_secret = getattr(get_settings().gitea, 'webhook_secret', None)
|
||||
if webhook_secret:
|
||||
body_bytes = await request.body()
|
||||
signature_header = request.headers.get('x-gitea-signature', None)
|
||||
if not signature_header:
|
||||
get_logger().error("Missing signature header")
|
||||
raise HTTPException(status_code=400, detail="Missing signature header")
|
||||
|
||||
try:
|
||||
verify_signature(body_bytes, webhook_secret, f"sha256={signature_header}")
|
||||
except Exception as ex:
|
||||
get_logger().error(f"Invalid signature: {ex}")
|
||||
raise HTTPException(status_code=401, detail="Invalid signature")
|
||||
|
||||
return body
|
||||
|
||||
async def handle_request(body: Dict[str, Any], event: str):
|
||||
"""Process Gitea webhook events"""
|
||||
action = body.get("action")
|
||||
if not action:
|
||||
get_logger().debug("No action found in request body")
|
||||
return {}
|
||||
|
||||
agent = PRAgent()
|
||||
|
||||
# Handle different event types
|
||||
if event == "pull_request":
|
||||
if action in ["opened", "reopened", "synchronized"]:
|
||||
await handle_pr_event(body, event, action, agent)
|
||||
elif event == "issue_comment":
|
||||
if action == "created":
|
||||
await handle_comment_event(body, event, action, agent)
|
||||
|
||||
return {}
|
||||
|
||||
async def handle_pr_event(body: Dict[str, Any], event: str, action: str, agent: PRAgent):
|
||||
"""Handle pull request events"""
|
||||
pr = body.get("pull_request", {})
|
||||
if not pr:
|
||||
return
|
||||
|
||||
api_url = pr.get("url")
|
||||
if not api_url:
|
||||
return
|
||||
|
||||
# Handle PR based on action
|
||||
if action in ["opened", "reopened"]:
|
||||
commands = get_settings().get("gitea.pr_commands", [])
|
||||
for command in commands:
|
||||
await agent.handle_request(api_url, command)
|
||||
elif action == "synchronized":
|
||||
# Handle push to PR
|
||||
await agent.handle_request(api_url, "/review --incremental")
|
||||
|
||||
async def handle_comment_event(body: Dict[str, Any], event: str, action: str, agent: PRAgent):
|
||||
"""Handle comment events"""
|
||||
comment = body.get("comment", {})
|
||||
if not comment:
|
||||
return
|
||||
|
||||
comment_body = comment.get("body", "")
|
||||
if not comment_body or not comment_body.startswith("/"):
|
||||
return
|
||||
|
||||
pr_url = body.get("pull_request", {}).get("url")
|
||||
if not pr_url:
|
||||
return
|
||||
|
||||
await agent.handle_request(pr_url, comment_body)
|
||||
|
||||
# FastAPI app setup
|
||||
middleware = [Middleware(RawContextMiddleware)]
|
||||
app = FastAPI(middleware=middleware)
|
||||
app.include_router(router)
|
||||
|
||||
def start():
|
||||
"""Start the Gitea webhook server"""
|
||||
port = int(os.environ.get("PORT", "3000"))
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||
|
||||
if __name__ == "__main__":
|
||||
start()
|
@ -68,6 +68,11 @@ webhook_secret = "<WEBHOOK SECRET>" # Optional, may be commented out.
|
||||
personal_access_token = ""
|
||||
shared_secret = "" # webhook secret
|
||||
|
||||
[gitea]
|
||||
# Gitea personal access token
|
||||
personal_access_token=""
|
||||
webhook_secret="" # webhook secret
|
||||
|
||||
[bitbucket]
|
||||
# For Bitbucket authentication
|
||||
auth_type = "bearer" # "bearer" or "basic"
|
||||
|
@ -281,6 +281,15 @@ push_commands = [
|
||||
"/review",
|
||||
]
|
||||
|
||||
[gitea_app]
|
||||
url = "https://gitea.com"
|
||||
handle_push_trigger = false
|
||||
pr_commands = [
|
||||
"/describe",
|
||||
"/review",
|
||||
"/improve",
|
||||
]
|
||||
|
||||
[bitbucket_app]
|
||||
pr_commands = [
|
||||
"/describe --pr_description.final_update_message=false",
|
||||
|
Reference in New Issue
Block a user