memstead_cli/commands/branch_reset.rs
1//! `memstead branch-reset <mem> <target_sha>` — the engine's history-
2//! rewrite primitive exposed as a CLI subcommand.
3//!
4//! Replay workflows that need to rewind a mem's branch pointer to
5//! retry against a different upstream state consume this. Safety
6//! contract lives on `Engine::branch_reset`: no pushed commit can be
7//! discarded by the reset (the engine's definition of "pushed" is
8//! reachability from any `refs/remotes/*` ref). Refusal surfaces as
9//! `PUSHED_COMMITS_PROTECTED` with the offending SHAs on stderr /
10//! `details.pushed_shas`.
11
12use clap::Args;
13
14use crate::CliError;
15use crate::setup::{CliContext, CliEngine};
16
17/// `memstead branch-reset <mem> <target_sha>` arguments.
18#[derive(Args, Debug)]
19pub struct BranchResetArgs {
20 /// Mem whose branch pointer to reset. Must be git-branch-backed.
21 pub mem: String,
22 /// Target ref or SHA. Accepts anything `git rev-parse` admits —
23 /// branch names, abbreviated SHAs, full SHAs, tags.
24 pub target_sha: String,
25}
26
27pub fn run(ctx: &CliContext, args: BranchResetArgs) -> anyhow::Result<()> {
28 let outcome = match ctx.cli_engine()? {
29 CliEngine::MemRepo(mut engine) => engine
30 .branch_reset(&args.mem, &args.target_sha)
31 .map_err(CliError::from_engine_op)?,
32 CliEngine::Filesystem(_) => {
33 // Folder mounts have no git refs — same refusal the
34 // engine surface emits, but the CLI catches it before
35 // wiring an irrelevant call.
36 return Err(CliError {
37 code: "INVALID_INPUT",
38 kind: crate::output::ExitKind::Validation,
39 message: format!(
40 "mem '{}' is not git-backed — `memstead branch-reset` requires a git-branch mount",
41 args.mem,
42 ),
43 details: None,
44 }
45 .into());
46 }
47 };
48
49 if ctx.json {
50 crate::output::print_json(&outcome)?;
51 } else {
52 let discarded = if outcome.discarded_commits.is_empty() {
53 " (no commits discarded — target equalled the current head)".to_string()
54 } else {
55 outcome
56 .discarded_commits
57 .iter()
58 .map(|s| format!(" - {s}"))
59 .collect::<Vec<_>>()
60 .join("\n")
61 };
62 crate::output::print_markdown(&format!(
63 "# Branch reset: `{}`\n\n- Branch ref: `{}`\n- Previous: `{}`\n- New: `{}`\n- Discarded commits:\n{}",
64 outcome.mem, outcome.branch_ref, outcome.previous_sha, outcome.new_sha, discarded,
65 ));
66 }
67 Ok(())
68}