ls_remote.py
python
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6
chore(tests): add docstring to tests/__init__.py so rc14 tr…
Human
32 days ago
| 1 | """muse plumbing ls-remote — list references on a remote repository. |
| 2 | |
| 3 | Plumbing command that contacts the remote and prints every branch and its |
| 4 | current commit ID without modifying any local state. Useful for scripting, |
| 5 | agent coordination, and pre-flight checks before push/pull. |
| 6 | |
| 7 | Output format (default ``--format json``):: |
| 8 | |
| 9 | { |
| 10 | "repo_id": "<uuid>", |
| 11 | "domain": "midi", |
| 12 | "default_branch": "main", |
| 13 | "branches": {"main": "<commit_id>", "feat/x": "<commit_id>"} |
| 14 | } |
| 15 | |
| 16 | Output format (``--format text`` — one line per branch, ``*`` marks the default branch):: |
| 17 | |
| 18 | <commit_id>\t<branch> |
| 19 | <commit_id>\t<branch> * |
| 20 | |
| 21 | Agent use |
| 22 | --------- |
| 23 | |
| 24 | Pass a remote name (configured via ``muse remote add``) or a full URL:: |
| 25 | |
| 26 | muse plumbing ls-remote origin --json |
| 27 | muse plumbing ls-remote https://musehub.ai/org/repo --json |
| 28 | |
| 29 | Plumbing contract |
| 30 | ----------------- |
| 31 | |
| 32 | - Exit 0: remote contacted, refs printed. |
| 33 | - Exit 1: remote not configured, URL looks invalid, or unknown ``--format``. |
| 34 | - Exit 3: transport error (network unreachable, HTTP error). |
| 35 | """ |
| 36 | |
| 37 | from __future__ import annotations |
| 38 | |
| 39 | import argparse |
| 40 | import json |
| 41 | import logging |
| 42 | import pathlib |
| 43 | import sys |
| 44 | |
| 45 | from muse.cli.config import get_signing_identity, get_remote |
| 46 | from muse.core.errors import ExitCode |
| 47 | from muse.core.repo import find_repo_root |
| 48 | from muse.core.transport import HttpTransport, TransportError |
| 49 | from muse.core.validation import sanitize_display |
| 50 | |
| 51 | logger = logging.getLogger(__name__) |
| 52 | |
| 53 | _FORMAT_CHOICES = ("json", "text") |
| 54 | |
| 55 | |
| 56 | def register(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None: |
| 57 | """Register the ls-remote subcommand.""" |
| 58 | parser = subparsers.add_parser( |
| 59 | "ls-remote", |
| 60 | help="List branch heads on a remote without modifying local state.", |
| 61 | description=__doc__, |
| 62 | ) |
| 63 | parser.add_argument( |
| 64 | "remote_or_url", |
| 65 | nargs="?", |
| 66 | default="origin", |
| 67 | help="Remote name (e.g. 'origin') or a full URL. Defaults to 'origin'.", |
| 68 | ) |
| 69 | parser.add_argument( |
| 70 | "--format", "-f", |
| 71 | dest="fmt", |
| 72 | default="json", |
| 73 | metavar="FORMAT", |
| 74 | help="Output format: json (default) or text.", |
| 75 | ) |
| 76 | parser.add_argument( |
| 77 | "--json", action="store_const", const="json", dest="fmt", |
| 78 | help="Shorthand for --format json." |
| 79 | ) |
| 80 | parser.set_defaults(func=run) |
| 81 | |
| 82 | |
| 83 | def run(args: argparse.Namespace) -> None: |
| 84 | """List branches and commit IDs on a remote. |
| 85 | |
| 86 | Contacts the remote and prints each branch HEAD without altering any local |
| 87 | state. Pass a remote name (configured via ``muse remote add``) or a full |
| 88 | URL. |
| 89 | |
| 90 | Defaults to ``--format json`` for machine-readable agent output. |
| 91 | Use ``--format text`` for human-readable one-line-per-branch output. |
| 92 | """ |
| 93 | fmt: str = args.fmt |
| 94 | remote_or_url: str = args.remote_or_url |
| 95 | |
| 96 | if fmt not in _FORMAT_CHOICES: |
| 97 | print( |
| 98 | json.dumps({"error": f"Unknown format {fmt!r}. Valid: {', '.join(_FORMAT_CHOICES)}"}), |
| 99 | file=sys.stderr, |
| 100 | ) |
| 101 | raise SystemExit(ExitCode.USER_ERROR) |
| 102 | |
| 103 | root = find_repo_root(pathlib.Path.cwd()) |
| 104 | token: str | None = None |
| 105 | |
| 106 | url: str | None = None |
| 107 | if root is not None: |
| 108 | token = get_signing_identity(root) |
| 109 | url = get_remote(remote_or_url, root) |
| 110 | |
| 111 | if url is None: |
| 112 | if remote_or_url.startswith("http://") or remote_or_url.startswith("https://"): |
| 113 | url = remote_or_url |
| 114 | else: |
| 115 | print( |
| 116 | f"❌ '{remote_or_url}' is not a configured remote and does not " |
| 117 | "look like a URL.", |
| 118 | file=sys.stderr, |
| 119 | ) |
| 120 | print(" Configure it with: muse remote add <name> <url>", file=sys.stderr) |
| 121 | raise SystemExit(ExitCode.USER_ERROR) |
| 122 | |
| 123 | transport = HttpTransport() |
| 124 | try: |
| 125 | info = transport.fetch_remote_info(url, token) |
| 126 | except TransportError as exc: |
| 127 | print(f"❌ Cannot reach remote: {sanitize_display(str(exc))}", file=sys.stderr) |
| 128 | raise SystemExit(ExitCode.INTERNAL_ERROR) |
| 129 | |
| 130 | if fmt == "json": |
| 131 | print( |
| 132 | json.dumps( |
| 133 | { |
| 134 | "repo_id": info["repo_id"], |
| 135 | "domain": info["domain"], |
| 136 | "default_branch": info["default_branch"], |
| 137 | "branches": info["branch_heads"], |
| 138 | }, |
| 139 | indent=2, |
| 140 | ) |
| 141 | ) |
| 142 | return |
| 143 | |
| 144 | if not info["branch_heads"]: |
| 145 | print("(no branches)") |
| 146 | return |
| 147 | |
| 148 | for branch, commit_id in sorted(info["branch_heads"].items()): |
| 149 | marker = " *" if branch == info["default_branch"] else "" |
| 150 | print(f"{sanitize_display(commit_id)}\t{sanitize_display(branch)}{marker}") |
File History
2 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6
chore(tests): add docstring to tests/__init__.py so rc14 tr…
Human
32 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62
baseline: rc14 re-baseline after rc3 store corruption recovery
Human
patch
32 days ago