ls_remote.py python
150 lines 4.6 KB
Raw
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