Join the RoClicks Discord
RoClicks

check_copies.py

Python, 63 lines from UI overview

Download check_copies.py
#!/usr/bin/env python3
"""check_copies.py: fail when copies of the same runtime module have drifted apart.

The bug it prevents: the UI runtime module lived in three places (live StarterGui copy, the
Builder's private copy in ServerStorage, the source tree). Fixes went into one; the Builder's copy
fell 430 lines behind, and the next rebuild would have installed it and crashed the HUD on init.

Give it a manifest of groups. Each group lists files that must be byte-identical (after normalising
line endings and trailing whitespace). Paths are relative to the manifest. Export Studio copies to
disk first (e.g. a snapshot folder) so they can be compared here.

copies.json:
  {
    "ui-runtime": ["src/ui/client/UI.luau", "snapshot/ServerStorage/UIBuild/Client/UI.luau",
                   "snapshot/StarterGui/MainUI/UI.luau"],
    "controller": ["src/ui/client/Controller.client.luau", "snapshot/ServerStorage/UIBuild/Client/Controller.luau"]
  }

Usage:  python3 check_copies.py copies.json        (exit 1 on any drift; run before and after every push)
"""
import hashlib
import json
import os
import sys


def norm_hash(path):
    text = open(path, encoding="utf-8").read().replace("\r\n", "\n")
    text = "\n".join(line.rstrip() for line in text.split("\n")).strip() + "\n"
    return hashlib.sha256(text.encode()).hexdigest()[:12], text.count("\n")


def main(manifest):
    base = os.path.dirname(os.path.abspath(manifest))
    groups = json.load(open(manifest))
    bad = 0
    for name, files in groups.items():
        seen = {}
        for rel in files:
            p = os.path.join(base, rel)
            if not os.path.exists(p):
                print(f"[{name}] MISSING {rel}")
                bad += 1
                continue
            h, lines = norm_hash(p)
            seen.setdefault(h, []).append((rel, lines))
        if len(seen) > 1:
            bad += 1
            print(f"[{name}] DRIFT: {len(seen)} different versions")
            for h, items in seen.items():
                for rel, lines in items:
                    print(f"   {h}  {lines:5d} lines  {rel}")
            print("   -> copy the canonical file (source tree) over the others, then re-push.")
        else:
            print(f"[{name}] ok ({len(files)} copies)")
    sys.exit(1 if bad else 0)


if __name__ == "__main__":
    if len(sys.argv) != 2:
        sys.exit(__doc__)
    main(sys.argv[1])