From 1e6804551063a2b382e63e5dc2428d314f307614 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 20 Jul 2026 11:30:16 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E5=A4=8Dauto-fix?= =?UTF-8?q?=E5=9C=A8tarball=20checkout=E6=A8=A1=E5=BC=8F=E4=B8=8Bgit?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E7=94=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/ci/auto_fix_formatting.py | 71 +++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/scripts/ci/auto_fix_formatting.py b/scripts/ci/auto_fix_formatting.py index 74baed74e..aa899fe16 100755 --- a/scripts/ci/auto_fix_formatting.py +++ b/scripts/ci/auto_fix_formatting.py @@ -23,6 +23,49 @@ def run(cmd, check=True, capture=True, cwd=None): return result +def ensure_git_repo(api_url, repo, token, pr_number): + """确保当前目录是git仓库。 + + checkout脚本用tarball方式下载代码,没有.git目录。 + 这里自动初始化git仓库、拉取远端分支,确保后续git操作可用。 + tarball内容与PR HEAD完全一致,checkout后git status为clean。 + """ + if os.path.exists(".git"): + return + + print("检测到tarball checkout(无.git目录),自动初始化git仓库...") + + # 构造带认证的远端URL + server_url = api_url.rsplit("/api/v1", 1)[0] + remote_url = f"{server_url.replace('https://', f'https://x-access-token:{token}@')}/{repo}.git" + + # 获取PR的源分支 + pr_api_url = f"{api_url}/repos/{repo}/pulls/{pr_number}" + req = urllib.request.Request(pr_api_url, headers={"Authorization": f"token {token}"}) + with urllib.request.urlopen(req) as resp: + pr = json.loads(resp.read()) + head_branch = pr["head"]["ref"] + + print(f"PR源分支: {head_branch}") + + # 初始化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("拉取远端分支...") + run(f"git fetch --depth=1 origin {head_branch}") + run(f"git checkout -B {head_branch} FETCH_HEAD") + + # tarball内容与HEAD一致,checkout后应该是clean状态 + result = run("git status --porcelain") + if result.stdout.strip(): + print(f"⚠️ 注意:tarball与HEAD有差异 ({len(result.stdout.strip().splitlines())} 个文件)") + else: + print("✅ git仓库就绪,工作区clean") + + def get_changed_files(pr_number, api_url, token): """获取PR中变更的文件列表""" url = f"{api_url}/pulls/{pr_number}/files?limit=100" @@ -91,9 +134,7 @@ def fix_frontend(target_fe_files, scan_mode, repo_root): if scan_mode == "incremental": # 增量模式:只格式化变更的前端文件 - # 转换为相对于 apps/web 的路径或用绝对路径 target_str = " ".join(target_fe_files) - # 从项目根目录运行,prettier 会找配置文件 cmd = f"{prettier_bin} --write {target_str}" else: # 全量模式:格式化整个前端目录 @@ -129,6 +170,9 @@ def main(): sys.exit(1) repo_root = os.getcwd() + + # 确保git仓库可用(tarball checkout模式下自动初始化) + ensure_git_repo(api_url, repo, token, pr_number) print("=== 检测到代码格式问题,尝试自动修复 ===") print(f"PR #{pr_number}") @@ -136,20 +180,9 @@ def main(): # 前端文件扩展名 fe_extensions = ( - ".ts", - ".tsx", - ".js", - ".jsx", - ".css", - ".scss", - ".less", - ".json", - ".html", - ".md", - ".yaml", - ".yml", + ".ts", ".tsx", ".js", ".jsx", ".css", ".scss", ".less", + ".json", ".html", ".md", ".yaml", ".yml", ) - # Python 文件扩展名 py_extensions = (".py",) # 确定要修复的文件范围 @@ -160,15 +193,13 @@ def main(): print(f"增量模式: {len(target_py_files)} 个Python文件, {len(target_fe_files)} 个前端文件") else: target_py_files = ["alembic", "apps", "packages", "tests", "scripts"] - # 全量模式下 prettier 在前端目录内部运行,无需传文件列表 - target_fe_files = ["apps/web"] # 标记为有前端文件需要处理 + target_fe_files = ["apps/web"] print("全量模式,修复所有文件") # Python 格式化 fix_python(target_py_files, scan_mode) # 前端格式化 - # 全量模式下直接传 web 目录标记 if scan_mode != "incremental": fix_frontend(["apps/web"], scan_mode, repo_root) else: @@ -186,10 +217,6 @@ def main(): for line in result.stdout.strip().split("\n"): print(f" {line}") - # 配置git - run('git config user.name "CI Bot"') - run('git config user.email "ci-bot@xiaoxiajianji.com"') - # 提交修复 run("git add -A") run('git commit -m "style: auto-format with black + isort + prettier [ci skip]"') -- 2.54.0 From 8286a46d9084874d65ecd169479cdb75e47819b8 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 20 Jul 2026 11:43:14 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E5=A4=8Dauto-fix=20git?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=96=B9=E5=BC=8F=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8Dtarball=E6=96=87=E4=BB=B6=E4=B8=8Echeckout=E5=86=B2?= =?UTF-8?q?=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/ci/auto_fix_formatting.py | 58 ++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/scripts/ci/auto_fix_formatting.py b/scripts/ci/auto_fix_formatting.py index aa899fe16..a6b083c89 100755 --- a/scripts/ci/auto_fix_formatting.py +++ b/scripts/ci/auto_fix_formatting.py @@ -25,43 +25,51 @@ def run(cmd, check=True, capture=True, cwd=None): def ensure_git_repo(api_url, repo, token, pr_number): """确保当前目录是git仓库。 - + checkout脚本用tarball方式下载代码,没有.git目录。 - 这里自动初始化git仓库、拉取远端分支,确保后续git操作可用。 - tarball内容与PR HEAD完全一致,checkout后git status为clean。 + 这里自动初始化git仓库,用read-tree将远端HEAD载入index, + 不修改工作区文件(tarball内容与PR HEAD一致)。 """ if os.path.exists(".git"): return print("检测到tarball checkout(无.git目录),自动初始化git仓库...") - + # 构造带认证的远端URL server_url = api_url.rsplit("/api/v1", 1)[0] remote_url = f"{server_url.replace('https://', f'https://x-access-token:{token}@')}/{repo}.git" - + # 获取PR的源分支 pr_api_url = f"{api_url}/repos/{repo}/pulls/{pr_number}" - req = urllib.request.Request(pr_api_url, headers={"Authorization": f"token {token}"}) - with urllib.request.urlopen(req) as resp: + req_obj = urllib.request.Request(pr_api_url, headers={"Authorization": f"token {token}"}) + with urllib.request.urlopen(req_obj) as resp: pr = json.loads(resp.read()) head_branch = pr["head"]["ref"] - + print(f"PR源分支: {head_branch}") - - # 初始化git并拉取远端分支 + + # 初始化git(不创建初始commit) 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\"") - + run('git config user.name "CI Bot"') + run('git config user.email "ci-bot@xiaoxiajianji.com"') + print("拉取远端分支...") run(f"git fetch --depth=1 origin {head_branch}") - run(f"git checkout -B {head_branch} FETCH_HEAD") - - # tarball内容与HEAD一致,checkout后应该是clean状态 + + # 用read-tree载入index,不碰工作区文件 + # 这样工作区保持tarball内容,index是远端HEAD的状态 + # 由于tarball = HEAD,git status应为clean + run("git read-tree FETCH_HEAD") + + # 设置HEAD指向本地分支 + run(f"git symbolic-ref HEAD refs/heads/{head_branch}") + + # 验证状态 result = run("git status --porcelain") if result.stdout.strip(): - print(f"⚠️ 注意:tarball与HEAD有差异 ({len(result.stdout.strip().splitlines())} 个文件)") + n = len(result.stdout.strip().splitlines()) + print(f"⚠️ 工作区与HEAD有差异 ({n} 个文件)") else: print("✅ git仓库就绪,工作区clean") @@ -170,7 +178,7 @@ def main(): sys.exit(1) repo_root = os.getcwd() - + # 确保git仓库可用(tarball checkout模式下自动初始化) ensure_git_repo(api_url, repo, token, pr_number) @@ -180,8 +188,18 @@ def main(): # 前端文件扩展名 fe_extensions = ( - ".ts", ".tsx", ".js", ".jsx", ".css", ".scss", ".less", - ".json", ".html", ".md", ".yaml", ".yml", + ".ts", + ".tsx", + ".js", + ".jsx", + ".css", + ".scss", + ".less", + ".json", + ".html", + ".md", + ".yaml", + ".yml", ) py_extensions = (".py",) -- 2.54.0 From 2fbb7fe00e49c43aed1c63a00eaf85a5c5567a1e Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 20 Jul 2026 12:15:06 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E5=A4=8D=E5=A2=9E?= =?UTF-8?q?=E9=87=8F=E6=A8=A1=E5=BC=8F=E4=B8=8BRUFF=5FFILES=E7=A9=BA?= =?UTF-8?q?=E6=A0=BC=E6=AE=8B=E7=95=99=E5=AF=BC=E8=87=B4ruff=E5=85=A8?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E6=89=AB=E6=8F=8F=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/ci/run_validate.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/ci/run_validate.sh b/scripts/ci/run_validate.sh index f39279abe..543f1848f 100755 --- a/scripts/ci/run_validate.sh +++ b/scripts/ci/run_validate.sh @@ -100,7 +100,11 @@ if [ "$SCAN_MODE" = "incremental" ]; then EXISTING_PY_FILES="" for f in $CHANGED_PY_FILES; do if [ -f "$f" ]; then - EXISTING_PY_FILES="$EXISTING_PY_FILES $f" + if [ -z "$EXISTING_PY_FILES" ]; then + EXISTING_PY_FILES="$f" + else + EXISTING_PY_FILES="$EXISTING_PY_FILES $f" + fi fi done CHANGED_PY_FILES="$EXISTING_PY_FILES" @@ -108,7 +112,7 @@ if [ "$SCAN_MODE" = "incremental" ]; then python3 -m compileall -q $CHANGED_PY_FILES python3 -m black --check --fast $CHANGED_PY_FILES python3 -m isort --check-only $CHANGED_PY_FILES - RUFF_FILES=$(echo "$CHANGED_PY_FILES" | tr ' ' '\n' | grep -v '^scripts/' | tr '\n' ' ') + RUFF_FILES=$(echo "$CHANGED_PY_FILES" | tr ' ' '\n' | grep -v '^scripts/' | grep -v '^$' | xargs) if [ -n "$RUFF_FILES" ]; then python3 -m ruff check $RUFF_FILES --statistics else -- 2.54.0 From 43a1ecc058253df97fc6250b059cf32dd293be29 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 20 Jul 2026 12:15:49 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(ci):=20auto-fix=E5=BC=BA=E5=88=B6checko?= =?UTF-8?q?ut=E5=88=B0PR=E6=BA=90=E5=88=86=E6=94=AF=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8Dmerge=20commit=E5=B7=AE=E5=BC=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/ci/auto_fix_formatting.py | 35 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/scripts/ci/auto_fix_formatting.py b/scripts/ci/auto_fix_formatting.py index a6b083c89..0ec56ad7c 100755 --- a/scripts/ci/auto_fix_formatting.py +++ b/scripts/ci/auto_fix_formatting.py @@ -24,11 +24,11 @@ def run(cmd, check=True, capture=True, cwd=None): def ensure_git_repo(api_url, repo, token, pr_number): - """确保当前目录是git仓库。 + """确保当前目录是git仓库,并切换到PR源分支。 - checkout脚本用tarball方式下载代码,没有.git目录。 - 这里自动初始化git仓库,用read-tree将远端HEAD载入index, - 不修改工作区文件(tarball内容与PR HEAD一致)。 + checkout脚本用tarball方式下载代码(PR merge后的commit),没有.git目录。 + 这里自动初始化git仓库,fetch PR源分支并强制checkout, + 使工作区变为PR源分支的代码,确保后续格式化修复基于源分支。 """ if os.path.exists(".git"): return @@ -48,31 +48,30 @@ def ensure_git_repo(api_url, repo, token, pr_number): print(f"PR源分支: {head_branch}") - # 初始化git(不创建初始commit) + # 初始化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("拉取远端分支...") + # fetch源分支(浅克隆,只要最新commit) + print("fetch源分支...") run(f"git fetch --depth=1 origin {head_branch}") - # 用read-tree载入index,不碰工作区文件 - # 这样工作区保持tarball内容,index是远端HEAD的状态 - # 由于tarball = HEAD,git status应为clean - run("git read-tree FETCH_HEAD") + # 强制checkout到源分支(覆盖tarball内容) + # tarball是merge后的commit,源分支才是我们要修改并推送的目标 + print("切换到源分支...") + run(f"git checkout -B {head_branch} FETCH_HEAD") - # 设置HEAD指向本地分支 - run(f"git symbolic-ref HEAD refs/heads/{head_branch}") - - # 验证状态 result = run("git status --porcelain") if result.stdout.strip(): n = len(result.stdout.strip().splitlines()) - print(f"⚠️ 工作区与HEAD有差异 ({n} 个文件)") + print(f"⚠️ 工作区有 {n} 个未追踪文件") else: print("✅ git仓库就绪,工作区clean") + return head_branch + def get_changed_files(pr_number, api_url, token): """获取PR中变更的文件列表""" @@ -180,7 +179,8 @@ def main(): repo_root = os.getcwd() # 确保git仓库可用(tarball checkout模式下自动初始化) - ensure_git_repo(api_url, repo, token, pr_number) + # 返回PR源分支名,供后续推送使用 + head_branch = ensure_git_repo(api_url, repo, token, pr_number) print("=== 检测到代码格式问题,尝试自动修复 ===") print(f"PR #{pr_number}") @@ -239,8 +239,7 @@ def main(): run("git add -A") run('git commit -m "style: auto-format with black + isort + prettier [ci skip]"') - # 获取来源分支并推送 - head_branch = get_pr_head_branch(pr_number, f"{api_url}/repos/{repo}", token) + # 推送(head_branch已从ensure_git_repo获取) print(f"\nPR来源分支: {head_branch}") run(f'git push origin "HEAD:{head_branch}"') -- 2.54.0