#!/usr/bin/env python3 # -*- coding: utf-8 -*- """compare-tree.py —— 把**本地 git 基线**(manifest:mode/type/sha\\tpath)与服务器工作树逐文件比对。 输出三类(这是"能不能整树覆盖"的唯一判据,不盲覆盖): · 一致 :服务器文件内容 == 本地基线 · 不一致 :两边都有但内容不同(**要逐个看是谁的改动**) · 服务器独有:服务器有、基线里没有(**绝不能抹**) 再加一类「仅在基线里」:本地有、服务器缺(同步时要补上)。 用法(在服务器上):python3 scripts/compare-tree.py <工作树根> """ 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))