Artifact ec1ed6d6dd008baff21a63c6225aa06768fcf67e0876e23e1813001bb8c329b6:
- Executable file
scripts/archive-compare.py
— part of check-in
[96a6d46d19]
at
2026-08-17 15:32:05
on branch trunk
— tarball: check zit's archives against fossil's, modes included (#95)
Comparing zit to itself cannot catch a wrong permission bit -- writer and reader agree in the same direction. Fossil is an independent witness that already gets it right, so the harness unpacks both archives and compares member set, modes, and contents. It found one: -R was dropped at dispatch, which every test missed by running inside a checkout. Fossil versions symlinks where zit add still refuses them, so an imported corpus is also what finally exercises the l-perm path. (user: dmestas, size: 3150) [annotate] [blame] [check-ins using]
#!/usr/bin/env python3 """Compare two unpacked-archive descriptions (#95). Each input is one member per line, tab separated: RELATIVE-PATH <TAB> KIND <TAB> SHA256 where KIND is `file`, `exec`, or `symlink`, and SHA256 is over the file's contents or, for a symlink, over its target path. The three categories are reported separately on purpose. "Same bytes, wrong bit" is a different defect from "wrong bytes": the first ships a release whose ./configure will not run, the second ships a corrupt file, and a comparator that only said "these differ" would leave the reader to work out which. Exits 0 when the two describe the same tree, 1 otherwise. """ import sys def load(path): members = {} with open(path) as handle: for lineno, line in enumerate(handle, 1): line = line.rstrip("\n") if not line: continue parts = line.split("\t") if len(parts) != 3: raise SystemExit(f"{path}:{lineno}: expected 3 tab-separated fields") rel, kind, digest = parts members[rel] = (kind, digest) return members def main(argv): if len(argv) != 3: raise SystemExit("usage: archive-compare.py FOSSIL.members ZIT.members") fossil, zit = load(argv[1]), load(argv[2]) bad = False only_fossil = sorted(set(fossil) - set(zit)) only_zit = sorted(set(zit) - set(fossil)) if only_fossil or only_zit: bad = True print(" member sets DIFFER") for path in only_fossil: print(f" only in fossil: {path}") for path in only_zit: print(f" only in zit: {path}") else: print(f" member sets identical ({len(fossil)})") shared = sorted(set(fossil) & set(zit)) mode_bad = [p for p in shared if fossil[p][0] != zit[p][0]] if mode_bad: bad = True print(" modes DIFFER (an executable that unpacks 0644 is the archetypal release bug)") for path in mode_bad: print(f" {path}: fossil={fossil[path][0]} zit={zit[path][0]}") else: print(" modes identical") hash_bad = [p for p in shared if fossil[p][1] != zit[p][1]] if hash_bad: bad = True print(" contents DIFFER") for path in hash_bad: print(f" {path}") else: print(" contents identical") # Say what was actually compared. "modes identical" over a corpus of plain # files proves nothing about modes, and reads exactly like a corpus that # exercised them -- silence is not success. Reported rather than enforced, # because a caller may legitimately point this at a corpus it did not # choose; the caller that builds its own fixture should check this line. kinds = sorted({fossil[p][0] for p in shared}) print(" kinds compared: %s" % (", ".join(kinds) if kinds else "none")) for expected in ("exec", "symlink"): if expected not in kinds: print(f" note: no {expected} member in this corpus, so that mode is untested") return 1 if bad else 0 if __name__ == "__main__": sys.exit(main(sys.argv))