mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 14:24:15 +08:00
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Read-only Git overlap and conflict preview for secondary-development branches."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def _git(root: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
|
|
result = subprocess.run(
|
|
["git", *args],
|
|
cwd=root,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if check and result.returncode != 0:
|
|
detail = (result.stderr or result.stdout).strip()
|
|
raise RuntimeError(detail or f"git {' '.join(args)} failed")
|
|
return result
|
|
|
|
|
|
def _lines(result: subprocess.CompletedProcess[str]) -> set[str]:
|
|
return {line.strip() for line in result.stdout.splitlines() if line.strip()}
|
|
|
|
|
|
def _resolve_repo(path: Path) -> Path:
|
|
result = _git(path, "rev-parse", "--show-toplevel")
|
|
return Path(result.stdout.strip()).resolve()
|
|
|
|
|
|
def _resolve_commit(root: Path, ref: str) -> str:
|
|
return _git(root, "rev-parse", "--verify", f"{ref}^{{commit}}").stdout.strip()
|
|
|
|
|
|
def _merge_tree_conflicts(root: Path, base: str, current: str, target: str) -> list[str]:
|
|
result = _git(root, "merge-tree", base, current, target)
|
|
lines = result.stdout.splitlines()
|
|
conflicts: list[str] = []
|
|
current_path: str | None = None
|
|
current_conflicted = False
|
|
for line in [*lines, ""]:
|
|
if line in {"changed in both", "added in both", "removed in both"}:
|
|
if current_path and current_conflicted:
|
|
conflicts.append(current_path)
|
|
current_path = None
|
|
current_conflicted = False
|
|
continue
|
|
parts = line.strip().split()
|
|
if current_path is None and len(parts) >= 4 and parts[0] in {"base", "our", "their"}:
|
|
current_path = parts[-1]
|
|
if line.startswith("+<<<<<<< ") or line.startswith("+=======") or line.startswith("+>>>>>>> "):
|
|
current_conflicted = True
|
|
if current_path and current_conflicted:
|
|
conflicts.append(current_path)
|
|
return sorted(set(conflicts))
|
|
|
|
|
|
def _print_paths(title: str, paths: list[str]) -> None:
|
|
print(f"\n{title}: {len(paths)}")
|
|
for path in paths:
|
|
print(f" - {path}")
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description="只读检查当前二开分支升级到目标 Git ref 时的源码重叠与文本冲突",
|
|
)
|
|
parser.add_argument("target", help="目标 Tag/分支/commit, 例如 v0.3.1")
|
|
parser.add_argument(
|
|
"--repo",
|
|
type=Path,
|
|
default=Path.cwd(),
|
|
help="仓库路径, 默认当前目录",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
try:
|
|
root = _resolve_repo(args.repo.resolve())
|
|
current = _resolve_commit(root, "HEAD")
|
|
target = _resolve_commit(root, args.target)
|
|
base = _git(root, "merge-base", current, target).stdout.strip()
|
|
if not base:
|
|
raise RuntimeError("当前分支与目标版本没有共同基线")
|
|
|
|
current_paths = _lines(_git(root, "diff", "--name-only", f"{base}..{current}"))
|
|
target_paths = _lines(_git(root, "diff", "--name-only", f"{base}..{target}"))
|
|
overlap = sorted(current_paths & target_paths)
|
|
conflicts = _merge_tree_conflicts(root, base, current, target)
|
|
dirty = sorted(_lines(_git(root, "status", "--short")))
|
|
|
|
print("Tick Stock Panel 二开升级预检")
|
|
print(f"仓库: {root}")
|
|
print(f"当前: {current[:12]}")
|
|
print(f"目标: {args.target} ({target[:12]})")
|
|
print(f"共同基线: {base[:12]}")
|
|
print(f"当前分支修改文件: {len(current_paths)}")
|
|
print(f"目标版本修改文件: {len(target_paths)}")
|
|
_print_paths("双方修改的同一文件", overlap)
|
|
_print_paths("Git 三方预演识别的文本冲突", conflicts)
|
|
|
|
if dirty:
|
|
print(f"\n未提交工作区条目: {len(dirty)}")
|
|
print(" 注意: 未提交内容不会进入 Git 三方预演, 请先提交到临时分支后再复检。")
|
|
if conflicts:
|
|
print("\n结论: 需要人工解决文本冲突并执行完整回归。")
|
|
return 2
|
|
if overlap:
|
|
print("\n结论: Git 未发现文本冲突, 但重叠文件需要检查语义兼容和测试。")
|
|
return 0
|
|
print("\n结论: 未发现源码重叠; 仍需执行目标版本的验证矩阵。")
|
|
return 0
|
|
except (OSError, RuntimeError) as exc:
|
|
print(f"升级预检失败: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|