describe.py
python
sha256:649be9ce77a127cd847c4f798d60104dab7fe830cbc928762fafe3deadf38839
chore: point muse-git-backup.sh's MUSE_COPY at the fresh 'm…
Sonnet 5
patch
13 days ago
| 1 | """``muse describe`` — describe a commit relative to the nearest version tag. |
| 2 | |
| 3 | Walks backward from a commit (default: HEAD) through the ancestry graph and |
| 4 | finds the nearest semantic version tag. |
| 5 | |
| 6 | Without ``--pre`` only stable version tags are considered. With ``--pre`` |
| 7 | pre-release tags (rc, beta, alpha) are also eligible. |
| 8 | |
| 9 | When no eligible tag is found the command exits 1 with ``tag: null``, |
| 10 | ``distance: null``, and ``description: null`` in JSON output. |
| 11 | |
| 12 | Usage:: |
| 13 | |
| 14 | muse describe # describe HEAD (stable tags only) |
| 15 | muse describe --pre # include pre-release tags |
| 16 | muse describe feat/audio # describe tip of a branch |
| 17 | muse describe sha256:<id> # describe a specific commit |
| 18 | muse describe --abbrev 12 # 12-char hex suffix in description |
| 19 | muse describe --first-parent # follow main-line ancestry only |
| 20 | muse describe --json # machine-readable output |
| 21 | |
| 22 | JSON output schema:: |
| 23 | |
| 24 | { |
| 25 | "commit_id": "<sha256:hex>", |
| 26 | "tag": "<nearest version tag>" | null, |
| 27 | "distance": <int> | null, |
| 28 | "short_sha": "<abbrev hex chars>", |
| 29 | "description": "<describe string>" | null, |
| 30 | "exact": true | false, |
| 31 | "repo_id": "<sha256:...>", |
| 32 | "branch": "<current branch>", |
| 33 | "duration_ms": <float>, |
| 34 | "exit_code": 0 | 1 |
| 35 | } |
| 36 | |
| 37 | Exit codes:: |
| 38 | |
| 39 | 0 — tag found and description produced |
| 40 | 1 — no eligible version tags in the ancestry |
| 41 | 2 — not a Muse repository |
| 42 | """ |
| 43 | |
| 44 | import argparse |
| 45 | import json |
| 46 | import logging |
| 47 | import sys |
| 48 | from typing import TypedDict |
| 49 | |
| 50 | from muse.core.describe import describe_commit |
| 51 | from muse.core.errors import ExitCode |
| 52 | from muse.core.repo import read_repo_id, require_repo |
| 53 | from muse.core.refs import get_head_commit_id, read_current_branch |
| 54 | from muse.core.commits import resolve_commit_ref |
| 55 | from muse.core.envelope import EnvelopeJson, make_envelope |
| 56 | from muse.core.timing import start_timer |
| 57 | |
| 58 | logger = logging.getLogger(__name__) |
| 59 | |
| 60 | |
| 61 | class _DescribeJson(EnvelopeJson): |
| 62 | """JSON output for ``muse describe --json``.""" |
| 63 | |
| 64 | commit_id: str |
| 65 | tag: str | None |
| 66 | distance: int | None |
| 67 | short_sha: str |
| 68 | description: str | None |
| 69 | exact: bool |
| 70 | repo_id: str |
| 71 | branch: str |
| 72 | |
| 73 | |
| 74 | def register(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None: |
| 75 | """Register the ``muse describe`` subcommand.""" |
| 76 | parser = subparsers.add_parser( |
| 77 | "describe", |
| 78 | help="Describe a commit relative to the nearest version tag.", |
| 79 | description=__doc__, |
| 80 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 81 | ) |
| 82 | parser.add_argument( |
| 83 | "ref", |
| 84 | nargs="?", |
| 85 | default=None, |
| 86 | help="Branch name or commit ID to describe (default: HEAD).", |
| 87 | ) |
| 88 | parser.add_argument( |
| 89 | "--pre", |
| 90 | action="store_true", |
| 91 | dest="include_pre", |
| 92 | help="Include pre-release version tags (rc, beta, alpha) as reference points.", |
| 93 | ) |
| 94 | parser.add_argument( |
| 95 | "--first-parent", |
| 96 | action="store_true", |
| 97 | dest="first_parent", |
| 98 | help="Follow only the first-parent chain (skip merge second-parents).", |
| 99 | ) |
| 100 | parser.add_argument( |
| 101 | "--abbrev", |
| 102 | type=int, |
| 103 | default=8, |
| 104 | dest="abbrev", |
| 105 | metavar="N", |
| 106 | help="Length of the hex suffix in the description string (default: 8).", |
| 107 | ) |
| 108 | parser.add_argument( |
| 109 | "--json", "-j", |
| 110 | action="store_true", |
| 111 | dest="json_out", |
| 112 | help="Emit machine-readable JSON on stdout.", |
| 113 | ) |
| 114 | parser.set_defaults(func=run) |
| 115 | |
| 116 | |
| 117 | def run(args: argparse.Namespace) -> None: |
| 118 | """Describe a commit relative to the nearest semantic version tag.""" |
| 119 | elapsed = start_timer() |
| 120 | ref: str | None = args.ref |
| 121 | include_pre: bool = args.include_pre |
| 122 | first_parent: bool = args.first_parent |
| 123 | abbrev: int = args.abbrev |
| 124 | json_out: bool = args.json_out |
| 125 | |
| 126 | if abbrev < 4 or abbrev > 64: |
| 127 | print( |
| 128 | f"❌ --abbrev must be between 4 and 64 (got {abbrev}).", |
| 129 | file=sys.stderr, |
| 130 | ) |
| 131 | raise SystemExit(ExitCode.USER_ERROR) |
| 132 | |
| 133 | root = require_repo() |
| 134 | repo_id = read_repo_id(root) |
| 135 | branch = read_current_branch(root) |
| 136 | |
| 137 | if ref is None: |
| 138 | commit_id = get_head_commit_id(root, branch) |
| 139 | if commit_id is None: |
| 140 | print("❌ No commits on current branch.", file=sys.stderr) |
| 141 | raise SystemExit(ExitCode.USER_ERROR) |
| 142 | else: |
| 143 | commit_rec = resolve_commit_ref(root, branch, ref) |
| 144 | if commit_rec is None: |
| 145 | print(f"❌ Ref '{ref}' not found.", file=sys.stderr) |
| 146 | raise SystemExit(ExitCode.USER_ERROR) |
| 147 | commit_id = commit_rec.commit_id |
| 148 | |
| 149 | result = describe_commit( |
| 150 | root, |
| 151 | repo_id, |
| 152 | commit_id, |
| 153 | include_pre=include_pre, |
| 154 | first_parent=first_parent, |
| 155 | abbrev=abbrev, |
| 156 | ) |
| 157 | |
| 158 | found = result["tag"] is not None |
| 159 | exit_code = 0 if found else ExitCode.USER_ERROR |
| 160 | |
| 161 | if json_out: |
| 162 | print(json.dumps(_DescribeJson( |
| 163 | **make_envelope(elapsed, exit_code=exit_code), |
| 164 | commit_id=result["commit_id"], |
| 165 | tag=result["tag"], |
| 166 | distance=result["distance"], |
| 167 | short_sha=result["short_sha"], |
| 168 | description=result["description"], |
| 169 | exact=result["exact"], |
| 170 | repo_id=repo_id, |
| 171 | branch=branch, |
| 172 | ))) |
| 173 | raise SystemExit(exit_code) |
| 174 | else: |
| 175 | if not found: |
| 176 | print(f"❌ No version tags found in the ancestry of {commit_id[:abbrev]}.", file=sys.stderr) |
| 177 | raise SystemExit(exit_code) |
| 178 | print(result["description"]) |
File History
1 commit
sha256:649be9ce77a127cd847c4f798d60104dab7fe830cbc928762fafe3deadf38839
chore: point muse-git-backup.sh's MUSE_COPY at the fresh 'm…
Sonnet 5
patch
13 days ago