memstead_cli/commands/
transport.rs1use clap::Args;
6
7use crate::CliError;
8use crate::output::ExitKind;
9use crate::setup::{CliContext, CliEngine};
10
11#[derive(Args, Debug)]
13pub struct FetchArgs {
14 pub mem: String,
15 #[arg(long, default_value = "origin")]
16 pub remote: String,
17 #[arg(num_args = 0..)]
20 pub refspecs: Vec<String>,
21}
22
23#[derive(Args, Debug)]
25pub struct PullArgs {
26 pub mem: String,
27 #[arg(long, default_value = "origin")]
28 pub remote: String,
29}
30
31#[derive(Args, Debug)]
33pub struct PushArgs {
34 pub mem: String,
35 #[arg(long, default_value = "origin")]
36 pub remote: String,
37 #[arg(long, default_value_t = false)]
41 pub force: bool,
42}
43
44pub fn run_fetch(ctx: &CliContext, args: FetchArgs) -> anyhow::Result<()> {
45 let outcome = match ctx.cli_engine()? {
46 CliEngine::MemRepo(engine) => engine
47 .fetch(&args.mem, &args.remote, &args.refspecs)
48 .map_err(CliError::from_engine_op)?,
49 CliEngine::Filesystem(_) => return Err(folder_refusal("memstead fetch", &args.mem)),
50 };
51 if ctx.json {
52 crate::output::print_json(&outcome)?;
53 } else {
54 let updated = if outcome.updated_refs.is_empty() {
55 " (no refs changed)".to_string()
56 } else {
57 outcome
58 .updated_refs
59 .iter()
60 .map(|u| {
61 let prev = if u.previous_sha.is_empty() {
62 "<new>".to_string()
63 } else {
64 u.previous_sha.clone()
65 };
66 format!(" - {} : {prev} -> {}", u.ref_name, u.new_sha)
67 })
68 .collect::<Vec<_>>()
69 .join("\n")
70 };
71 crate::output::print_markdown(&format!(
72 "# Fetched from `{}`\n\n- Refspecs: {}\n- Updated refs:\n{}",
73 outcome.remote,
74 if outcome.refspecs.is_empty() {
75 "<defaults>".to_string()
76 } else {
77 outcome.refspecs.join(", ")
78 },
79 updated,
80 ));
81 }
82 Ok(())
83}
84
85pub fn run_pull(ctx: &CliContext, args: PullArgs) -> anyhow::Result<()> {
86 let outcome = match ctx.cli_engine()? {
87 CliEngine::MemRepo(mut engine) => engine
88 .pull(&args.mem, &args.remote)
89 .map_err(CliError::from_engine_op)?,
90 CliEngine::Filesystem(_) => return Err(folder_refusal("memstead pull", &args.mem)),
91 };
92 if ctx.json {
93 crate::output::print_json(&outcome)?;
94 } else {
95 let prev = if outcome.previous_sha.is_empty() {
96 "<new branch>".to_string()
97 } else {
98 outcome.previous_sha.clone()
99 };
100 crate::output::print_markdown(&format!(
101 "# Pulled `{}`\n\n- Branch ref: `{}`\n- Source ref: `{}`\n- Previous: `{prev}`\n- New: `{}`",
102 outcome.mem, outcome.branch_ref, outcome.source_ref, outcome.new_sha,
103 ));
104 }
105 Ok(())
106}
107
108pub fn run_push(ctx: &CliContext, args: PushArgs) -> anyhow::Result<()> {
109 let outcome = match ctx.cli_engine()? {
110 CliEngine::MemRepo(engine) => engine
111 .push(&args.mem, &args.remote, args.force)
112 .map_err(CliError::from_engine_op)?,
113 CliEngine::Filesystem(_) => return Err(folder_refusal("memstead push", &args.mem)),
114 };
115 if ctx.json {
116 crate::output::print_json(&outcome)?;
117 } else {
118 let force_note = if outcome.forced { " (forced)" } else { "" };
119 crate::output::print_markdown(&format!(
120 "# Pushed `{}` to `{}`{force_note}\n\n- Branch ref: `{}`\n- New SHA at remote: `{}`",
121 outcome.mem, outcome.remote, outcome.branch_ref, outcome.new_sha,
122 ));
123 }
124 Ok(())
125}
126
127fn folder_refusal(op: &str, mem: &str) -> anyhow::Error {
128 CliError {
129 code: "INVALID_INPUT",
130 kind: ExitKind::Validation,
131 message: format!("mem '{mem}' is not git-backed — `{op}` requires a git-branch mount",),
132 details: None,
133 }
134 .into()
135}