cgcardona / muse public
revert.py python
93 lines 2.9 KB
12901c5a Initial extraction from tellurstori/maestro cgcardona <gabriel@tellurstori.com> 4d ago
1 """muse revert — create a new commit that undoes a prior commit.
2
3 Safe undo: given a commit C with parent P, ``muse revert <commit>`` creates
4 a new commit whose snapshot is P's state (the world before C was applied).
5 History is never rewritten — the revert is a forward commit.
6
7 Domain analogy: a producer accidentally committed a bad drum arrangement.
8 Rather than resetting (which loses history), ``muse revert`` creates an
9 "undo commit" so the full timeline remains auditable.
10
11 Flags
12 -----
13 COMMIT TEXT Commit ID to revert (required, positional, accepts prefix).
14 --no-commit Apply the inverse changes to muse-work/ without committing.
15 --track TEXT Scope the revert to paths under tracks/<track>/.
16 --section TEXT Scope the revert to paths under sections/<section>/.
17 """
18 from __future__ import annotations
19
20 import asyncio
21 import logging
22 from typing import Optional
23
24 import typer
25
26 from maestro.muse_cli._repo import require_repo
27 from maestro.muse_cli.db import open_session
28 from maestro.muse_cli.errors import ExitCode
29 from maestro.services.muse_revert import _revert_async
30
31 logger = logging.getLogger(__name__)
32
33 app = typer.Typer()
34
35
36 @app.callback(invoke_without_command=True)
37 def revert(
38 ctx: typer.Context,
39 commit: str = typer.Argument(
40 ...,
41 help="Commit ID to revert (full or abbreviated SHA).",
42 metavar="COMMIT",
43 ),
44 no_commit: bool = typer.Option(
45 False,
46 "--no-commit",
47 help=(
48 "Apply the inverse changes to muse-work/ without creating a new commit. "
49 "Note: file bytes not retained by the object store cannot be restored "
50 "automatically — missing paths are listed as warnings."
51 ),
52 ),
53 track: Optional[str] = typer.Option(
54 None,
55 "--track",
56 help=(
57 "Scope the revert to a specific track (instrument) path prefix. "
58 "Only files under tracks/<track>/ are reverted; all other paths "
59 "remain at HEAD."
60 ),
61 ),
62 section: Optional[str] = typer.Option(
63 None,
64 "--section",
65 help=(
66 "Scope the revert to a specific section path prefix. "
67 "Only files under sections/<section>/ are reverted; all other paths "
68 "remain at HEAD."
69 ),
70 ),
71 ) -> None:
72 """Create a new commit that undoes a prior commit without rewriting history."""
73 root = require_repo()
74
75 async def _run() -> None:
76 async with open_session() as session:
77 await _revert_async(
78 commit_ref=commit,
79 root=root,
80 session=session,
81 no_commit=no_commit,
82 track=track,
83 section=section,
84 )
85
86 try:
87 asyncio.run(_run())
88 except typer.Exit:
89 raise
90 except Exception as exc:
91 typer.echo(f"❌ muse revert failed: {exc}")
92 logger.error("❌ muse revert error: %s", exc, exc_info=True)
93 raise typer.Exit(code=ExitCode.INTERNAL_ERROR)