gabriel / muse public
feat patch format-patch,rev-list feat/37-dash-n-range-parity #1 / 1
AI Agent gabriel · 1 day ago · Oct 22, 2026 · Diff

feat(format-patch,rev-list): -N shorthand + A..B range parity with git (muse#37)

Audit found muse log already had both -N shorthand and A..B range support (implemented in an earlier session) -- only format-patch and rev-list had the two git-parity gaps this ticket describes.

format-patch: - Refactored the single-commit patch-building logic into _build_patch_record(), reused by both the unchanged single-ref path and the new multi-commit paths. - Added -N shorthand (`muse format-patch -3`) and A..B range syntax (`muse format-patch base..HEAD`), both producing commits oldest-first (git's own format-patch numbering convention) via muse.core.graph's iter_ancestors/ancestor_ids. - Bare <ref> (no -N, no "..") keeps its EXISTING single-commit semantics unchanged -- per the ticket's own accepted alternative, chosen over silently changing bare-<ref> behavior to ref..HEAD (which would be a much larger, harder-to-review behavior change for an existing, shipped command). The divergence from git is now prominently documented in --help, the module docstring, and the Gitism Glossary. - New _FormatPatchMultiJson schema ({"count": N, "patches": [...]}) for -N/A..B mode, distinct from the existing flat single-patch schema so agents can detect which mode they got via the presence of "count". - --output-dir in multi-commit mode writes numbered files (0001-*.mpatch, 0002-*.mpatch, ...), matching git format-patch's own convention.

rev-list: - Added the same bare "-N" shorthand (ref positional changed to nargs='*' so "-3" and an explicit ref can appear in either order, mirroring git). - Also fixed a second, independently-discovered bug while here: rev-list's own docstring agent-quickstart example ("muse rev-list -n 10 HEAD") has been broken since "-n" was never actually registered as a --max-count alias -- only the long form existed. Added "-n" as the short flag.

Docs: new Gitism Glossary entries in docs/agent-guide.md for both gaps. `muse agent-config sync` reports zero drift.

17 new tests in tests/test_format_patch_range_shorthand.py covering -N (including exceeding available history, -0 rejection, numbered file output), A..B range (including the ticket's exact bug scenario: bare base ref exports only itself, base..HEAD exports everything since), and the rev-list shorthand/alias/ordering/range-regression cases. Confirmed via genuine RED (reverted both command files, kept the new test file) -> 11 of 17 tests failed as expected (6 are mechanism-agnostic edge cases that correctly pass either way) -> GREEN. 380-test regression sweep across every format-patch/rev-list/log-relevant suite passes with zero regressions.

Live-verified via muse-dev: format-patch -1/-2 oldest-first multi-patch output, format-patch base..HEAD reproducing and resolving the ticket's own reported footgun from the rc13 ship-out, and rev-list -1/-n/either-ordering.

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

sha256:8fff16f1bc41d40757bb7488701ddbc520adbed1effb6429da72f06b84631e56 sha
+50 ~6 symbols
4 changed · 1251 in snapshot files
sha256:17b0137991b77e072693f278787916ec87ed42578e63253e2a8afb4b80275082 snapshot
+50
symbols added
~6
symbols modified
4
files changed
1251
files in snapshot
0
dead code introduced
Semantic Changes 56 symbols
+ TestFormatPatchDashN class class TestFormatPatchDashN L102–152
+ test_dash_1_exports_last_commit_only method method test_dash_1_exports_last_commit_only L103–111
+ test_dash_2_exports_last_two_oldest_first method method test_dash_2_exports_last_two_oldest_first L113–123
+ test_dash_n_exceeding_history_returns_all_available method method test_dash_n_exceeding_history_returns_all_available L125–132
+ test_dash_n_output_dir_writes_numbered_files method method test_dash_n_output_dir_writes_numbered_files L141–152
+ test_dash_zero_is_rejected method method test_dash_zero_is_rejected L134–139
+ TestFormatPatchRange class class TestFormatPatchRange L160–206
+ test_bare_ref_still_exports_only_that_single_commit method method test_bare_ref_still_exports_only_that_single_commit L175–187
+ test_default_head_unchanged_single_commit_schema method method test_default_head_unchanged_single_commit_schema L196–206
+ test_range_exports_commits_since_base_exclusive method method test_range_exports_commits_since_base_exclusive L161–173
+ test_range_with_empty_local_side_rejected method method test_range_with_empty_local_side_rejected L189–194
+ TestRevListDashN class class TestRevListDashN L214–283
+ test_dash_1_alone_defaults_to_head method method test_dash_1_alone_defaults_to_head L215–222
+ test_dash_n_either_order method method test_dash_n_either_order L233–240
+ test_dash_n_with_explicit_ref method method test_dash_n_with_explicit_ref L224–231
+ test_plain_ref_unaffected method method test_plain_ref_unaffected L276–283
+ test_range_syntax_unaffected method method test_range_syntax_unaffected L265–274
+ test_short_flag_n_alias_for_max_count method method test_short_flag_n_alias_for_max_count L242–249
+ test_too_many_refs_rejected method method test_too_many_refs_rejected L251–256
+ test_two_dash_n_tokens_rejected method method test_two_dash_n_tokens_rejected L258–263
+ _commit function function _commit L46–73
+ _fp function function _fp L76–77
+ _init_repo function function _init_repo L31–37
+ _rev_list function function _rev_list L80–81
+ _write_object function function _write_object L40–43
+ CliRunner import import CliRunner L15–15
+ CommitRecord import import CommitRecord L17–17
+ InvokeResult import import InvokeResult L15–15
+ SnapshotRecord import import SnapshotRecord L18–18
+ annotations import import annotations L7–7
+ blob_id import import blob_id L20–20
+ datetime import import datetime L9–9
+ hash_commit import import hash_commit L16–16
+ hash_snapshot import import hash_snapshot L16–16
+ json import import json L10–10
+ muse_dir import import muse_dir L21–21
+ pathlib import import pathlib L11–11
+ pytest import import pytest L13–13
+ ref_path import import ref_path L21–21
+ write_commit import import write_commit L17–17
+ write_object import import write_object L19–19
+ write_snapshot import import write_snapshot L18–18
+ runner variable variable runner L23–23
+ three_commit_repo function function three_commit_repo L85–94
~ docs/agent-guide.md .md 2 symbols added, 2 symbols modified
+ -N commit-count shorthand — now works everywhere git expects it section -N commit-count shorthand — now works everywhere git expects it L1470–1481
+ format-patch <ref> single-commit semantics — diverges from git on purpose (muse#37) section format-patch <ref> single-commit semantics — diverges from git on purpose (muse#37) L1482–1501
~ muse/cli/commands/format_patch.py .py 4 symbols added, 2 symbols modified
+ _FormatPatchMultiJson class class _FormatPatchMultiJson L176–182
+ _build_patch_record function function _build_patch_record L385–501
+ ancestor_ids import import ancestor_ids L118–118
+ iter_ancestors import import iter_ancestors L118–118
~ run
~ muse/cli/commands/rev_list.py .py 2 symbols modified
Files Changed
+1 ~3
1251 in snapshot
← Older Oldest on feat/37-dash-n-range-parity
All commits
Newer → Latest on feat/37-dash-n-range-parity

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:8fff16f1bc41d40757bb7488701ddbc520adbed1effb6429da72f06b84631e56 --body "your comment"