Configuration scopes: system, user, and project config resolution#564
Open
tony wants to merge 7 commits into
Open
Configuration scopes: system, user, and project config resolution#564tony wants to merge 7 commits into
tony wants to merge 7 commits into
Conversation
why: Configuration came from one home dotfile and whatever find_config_files happened to turn up, so a repository could not ship a checkout manifest next to its code. Resolving system, user, and project scopes into one ordered stack gives every directory a predictable answer to "which configuration is in effect here?". what: - Add a resolver that orders system, user, and per-ancestor project configuration files weakest to strongest. - Bound the upward project walk at $HOME, overridable through VCSPULL_CEILING_PATHS, and follow symlinks on both sides so a linked home still stops the walk. - Let VCSPULL_NO_PROJECT drop the project scope entirely. - Classify a caller-supplied path as system, user, project, or external, and have discover share that classification. - Tolerate scope directories that are unreadable or hold a directory named like a configuration file.
why: Loading several configuration files kept the first entry for a repository and aborted outright when two files named the same destination with different VCS. With a resolved scope stack that is the normal case, not an error: a project file exists precisely to repoint a repository the user config also names. what: - Union entries across files by destination and let the strongest file replace a colliding entry whole, so a repointed repository does not inherit the weaker file's options. - Warn only when a replaced entry actually disagrees on url or vcs; identical duplicates across scopes stay silent.
why: A .vcspull.yaml found by walking up from the working directory is code from whoever wrote that repository. Loading it unconditionally would let a cloned project choose where vcspull checks repositories out, including paths like ~/.ssh. what: - Read every scope through one loader that asks each project config whether its checkout destinations stay inside its own directory. - Prompt once per project directory when a destination escapes, remember an "always" answer, and hard-error instead of prompting when there is no terminal. - Check destinations rather than workspace-root keys, so a per-entry path: override cannot smuggle a checkout out of a contained tree. - Gate add, discover, fmt, and migrate on the same question before they rewrite a project file vcspull found on its own; naming a file with --file remains consent. - Accept --trust-project and --no-project both before and after the subcommand, and honour VCSPULL_YES. - Resolve fmt --all and migrate --all through the scope stack.
why: A VCSPullException is vcspull telling the user something they can act on, such as an untrusted project config or an unreadable file. Letting it reach the interpreter buried that sentence under a traceback and exited with the wrong status. what: - Catch VCSPullException at the entry point, print it as a single stderr line, and exit 1. - Keep the traceback available under --log-level debug.
why: With several files contributing to one stack, a user who sees an unexpected repository — or a missing one — has no way to ask which files were read, which entries a nearer file overrode, and which project config is waiting on consent. what: - Add 'vcspull config ls', printing every resolved file weakest first with its scope, repository count, overridden count, and whether it is untrusted. - Add 'vcspull trust' to record, remove, or print trusted project directories.
why: find_config_files returned after its first path whenever it was handed a list, so only one directory was ever searched. It also ignored the .yml suffix, leaving those files invisible to every command that discovers configuration. what: - Search every path in a list instead of returning after the first. - Accept .yml alongside .yaml and .json.
why: Scope resolution changes which files vcspull reads and adds a consent step that can stop a load, so the reference needs to say where configuration comes from, how a nearer file overrides a weaker one, and what to do when a project config is refused. what: - Add a scopes page covering the resolution order, the bounded project walk, the containment rule, and the environment overrides. - Add reference pages for the config and trust commands. - Note the scope stack and its flags on the CLI index and on the commands that resolve it.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #564 +/- ##
==========================================
+ Coverage 83.30% 83.99% +0.68%
==========================================
Files 32 34 +2
Lines 4492 4716 +224
Branches 903 934 +31
==========================================
+ Hits 3742 3961 +219
- Misses 499 506 +7
+ Partials 251 249 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/etc/vcspull/), user (XDG dir and~/.vcspull.*), and project (.vcspull.*discovered by walking up from the working directory to$HOME). All scopes are unioned, so repositories in your home config stay available from any directory while a project can add its own on top.[y/N/always]), errors cleanly without a TTY instead of hanging, and is silent when self-contained. Containment is checked against resolved destinations, honoring per-entrypath:overrides.vcspull config ls(shows the scopes in effect, weakest-first, with override signals) andvcspull trust/--untrust/--show.--no-projectand--trust-project(plusVCSPULL_NO_PROJECT/VCSPULL_YES), accepted before or after the subcommand..ymluniformly and to scan every configured path rather than only the first.Changes by area
Resolution
src/vcspull/_internal/scopes.py: New pure resolver — emits an orderedConfigSourcesequence and answers containment/trust questions with no parsing and no prompting. Consumed by loading,config ls,trust, anddiscover's label logic.src/vcspull/config.py:load_scoped_configswalks the resolved sources; the merge applies nearest-wins by destination key; the consent gate runs after destinations are resolved.CLI
src/vcspull/cli/config.py:config lsandtrustcommand handlers.src/vcspull/cli/__init__.py: Subcommand wiring, shared scope/trust flag parsers, and a top-level handler that reportsVCSPullExceptionas one line to stderr instead of a traceback.sync,list,status,search,worktree): resolve the scope stack by default.add,discover,fmt,migrate): consent gate applied at the funnel each path traverses.Docs
docs/configuration/scopes.md: New scopes-and-trust page.docs/cli/config.md,docs/cli/trust.md: Reference pages with generated argument blocks.docs/cli/add.md/docs/cli/list.mdand the--fileprecedence infmt/migratehelp.Design decisions
rev:from a home entry it shadows.--fileare auto-trusted; only discovered project configs, which may come from a cloned repository, are gated.--fileis not gated because it replaces the stack rather than joining the project tier.path:can redirect a clone outside a workspace root, so containment resolves the actual destinationextract_reposwill use.expand_dirasserts against a symlinked cwd (expand_dir assertion fails on a relative workspace root under a symlinked cwd #561). Both are pre-existing and made more visible by discovered ancestor configs; neither is changed here.Verification
Read commands resolve project configs (returns discovered scopes, not just the home config):
No command bypasses the consent gate on the write path:
Test plan
uv run ruff format --check .anduv run ruff check .— cleanuv run mypy— clean understrict = trueuv run py.test— full suite greenjust build-docs— no Sphinx warningstests/test_scopes.py— tier order,$HOMEceiling exclusion (including symlinked$HOME), union with nearest-wins,--filereplaces the stack,VCSPULL_NO_PROJECTdrops the project tiertests/cli/test_config.py—config lsorder and override signal, untrusted marker, no-TTY one-line error, trust round-trip,fmt -fgating, and thatdiscover --yesdoes not grant trustpath:override cannot smuggle a destination outside a contained config*.yaml, unreadable dir) do not crash discovery