cgcardona / muse public
status.py python
113 lines 3.0 KB
a82406f1 Wire MuseDomainPlugin into all CLI commands via plugin registry Gabriel Cardona <gabriel@tellurstori.com> 3d ago
1 """muse status — show working-tree drift against HEAD.
2
3 Output modes
4 ------------
5
6 Default::
7
8 On branch main
9
10 Changes since last commit:
11 (use "muse commit -m <msg>" to record changes)
12
13 modified: tracks/drums.mid
14 new file: tracks/lead.mp3
15 deleted: tracks/scratch.mid
16
17 --short::
18
19 M tracks/drums.mid
20 A tracks/lead.mp3
21 D tracks/scratch.mid
22
23 --porcelain (machine-readable, stable for scripting)::
24
25 ## main
26 M tracks/drums.mid
27 A tracks/lead.mp3
28 D tracks/scratch.mid
29 """
30 from __future__ import annotations
31
32 import json
33 import logging
34 import pathlib
35
36 import typer
37
38 from muse.core.errors import ExitCode
39 from muse.core.repo import require_repo
40 from muse.core.store import get_head_snapshot_manifest
41 from muse.domain import SnapshotManifest
42 from muse.plugins.registry import read_domain, resolve_plugin
43
44 logger = logging.getLogger(__name__)
45
46 app = typer.Typer()
47
48
49 def _read_branch(root: pathlib.Path) -> str:
50 head_ref = (root / ".muse" / "HEAD").read_text().strip()
51 return head_ref.removeprefix("refs/heads/").strip()
52
53
54 def _read_repo_id(root: pathlib.Path) -> str:
55 return str(json.loads((root / ".muse" / "repo.json").read_text())["repo_id"])
56
57
58 @app.callback(invoke_without_command=True)
59 def status(
60 ctx: typer.Context,
61 short: bool = typer.Option(False, "--short", "-s", help="Condensed output."),
62 porcelain: bool = typer.Option(False, "--porcelain", help="Machine-readable output."),
63 branch_only: bool = typer.Option(False, "--branch", "-b", help="Show branch info only."),
64 ) -> None:
65 """Show working-tree drift against HEAD."""
66 root = require_repo()
67 branch = _read_branch(root)
68 repo_id = _read_repo_id(root)
69
70 if porcelain:
71 typer.echo(f"## {branch}")
72 if branch_only:
73 return
74
75 elif not short:
76 typer.echo(f"On branch {branch}")
77 if branch_only:
78 return
79
80 head_manifest = get_head_snapshot_manifest(root, repo_id, branch) or {}
81 workdir = root / "muse-work"
82
83 plugin = resolve_plugin(root)
84 committed_snap = SnapshotManifest(files=head_manifest, domain=read_domain(root))
85 report = plugin.drift(committed_snap, workdir)
86 delta = report.delta
87
88 added: set[str] = set(delta["added"])
89 modified: set[str] = set(delta["modified"])
90 deleted: set[str] = set(delta["removed"])
91
92 if not any([added, modified, deleted]):
93 if not short and not porcelain:
94 typer.echo("\nNothing to commit, working tree clean")
95 return
96
97 if short or porcelain:
98 for p in sorted(modified):
99 typer.echo(f" M {p}")
100 for p in sorted(added):
101 typer.echo(f" A {p}")
102 for p in sorted(deleted):
103 typer.echo(f" D {p}")
104 return
105
106 typer.echo("\nChanges since last commit:")
107 typer.echo(' (use "muse commit -m <msg>" to record changes)\n')
108 for p in sorted(modified):
109 typer.echo(f"\t modified: {p}")
110 for p in sorted(added):
111 typer.echo(f"\t new file: {p}")
112 for p in sorted(deleted):
113 typer.echo(f"\t deleted: {p}")