Split --snapshot installer into installer-exact and installer-updated#122
Conversation
The base-protection fix was computing the keep-list from permanent_dependencies(), which only includes conda, plugins, and their deps. This caused installer-provided packages like mamba (in Miniforge) to be removed during the reset step. Now the fix checks for the installer snapshot (initial-state.explicit.txt) and uses it for the reset, preserving whatever the installer shipped. Falls back to permanent_dependencies() when no snapshot exists. Closes #121
Addresses Marco's review: resetting base with the installer snapshot via diff_for_unlink_link_precs would downgrade packages the user has updated (e.g. mamba in Miniforge). Splits the ambiguous `installer` choice into two explicit options: - `installer-exact`: restores the exact installer state (may downgrade). - `installer-updates`: keeps the installer-shipped package names at their currently installed versions (no downgrade). `conda doctor base-protection --fix` now uses the names-only strategy so it no longer downgrades installer-provided packages. Auto-fallback order becomes base-protection > installer-updates > current. Adds `names_from_explicit()` that parses a CEP-23 @explicit file without fetching, using only public conda APIs (MatchSpec + EXPLICIT_MARKER).
--snapshot installer into installer-exact and installer-updates
--snapshot installer into installer-exact and installer-updates--snapshot installer into installer-exact and installer-updated
…aller-packages # Conflicts: # tests/test_cli_reset.py
Per review: reserve assert for tests. The invariant "snapshot_choice is set => reset_file is set" is established earlier, so we reorder the dispatch to lead with the installer-updated branch whose condition (`reset_file is not None`) doubles as the type narrowing mypy needs, and fall through to the other branches otherwise.
Replace the string-literal choices and TypedDict/dict dispatch table in
`conda self reset` with an `enum.Enum` that owns each snapshot's display
name and on-disk file path. The `execute` function now dispatches via a
`match` statement, which makes the exhaustiveness explicit and lets
mypy narrow `reset_file` without an assert.
Also rename `_FALLBACK_ORDER` to `FALLBACK_ORDER` (it's referenced by
tests, so the leading underscore was misleading) and expand the test
suite to cover:
- `--snapshot current` dispatch
- `installer-exact` with a missing snapshot file raises FileNotFoundError
- invalid `--snapshot` values are rejected
- `--help` lists all four snapshot modes
- `Snapshot.display_name` and `Snapshot.file_path` unit tests
- regression: `reset(uninstallable_packages=...)` never installs
(keep-list semantics, covering the conda-libmamba-solver /
conda-rattler-solver coexistence case)
Promote `_file_paths_from_extracted_package` from a module-level private helper to `PackageInfo.read_manifest`, a public classmethod. The helper is only used by `PackageInfo.from_record`, so scoping it to the class and giving it an intent-focused name (it reads the on-disk file manifest, falling back from info/paths.json to info/files) removes an unnecessary module-level private symbol. Also switch embedded path literals like `pkg_path / "info/paths.json"` to the idiomatic chained form `pkg_path / "info" / "paths.json"`.
|
@marcoesters — two updates on this PR:
|
|
Jesus, Opus 4.7 is NOT good at keeping it straight, those comments are weird! |
- reset, quickstart: now show `installer-provided (with updates)` snapshot message - base-protection: uses the new installer-updated strategy in the fix step; tighten comment to drop "to essentials" (installer-provided packages are preserved) - remove: add a `--force --dry-run` follow-up to showcase the override, and reword the comment since permanent packages are now protected rather than unremovable
The WHAT_TO_EXPECT preamble on `conda self reset` claimed the command would reset base to only conda, its plugins, and their dependencies -- but since the installer-updated snapshot became the default (#122), the reset also preserves installer-provided packages. The demos surfaced the contradiction between this preamble and the trailing "installer-provided (with updates) snapshot" success line. - Split WHAT_TO_EXPECT into snapshot/essentials variants and print the right one after snapshot resolution - Update the matching prose in docs/features.md and docs/quickstart.md - Reword the reset/quickstart tape comments to "installer-provided snapshot" - Re-record reset, quickstart, and base-protection demos
Summary
conda doctor base-protection --fixwas stripping installer-provided packages likemamba(Miniforge) andpipfrom base.The first attempt used
reset(snapshot=initial-state.explicit.txt), but as @marcoesters pointed out that also downgrades any package the user updated, becausediff_for_unlink_link_precspins to the exact URLs from the snapshot.Now we only read the names from the installer snapshot and keep those packages at whatever versions they currently are.
The old
--snapshot installeris split into two:installer-exact: exact restore, may downgrade (factory reset).installer-updated: names from the snapshot, current versions preserved.conda doctor base-protection --fixusesinstaller-updatedsemantics pluspermanent_dependencies()(which already composes incontext.plugins.self_permanent_packagesfrom #116). Fallback on bareconda self resetisbase-protection>installer-updated>current.names_from_explicit()parses@EXPLICITURL lines with publicMatchSpec. NoProgressiveFetchExtract, no network.initial-state.explicit.txtcomes from conda/constructor#1059, shipped with Miniforge and Miniconda installers built with constructor >= 3.13.0 (Nov 2025).Closes #121
Breaking change
--snapshot installeris gone. Safe, it was never in a release.Also in this PR
--snapshotchoices are backed by aSnapshotenum;executedispatches viamatch.PackageInfo.read_manifestclassmethod replaces a module-level private helper (unrelated cleanup; happy to split out).Test plan
installerrejection, fallback ordering, and the health-check pathreset(uninstallable_packages=...)is strictly a keep-list (no installs), covering the rattler/libmamba coexistence case