use std::path::PathBuf;
use std::process::Command as SysCommand;
use clap::Args;
use indexmap::IndexMap;
use tokio::runtime::Runtime;
use crate::cli::{remove_symlink_dir, symlink_dir};
use crate::cmd::clone::get_path_subdeps;
use crate::config::{Dependency, Locked, LockedSource};
use crate::diagnostic::Warnings;
use crate::error::*;
use crate::sess::{DependencySource, Session, SessionIo};
use crate::{debugln, fmt_path, fmt_pkg, stageln};
#[derive(Args, Debug)]
pub struct SnapshotArgs {
#[arg(long, default_value = "working_dir")]
pub working_dir: String,
#[arg(long)]
pub no_skip: bool,
#[arg(short, long)]
pub checkout: bool,
#[arg(long, requires = "checkout")]
pub force: bool,
}
pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> {
let mut snapshot_list = Vec::new();
for (name, dep) in sess.config.overrides.iter() {
if let Dependency::Path {
path: override_path,
..
} = dep
{
if override_path.starts_with(sess.root.join(&args.working_dir)) {
if let DependencySource::Path(dep_path) =
sess.dependency_source(sess.dependency_with_name(name)?)
{
if dep_path == *override_path {
if !SysCommand::new(&sess.config.git)
.arg("status")
.arg("--porcelain")
.current_dir(&dep_path)
.output()?
.stdout
.is_empty()
&& !args.no_skip
{
Warnings::SkippingDirtyDep { pkg: name.clone() }.emit();
continue;
}
let url = match String::from_utf8(
SysCommand::new(&sess.config.git)
.arg("remote")
.arg("get-url")
.arg("origin")
.current_dir(&dep_path)
.output()?
.stdout,
) {
Ok(url) => url.trim_end_matches(&['\r', '\n'][..]).to_string(),
Err(_) => Err(Error::new("Failed to get git url.".to_string()))?,
};
let hash = match String::from_utf8(
SysCommand::new(&sess.config.git)
.arg("rev-parse")
.arg("HEAD")
.current_dir(&dep_path)
.output()?
.stdout,
) {
Ok(hash) => hash.trim_end_matches(&['\r', '\n'][..]).to_string(),
Err(_) => Err(Error::new("Failed to get git hash.".to_string()))?,
};
eprintln!("Snapshotting {} at {} from {}", name, hash, url);
snapshot_list.push((name.clone(), url, hash));
}
}
}
}
}
let local_path = sess.root.join("Bender.local");
if local_path.exists() && !snapshot_list.is_empty() {
let local_file_str = match std::fs::read_to_string(&local_path) {
Err(why) => Err(Error::new(format!(
"Reading Bender.local failed with msg:\n\t{}",
why
)))?,
Ok(local_file_str) => local_file_str,
};
let mut new_str = String::new();
if local_file_str.contains("overrides:") {
let split = local_file_str.split('\n');
let test = split.clone().next_back().unwrap().is_empty();
for i in split {
for (name, _, _) in &snapshot_list {
if i.contains(name) {
new_str.push('#');
}
}
new_str.push_str(i);
new_str.push('\n');
if i.contains("overrides:") {
for (name, url, hash) in &snapshot_list {
let dep_str = format!(
" {}: {{ git: \"{}\", rev: \"{}\" }} # Temporary override by Bender using `bender snapshot` command\n",
name, url, hash
);
new_str.push_str(&dep_str);
}
}
}
if test {
new_str.pop();
}
if let Err(why) = std::fs::write(local_path, new_str) {
Err(Error::new(format!(
"Writing new Bender.local failed with msg:\n\t{}",
why
)))?
}
eprintln!("Bender.local updated with snapshots.");
}
}
let rt = Runtime::new()?;
let io = SessionIo::new(sess);
let mut path_subdeps: IndexMap<String, PathBuf> = IndexMap::new();
for (name, url, _) in &snapshot_list {
get_path_subdeps(
&io,
&rt,
&io.get_depsource_path(name, &DependencySource::Git(url.clone())),
sess.dependency_with_name(name)?,
)?
.into_iter()
.for_each(|(k, v)| {
path_subdeps.insert(k, v);
});
}
use std::fs::File;
let file = File::open(sess.root.join("Bender.lock"))
.map_err(|cause| Error::chain(format!("Cannot open lockfile {:?}.", sess.root), cause))?;
let mut locked: Locked = serde_yaml_ng::from_reader(&file).map_err(|cause| {
Error::chain(format!("Syntax error in lockfile {:?}.", sess.root), cause)
})?;
for (name, url, hash) in &snapshot_list {
let mut mod_package = locked.packages.get_mut(name).unwrap().clone();
mod_package.revision = Some(hash.to_string());
mod_package.version = None;
mod_package.source = LockedSource::Git(url.to_string());
locked.packages.insert(name.to_string(), mod_package);
}
for (path_dep, path_dep_path) in &path_subdeps {
let mut mod_package = locked.packages[path_dep].clone();
mod_package.revision = None;
mod_package.version = None;
mod_package.source = LockedSource::Path(
path_dep_path
.strip_prefix(sess.root)
.unwrap_or(path_dep_path)
.to_path_buf(),
);
locked.packages.insert(path_dep.clone(), mod_package);
}
let file = File::create(sess.root.join("Bender.lock"))
.map_err(|cause| Error::chain(format!("Cannot create lockfile {:?}.", sess.root), cause))?;
serde_yaml_ng::to_writer(&file, &locked)
.map_err(|cause| Error::chain(format!("Cannot write lockfile {:?}.", sess.root), cause))?;
if args.checkout {
sess.load_locked(&locked)?;
let rt = Runtime::new()?;
let io = SessionIo::new(sess);
let _srcs = rt.block_on(io.sources(args.force, &[]))?;
}
let snapshotted_deps = snapshot_list
.iter()
.map(|(name, _, _)| name.as_str())
.collect::<Vec<&str>>();
let subdeps = path_subdeps
.iter()
.map(|(name, _)| name.as_str())
.collect::<Vec<&str>>();
let updated_deps: Vec<&str> = [snapshotted_deps.clone(), subdeps.clone()].concat();
for (link_path, pkg_name) in &sess.manifest.workspace.package_links {
if updated_deps.contains(&pkg_name.as_str()) {
debugln!("main: maintaining link to {} at {:?}", pkg_name, link_path);
let pkg_path = if snapshotted_deps.contains(&pkg_name.as_str()) {
&io.get_depsource_path(
pkg_name,
&DependencySource::Git(
snapshot_list
.iter()
.find(|(n, _, _)| n == pkg_name)
.unwrap()
.1
.clone(),
),
)
} else {
path_subdeps.get(pkg_name).unwrap()
};
let pkg_path = link_path
.parent()
.and_then(|path| pathdiff::diff_paths(pkg_path, path))
.unwrap_or_else(|| pkg_path.into());
if link_path.exists() {
let meta = link_path.symlink_metadata().map_err(|cause| {
Error::chain(
format!("Failed to read metadata of path {:?}.", link_path),
cause,
)
})?;
if !meta.file_type().is_symlink() {
Warnings::SkippingPackageLink(pkg_name.clone(), link_path.to_path_buf()).emit();
continue;
}
if link_path.read_link().map(|d| d != pkg_path).unwrap_or(true) {
debugln!("main: removing existing link {:?}", link_path);
remove_symlink_dir(link_path).map_err(|cause| {
Error::chain(
format!("Failed to remove symlink at path {:?}.", link_path),
cause,
)
})?;
}
}
if !link_path.exists() {
if let Some(parent) = link_path.parent() {
std::fs::create_dir_all(parent).map_err(|cause| {
Error::chain(format!("Failed to create directory {:?}.", parent), cause)
})?;
}
let previous_dir = match link_path.parent() {
Some(parent) => {
let d = std::env::current_dir().unwrap();
std::env::set_current_dir(parent).unwrap();
Some(d)
}
None => None,
};
symlink_dir(&pkg_path, link_path).map_err(|cause| {
Error::chain(
format!(
"Failed to create symlink to {:?} at path {:?}.",
pkg_path, link_path
),
cause,
)
})?;
if let Some(d) = previous_dir {
std::env::set_current_dir(d).unwrap();
}
stageln!(
"Linked",
"{} to {}",
fmt_pkg!(pkg_name),
fmt_path!(link_path.display())
);
}
}
}
Ok(())
}