Download scripts/check-case-collisions.py from SaylorTwift/hermes-agent: direct link, hf CLI and curl.
- Browser
- Download file 4.09 kB
-
https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/scripts/check-case-collisions.py
- Command line
-
hf download hf://SaylorTwift/hermes-agent/scripts/check-case-collisions.py
-
curl -L -o check-case-collisions.py https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/scripts/check-case-collisions.py
4.09 kB
| #!/usr/bin/env python3 | |
| """ | |
| Blocking check for tracked files whose paths collide when case is ignored. | |
| Linux is case-sensitive; Windows and macOS (default) are not. Two tracked | |
| paths that differ only by case — ``README.md`` and ``readme.md``, or | |
| ``src/Foo.py`` and ``SRC/foo.py`` — coexist happily in a Linux checkout and | |
| silently break every clone on a case-insensitive host: the filesystem can | |
| hold only one of them, so checkout either refuses or whichever file is | |
| written last wins and clobbers the other. Git itself won't stop the pair | |
| from landing — it only warns at checkout time, on a case-insensitive FS, | |
| for whichever client happens to do the checkout, and the collision is | |
| invisible on Linux. This check is the enforcement point: scan the index, | |
| fail the build, name the offenders. | |
| Usage: | |
| # Check the checkout this script lives in (CI + the common local case) | |
| python scripts/check-case-collisions.py | |
| # Check an arbitrary git checkout (tests, other worktrees) | |
| python scripts/check-case-collisions.py /path/to/other/repo | |
| Exit status: | |
| 0 — no case-colliding tracked paths | |
| 1 — at least one collision group (paths printed to stdout) | |
| 2 — not in a git repository / git failed | |
| Comparison key: the casefolded FULL path (``str.casefold``), not the | |
| basename — on a case-insensitive filesystem the entire path is | |
| case-insensitive, so ``dir/Foo.txt`` and ``DIR/foo.txt`` collide just like | |
| same-directory pairs. ``casefold`` (not ``lower``) is used because it | |
| matches how the OSes fold case for non-ASCII text (straße vs strasse, | |
| sigma variants); a pair it flags is a genuine collision on macOS/Windows | |
| even when Linux disagrees. | |
| Deliberately out of scope: Unicode NFC/NFD normalization collisions (macOS | |
| stores NFD, Linux NFC). git already handles those at checkout via | |
| ``core.precomposeunicode``; this check is strictly about case. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import subprocess | |
| import sys | |
| from collections import defaultdict | |
| from pathlib import Path | |
| REPO_ROOT = Path(__file__).resolve().parent.parent | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| "root", | |
| nargs="?", | |
| default=str(REPO_ROOT), | |
| help="git checkout to scan (default: the repo this script lives in)", | |
| ) | |
| args = parser.parse_args() | |
| try: | |
| os.chdir(args.root) | |
| except OSError as exc: | |
| print(f"::error::cannot enter {args.root}: {exc}") | |
| return 2 | |
| proc = subprocess.run(["git", "ls-files", "-z"], capture_output=True) | |
| if proc.returncode != 0: | |
| msg = proc.stderr.decode("utf-8", errors="replace").strip() | |
| print(f"::error::git ls-files failed in {args.root}: {msg}") | |
| return 2 | |
| paths = [ | |
| p.decode("utf-8", errors="surrogateescape") | |
| for p in proc.stdout.split(b"\0") | |
| if p | |
| ] | |
| by_casefold: dict[str, list[str]] = defaultdict(list) | |
| for path in paths: | |
| by_casefold[path.casefold()].append(path) | |
| collisions = {key: group for key, group in by_casefold.items() if len(group) > 1} | |
| if not collisions: | |
| print(f"::notice::{len(paths)} tracked files, no case-colliding paths.") | |
| return 0 | |
| print( | |
| f"::error::Found {len(collisions)} case-collision group(s) among " | |
| f"{len(paths)} tracked files." | |
| ) | |
| print( | |
| "Paths that differ only by case are ONE file on Windows/macOS but " | |
| "several on Linux - the pair breaks every clone on a case-insensitive " | |
| "host. Rename one member of each group so the paths differ beyond case." | |
| ) | |
| print() | |
| for key, group in sorted(collisions.items()): | |
| for path in sorted(group): | |
| print(f" {path}") | |
| print() | |
| print( | |
| "Fix: `git mv` one path in each group to a name that doesn't collide. " | |
| "On Windows/macOS you may need two steps (`git mv a.txt tmp && git mv " | |
| "tmp A.txt`) because the filesystem can't hold both spellings at once." | |
| ) | |
| return 1 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |