diff --git a/app/api/endpoints/webhook.py b/app/api/endpoints/webhook.py index b948837..51253ae 100644 --- a/app/api/endpoints/webhook.py +++ b/app/api/endpoints/webhook.py @@ -45,6 +45,12 @@ def extract_pr_data(data: dict) -> dict: if note not in [cmd.strip().lower() for cmd in settings.accept_cmds]: raise ValueError("Unsupported command") + # Check for ci_failed label + labels = data.get("pull_request", {}).get("labels", []) + logger.info(f"labels is {labels}") + if not any(label.get("name") == "ci_failed" for label in labels): + raise ValueError("PR does not have ci_failed label") + pull_request = data.get("pull_request", {}) project = data.get("project", {}) @@ -162,6 +168,11 @@ async def handle_build_retries(pr_data: dict, current_spec: str, srcDir: str, bu commit_url: str, maker_url: str): """处理构建重试逻辑""" + old_version, new_version = git_api.get_upgrade_versions( + pr_data["repo_url"], + pr_data["pr_number"], + f'{pr_data["repo_name"]}.spec' + ) try: build_status = await wait_for_build_completion(build_id) logger.info(f'the build result is {build_status}') @@ -211,11 +222,20 @@ async def handle_build_retries(pr_data: dict, current_spec: str, srcDir: str, bu else: # 达到最大重试次数 - comment = settings.fix_failure_comment.format(max_retries=MAX_RETRIES, commit_url=commit_url, - maker_url=maker_url) + logger.info(f"old_version is {old_version}, new_version is {new_version}") + comment = settings.fix_failure_comment.format( + package=pr_data["repo_name"], + old_version=old_version, + new_version=new_version, + commit_url=commit_url, + maker_url=maker_url, + ) + issue_url = await analyze_error_and_create_issue(pr_data, old_version, new_version) + if issue_url: + comment += (f"\n升级后,发现缺少依赖包情况,openEuler-AutoRepair已经提出Issue,请留意处理进度.\n" + f"Issue链接:{issue_url}。") git_api.comment_on_pr(pr_data["repo_url"], pr_data["pr_number"], comment) logger.error(f"PR #{pr_data['pr_number']} 构建失败,已达最大重试次数") - await analyze_error_and_create_issue(pr_data) except Exception as e: logger.error(f"处理重试时发生异常: {e}") @@ -287,8 +307,8 @@ async def process_initial_repair(pr_data: dict, original_spec: str): git_api.comment_on_pr(pr_data["repo_url"], pr_data["pr_number"], comment) -async def analyze_error_and_create_issue(pr_data: dict): - """分析错误并创建问题""" +async def analyze_error_and_create_issue(pr_data: dict, old_version, new_version): + """分析错误并创建Issue""" # 分析错误日志 try: # Get build log @@ -312,8 +332,12 @@ async def analyze_error_and_create_issue(pr_data: dict): chat = silicon_client.SiliconFlowChat(settings.silicon_token) title, content = chat.analyze_missing_package(warnings) if title and content: - git_api.create_issue(pr_data["repo_url"], title, content) - + issue_url = git_api.create_issue(pr_data["repo_url"], title, settings.missing_package_comment.format( + old_version=old_version, + new_version=new_version, + missing_packages=content)) + return issue_url + return "" except Exception as e: logger.error(f"获取构建日志失败: {e}") - return + return "" diff --git a/app/config.py b/app/config.py index 02e8c71..398ec6e 100644 --- a/app/config.py +++ b/app/config.py @@ -31,6 +31,7 @@ def __init__(self): self.system_prompt: str = config.get("SYSTEM_PROMPT") self.user_prompt: str = config.get("USER_PROMPT") self.analyze_system_prompt: str = config.get("ANALYZE_SYSTEM_PROMPT") + self.missing_package_comment: str = config.get("MISSING_PACKAGE_COMMENT") self.ai_model: str = config.get("AI_MODEL") self.model_max_tokens: int = config.get("MODEL_MAX_TOKENS") self.model_temperature: float = config.get("MODEL_TEMPERATURE") diff --git a/app/utils/git_api.py b/app/utils/git_api.py index 56e7f0b..0ff4b73 100644 --- a/app/utils/git_api.py +++ b/app/utils/git_api.py @@ -1,5 +1,6 @@ # 标准库 import os +import re import shutil import stat import subprocess @@ -42,6 +43,10 @@ def get_spec_content(self, owner, repo, pr_number, file_path, token=None): def create_issue(self, owner, repo, title, content): pass + @abstractmethod + def get_upgrade_versions(self, owner, repo, pr_number, file_path, token=None): + pass + # 平台具体实现层 class GiteeForkService(ForkServiceInterface): @@ -172,6 +177,41 @@ async def get_spec_content(self, owner, repo, pr_number, file_path, token=None): logger.error(f"Unexpected error: {str(e)}") return None + def get_upgrade_versions(self, owner, repo, pr_number, file_path, token=None) -> tuple: + try: + headers = { + "Authorization": f"token {token}" + } + # 异步获取PR文件列表 + files_resp = self.client.get(f"/repos/{owner}/{repo}/pulls/{pr_number}/files") + files_resp.raise_for_status() + files = files_resp.json() + + # 查找目标文件 + target_file = next((f for f in files if f["filename"] == file_path), None) + if not target_file: + logger.warning(f"File {file_path} not found in PR #{pr_number}") + return "", "" + + diff = target_file['patch']['diff'] + version_pattern = re.compile(r'([+-])\s*Version:\s*([\w.]+)') + matches = version_pattern.findall(diff) + + old_version = None + new_version = None + for sign, version in matches: + if sign == '-': + old_version = version + elif sign == '+': + new_version = version + return old_version, new_version + + except httpx.HTTPStatusError as e: + logger.error(f"Gitee API error: {e.response.status_code} {e.response.text}") + except Exception as e: + logger.error(f"Unexpected error: {str(e)}") + return "", "" + def create_issue(self, owner, repo, title, content): data = { "repo": repo, @@ -179,6 +219,7 @@ def create_issue(self, owner, repo, title, content): "body": content } response = self.client.post(f"/repos/{owner}/issues", data) + return response.json().get('html_url', "") class GitHubForkService(ForkServiceInterface): @@ -211,6 +252,9 @@ def get_spec_content(self, owner, repo, pr_number, file_path, token=None): def create_issue(self, owner, repo, title, content): pass + def get_upgrade_versions(self, owner, repo, pr_number, file_path, token=None): + pass + class GitCodeForkService(ForkServiceInterface): def __init__(self, token): @@ -248,6 +292,9 @@ def get_spec_content(self, owner, repo, pr_number, file_path, token=None): def create_issue(self, owner, repo, title, content): pass + def get_upgrade_versions(self, owner, repo, pr_number, file_path, token=None): + pass + # 工厂层 class ForkServiceFactory: @@ -360,14 +407,16 @@ def create_issue(repo_url, title, content): platform, token, owner, repo = parse_repo_url(repo_url) service = ForkServiceFactory.get_service(platform, token) try: - service.create_issue( + issue_url = service.create_issue( owner=owner, repo=repo, title=title, content=content, ) + return issue_url except Exception as e: logger.info(f"创建失败: {str(e)}") + return "" async def get_spec_content(repo_url, pr_number, file_path): @@ -376,6 +425,12 @@ async def get_spec_content(repo_url, pr_number, file_path): return await service.get_spec_content(owner, repo, pr_number, file_path, token) +def get_upgrade_versions(repo_url, pr_number, file_path): + platform, token, owner, repo = parse_repo_url(repo_url) + service = ForkServiceFactory.get_service(platform, token) + return service.get_upgrade_versions(owner, repo, pr_number, file_path, token) + + def check_and_push(repo_url, new_content, pr_num): logger.info(f'repo_url is {repo_url}') platform, token, owner, repo = parse_repo_url(repo_url) diff --git a/app/utils/gitee_tool.py b/app/utils/gitee_tool.py index 1bddb53..c4b9d57 100644 --- a/app/utils/gitee_tool.py +++ b/app/utils/gitee_tool.py @@ -73,13 +73,10 @@ def download_gitee_file(raw_url, token=None): _, ext = os.path.splitext(raw_url) fd, path = tempfile.mkstemp(suffix=ext) - try: - with os.fdopen(fd, 'wb') as f: - for chunk in response.iter_content(chunk_size=8192): - if chunk: - f.write(chunk) - finally: - os.remove(path) + with os.fdopen(fd, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) return path