use clap::Args;
use crate::CliError;
use crate::output::ExitKind;
use crate::setup::{CliContext, CliEngine};
#[derive(Args, Debug)]
pub struct FetchArgs {
pub mem: String,
#[arg(long, default_value = "origin")]
pub remote: String,
#[arg(num_args = 0..)]
pub refspecs: Vec<String>,
}
#[derive(Args, Debug)]
pub struct PullArgs {
pub mem: String,
#[arg(long, default_value = "origin")]
pub remote: String,
}
#[derive(Args, Debug)]
pub struct PushArgs {
pub mem: String,
#[arg(long, default_value = "origin")]
pub remote: String,
#[arg(long, default_value_t = false)]
pub force: bool,
}
pub fn run_fetch(ctx: &CliContext, args: FetchArgs) -> anyhow::Result<()> {
let outcome = match ctx.cli_engine()? {
CliEngine::MemRepo(engine) => engine
.fetch(&args.mem, &args.remote, &args.refspecs)
.map_err(CliError::from_engine_op)?,
CliEngine::Filesystem(_) => return Err(folder_refusal("memstead fetch", &args.mem)),
};
if ctx.json {
crate::output::print_json(&outcome)?;
} else {
let updated = if outcome.updated_refs.is_empty() {
" (no refs changed)".to_string()
} else {
outcome
.updated_refs
.iter()
.map(|u| {
let prev = if u.previous_sha.is_empty() {
"<new>".to_string()
} else {
u.previous_sha.clone()
};
format!(" - {} : {prev} -> {}", u.ref_name, u.new_sha)
})
.collect::<Vec<_>>()
.join("\n")
};
crate::output::print_markdown(&format!(
"# Fetched from `{}`\n\n- Refspecs: {}\n- Updated refs:\n{}",
outcome.remote,
if outcome.refspecs.is_empty() {
"<defaults>".to_string()
} else {
outcome.refspecs.join(", ")
},
updated,
));
}
Ok(())
}
pub fn run_pull(ctx: &CliContext, args: PullArgs) -> anyhow::Result<()> {
let outcome = match ctx.cli_engine()? {
CliEngine::MemRepo(mut engine) => engine
.pull(&args.mem, &args.remote)
.map_err(CliError::from_engine_op)?,
CliEngine::Filesystem(_) => return Err(folder_refusal("memstead pull", &args.mem)),
};
if ctx.json {
crate::output::print_json(&outcome)?;
} else {
let prev = if outcome.previous_sha.is_empty() {
"<new branch>".to_string()
} else {
outcome.previous_sha.clone()
};
crate::output::print_markdown(&format!(
"# Pulled `{}`\n\n- Branch ref: `{}`\n- Source ref: `{}`\n- Previous: `{prev}`\n- New: `{}`",
outcome.mem, outcome.branch_ref, outcome.source_ref, outcome.new_sha,
));
}
Ok(())
}
pub fn run_push(ctx: &CliContext, args: PushArgs) -> anyhow::Result<()> {
let outcome = match ctx.cli_engine()? {
CliEngine::MemRepo(engine) => engine
.push(&args.mem, &args.remote, args.force)
.map_err(CliError::from_engine_op)?,
CliEngine::Filesystem(_) => return Err(folder_refusal("memstead push", &args.mem)),
};
if ctx.json {
crate::output::print_json(&outcome)?;
} else {
let force_note = if outcome.forced { " (forced)" } else { "" };
crate::output::print_markdown(&format!(
"# Pushed `{}` to `{}`{force_note}\n\n- Branch ref: `{}`\n- New SHA at remote: `{}`",
outcome.mem, outcome.remote, outcome.branch_ref, outcome.new_sha,
));
}
Ok(())
}
fn folder_refusal(op: &str, mem: &str) -> anyhow::Error {
CliError {
code: "INVALID_INPUT",
kind: ExitKind::Validation,
message: format!("mem '{mem}' is not git-backed — `{op}` requires a git-branch mount",),
details: None,
}
.into()
}