use anyhow::{Context, anyhow, bail};
use dolly_cli::{
binary, build, git, paths,
recipe::{Recipe, RecipeError},
ui,
};
pub fn handle(repo: Option<&str>, dry_run: bool) -> anyhow::Result<()> {
match repo {
Some(r) => handle_repo(r, dry_run),
None => handle_all(dry_run),
}
}
fn handle_repo(repo: &str, dry_run: bool) -> anyhow::Result<()> {
let repo_path = paths::repos_dir()?.join(repo);
if !repo_path.exists() {
bail!("repo `{repo}` is not cloned")
}
let recipe = match Recipe::find(repo) {
Ok(r) => Some(r),
Err(RecipeError::NotFound(_)) => None,
Err(e) => {
return Err(anyhow::Error::from(e).context(format!("loading recipe for `{repo}`")));
}
};
let old = git::head_commit(&repo_path).with_context(|| format!("reading HEAD for `{repo}`"))?;
if dry_run {
git::fetch(&repo_path).with_context(|| format!("fetching `{repo}`"))?;
let upstream = git::upstream_commit(&repo_path)
.with_context(|| format!("reading upstream HEAD for `{repo}`"))?;
if upstream != old {
ui::status("Would update", &format!("{repo} ({old} -> {upstream})"));
} else {
ui::status("Up-to-date", repo);
}
return Ok(());
}
ui::status("Pulling", repo);
git::pull(&repo_path).with_context(|| format!("pulling `{repo}`"))?;
let new = git::head_commit(&repo_path).with_context(|| format!("reading HEAD for `{repo}`"))?;
if new == old {
ui::status("Up-to-date", repo);
return Ok(());
}
let Some(recipe) = recipe else {
ui::status("Updated", &format!("{repo} ({old} -> {new})"));
return Ok(());
};
ui::status("Building", repo);
build::run(&recipe.build.steps, &repo_path).with_context(|| format!("building `{repo}`"))?;
let binary_path = repo_path.join(&recipe.build.output);
let bin_name = binary_path
.file_name()
.ok_or_else(|| anyhow!("`build.output` has no filename"))?;
let install_path = paths::default_install_dir()?.join(bin_name);
binary::place(&binary_path, &install_path).with_context(|| format!("installing `{repo}`"))?;
binary::make_executable(&install_path)
.with_context(|| format!("setting executable bit on {}", install_path.display()))?;
ui::status("Updated", &format!("{repo} ({old} -> {new})"));
Ok(())
}
fn handle_all(dry_run: bool) -> anyhow::Result<()> {
let repos_dir = paths::repos_dir()?;
if !repos_dir.exists() {
return Ok(());
}
let mut repos: Vec<String> = Vec::new();
for entry in repos_dir
.read_dir()
.with_context(|| format!("reading {}", repos_dir.display()))?
{
let entry = entry?;
if !entry.file_type()?.is_dir() {
continue;
}
repos.push(entry.file_name().to_string_lossy().into_owned());
}
repos.sort();
for repo in repos {
handle_repo(&repo, dry_run)?;
}
Ok(())
}