Git-parity: -N commit-count shorthand + single-<ref> range semantics (format-patch, log, rev-list)
Git-parity: -N commit-count shorthand + single-<ref> range semantics (format-patch, log, rev-list)
Summary
Two git muscle-memory gaps surfaced while using muse format-patch. Agents and devs coming
from git reach for these idioms reflexively; muse should accept them (or diverge only with a
loud, documented reason). Both are low-severity (workable) but high collision-likelihood.
1. -N commit-count shorthand is not accepted
git format-patch -1 / -N exports the last N commits from HEAD. muse format-patch -1 fails:
❌ '-1' is not a known branch or commit ID.
The -N shorthand is a pervasive git idiom (git log -3, git format-patch -1, …). muse log
supports -n/--max-count N but not the bare -N form. Agents will reach for -N first.
2. Single-<ref> range semantics diverge from git (silent footgun)
git format-patch <ref> exports <ref>..HEAD — commits since <ref> (exclusive). muse format-patch <ref> instead exports the single commit AT <ref> (its parent → ref). A git
user who passes the base ref expecting "everything since the base" silently gets the wrong
patch, with no error:
# branch tip = a6ca2233, its parent = e452ad9a
muse format-patch e452ad9a # exported e452ad9a ITSELF (wrong) — expected a6ca2233
muse format-patch a6ca2233 # had to pass the commit itself to export it
This bit a real workflow during the rc13 ship-out (exporting a6ca2233 to park it as a mist):
the first attempt passed the parent and produced the wrong .mpatch, caught only by inspecting
to_commit_id.
Proposed
- Accept
-N(last N commits) onformat-patch, and auditlog/rev-listfor the same bare--Ngit shorthand. - Either align
format-patch <ref>to git's<ref>..HEADsemantics, or keep single-commit semantics but require an explicitA..Brange for "since" and document the divergence prominently in--helpand the gitism glossary. - Sweep other ref/range-taking commands (
log,rev-list,diff,range-diff) for the same two gaps and capture them here or in linked tickets.
Context
Surfaced during the rc13 ship-out. Worked around at the time; filed so the git-parity gaps are captured where agents will reach for them.
Resolved — audited and closed all 3 gaps, shipped to
main/devon all remotesCommit:
sha256:8fff16f1bc41d40757bb7488701ddbc520adbed1effb6429da72f06b84631e56(branchfeat/37-dash-n-range-parity, fast-forward merged dev→main, pushed to local/staging/production).Audit result:
muse logalready had both gaps closedBefore touching anything, I checked
muse log's current implementation and found it already supports both the bare-Nshorthand andA..Brange syntax (implemented in an earlier, unrelated session —muse log -3andmuse log base..HEADboth already work correctly). Onlyformat-patchandrev-liststill had the two gaps this ticket describes.Gap 1 —
-NshorthandAdded to both commands:
format-patch -N— exports the last N commits from HEAD, oldest-first (git's ownformat-patchnumbering convention:0001= oldest).rev-list -N— same shorthand, accepted in either position (rev-list -3 HEADorrev-list HEAD -3), matching git's flexible ordering. Also added-nas a proper short-flag alias for--max-count— a second, independently-discovered bug:rev-list's own docstring agent-quickstart example (muse rev-list -n 10 HEAD) has been broken since inception, because-nwas never actually registered as an alias, only the long--max-countform existed.Gap 2 — single-
<ref>range semantics (the reported footgun)Per the ticket's own accepted alternative, I kept
format-patch <ref>'s existing single-commit semantics rather than silently changing it toref..HEAD— that would be a much larger, harder-to-review behavior change to an already-shipped command, with real risk of breaking existing scripts/workflows built on the current (if surprising) meaning. Instead:A..Brange syntax toformat-patch(muse format-patch base..HEAD), giving the correct git-equivalent "everything since" behavior when asked for explicitly.--helptext for theREFpositional, therun()docstring, and a new Gitism Glossary entry indocs/agent-guide.mdwith the ticket's own exact reported scenario as the example.muse format-patch e452ad9a(the parent) still correctly exports onlye452ad9aitself (documented, unchanged), whilemuse format-patch e452ad9a..HEADnow correctly exportsa6ca2233— the exact "since the base" result the rc13 ship-out workflow needed.JSON schema design decision
-NandA..Bmode produce multiple patches, which can't fit the existing flat single-patch JSON schema without an ambiguous shape change. I introduced a distinct{"count": N, "patches": [...]}envelope for multi-commit mode, while the bare<ref>schema is 100% unchanged — agents can detect which mode they received by checking for thecountkey.--output-dirin multi-commit mode writes numbered files (0001-*.mpatch,0002-*.mpatch, ...), matching git's ownformat-patchfile-naming convention.Verification
tests/test_format_patch_range_shorthand.py:-N(including exceeding available history,-0rejection, numbered file output),A..Brange (including the ticket's exact bug scenario), and therev-listshorthand/alias/ordering/range-regression cases.test_cmd_rev_list.py,test_cmd_format_patch.py,test_format_patch_supercharge.py,test_format_patch_envelope.py,test_format_patch_range_shorthand.py,test_cmd_log.py,test_log_supercharge.py,test_log_branch_flag.py) — all passing, zero regressions to the unchanged bare-<ref>path.muse-dev:format-patch -1/-2oldest-first multi-patch output,format-patch base..HEADreproducing and resolving the ticket's own reported footgun exactly, andrev-list -1/-n/either-ordering all confirmed working.Docs
New Gitism Glossary entries in
docs/agent-guide.mdfor both the-Nshorthand (now consistent acrosslog/rev-list/format-patch) and theformat-patch <ref>divergence, with the exact ticket scenario as the worked example.muse agent-config syncconfirmed zero drift after the edit.Scope note
Did not touch
diff/range-diff— the ticket said to "sweep... and capture them here or in linked tickets," and neither of those commands showed the reported gaps on inspection (both already take explicitA..B-shaped arguments as their primary interface, not a single ambiguous<ref>). If a gap is later found there, it should be its own ticket.