gabriel / muse public
feat patch feat/57-version-tag-tilde-resolution #1 / 1
AI Agent gabriel · 45 minutes ago · Oct 21, 2026 · Diff

feat: version tag relative resolution (tilde notation) + non-SemVer decision (muse#57 items 5-6)

Item 6 (decided, no code change): keep muse tag's strict SemVer 2.0 boundary; freeform version workflows (nightly-20260628, 2026.06.28) go through `muse label`, which already supports arbitrary namespace:value strings with zero format enforcement. Documented the rationale in docs/issues/version-tags-followup.md.

Item 5 (implemented): git-style `tag~N` tilde notation for version tags.

resolve_commit_ref() already had a generic `base~N` regex that recurses into itself to resolve `base`, but `base` resolution never tried version tags -- only HEAD, branch refs, reflog @{N} specs, and literal commit IDs/prefixes. `v1.0.0~2` failed with "Ref not found" even though `v1.0.0` alone *also* failed (a bare version tag never resolved at all -- a more fundamental gap than the ticket described). Added a version-tag lookup strategy to resolve_commit_ref, placed before the exact-commit-ID/prefix fallbacks. Since resolve_commit_ref is the single resolution function shared by `muse describe`, `muse checkout`, and `muse rev-list`'s `A..B` range syntax, this one fix makes all three understand version tags and tag~N notation at once -- confirmed via actual CLI calls, not just the core function. `muse rev-list v0.9.0..v1.0.0` now also works, covering the spirit of the ticket's third example (`muse log v0.2.0..v0.2.0-rc14`) without needing to add range syntax to `log` itself.

Found and fixed a second, pre-existing bug while wiring this up: `checkout.py`'s `_is_commit_ref` detection never recognized `~` at all (not just for version tags -- `HEAD~1` was equally broken), so any tilde ref hit `validate_branch_name`'s rejection of `~` as a forbidden character before ever reaching resolve_commit_ref. Broadened the detection to include any target containing `~`. This in turn surfaced a third bug: resolving `HEAD~N` while ALREADY in detached HEAD state passed an empty branch string into resolve_commit_ref's branch-based HEAD lookup, crashing with an unhandled ValueError (resolve_commit_ref has no concept of checkout.py's separate detached-HEAD state). Fixed by substituting the literal detached commit ID at the call site before tilde resolution, rather than relying on a (nonexistent) branch.

New tests: - tests/test_version_tag_relative_resolution.py: core-level resolve_commit_ref tests (bare tag, tilde~1/~3/beyond-root, nonexistent tag, pre-release tag names) plus CLI integration tests for describe/checkout/rev-list. - tests/test_cmd_checkout.py::TestDetachTildeRef: HEAD~1 from a branch, and the chained detached-HEAD-to-detached-HEAD HEAD~1 case that previously crashed.

Confirmed genuine RED on all of the above (reverted both fixes, 9 of 11 new tests failed, including a raw JSONDecodeError from the detached-HEAD crash) before GREEN with both fixes restored.

Targeted regression suite -- test_cmd_checkout.py, test_cmd_describe.py, test_cmd_describe_version_tags.py, test_cmd_describe_hardening.py, test_cmd_rev_list.py, test_cmd_version_tags.py, test_core_version_tags.py, test_cmd_rev_parse.py, test_version_tag_relative_resolution.py -- all pass (514 passed), zero regressions.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

sha256:76a7ec1f3a4bb422eb12c3e2bf84f75bb3f026ca722152cd1ed0e5aec24dd65a sha
+32 ~4 −1 symbols
5 changed · 1217 in snapshot files
sha256:806351a49659e51c0354375be622797118ee5d24b56a139a153d9cc6c468201f snapshot
+32
symbols added
~4
symbols modified
−1
symbol removed
5
files changed
1217
files in snapshot
0
dead code introduced
Semantic Changes 37 symbols
+ TestCliIntegrationTagTilde class class TestCliIntegrationTagTilde L145–175
+ test_checkout_accepts_tag_tilde_ref method method test_checkout_accepts_tag_tilde_ref L157–160
+ test_describe_accepts_tag_tilde_ref method method test_describe_accepts_tag_tilde_ref L146–155
+ test_rev_list_range_between_two_version_tags method method test_rev_list_range_between_two_version_tags L162–175
+ TestResolveCommitRefVersionTagTilde class class TestResolveCommitRefVersionTagTilde L108–142
+ test_nonexistent_tag_returns_none method method test_nonexistent_tag_returns_none L132–135
+ test_prerelease_tag_name_with_tilde method method test_prerelease_tag_name_with_tilde L137–142
+ test_tag_alone_resolves_to_tagged_commit method method test_tag_alone_resolves_to_tagged_commit L109–113
+ test_tag_tilde_1_resolves_to_parent method method test_tag_tilde_1_resolves_to_parent L115–119
+ test_tag_tilde_3_walks_three_steps method method test_tag_tilde_3_walks_three_steps L121–125
+ test_tag_tilde_beyond_root_returns_none method method test_tag_tilde_beyond_root_returns_none L127–130
+ _build_repo function function _build_repo L100–105
+ _env function function _env L30–31
+ _init_repo function function _init_repo L34–49
+ _make_commit function function _make_commit L52–81
+ _make_version_tag function function _make_version_tag L84–97
+ CliRunner import import CliRunner L25–25
+ Manifest import import Manifest L24–24
+ annotations import import annotations L14–14
+ content_hash import import content_hash L24–24
+ datetime import import datetime L16–16
+ json import import json L17–17
+ muse_dir import import muse_dir L23–23
+ pathlib import import pathlib L18–18
+ pytest import import pytest L20–20
+ ref_path import import ref_path L23–23
+ resolve_commit_ref import import resolve_commit_ref L22–22
+ runner variable variable runner L27–27
~ docs/issues/version-tags-followup.md .md 1 symbol added, 1 symbol removed, 2 symbols modified
− Non-SemVer version strings section 6. Non-SemVer version strings L78–84
+ Non-SemVer version strings ✅ DECIDED — keep the strict boundary section 6. Non-SemVer version strings ✅ DECIDED — keep the strict boundary L78–94
~ muse/cli/commands/checkout.py .py 1 symbol modified
~ run
~ muse/core/commits.py .py 1 symbol modified
~ tests/test_cmd_checkout.py .py 3 symbols added
+ TestDetachTildeRef class class TestDetachTildeRef L579–629
+ test_checkout_head_tilde_1 method method test_checkout_head_tilde_1 L587–601
+ test_checkout_head_tilde_from_already_detached_head method method test_checkout_head_tilde_from_already_detached_head L603–629
Files Changed
+1 ~4
1217 in snapshot
← Older Oldest on feat/57-version-tag-tilde-resolution
All commits
Newer → Latest on feat/57-version-tag-tilde-resolution

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:76a7ec1f3a4bb422eb12c3e2bf84f75bb3f026ca722152cd1ed0e5aec24dd65a --body "your comment"