log-center/repair_agent/agent/git_manager.py
zyc 9a347d7929
All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 9m30s
feat(repair-agent): auto-merge fix branch to main after repair
After pushing the fix branch, automatically merge it back to main and
push, so that CI/CD is triggered with the fix. Also cleans up the
remote fix branch after merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 14:03:17 +08:00

232 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Git Manager - Git 操作管理
"""
import os
from typing import Optional
from git import Repo, InvalidGitRepositoryError
from loguru import logger
from ..config import settings
class GitManager:
"""负责 Git 操作"""
def __init__(self, project_path: str, github_repo: str = ""):
self.project_path = project_path
self.github_repo = github_repo.strip()
self.repo: Optional[Repo] = None
self._init_repo()
def _init_repo(self):
"""初始化 Git 仓库"""
try:
self.repo = Repo(self.project_path)
# 配置 Git 用户
with self.repo.config_writer() as config:
config.set_value("user", "name", settings.git_user_name)
config.set_value("user", "email", settings.git_user_email)
# 如果指定了 GitHub 仓库地址,确保 origin 指向正确的仓库
if self.github_repo:
self._ensure_remote(self.github_repo)
logger.info(f"Git 仓库初始化成功: {self.project_path}")
except InvalidGitRepositoryError:
logger.error(f"无效的 Git 仓库: {self.project_path}")
self.repo = None
def _ensure_remote(self, repo_url: str):
"""确保 origin remote 指向指定的仓库地址"""
if not self.repo:
return
try:
if "origin" in [r.name for r in self.repo.remotes]:
current_url = self.repo.remotes.origin.url
if current_url != repo_url:
self.repo.remotes.origin.set_url(repo_url)
logger.info(f"更新 origin 地址: {repo_url}")
else:
self.repo.create_remote("origin", repo_url)
logger.info(f"添加 origin 地址: {repo_url}")
except Exception as e:
logger.error(f"配置 remote 失败: {e}")
def pull(self) -> bool:
"""拉取最新代码"""
if not self.repo:
return False
try:
origin = self.repo.remotes.origin
origin.pull()
logger.info("代码拉取成功")
return True
except Exception as e:
logger.error(f"拉取代码失败: {e}")
return False
def create_branch(self, branch_name: str) -> bool:
"""
创建并切换到新分支
Args:
branch_name: 分支名称
Returns:
是否成功
"""
if not self.repo:
return False
try:
# 先切换到 main 分支
if "main" in self.repo.heads:
self.repo.heads.main.checkout()
elif "master" in self.repo.heads:
self.repo.heads.master.checkout()
# 创建新分支
new_branch = self.repo.create_head(branch_name)
new_branch.checkout()
logger.info(f"创建并切换到分支: {branch_name}")
return True
except Exception as e:
logger.error(f"创建分支失败: {e}")
return False
def checkout(self, branch_name: str) -> bool:
"""切换分支"""
if not self.repo:
return False
try:
if branch_name in self.repo.heads:
self.repo.heads[branch_name].checkout()
logger.info(f"切换到分支: {branch_name}")
return True
else:
logger.error(f"分支不存在: {branch_name}")
return False
except Exception as e:
logger.error(f"切换分支失败: {e}")
return False
def get_diff(self) -> str:
"""获取当前修改的 diff"""
if not self.repo:
return ""
try:
return self.repo.git.diff()
except Exception as e:
logger.error(f"获取 diff 失败: {e}")
return ""
def get_modified_files(self) -> list[str]:
"""获取已修改的文件列表"""
if not self.repo:
return []
try:
return [item.a_path for item in self.repo.index.diff(None)]
except Exception as e:
logger.error(f"获取修改文件失败: {e}")
return []
def commit(self, message: str) -> bool:
"""提交更改"""
if not self.repo:
return False
try:
# 添加所有修改
self.repo.git.add(A=True)
# 检查是否有更改
if not self.repo.is_dirty():
logger.warning("没有需要提交的更改")
return False
self.repo.index.commit(message)
logger.info(f"提交成功: {message}")
return True
except Exception as e:
logger.error(f"提交失败: {e}")
return False
def push(self, branch_name: Optional[str] = None) -> bool:
"""推送到远程(自动设置上游跟踪分支)"""
if not self.repo:
return False
try:
branch = branch_name or self.repo.active_branch.name
origin = self.repo.remotes.origin
origin.push(refspec=f"{branch}:{branch}", set_upstream=True)
logger.info(f"推送成功: {branch}")
return True
except Exception as e:
logger.error(f"推送失败: {e}")
return False
def merge_to_main_and_push(self) -> bool:
"""将当前 fix 分支合并到 main 并推送,然后删除 fix 分支"""
if not self.repo:
return False
try:
fix_branch = self.repo.active_branch.name
# 切换到 main或 master
main_name = "main" if "main" in self.repo.heads else "master"
self.repo.heads[main_name].checkout()
# 合并 fix 分支
self.repo.git.merge(fix_branch)
logger.info(f"合并 {fix_branch}{main_name}")
# 推送 main
origin = self.repo.remotes.origin
origin.push(refspec=f"{main_name}:{main_name}")
logger.info(f"推送 {main_name} 成功")
# 删除本地和远程 fix 分支
self.repo.git.branch("-d", fix_branch)
try:
origin.push(refspec=f":{fix_branch}")
logger.info(f"删除远程分支 {fix_branch}")
except Exception:
pass # 远程分支可能不存在
return True
except Exception as e:
logger.error(f"合并到 main 失败: {e}")
return False
def reset_hard(self) -> bool:
"""重置所有更改"""
if not self.repo:
return False
try:
self.repo.git.reset("--hard")
self.repo.git.clean("-fd")
logger.info("重置成功")
return True
except Exception as e:
logger.error(f"重置失败: {e}")
return False
def get_current_branch(self) -> str:
"""获取当前分支名"""
if not self.repo:
return ""
try:
return self.repo.active_branch.name
except Exception:
return ""