xianyufaka/release_precheck.py
2026-04-15 22:56:44 +08:00

204 lines
6.4 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.

#!/usr/bin/env python3
"""
发版前预检查工具
检查内容:
1. 当前版本号是否相对最近 tag 有变化
2. 本次热更新清单中新增/修改/删除了哪些文件
3. 是否存在重命名、删除或未跟踪但会被纳入热更新的文件
"""
from __future__ import annotations
import sys
from pathlib import Path
from typing import Any, Dict, List, Tuple
from generate_update_manifest import (
generate_manifest,
get_previous_release_tag,
load_manifest_from_tag,
read_repo_config,
read_version,
run_git_command,
)
def get_latest_tag(base_dir: Path) -> str:
"""获取仓库中最新的版本 tag"""
try:
output = run_git_command(base_dir, ['tag', '--list', 'v*', '--sort=-version:refname'])
except Exception:
return ""
for raw_tag in output.splitlines():
tag = raw_tag.strip()
if tag:
return tag
return ""
def build_file_map(manifest: Dict[str, Any]) -> Dict[str, Dict[str, Any]]:
"""将 manifest 文件列表转换成路径索引"""
return {entry['path']: entry for entry in manifest.get('files', [])}
def get_manifest_diff(
current_manifest: Dict[str, Any],
previous_manifest: Dict[str, Any] | None,
) -> Tuple[List[str], List[str], List[str]]:
"""比较当前 manifest 与上一个版本 manifest 的差异"""
current_files = build_file_map(current_manifest)
previous_files = build_file_map(previous_manifest or {})
new_files = sorted(path for path in current_files if path not in previous_files)
changed_files = sorted(
path
for path, file_info in current_files.items()
if path in previous_files and previous_files[path].get('md5') != file_info.get('md5')
)
previous_deleted = {
entry['path']
for entry in (previous_manifest or {}).get('deleted_files', [])
if entry.get('path')
}
current_deleted = {
entry['path']
for entry in current_manifest.get('deleted_files', [])
if entry.get('path')
}
deleted_files = sorted(current_deleted - previous_deleted)
return new_files, changed_files, deleted_files
def get_git_name_status(base_dir: Path, base_ref: str) -> List[Tuple[str, List[str]]]:
"""读取 git diff 的 name-status 结果"""
if not base_ref:
return []
try:
output = run_git_command(base_dir, ['diff', '--name-status', '--find-renames=90%', base_ref])
except Exception:
return []
results: List[Tuple[str, List[str]]] = []
for raw_line in output.splitlines():
line = raw_line.strip()
if not line:
continue
parts = line.split('\t')
if not parts:
continue
status = parts[0]
paths = parts[1:]
results.append((status, paths))
return results
def get_untracked_files(base_dir: Path) -> List[str]:
"""获取未跟踪文件列表"""
try:
output = run_git_command(base_dir, ['ls-files', '--others', '--exclude-standard'])
except Exception:
return []
return sorted(line.strip() for line in output.splitlines() if line.strip())
def print_section(title: str, items: List[str], limit: int = 15):
"""打印列表区块"""
print(f"\n[{title}] {len(items)}")
if not items:
print(" - 无")
return
for item in items[:limit]:
print(f" - {item}")
if len(items) > limit:
print(f" - ... 还有 {len(items) - limit}")
def main() -> int:
base_dir = Path(__file__).parent
current_version = read_version(base_dir)
latest_tag = get_latest_tag(base_dir)
previous_tag = get_previous_release_tag(base_dir, current_version)
previous_manifest = load_manifest_from_tag(base_dir, previous_tag)
owner, repo = read_repo_config()
current_manifest = generate_manifest(
base_dir,
version=current_version,
owner=owner,
repo=repo,
previous_manifest=previous_manifest,
)
new_files, changed_files, deleted_files = get_manifest_diff(current_manifest, previous_manifest)
git_name_status = get_git_name_status(base_dir, latest_tag)
renamed_files = [
f"{paths[0]} -> {paths[1]}"
for status, paths in git_name_status
if status.startswith('R') and len(paths) == 2
]
removed_files = [paths[0] for status, paths in git_name_status if status == 'D' and paths]
untracked_files = get_untracked_files(base_dir)
tracked_paths = set(build_file_map(current_manifest))
untracked_updatable_files = sorted(path for path in untracked_files if path in tracked_paths)
version_unchanged = bool(latest_tag and current_version == latest_tag)
has_manifest_changes = bool(new_files or changed_files or deleted_files)
print("发版预检查")
print("=" * 60)
print(f"当前版本: {current_version}")
print(f"最新 tag: {latest_tag or ''}")
print(f"用于对比的上一版本 tag: {previous_tag or ''}")
print(f"本次将纳入热更新的文件数: {len(current_manifest.get('files', []))}")
print(f"累计待删除文件数: {len(current_manifest.get('deleted_files', []))}")
print_section('新增热更新文件', new_files)
print_section('修改的热更新文件', changed_files)
print_section('待删除的旧文件', deleted_files)
print_section('git 检测到的重命名', renamed_files)
print_section('git 检测到的删除', removed_files)
print_section('未跟踪但会纳入热更新的文件', untracked_updatable_files)
warnings: List[str] = []
blocked: List[str] = []
if version_unchanged and has_manifest_changes:
blocked.append(
f"检测到可热更新内容发生变化,但 static/version.txt 仍是 {current_version}"
)
if renamed_files:
warnings.append("存在重命名文件。热更新虽然可新增新文件,但旧文件清理仍需确认。")
if deleted_files:
warnings.append("存在待删除文件。请确认本次删除符合预期。")
if untracked_updatable_files:
warnings.append("存在未跟踪但会纳入热更新规则的文件。若不提交到 GitAction 不会包含它们。")
print("\n[结论]")
if blocked:
for item in blocked:
print(f" - 阻塞: {item}")
if warnings:
for item in warnings:
print(f" - 提示: {item}")
if not blocked and not warnings:
print(" - 可以直接发版")
return 1 if blocked else 0
if __name__ == '__main__':
sys.exit(main())