use super::git_error::{failed, Failure};
use super::git_open::scoped;
use super::git_resolve::conflicts;
use super::git_run::{Git, Out};
use super::git_share::{first_remote, has_upstream, pull, share, Shared};
const DEFAULT_MESSAGE: &str = "Saved from Fusible Studio";
#[derive(Debug)]
pub(super) struct Done {
pub message: String,
pub changed: Vec<String>,
}
pub(super) type Outcome = Result<Done, Failure>;
pub(super) fn step(git: &Git, args: &[&str]) -> Result<Out, Failure> {
let out = git.run(args)?;
if out.ok() {
Ok(out)
} else {
Err(failed(&out))
}
}
fn head(git: &Git) -> Option<String> {
git.text(&["rev-parse", "--verify", "-q", "HEAD"])
}
fn changed_since(git: &Git, before: Option<String>) -> Vec<String> {
let Some(before) = before else {
return Vec::new();
};
let text = git.text(&["diff", "--name-only", &before, "HEAD"]);
text.unwrap_or_default().lines().map(String::from).collect()
}
fn commit(git: &Git, message: &str) -> Result<bool, Failure> {
step(git, &scoped(&["add", "-A"]))?;
let staged = git.run(&["diff", "--cached", "--quiet"])?;
match staged.code {
Some(0) => return Ok(false),
Some(1) => {}
_ => return Err(failed(&staged)),
}
let known = git
.text(&["config", "user.email"])
.is_some_and(|e| !e.is_empty());
let who = [
"-c",
"user.name=Fusible Studio",
"-c",
"user.email=studio@fusible.invalid",
];
let mut args: Vec<&str> = if known { Vec::new() } else { who.to_vec() };
args.extend(["commit", "-m", message]);
step(git, &args)?;
Ok(true)
}
fn blocked(git: &Git) -> Result<(), Failure> {
if conflicts(git).is_empty() {
return Ok(());
}
Err(Failure::new(
409,
"Some files need your decision first. Choose which version to keep.",
))
}
pub(super) fn save(git: &Git, message: &str) -> Outcome {
blocked(git)?;
let message = Some(message.trim()).filter(|m| !m.is_empty());
commit(git, message.unwrap_or(DEFAULT_MESSAGE))?;
let before = head(git);
let shared = share(git)?;
let message = match shared {
Shared::Local => "Saved on this computer. Add a remote to share.",
Shared::Waiting => "Saved, but some files need your decision.",
Shared::Pushed { sent: true } => "Saved and shared.",
Shared::Pushed { sent: false } => "Everything was already saved and shared.",
};
Ok(Done {
message: message.into(),
changed: changed_since(git, before),
})
}
pub(super) fn update(git: &Git) -> Outcome {
blocked(git)?;
if !has_upstream(git) {
let message = match first_remote(git) {
Some(_) => "Nothing is shared yet. Press Save to share your work first.",
None => "This project is only on this computer. Add a remote to get updates.",
};
return Ok(Done {
message: message.into(),
changed: Vec::new(),
});
}
let before = head(git);
let clean = pull(git)?;
let changed = changed_since(git, before);
let message = match (clean, changed.len()) {
(false, _) => "Updates arrived, but some files need your decision.".into(),
(true, 0) => "You already have the latest work.".into(),
(true, n) => format!("Got updates to {n} file{}.", if n == 1 { "" } else { "s" }),
};
Ok(Done { message, changed })
}