galaxis-agent/agent/tools/commit_and_open_pr.py

155 lines
5.3 KiB
Python
Raw Normal View History

2026-03-20 14:38:07 +09:00
import asyncio
import logging
import os
2026-03-20 14:38:07 +09:00
from typing import Any
from langgraph.config import get_config
from agent.utils.gitea_client import get_gitea_client
from ..utils.git_utils import (
2026-03-20 14:38:07 +09:00
git_add_all,
git_checkout_branch,
git_commit,
git_config_user,
git_current_branch,
git_fetch_origin,
git_has_uncommitted_changes,
git_has_unpushed_commits,
git_push,
)
from ..utils.sandbox_paths import resolve_repo_dir
from ..utils.sandbox_state import get_sandbox_backend_sync
logger = logging.getLogger(__name__)
def commit_and_open_pr(
title: str,
body: str,
commit_message: str | None = None,
) -> dict[str, Any]:
"""Commit all current changes and open a Gitea Pull Request.
2026-03-20 14:38:07 +09:00
Args:
title: PR title (under 70 characters)
body: PR description with ## Description and ## Test Plan
2026-03-20 14:38:07 +09:00
commit_message: Optional git commit message. If not provided, the PR title is used.
Returns:
Dictionary with success, error, pr_url, and pr_existing keys.
2026-03-20 14:38:07 +09:00
"""
try:
config = get_config()
configurable = config.get("configurable", {})
thread_id = configurable.get("thread_id")
if not thread_id:
return {"success": False, "error": "Missing thread_id in config", "pr_url": None}
repo_config = configurable.get("repo", {})
repo_owner = repo_config.get("owner")
repo_name = repo_config.get("name")
if not repo_owner or not repo_name:
return {
"success": False,
"error": "Missing repo owner/name in config",
"pr_url": None,
}
sandbox_backend = get_sandbox_backend_sync(thread_id)
if not sandbox_backend:
return {"success": False, "error": "No sandbox found for thread", "pr_url": None}
repo_dir = resolve_repo_dir(sandbox_backend, repo_name)
has_uncommitted_changes = git_has_uncommitted_changes(sandbox_backend, repo_dir)
git_fetch_origin(sandbox_backend, repo_dir)
has_unpushed_commits = git_has_unpushed_commits(sandbox_backend, repo_dir)
if not (has_uncommitted_changes or has_unpushed_commits):
return {"success": False, "error": "No changes detected", "pr_url": None}
current_branch = git_current_branch(sandbox_backend, repo_dir)
target_branch = f"galaxis-agent/{thread_id}"
2026-03-20 14:38:07 +09:00
if current_branch != target_branch:
if not git_checkout_branch(sandbox_backend, repo_dir, target_branch):
return {
"success": False,
"error": f"Failed to checkout branch {target_branch}",
"pr_url": None,
}
git_config_user(
sandbox_backend,
repo_dir,
"galaxis-agent[bot]",
"galaxis-agent@users.noreply.gitea.local",
2026-03-20 14:38:07 +09:00
)
git_add_all(sandbox_backend, repo_dir)
commit_msg = commit_message or title
if has_uncommitted_changes:
commit_result = git_commit(sandbox_backend, repo_dir, commit_msg)
if commit_result.exit_code != 0:
return {
"success": False,
"error": f"Git commit failed: {commit_result.output.strip()}",
"pr_url": None,
}
gitea_token = os.environ.get("GITEA_TOKEN", "")
if not gitea_token:
logger.error("commit_and_open_pr missing Gitea token for thread %s", thread_id)
2026-03-20 14:38:07 +09:00
return {
"success": False,
"error": "Missing Gitea token",
2026-03-20 14:38:07 +09:00
"pr_url": None,
}
push_result = git_push(sandbox_backend, repo_dir, target_branch, gitea_token)
2026-03-20 14:38:07 +09:00
if push_result.exit_code != 0:
return {
"success": False,
"error": f"Git push failed: {push_result.output.strip()}",
"pr_url": None,
}
# --- PR 생성 (GiteaClient) ---
gitea_external_url = os.environ.get("GITEA_EXTERNAL_URL", "")
gitea_internal_url = os.environ.get("GITEA_URL", "http://gitea:3000")
default_branch = os.environ.get("DEFAULT_BRANCH", "main")
client = get_gitea_client()
try:
pr_result = asyncio.run(
client.create_pull_request(
owner=repo_owner,
repo=repo_name,
title=title,
head=target_branch,
base=default_branch,
body=body,
)
)
pr_url = pr_result.get("html_url", "")
if gitea_external_url and pr_url:
pr_url = pr_url.replace(gitea_internal_url, gitea_external_url)
return {
"success": True,
"pr_url": pr_url,
"pr_number": pr_result.get("number"),
}
except Exception as e:
logger.exception("Failed to create PR (push succeeded)")
return {
"success": True,
"pr_url": "",
"error": f"Push succeeded but PR creation failed: {e}",
}
2026-03-20 14:38:07 +09:00
except Exception as e:
logger.exception("commit_and_open_pr failed")
return {"success": False, "error": f"{type(e).__name__}: {e}", "pr_url": None}