#!/usr/bin/env python3 """ treediff.py - case-insensitive loose-file diff of two directory trees. python3 treediff.py [labelA] [labelB] Compares by relative path (lowercased, '/'-normalised) and md5. Written for the hsh/, content/ and root config comparisons, where the two builds disagree on filename case but not on identity. """ import sys, os, hashlib def scan(root): out = {} for dirpath, _dirs, files in os.walk(root): for f in files: p = os.path.join(dirpath, f) rel = os.path.relpath(p, root).replace("\\", "/") with open(p, "rb") as fh: h = hashlib.md5(fh.read()).hexdigest() out[rel.lower()] = (os.path.getsize(p), h, rel) return out def main(): if len(sys.argv) < 3: sys.exit(__doc__) la = sys.argv[3] if len(sys.argv) > 3 else "A" lb = sys.argv[4] if len(sys.argv) > 4 else "B" a, b = scan(sys.argv[1]), scan(sys.argv[2]) aonly = sorted(set(a) - set(b)) bonly = sorted(set(b) - set(a)) diff = sorted(k for k in set(a) & set(b) if a[k][1] != b[k][1]) print(f"# {la}={len(a)} files, {lb}={len(b)} files | " f"identical={len(set(a) & set(b)) - len(diff)} differ={len(diff)} " f"{la}_only={len(aonly)} {lb}_only={len(bonly)}") for k in aonly: print(f"+{la}\t{a[k][2]}\t{a[k][0]}") for k in bonly: print(f"-{lb}\t{b[k][2]}\t{b[k][0]}") for k in diff: print(f"~DIF\t{a[k][2]}\t{la}={a[k][0]}B {lb}={b[k][0]}B") if __name__ == "__main__": main()