""" Git 协作管理系统 管理 Agent 之间基于 Git 的真实协作 """ import os import subprocess import json from pathlib import Path from typing import Dict, List, Optional, Tuple, Any from dataclasses import dataclass import logging from .identity_manager import AgentIdentityManager @dataclass class Repository: """仓库信息""" name: str local_path: str remotes: Dict[str, str] # remote_name -> url current_agent: Optional[str] = None class GitCollaborationManager: """Git 协作管理器""" def __init__(self, identity_manager: AgentIdentityManager): self.identity_manager = identity_manager self.logger = logging.getLogger(__name__) self.repositories = {} self._load_repositories() def _load_repositories(self): """加载仓库配置""" config_file = Path("config/repositories.json") if config_file.exists(): with open(config_file, 'r', encoding='utf-8') as f: data = json.load(f) self.repositories = { name: Repository(**repo_data) for name, repo_data in data.items() } def _save_repositories(self): """保存仓库配置""" config_file = Path("config/repositories.json") config_file.parent.mkdir(exist_ok=True) data = { name: { 'name': repo.name, 'local_path': repo.local_path, 'remotes': repo.remotes, 'current_agent': repo.current_agent } for name, repo in self.repositories.items() } with open(config_file, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) def setup_progressive_deployment(self, repo_name: str, gitea_url: str, bitbucket_url: str, github_url: str, local_path: Optional[str] = None): """设置渐进发布的三个远程仓库""" if not local_path: local_path_str = f"./repos/{repo_name}" else: local_path_str = local_path local_path_obj = Path(local_path_str) local_path_obj.mkdir(parents=True, exist_ok=True) # 初始化本地仓库(如果不存在) if not (local_path_obj / ".git").exists(): subprocess.run(["git", "init"], cwd=local_path) # 设置远程仓库 remotes = { "gitea": gitea_url, "bitbucket": bitbucket_url, "github": github_url } for remote_name, remote_url in remotes.items(): # 检查远程是否已存在 result = subprocess.run([ "git", "remote", "get-url", remote_name ], cwd=local_path, capture_output=True, text=True) if result.returncode != 0: # 添加新的远程 subprocess.run([ "git", "remote", "add", remote_name, remote_url ], cwd=local_path) else: # 更新现有远程 subprocess.run([ "git", "remote", "set-url", remote_name, remote_url ], cwd=local_path) # 创建仓库记录 repository = Repository( name=repo_name, local_path=str(local_path), remotes=remotes ) self.repositories[repo_name] = repository self._save_repositories() self.logger.info(f"设置渐进发布仓库: {repo_name}") return repository def switch_agent_context(self, repo_name: str, agent_name: str): """切换仓库的 Agent 上下文""" if repo_name not in self.repositories: raise ValueError(f"仓库 {repo_name} 不存在") repository = self.repositories[repo_name] # 设置 Git 配置 self.identity_manager.setup_git_config(agent_name, repository.local_path) # 设置 SSH 密钥 identity = self.identity_manager.get_agent_identity(agent_name) if identity: self._setup_ssh_agent(identity.ssh_key_path) repository.current_agent = agent_name self._save_repositories() self.logger.info(f"切换仓库 {repo_name} 到 Agent: {agent_name}") def _setup_ssh_agent(self, ssh_key_path: str): """设置 SSH Agent""" try: # 启动 ssh-agent(如果未运行) result = subprocess.run([ "ssh-add", "-l" ], capture_output=True, text=True) if result.returncode != 0: # 启动 ssh-agent result = subprocess.run([ "ssh-agent", "-s" ], capture_output=True, text=True) if result.returncode == 0: # 解析环境变量 for line in result.stdout.split('\n'): if 'SSH_AUTH_SOCK' in line: sock = line.split('=')[1].split(';')[0] os.environ['SSH_AUTH_SOCK'] = sock elif 'SSH_AGENT_PID' in line: pid = line.split('=')[1].split(';')[0] os.environ['SSH_AGENT_PID'] = pid # 添加 SSH 密钥 subprocess.run(["ssh-add", ssh_key_path]) except Exception as e: self.logger.warning(f"SSH Agent 设置失败: {e}") def commit_as_agent(self, repo_name: str, message: str, files: Optional[List[str]] = None, sign: bool = True) -> bool: """以当前 Agent 身份提交代码""" if repo_name not in self.repositories: raise ValueError(f"仓库 {repo_name} 不存在") repository = self.repositories[repo_name] repo_path = Path(repository.local_path) try: # 添加文件 if files: for file in files: subprocess.run(["git", "add", file], cwd=repo_path) else: subprocess.run(["git", "add", "."], cwd=repo_path) # 提交 commit_cmd = ["git", "commit", "-m", message] if sign: commit_cmd.append("-S") result = subprocess.run(commit_cmd, cwd=repo_path, capture_output=True, text=True) if result.returncode == 0: self.logger.info(f"Agent {repository.current_agent} 提交成功: {message}") return True else: self.logger.error(f"提交失败: {result.stderr}") return False except Exception as e: self.logger.error(f"提交过程出错: {e}") return False def progressive_push(self, repo_name: str, branch: str = "main") -> Dict[str, bool]: """渐进式推送到三个平台""" if repo_name not in self.repositories: raise ValueError(f"仓库 {repo_name} 不存在") repository = self.repositories[repo_name] repo_path = Path(repository.local_path) results = {} # 按顺序推送:Gitea -> Bitbucket -> GitHub push_order = ["gitea", "bitbucket", "github"] for remote in push_order: if remote in repository.remotes: try: result = subprocess.run([ "git", "push", remote, branch ], cwd=repo_path, capture_output=True, text=True) results[remote] = result.returncode == 0 if result.returncode == 0: self.logger.info(f"推送到 {remote} 成功") else: self.logger.error(f"推送到 {remote} 失败: {result.stderr}") # 如果某个平台失败,停止后续推送 break except Exception as e: self.logger.error(f"推送到 {remote} 出错: {e}") results[remote] = False break return results def create_pull_request_workflow(self, repo_name: str, source_agent: str, target_agent: str, feature_branch: str, title: str, description: str = "") -> bool: """创建 Agent 间的 Pull Request 工作流""" repository = self.repositories[repo_name] repo_path = Path(repository.local_path) try: # 1. 切换到源 Agent self.switch_agent_context(repo_name, source_agent) # 2. 创建功能分支 subprocess.run([ "git", "checkout", "-b", feature_branch ], cwd=repo_path) # 3. 推送功能分支 subprocess.run([ "git", "push", "-u", "gitea", feature_branch ], cwd=repo_path) # 4. 这里可以集成 API 调用来创建实际的 PR # 具体实现取决于使用的 Git 平台 self.logger.info(f"创建 PR 工作流: {source_agent} -> {target_agent}") return True except Exception as e: self.logger.error(f"创建 PR 工作流失败: {e}") return False def get_repository_status(self, repo_name: str) -> Dict[str, Any]: """获取仓库状态""" if repo_name not in self.repositories: raise ValueError(f"仓库 {repo_name} 不存在") repository = self.repositories[repo_name] repo_path = Path(repository.local_path) status = { "current_agent": repository.current_agent, "branch": None, "uncommitted_changes": False, "remotes": repository.remotes } try: # 获取当前分支 result = subprocess.run([ "git", "branch", "--show-current" ], cwd=repo_path, capture_output=True, text=True) if result.returncode == 0: status["branch"] = result.stdout.strip() # 检查未提交的更改 result = subprocess.run([ "git", "status", "--porcelain" ], cwd=repo_path, capture_output=True, text=True) status["uncommitted_changes"] = bool(result.stdout.strip()) except Exception as e: self.logger.error(f"获取仓库状态失败: {e}") return status