use crate::cache::Cache;
use crate::config::Manifest;
use crate::error::{Error, Result};
use crate::git::Repo;
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use inflector::Inflector;
use log::{debug, info};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
pub fn main() -> Result<bool> {
env_logger::from_env(env_logger::Env::default().default_filter_or("info")).init();
let name = env!("CARGO_PKG_NAME").to_title_case();
let version = env!("CARGO_PKG_VERSION");
let app = App::new(&name)
.setting(AppSettings::SubcommandRequiredElseHelp)
.version(version)
.author(env!("CARGO_PKG_AUTHORS"))
.about("A Build Artifact Cache for Git Repositories.")
.arg(Arg::with_name("working_dir")
.short("C")
.takes_value(true)
.help("Run as if started in this path.")
)
.arg(Arg::with_name("ignore_uncommitted_changes")
.long("ignore-uncommitted-changes")
.help("Ignores uncommitted changes")
)
.subcommand(SubCommand::with_name("get")
.about("Get the outputs of an artifact from the cache or exit non-zero if the artifact is not cached.")
.arg(Arg::with_name("artifact")
.takes_value(true)
.required(true)
)
)
.subcommand(SubCommand::with_name("insert")
.about("Insert the outputs of an artifact into the cache.")
.arg(Arg::with_name("artifact")
.takes_value(true)
.required(true)
)
)
.subcommand(SubCommand::with_name("lookup")
.about("Look an artifact up in the cache. Exit zero iff the artifact is cached.")
.arg(Arg::with_name("artifact")
.takes_value(true)
.required(true)
)
);
let matches = app.get_matches();
debug!("{} v{}", name, version);
let working_dir: PathBuf = {
let path_str = matches.value_of("working_dir").unwrap_or(".");
fs::canonicalize(path_str).map_err(|cause| {
Error::chain(
format!(
"Failed to canonicalize path of working directory {:?}!",
path_str
),
cause,
)
})
}?;
debug!("Working directory: {:?}.", working_dir);
let repo: Repo = {
let tmp = Repo::new(working_dir);
let git_path = match tmp.cmd_output(&["rev-parse", "--show-toplevel"]) {
None => Error::result(format!("Could not find Git repository.")),
Some(s) => fs::canonicalize(&s).map_err(|cause| {
Error::chain(
format!("Failed to canonicalize path of Git repository {:?}!", s),
cause,
)
}),
}?;
Repo::new(git_path)
};
debug!("Git repository: {:?}.", repo);
let manifest: Manifest = {
let manifest_path = {
let mut iter = ["Memora.yml", ".ci/Memora.yml", ".gitlab-ci.d/Memora.yml"]
.iter()
.map(|s| Path::new(s))
.map(|p| repo.path.join(p))
.map(|p| fs::canonicalize(p))
.filter(|r| r.is_ok())
.map(|r| r.unwrap());
match iter.next() {
None => Error::result(format!("Could not find Memora manifest.")),
Some(p) => Ok(p),
}?
};
Manifest::from_path(&manifest_path)?
};
debug!("Memora manifest: {:?}.", manifest);
let disabled = match manifest.disable_env_var {
Some(e) => match env::var(&e) {
Ok(_) => {
info!(
"{} disabled because environment variable \"{}\" is set.",
name, e
);
true
}
_ => false,
},
_ => false,
};
let cache: Cache = {
let root_dir = match manifest.cache_root_dir.is_absolute() {
true => manifest.cache_root_dir.clone(),
false => repo.path.join(&manifest.cache_root_dir),
};
let cache_path = fs::canonicalize(&root_dir).map_err(|cause| {
Error::chain(
format!("Failed to canonicalize path of cache {:?}!", root_dir),
cause,
)
})?;
Cache::new(cache_path, &repo, &manifest.artifacts)
};
debug!("Cache: {:?}.", cache);
let ignore_uncommitted_changes = matches.is_present("ignore_uncommitted_changes");
if ignore_uncommitted_changes {
debug!("Ignoring uncommitted changes.");
}
match matches.subcommand() {
("get", Some(matches)) => match disabled {
false => get(&cache, matches, ignore_uncommitted_changes),
true => Ok(false),
},
("insert", Some(matches)) => match disabled {
false => insert(&cache, matches, ignore_uncommitted_changes),
true => Ok(true),
},
("lookup", Some(matches)) => match disabled {
false => lookup(&cache, matches, ignore_uncommitted_changes),
true => Ok(false),
},
_ => Error::result("Unknown combination of subcommand and arguments!"),
}
}
fn artifact_name<'a>(matches: &'a ArgMatches) -> Result<&'a str> {
match matches.value_of("artifact") {
None => Error::result("Required \"artifact\" argument was not provided!"),
Some(s) => Ok(s),
}
}
pub fn get(cache: &Cache, matches: &ArgMatches, ignore_uncommitted_changes: bool) -> Result<bool> {
let artifact_name = artifact_name(matches)?;
let artifact = cache.artifact(artifact_name)?;
match cache.get(&artifact, ignore_uncommitted_changes) {
Ok(Some(obj)) => {
info!("Got artifact \"{}\" from {:?}.", artifact_name, obj.oid);
Ok(true)
}
Ok(None) => {
info!("Artifact \"{}\" not found in cache.", artifact_name);
Ok(false)
}
Err(e) => Err(e),
}
}
pub fn insert(
cache: &Cache,
matches: &ArgMatches,
ignore_uncommitted_changes: bool,
) -> Result<bool> {
let artifact_name = artifact_name(matches)?;
let artifact = cache.artifact(artifact_name)?;
match cache.insert(&artifact, ignore_uncommitted_changes) {
Ok((false, obj)) => {
info!(
"Artifact artifact \"{}\" already exists under {:?}, did not insert.",
artifact_name, obj.oid
);
Ok(true)
}
Ok((true, obj)) => {
info!(
"Inserted artifact \"{}\" under {:?}.",
artifact_name, obj.oid
);
Ok(true)
}
Err(e) => Err(e),
}
}
pub fn lookup(
cache: &Cache,
matches: &ArgMatches,
ignore_uncommitted_changes: bool,
) -> Result<bool> {
let artifact_name = artifact_name(matches)?;
let artifact = cache.artifact(artifact_name)?;
match cache.cached_object(&artifact, ignore_uncommitted_changes) {
Some(obj) => {
info!("Found artifact \"{}\" in {:?}.", artifact_name, obj.oid);
Ok(true)
}
None => {
info!("Artifact \"{}\" not found in cache.", artifact_name);
Ok(false)
}
}
}