"""muse plumbing rev-parse — resolve a ref to a full commit ID. Resolves a branch name, ``HEAD``, or an abbreviated SHA prefix to the full 64-character SHA-256 commit ID. Output (JSON, default):: {"ref": "main", "commit_id": ""} With ``--abbrev-ref``:: {"ref": "HEAD", "branch": "main"} # JSON main # text Output (--format text):: Plumbing contract ----------------- - Exit 0: ref resolved successfully. - Exit 1: ref not found, ambiguous, empty, or unknown --format value. Agent use --------- Canonical "what branch am I on?" query:: muse plumbing rev-parse --abbrev-ref HEAD --json # → {"ref": "HEAD", "branch": "main"} Canonical "what is HEAD?" query:: muse plumbing rev-parse HEAD --format text # → <64 hex chars> """ from __future__ import annotations import argparse import json import logging import sys from muse.core.errors import ExitCode from muse.core.repo import require_repo from muse.core.store import ( find_commits_by_prefix, get_head_commit_id, read_commit, read_current_branch, ) logger = logging.getLogger(__name__) _FORMAT_CHOICES = ("json", "text") def register(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None: """Register the rev-parse subcommand.""" parser = subparsers.add_parser( "rev-parse", help="Resolve branch/HEAD/SHA prefix → full commit_id.", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument( "ref", help="Ref to resolve: branch name, 'HEAD', or commit ID prefix.", ) parser.add_argument( "--abbrev-ref", action="store_true", dest="abbrev_ref", help=( "Resolve HEAD (or a commit_id) to the branch name rather than " "the commit ID. Canonical agent query: " "`muse plumbing rev-parse --abbrev-ref HEAD`." ), ) parser.add_argument( "--format", "-f", dest="fmt", default="json", metavar="FORMAT", help="Output format: json or text. (default: json)", ) parser.add_argument( "--json", action="store_const", const="json", dest="fmt", help="Shorthand for --format json.", ) parser.set_defaults(func=run) def run(args: argparse.Namespace) -> None: """Resolve a branch name, HEAD, or SHA prefix to a full commit ID. Analogous to ``git rev-parse``. Useful for canonicalising refs in scripts and agent pipelines before passing them to other plumbing commands. With ``--abbrev-ref``: return the symbolic branch name rather than the commit hash — the idiomatic way to answer "what branch am I on?". """ fmt: str = args.fmt ref: str = args.ref abbrev_ref: bool = args.abbrev_ref if not ref: print( json.dumps({"ref": ref, "commit_id": None, "error": "ref must not be empty"}), file=sys.stderr, ) raise SystemExit(ExitCode.USER_ERROR) if fmt not in _FORMAT_CHOICES: print( f"❌ Unknown format {fmt!r}. Valid choices: {', '.join(_FORMAT_CHOICES)}", file=sys.stderr, ) raise SystemExit(ExitCode.USER_ERROR) root = require_repo() # ── --abbrev-ref: resolve HEAD → branch name ───────────────────────────── if abbrev_ref: branch = read_current_branch(root) if fmt == "text": print(branch) else: print(json.dumps({"ref": ref, "branch": branch})) return # ── normal ref resolution ───────────────────────────────────────────────── commit_id: str | None = None if ref.upper() == "HEAD": branch = read_current_branch(root) commit_id = get_head_commit_id(root, branch) if commit_id is None: print(json.dumps({"ref": ref, "commit_id": None, "error": "HEAD has no commits"})) raise SystemExit(ExitCode.USER_ERROR) else: # Try as branch name first. validate_branch_name (called inside # get_head_commit_id) raises ValueError for refs containing control # characters, ANSI escapes, or other forbidden sequences — treat these # as "not found" rather than letting the exception escape. try: candidate = get_head_commit_id(root, ref) except ValueError: candidate = None if candidate is not None: commit_id = candidate else: # Try as full or abbreviated commit ID. if len(ref) == 64: record = read_commit(root, ref) if record is not None: commit_id = record.commit_id else: matches = find_commits_by_prefix(root, ref) if len(matches) == 1: commit_id = matches[0].commit_id elif len(matches) > 1: print( json.dumps( { "ref": ref, "commit_id": None, "error": "ambiguous", "candidates": [m.commit_id for m in matches], } ) ) raise SystemExit(ExitCode.USER_ERROR) if commit_id is None: print(json.dumps({"ref": ref, "commit_id": None, "error": "not found"})) raise SystemExit(ExitCode.USER_ERROR) if fmt == "text": print(commit_id) return print(json.dumps({"ref": ref, "commit_id": commit_id}))