@@ -1,8 +1,9 @@
#!/usr/bin/env python3
""" CI中自动修复代码格式(Python: black + isort | Frontend: prettier),并推送回PR 分支。
""" CI中自动修复代码格式(Python: black + isort | Frontend: prettier),并推送回原 分支。
只在 PR事件中执行,避免直接修改主干。
当code quality检查因格式问题失败时触发, 自动修复后 push回原分支。
- PR事件:自动修复并push回PR源分支(Agent提交的PR自动修,人提交的仅诊断)
- Push事件(develop/main): 自动修复并 push回原分支,保持主干格式永远正确
当code quality检查因格式问题失败时触发。
"""
import json
@@ -74,6 +75,45 @@ def ensure_git_repo(api_url, repo, token, pr_number):
return head_branch
def ensure_git_repo_for_push ( api_url , repo , token , branch_name ) :
""" push事件下确保git仓库可用,并切换到目标分支。
checkout脚本用tarball方式下载代码,没有.git目录。
这里自动初始化git仓库,fetch目标分支并checkout。
"""
if os . path . exists ( " .git " ) :
# 已有git,确认在正确分支
result = run ( " git rev-parse --abbrev-ref HEAD " , check = False )
if result . stdout . strip ( ) == branch_name :
return
# 不在目标分支,切换
run ( f " git checkout { branch_name } " , check = False )
return
print ( f " 检测到tarball checkout(无.git目录),初始化git仓库(push模式,分支: { branch_name } ) ..." )
server_url = api_url . rsplit ( " /api/v1 " , 1 ) [ 0 ]
remote_url = f " { server_url . replace ( ' https:// ' , f ' https://x-access-token: { token } @ ' ) } / { repo } .git "
run ( " git init -q " )
run ( f " git remote add origin { remote_url } " )
run ( ' git config user.name " CI Bot " ' )
run ( ' git config user.email " ci-bot@xiaoxiajianji.com " ' )
print ( f " fetch { branch_name } 分支... " )
run ( f " git fetch --depth=1 origin { branch_name } " )
print ( f " 切换到 { branch_name } 分支... " )
run ( f " git checkout -f -B { branch_name } FETCH_HEAD " )
result = run ( " git status --porcelain " )
if result . stdout . strip ( ) :
n = len ( result . stdout . strip ( ) . splitlines ( ) )
print ( f " ⚠️ 工作区有 { n } 个未追踪文件 " )
else :
print ( " ✅ git仓库就绪,工作区clean " )
def get_changed_files ( pr_number , api_url , token ) :
""" 获取PR中变更的文件列表 """
url = f " { api_url } /pulls/ { pr_number } /files?limit=100 "
@@ -155,43 +195,11 @@ def fix_frontend(target_fe_files, scan_mode, repo_root):
def main ( ) :
# 只在PR事件中执行
event_name = os . environ . get ( " GITHUB_EVENT_NAME " , " " )
if event_name != " pull_request " :
print ( " 非PR事件,跳过自动修复 " )
return
github_ref = os . environ . get ( " GITHUB_REF " , " " )
pr_number = github_ref . split ( " / " ) [ 2 ] if github_ref . startswith ( " refs/pull/ " ) else " "
if not pr_number :
print ( " 无法获取PR号,跳过自动修复 " )
return
api_url = os . environ . get ( " GITHUB_API_URL " , " " )
repo = os . environ . get ( " GITHUB_REPOSITORY " , " " )
token = os . environ . get ( " REVIEW_TOKEN " , " " ) or os . environ . get ( " GITHUB_TOKEN " , " " )
# 获取PR作者信息,判断是人还是Agent提交的
pr_info_url = f " { api_url } /repos/ { repo } /pulls/ { pr_number } "
req_pr = urllib . request . Request ( pr_info_url , headers = { " Authorization " : f " token { token } " } )
with urllib . request . urlopen ( req_pr ) as resp :
pr_info = json . loads ( resp . read ( ) )
pr_author = pr_info . get ( " user " , { } ) . get ( " login " , " " )
print ( f " PR作者: { pr_author } " )
# 判断是否为Agent提交的PR
# Agent账号:actions, auto-approve-bot 等bot用户
# 人提交的PR(如xiaoxia):只诊断不自动修
agent_authors = { " actions " , " auto-approve-bot " , " gitea-actions " }
is_agent_pr = pr_author in agent_authors or " bot " in pr_author . lower ( )
if is_agent_pr :
print ( f " 检测到Agent提交的PR(作者: { pr_author } ),将自动修复并推送 " )
fix_mode = " auto_fix_and_push "
else :
print ( f " 检测到人提交的PR(作者: { pr_author } ),仅诊断不自动修改 " )
print ( " (如需自动修复,请用Agent账号提交PR,或手动运行格式化脚本) " )
fix_mode = " diagnose_only "
scan_mode = os . environ . get ( " SCAN_MODE " , " full " )
changed_files_env = os . environ . get ( " CHANGED_FILES " , " " )
@@ -201,13 +209,67 @@ def main():
repo_root = os . getcwd ( )
# 确保git仓库可用(tarball checkout模式下自动初始化)
# 返回PR源分支名,供后续推送使用
head_branch = ensure_git_repo ( api_url , repo , token , pr_number )
# ====== Push事件处理(develop/main等受保护分支) ======
if event_name == " push " :
# 从 refs/heads/xxx 提取分支名
if not github_ref . startswith ( " refs/heads/ " ) :
print ( f " push事件但refs格式异常: { github_ref } ,跳过 " )
return
branch_name = github_ref . replace ( " refs/heads/ " , " " )
print ( " === 检测到代码格式问题,尝试自动修复 === " )
print ( f " PR # { pr_number } " )
print ( f " 扫描模式: { scan_mode } " )
# 只在受保护分支(develop/main)上自动修复并推送
protected_branches = { " develop " , " main " , " master " }
if branch_name not in protected_branches :
print ( f " push事件,分支 { branch_name } 不是受保护分支,跳过自动修复 " )
return
print ( " === Push事件:检测到格式问题,自动修复并推送回原分支 === " )
print ( f " 分支: { branch_name } " )
print ( f " 扫描模式: { scan_mode } " )
# 初始化git仓库
ensure_git_repo_for_push ( api_url , repo , token , branch_name )
head_branch = branch_name
fix_mode = " auto_fix_and_push "
# ====== PR事件处理 ======
elif event_name == " pull_request " :
pr_number = github_ref . split ( " / " ) [ 2 ] if github_ref . startswith ( " refs/pull/ " ) else " "
if not pr_number :
print ( " 无法获取PR号,跳过自动修复 " )
return
# 获取PR作者信息,判断是人还是Agent提交的
pr_info_url = f " { api_url } /repos/ { repo } /pulls/ { pr_number } "
req_pr = urllib . request . Request ( pr_info_url , headers = { " Authorization " : f " token { token } " } )
with urllib . request . urlopen ( req_pr ) as resp :
pr_info = json . loads ( resp . read ( ) )
pr_author = pr_info . get ( " user " , { } ) . get ( " login " , " " )
print ( f " PR作者: { pr_author } " )
# 判断是否为Agent提交的PR
agent_authors = { " actions " , " auto-approve-bot " , " gitea-actions " }
is_agent_pr = pr_author in agent_authors or " bot " in pr_author . lower ( )
if is_agent_pr :
print ( f " 检测到Agent提交的PR(作者: { pr_author } ),将自动修复并推送 " )
fix_mode = " auto_fix_and_push "
else :
print ( f " 检测到人提交的PR(作者: { pr_author } ),仅诊断不自动修改 " )
print ( " (如需自动修复,请用Agent账号提交PR,或手动运行格式化脚本) " )
fix_mode = " diagnose_only "
print ( " === 检测到代码格式问题,尝试自动修复 === " )
print ( f " PR # { pr_number } " )
print ( f " 扫描模式: { scan_mode } " )
# 确保git仓库可用(tarball checkout模式下自动初始化)
head_branch = ensure_git_repo ( api_url , repo , token , pr_number )
# ====== 其他事件跳过 ======
else :
print ( f " 事件 { event_name } 不支持自动修复,跳过 " )
return
# 前端文件扩展名
fe_extensions = (