use crate::error::{Result, ToriiError};
use std::path::Path;
use std::process::{Command, Stdio};
#[derive(Debug, Default)]
pub struct CommonOpts {
pub squash: bool,
}
fn ensure_git_subtree() -> Result<()> {
let probe = Command::new("git")
.args(["subtree", "--help"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
match probe {
Ok(s) if s.success() => Ok(()),
Ok(_) | Err(_) => Err(ToriiError::InvalidConfig(
"`git subtree` is not available. On Debian/Ubuntu: install `git-subtree`. \
On Arch: `pacman -S git` (it's in the main package). \
On Fedora: `dnf install git-subtree`."
.to_string(),
)),
}
}
pub fn add(repo_path: &Path, prefix: &str, url: &str, refname: &str, opts: &CommonOpts) -> Result<()> {
ensure_git_subtree()?;
println!("🌿 subtree add prefix={prefix} url={url} ref={refname}");
let mut args = vec!["subtree".to_string(), "add".to_string()];
if opts.squash {
args.push("--squash".to_string());
}
args.push(format!("--prefix={prefix}"));
args.push(url.to_string());
args.push(refname.to_string());
run_git(repo_path, &args)
}
pub fn pull(repo_path: &Path, prefix: &str, url: &str, refname: &str, opts: &CommonOpts) -> Result<()> {
ensure_git_subtree()?;
println!("⬇ subtree pull prefix={prefix} url={url} ref={refname}");
let mut args = vec!["subtree".to_string(), "pull".to_string()];
if opts.squash {
args.push("--squash".to_string());
}
args.push(format!("--prefix={prefix}"));
args.push(url.to_string());
args.push(refname.to_string());
run_git(repo_path, &args)
}
pub fn push(repo_path: &Path, prefix: &str, url: &str, refname: &str) -> Result<()> {
ensure_git_subtree()?;
println!("⬆ subtree push prefix={prefix} url={url} ref={refname}");
let args = vec![
"subtree".to_string(),
"push".to_string(),
format!("--prefix={prefix}"),
url.to_string(),
refname.to_string(),
];
run_git(repo_path, &args)
}
pub fn split(
repo_path: &Path,
prefix: &str,
branch: Option<&str>,
annotate: Option<&str>,
) -> Result<()> {
ensure_git_subtree()?;
println!("✂ subtree split prefix={prefix}");
let mut args = vec![
"subtree".to_string(),
"split".to_string(),
format!("--prefix={prefix}"),
];
if let Some(b) = branch {
args.push("-b".to_string());
args.push(b.to_string());
}
if let Some(a) = annotate {
args.push(format!("--annotate={a}"));
}
run_git(repo_path, &args)
}
pub fn merge(repo_path: &Path, prefix: &str, refname: &str, opts: &CommonOpts) -> Result<()> {
ensure_git_subtree()?;
println!("🔀 subtree merge prefix={prefix} ref={refname}");
let mut args = vec!["subtree".to_string(), "merge".to_string()];
if opts.squash {
args.push("--squash".to_string());
}
args.push(format!("--prefix={prefix}"));
args.push(refname.to_string());
run_git(repo_path, &args)
}
fn run_git(repo_path: &Path, args: &[String]) -> Result<()> {
let status = Command::new("git")
.args(args)
.current_dir(repo_path)
.status()
.map_err(|e| ToriiError::InvalidConfig(format!("invoke git: {e}")))?;
if !status.success() {
return Err(ToriiError::InvalidConfig(format!(
"git {} exited with {}",
args.join(" "),
status
)));
}
Ok(())
}