feat: fetch.prune config option; muse pull now actually prunes stale refs (muse#22)
Implements the user's own proposed solution on the ticket: a fetch.prune config switch (repo + global, repo wins), mirroring git's fetch.prune exactly. Also fixes the root cause the ticket actually reported: muse pull had its own independent fetch logic and never called the pruning routine muse fetch --prune already used — so even an explicit muse fetch --prune run immediately before a pull did nothing to help, since pull's next invocation would just leave stale refs behind again.
muse/cli/config.py: - New [fetch] config section (FetchConfig TypedDict, parse/serialize, settable namespace), prune: bool, same boolean pattern as [push]. - New get_fetch_prune(repo_root) — repo-level fetch.prune wins when set; falls back to ~/.muse/config.toml (global) when repo-level is unset; defaults to False (pruning stays opt-in, matching git's own default).
muse/cli/commands/config_cmd.py: - New --global flag on both `muse config get` and `muse config set`, mirroring `git config --global` exactly: reads/writes ~/.muse/config.toml instead of the repo config, works outside a repo too.
muse/cli/commands/fetch.py: - --prune/-p and new --no-prune are now mutually exclusive, default None (previously --prune defaulted to False with no way to express "use whatever the config says"). When neither flag is passed, prune is resolved from get_fetch_prune(root) rather than hardcoded False.
muse/cli/commands/pull.py: - New --prune/-p / --no-prune flags, same resolution as fetch.py. - Pruning actually happens now: calls fetch.py's _prune_stale_refs() right after fetch_remote_info() returns the remote's live branch_heads — before resolving the branch being pulled, so a hub-deleted branch's stale ref is cleared even when pulling a completely different, still-existing branch (the exact muse#22 repro: MP merged with "source branch deleted", then `muse pull local dev` left .muse/remotes/local/<deleted-branch> behind). - New `pruned: list[str]` field on _PullJson, populated at all 9 construction sites (every status: up_to_date, fast_forward, merged, conflict, fetched, dry_run). - Pruning itself respects --dry-run (previews via "Would prune", writes nothing) same as muse fetch --dry-run --prune already did.
docs/agent-guide.md: two new Gitism Glossary entries, framed like the existing refspec-syntax entry ("now works identically to git, not a gotcha") — fetch.prune config and muse config --global.
Tests: new tests/test_pull_fetch_prune_config.py (13 tests) — get_fetch_prune precedence (4), muse config --global read/write/isolation (3), muse fetch honouring fetch.prune config + --no-prune override (2), muse pull actually pruning: explicit --prune, config-driven, default-off, --dry-run preview-only (4).
Confirmed genuine RED by shelving the entire implementation (config.py, config_cmd.py, fetch.py, pull.py) and re-running: all 13 new tests failed (ImportError for get_fetch_prune, "Unknown config namespace 'fetch'", argparse exit 2 for the new flags, missing "pruned" JSON key) — restored, all 13 green.
One test-authoring bug caught and fixed along the way (not a product bug): three of the pull tests initially included the "stale" branch inside the mocked remote's own branch_heads dict, which made it not stale at all from _prune_stale_refs' perspective — fixed by correctly omitting it, matching the real-world scenario of a branch deleted on the remote.
Regression: 456 tests across test_pull_fetch_prune_config.py, test_cmd_fetch_hardening.py, test_fetch_envelope.py, test_fetch_mpack_command_wiring.py, test_transport_fetch_phase2.py, test_cmd_pull_hardening.py, test_pull_missing_snapshot_guard.py, test_coord_pull_null_records.py, test_cmd_config.py, test_config_cmd_envelope.py, test_cmd_config_hardening.py — all green, no regressions.
Semantic Changes
73 symbols
Files Changed
+1
~5
1256 in snapshot
0 comments
muse hub commit comment sha256:ec9f1f4725bce6f66bf08946ea05413572841d9ba43e02df2f3d1cf94e14eb68 --body "your comment"
No comments yet. Be the first to start the discussion.