tools: add canonical sweep.py with explicit coa-elvui exclusion
Two failures that compounded in the last round of ElvUI breakage:
1. The sweep script only lived in /tmp during a session — re-derived
from scratch each time, so the EXCLUDE_FORKS knowledge wasn't
anywhere reviewable.
2. The old filename-only filter ('if -ElvUI in name skip') missed
ElvUI's customizations inside otherwise-stock-named files AND
rsync --delete killed -ElvUI suffixed widgets that exist only in
ElvUI's bundle (e.g. AceGUIWidget-Button-ElvUI.lua).
This tool fixes both:
- Lives in the repo (tools/sweep.py), so the exclusion list is
visible in version control and reviewable.
- EXCLUDED_FORKS = {'coa-elvui'} hardcoded with an in-source comment
explaining why.
- --exclude='*-ElvUI*' passed to every rsync as belt-and-braces, so
even if a future fork accidentally carries an ElvUI-namespaced
file we never wanted to overwrite, the sweep won't touch it.
- Refuses to add new lib dirs — only updates ones already present
in the fork.
- --dry-run flag for safe verification.
README updated with a 'Forks excluded from sweep' section documenting
the same.
This commit is contained in:
@@ -69,3 +69,20 @@ libs. Note the new upstream commit in the README's commit-pin line above, and re
|
||||
the CoA-compat patches listed above against the new revision (the FDID one is mechanical —
|
||||
see `/tmp/fix_fdid.py` history). Keep patches **minimal and documented in this README**;
|
||||
prefer fixing them upstream where reasonable.
|
||||
|
||||
Run the sweep via `tools/sweep.py` from this repo — it walks every sibling `coa-*` fork
|
||||
under `/home/sub/repos/coa`, finds each fork's bundled `LibStub` / `CallbackHandler-1.0` /
|
||||
`Ace*-3.0` dirs, and rsyncs them from this bundle. Use `--dry-run` first.
|
||||
|
||||
### Forks excluded from sweep
|
||||
|
||||
`coa-elvui` is excluded from the sweep — see `EXCLUDED_FORKS` in `tools/sweep.py`. ElvUI
|
||||
ships its own bundled Ace3 stack with ElvUI-specific patches inside otherwise-stock-named
|
||||
files (`AceLocale-3.0.lua`, `AceConfigDialog-3.0.lua`, every `AceGUI-3.0/widgets/*.lua`)
|
||||
**plus** `-ElvUI`-suffixed widgets that don't exist in canonical at all (e.g.
|
||||
`AceGUIWidget-Button-ElvUI.lua`). `rsync --delete` obliterates the latter; an in-place
|
||||
sync overwrites the former. Either failure breaks `/ec` and floods locale errors. ElvUI
|
||||
maintains its own bundle on its own cadence and must never be touched by this tool.
|
||||
|
||||
The sweep also passes `--exclude='*-ElvUI*'` to every rsync as a belt-and-braces guard
|
||||
against future forks that happen to carry an ElvUI-namespaced file we didn't anticipate.
|
||||
|
||||
Executable
+163
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sweep this coa-ace3 bundle into every Exiles fork that embeds Ace3 libs.
|
||||
|
||||
Run from anywhere; the script locates itself and walks sibling repos at
|
||||
$REPOS_ROOT (default /home/sub/repos/coa). For each fork that bundles
|
||||
any AceXxx-3.0 library, the matching directory is rsynced from this
|
||||
bundle with --delete so the fork stays byte-identical to canonical.
|
||||
|
||||
USAGE
|
||||
tools/sweep.py # apply sweep
|
||||
tools/sweep.py --dry-run # show what would change without writing
|
||||
|
||||
WHY THIS SCRIPT EXISTS
|
||||
Previous sweeps were ad-hoc scripts living in /tmp during a session
|
||||
and re-derived by hand each time. That made it easy to forget the
|
||||
ElvUI exclusion (see EXCLUDED_FORKS below) and re-clobber ElvUI's
|
||||
customized lib stack on every sync. Committing the canonical script
|
||||
here keeps the exclusion list visible and reviewable.
|
||||
|
||||
EXCLUDED_FORKS
|
||||
coa-elvui ships its OWN bundled Ace3 with ElvUI-specific patches
|
||||
inside otherwise-stock-named files (AceLocale, AceConfigDialog, the
|
||||
AceGUI widgets) PLUS files that don't exist in canonical at all
|
||||
(AceGUIWidget-Button-ElvUI.lua). rsync --delete obliterates the
|
||||
latter; an in-place sync overwrites the former. Either failure
|
||||
breaks /ec and floods locale errors. ElvUI maintains its own bundle
|
||||
on its own cadence and must never be swept by this tool.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# --- configuration -----------------------------------------------------------
|
||||
|
||||
REPOS_ROOT = Path(os.environ.get("REPOS_ROOT", "/home/sub/repos/coa"))
|
||||
BUNDLE = Path(__file__).resolve().parent.parent # the coa-ace3 repo root
|
||||
|
||||
# Forks that this sweep MUST NOT touch. Document the reason inline.
|
||||
EXCLUDED_FORKS = {
|
||||
"coa-elvui", # ships its own ElvUI-patched Ace3 stack; sweep would clobber it
|
||||
}
|
||||
|
||||
# Lib names whose directories we sync from the bundle.
|
||||
LIB_NAMES = re.compile(r"^(LibStub|CallbackHandler-1\.0|Ace\w*-3\.0)$")
|
||||
|
||||
# rsync excludes — repo metadata never deployed, plus a belt-and-braces
|
||||
# guard against deleting any -ElvUI file that might exist in destinations we
|
||||
# didn't intend to touch (e.g. a future fork that pulls in some ElvUI widget).
|
||||
RSYNC_EXCLUDES = [
|
||||
".git", ".gitattributes", ".gitignore", ".github", ".idea",
|
||||
".editorconfig", ".luacheckrc", ".pkgmeta",
|
||||
"*-ElvUI*", # never delete or overwrite ElvUI-namespaced files
|
||||
]
|
||||
|
||||
|
||||
# --- discovery ---------------------------------------------------------------
|
||||
|
||||
def bundle_lib_sources():
|
||||
"""Return {libname: absolute path inside bundle} for every shippable lib.
|
||||
|
||||
Handles the nested AceConfig children (AceConfigCmd/Dialog/Registry).
|
||||
"""
|
||||
out = {}
|
||||
for path in BUNDLE.rglob("*"):
|
||||
if not path.is_dir():
|
||||
continue
|
||||
if path.name in {".git", "tools"}:
|
||||
continue
|
||||
if LIB_NAMES.match(path.name):
|
||||
out.setdefault(path.name, path)
|
||||
return out
|
||||
|
||||
|
||||
def fork_lib_targets(fork: Path, src_names):
|
||||
"""Return {libname: absolute path inside fork} for each lib the fork bundles.
|
||||
|
||||
A lib is "bundled" when there's a top-level directory containing
|
||||
<libname>/<libname>.lua. We don't add new libs to forks that didn't
|
||||
already ship them.
|
||||
"""
|
||||
targets = {}
|
||||
for path in fork.rglob("*.lua"):
|
||||
if "/.git/" in str(path):
|
||||
continue
|
||||
if "-ElvUI" in path.name:
|
||||
continue
|
||||
name = path.stem
|
||||
if name not in src_names:
|
||||
continue
|
||||
parent = path.parent
|
||||
if parent.name != name:
|
||||
continue
|
||||
targets.setdefault(name, parent)
|
||||
return targets
|
||||
|
||||
|
||||
# --- sweep -------------------------------------------------------------------
|
||||
|
||||
def sweep(dry_run: bool) -> int:
|
||||
src_map = bundle_lib_sources()
|
||||
if not src_map:
|
||||
print(f"no libs found inside {BUNDLE}; nothing to sweep", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
forks = sorted(p for p in REPOS_ROOT.iterdir()
|
||||
if p.is_dir() and p.name.startswith("coa-")
|
||||
and p.name != "coa-ace3"
|
||||
and (p / ".git").exists())
|
||||
|
||||
print(f"bundle: {BUNDLE}")
|
||||
print(f"forks scan: {REPOS_ROOT}")
|
||||
print(f"excluded: {sorted(EXCLUDED_FORKS)}")
|
||||
print(f"libs: {sorted(src_map)}")
|
||||
print()
|
||||
|
||||
total_synced = total_skipped = 0
|
||||
for fork in forks:
|
||||
if fork.name in EXCLUDED_FORKS:
|
||||
print(f" skip {fork.name} (excluded — ships its own customized Ace3)")
|
||||
total_skipped += 1
|
||||
continue
|
||||
|
||||
targets = fork_lib_targets(fork, src_map.keys())
|
||||
if not targets:
|
||||
print(f" no-libs {fork.name}")
|
||||
continue
|
||||
|
||||
for libname in sorted(targets):
|
||||
src = src_map[libname]
|
||||
dst = targets[libname]
|
||||
cmd = ["rsync", "-a", "--delete"]
|
||||
if dry_run:
|
||||
cmd += ["--dry-run", "--itemize-changes"]
|
||||
for exc in RSYNC_EXCLUDES:
|
||||
cmd += ["--exclude", exc]
|
||||
cmd += [f"{src}/", f"{dst}/"]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if res.returncode != 0:
|
||||
print(f" ! rsync failed for {fork.name}/{libname}: {res.stderr.strip()}")
|
||||
continue
|
||||
tag = "would-sync" if dry_run else "synced"
|
||||
print(f" {tag:<9} {fork.name}/{dst.relative_to(fork)}")
|
||||
total_synced += 1
|
||||
|
||||
print()
|
||||
print(f"summary: {total_synced} lib dirs {'would be synced' if dry_run else 'synced'}, "
|
||||
f"{total_skipped} forks excluded")
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--dry-run", action="store_true",
|
||||
help="show what would change without writing")
|
||||
args = ap.parse_args()
|
||||
sys.exit(sweep(args.dry_run))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user