**背景**:这些改动原属本工作区另外几个会话(T01/T02 等),**那些会话已被用户删除** ⇒
工作树里的成果处于"无主"状态,一次错误 checkout / 覆盖即**永久丢失** ⇒ 代入库保全。
口径遵循本项目**先例**(`39a1f2e` / `b617cdb`:**别人的活,代入库并在提交信息里注明**)。
**内容**:档案 101「能力管理」改名 + 页内 tab 分页|档案 102 语言切换搬入「用户设置」|
`07-实例UI分区登记表.md`|`scripts/find-ui*.mjs`(UI 元素定位工具)|`poc/portal-entry/`(0.5.3)|
`src/web/locale-pref.ts` + `home-files.ts`(语言偏好持久化)|`test/locale-pref.test.mjs`|
`package.json`|`BRIEF.md` / `INDEX.md` / `docs-manifest.json` / `03-路线图与待办.md` / 档案 100 增量。
**已做最小健全性检查**(⚠️ **未跑完整构建 / 单测** —— 那是原会话的验收职责,本次只求"不丢"):
- JSON 合法:`package.json` / `poc/business-plugins/package.json` / `docs-manifest.json` ✓
- 4 个 TS 文件 `{}`/`()` 配平 ✓;新增文件均非空 ✓
- 规模:13 文件改动 +340/−210,新增 12 条
**未 push**(按 §4 提交边界:用户说"提交",未说"推送")。
91 lines
3.1 KiB
Python
91 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""compare-tree.py —— 把**本地 git 基线**(manifest:mode/type/sha\\tpath)与服务器工作树逐文件比对。
|
||
|
||
输出三类(这是"能不能整树覆盖"的唯一判据,不盲覆盖):
|
||
· 一致 :服务器文件内容 == 本地基线
|
||
· 不一致 :两边都有但内容不同(**要逐个看是谁的改动**)
|
||
· 服务器独有:服务器有、基线里没有(**绝不能抹**)
|
||
再加一类「仅在基线里」:本地有、服务器缺(同步时要补上)。
|
||
|
||
用法(在服务器上):python3 scripts/compare-tree.py <manifest> <工作树根>
|
||
"""
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
|
||
manifest, root = sys.argv[1], sys.argv[2]
|
||
entries = []
|
||
for line in open(manifest, encoding='utf-8'):
|
||
line = line.rstrip('\n')
|
||
if not line.strip():
|
||
continue
|
||
head, path = line.split('\t', 1)
|
||
mode, typ, sha = head.split()
|
||
entries.append((path, sha))
|
||
|
||
paths = [p for p, _ in entries]
|
||
present = [p for p in paths if os.path.isfile(os.path.join(root, p))]
|
||
missing = [p for p in paths if p not in set(present)]
|
||
|
||
# 批量算 sha(git hash-object --stdin-paths)
|
||
hash_out = {}
|
||
if present:
|
||
proc = subprocess.run(['git', 'hash-object', '--stdin-paths'], cwd=root,
|
||
input='\n'.join(present) + '\n', capture_output=True, text=True)
|
||
for p, h in zip(present, proc.stdout.split('\n')):
|
||
if h.strip():
|
||
hash_out[p] = h.strip()
|
||
|
||
same, diff = [], []
|
||
for p, sha in entries:
|
||
if p in missing:
|
||
continue
|
||
if hash_out.get(p) == sha:
|
||
same.append(p)
|
||
else:
|
||
diff.append((p, sha, hash_out.get(p)))
|
||
|
||
# 服务器独有:扫同一批顶层目录,取基线里没有的文件
|
||
tops = sorted({p.split('/')[0] for p, _ in entries})
|
||
known = set(paths)
|
||
extra = []
|
||
for top in tops:
|
||
base = os.path.join(root, top)
|
||
if not os.path.isdir(base):
|
||
continue
|
||
for dirpath, dirnames, filenames in os.walk(base):
|
||
dirnames[:] = [d for d in dirnames if d not in ('node_modules', '.git', 'dist')]
|
||
for fn in filenames:
|
||
rel = os.path.relpath(os.path.join(dirpath, fn), root).replace(os.sep, '/')
|
||
if rel not in known:
|
||
extra.append(rel)
|
||
|
||
print('=== 汇总 ===')
|
||
print(' 基线条目 : %d' % len(entries))
|
||
print(' 一致 : %d' % len(same))
|
||
print(' 不一致 : %d' % len(diff))
|
||
print(' 仅基线有(缺) : %d' % len(missing))
|
||
print(' 服务器独有 : %d' % len(extra))
|
||
|
||
if diff:
|
||
print('\n=== 不一致(要逐个判断是谁的改动)===')
|
||
for p, want, got in diff[:60]:
|
||
print(' %-58s 基线 %s 服务器 %s' % (p, want[:10], (got or '')[:10]))
|
||
if len(diff) > 60:
|
||
print(' … 还有 %d 个' % (len(diff) - 60))
|
||
|
||
if missing:
|
||
print('\n=== 仅基线有(服务器缺)===')
|
||
for p in missing[:40]:
|
||
print(' ' + p)
|
||
if len(missing) > 40:
|
||
print(' … 还有 %d 个' % (len(missing) - 40))
|
||
|
||
if extra:
|
||
print('\n=== 服务器独有(⛔ 覆盖前必须保留/处理)===')
|
||
for p in extra[:60]:
|
||
print(' ' + p)
|
||
if len(extra) > 60:
|
||
print(' … 还有 %d 个' % (len(extra) - 60))
|