90 lines
3.1 KiB
Python
90 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))
|