Files
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

59 lines
2.8 KiB
Python

"""Changed-file detection shared by the insightscli dev commands.
A thin wrapper over ``git diff`` + ``git status`` so ``insightscli build``, ``ci:preflight``,
and ``test --changed`` agree on "what changed" instead of each rolling its own.
``matches_globs`` is the shared fnmatch predicate. CI computes affectedness its own
way (Turbo, snob); this is just the local dev-CLI helper, not a repo-wide authority.
"""
from __future__ import annotations
import fnmatch
import subprocess
from collections.abc import Iterable
import click
from insightscli.manifest import REPO_ROOT
def _git(*args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(["git", *args], cwd=REPO_ROOT, capture_output=True, text=True)
def changed_files(against: str | None = None, *, include_worktree: bool = True) -> list[str]:
"""Files the branch touches vs a base ref (merge-base), plus uncommitted/untracked work.
An explicit *against* raises on a bad ref so a typo can't masquerade as a clean
diff. The default base tries ``origin/master`` then ``master`` (origin/master is
what CI diffs against and what the staleness check compares to) and degrades to
working-tree-only detection when neither exists (single-branch clones, bare
sandboxes). ``-z`` output keeps paths with spaces/non-ASCII unquoted, and
``--untracked-files=all`` lists files inside brand-new directories.
``include_worktree=False`` restricts the result to the committed branch diff,
dropping uncommitted and untracked work — the correct scope for a pre-push gate,
where only committed changes are actually pushed (and can reach CI). Callers that
iterate on local edits (``build``, ``test --changed``) keep the default.
"""
files: set[str] = set()
for base in [against] if against is not None else ["origin/master", "master"]:
diff = _git("diff", "--name-only", "-z", f"{base}...HEAD")
if diff.returncode == 0:
files.update(path for path in diff.stdout.split("\0") if path)
break
if against is not None:
raise click.UsageError(f"git diff against {against!r} failed: {diff.stderr.strip()}")
if not include_worktree:
return sorted(files)
status = _git("status", "--porcelain", "-z", "--no-renames", "--untracked-files=all")
if status.returncode != 0:
# A failed status (e.g. index.lock contention) must not read as "no uncommitted work".
raise click.UsageError(f"git status failed: {status.stderr.strip()}")
files.update(entry[3:] for entry in status.stdout.split("\0") if len(entry) > 3)
return sorted(files)
def matches_globs(path: str, globs: Iterable[str]) -> bool:
"""True if *path* matches any fnmatch glob (``*`` spans ``/``, matching build & CI)."""
return any(fnmatch.fnmatch(path, glob) for glob in globs)