use std::path::PathBuf;
use tracing::debug;
use tracing::error;
use tracing::info;
use tracing::warn;
use crate::flow;
use crate::io::manifest::resolve_latest;
use crate::io::remote::Remote;
use crate::io::storage::Storage;
use crate::lineage::InstalledPackageStatus;
use crate::lineage::PackageLineage;
use crate::manifest::Table;
use crate::paths::copy_cached_to_installed;
use crate::paths::DomainPaths;
use crate::uri::ManifestUri;
use crate::uri::Namespace;
use crate::Error;
use crate::Res;
#[allow(clippy::too_many_arguments)]
pub async fn pull_package(
lineage: PackageLineage,
manifest: &mut Table,
paths: &DomainPaths,
storage: &(impl Storage + Sync),
remote: &impl Remote,
working_dir: PathBuf,
status: InstalledPackageStatus,
namespace: Namespace,
) -> Res<PackageLineage> {
info!("⏳ Starting pull for package {}", namespace);
if !status.changes.is_empty() {
error!("❌ Found pending changes, cannot pull");
return Err(Error::Package("package has pending changes".to_string()));
}
if lineage.commit.is_some() {
error!("❌ Found pending commits, cannot pull");
return Err(Error::Package("package has pending commits".to_string()));
}
if lineage.remote.hash != lineage.base_hash {
error!("❌ Package has diverged from remote");
return Err(Error::Package("package has diverged".to_string()));
}
if lineage.base_hash == lineage.latest_hash {
error!("❌ Package is already up-to-date");
return Err(Error::Package("package is already up-to-date".to_string()));
}
let installed_paths: Vec<PathBuf> = lineage.paths.keys().cloned().collect();
debug!("⏳ Uninstalling {} paths", installed_paths.len());
let mut lineage =
flow::uninstall_paths(lineage, working_dir.clone(), storage, &installed_paths).await?;
debug!("⏳ Updating lineage hashes");
lineage.remote.hash.clone_from(&lineage.latest_hash);
lineage.base_hash.clone_from(&lineage.latest_hash);
debug!("⏳ Resolving latest manifest");
let manifest_uri = resolve_latest(
remote,
&lineage.remote.catalog,
&lineage.remote.clone().into(),
)
.await?;
debug!("✔️ Latest manifest resolved: {}", manifest_uri.display());
debug!("⏳ Caching remote manifest");
flow::cache_remote_manifest(paths, storage, remote, &manifest_uri).await?;
debug!("⏳ Installing cached manifest");
copy_cached_to_installed(
paths,
storage,
&ManifestUri {
namespace: namespace.clone(),
..lineage.remote.clone()
},
)
.await?;
debug!("⏳ Checking which paths to reinstall");
let mut paths_to_install = Vec::new();
for x in installed_paths {
if manifest.contains_record(&x).await {
debug!("✔️ Will reinstall path: {}", x.display());
paths_to_install.push(x)
} else {
warn!("❌ Path no longer exists in manifest: {}", x.display());
}
}
info!("⏳ Reinstalling {} paths", paths_to_install.len());
let package_lineage = flow::install_paths(
lineage,
manifest,
paths,
working_dir,
namespace,
storage,
remote,
&paths_to_install,
)
.await?;
info!("✔️ Successfully pulled and updated package");
Ok(package_lineage)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
use crate::lineage::Change;
use crate::lineage::PackageFileFingerprint;
use crate::mocks;
#[tokio::test]
async fn test_no_pull_if_changes() -> Res {
let storage = mocks::storage::MockStorage::default();
let lineage = mocks::lineage::with_paths(vec![PathBuf::from("a/a")]);
let status = InstalledPackageStatus {
changes: BTreeMap::from([(
PathBuf::from("foo"),
Change::Added(PackageFileFingerprint::default()),
)]),
..InstalledPackageStatus::default()
};
let remote = mocks::remote::MockRemote::default();
let error = pull_package(
lineage,
&mut mocks::manifest::with_record_keys(vec![PathBuf::from("a/a")]),
&DomainPaths::default(),
&storage,
&remote,
PathBuf::default(),
status,
Namespace::default(),
)
.await;
assert_eq!(
error.unwrap_err().to_string(),
"General error regarding package: package has pending changes".to_string()
);
Ok(())
}
#[tokio::test]
async fn test_no_pull_if_commit() {
let storage = mocks::storage::MockStorage::default();
let remote = mocks::remote::MockRemote::default();
let lineage = mocks::lineage::with_commit();
let error = pull_package(
lineage,
&mut Table::default(),
&DomainPaths::default(),
&storage,
&remote,
PathBuf::default(),
InstalledPackageStatus::default(),
Namespace::default(),
)
.await;
assert_eq!(
error.unwrap_err().to_string(),
"General error regarding package: package has pending commits".to_string()
);
}
#[tokio::test]
async fn test_no_pull_if_diverged() {
let storage = mocks::storage::MockStorage::default();
let remote = mocks::remote::MockRemote::default();
let lineage = PackageLineage {
remote: ManifestUri {
hash: "a".to_string(),
..ManifestUri::default()
},
base_hash: "b".to_string(),
..PackageLineage::default()
};
let error = pull_package(
lineage,
&mut Table::default(),
&DomainPaths::default(),
&storage,
&remote,
PathBuf::default(),
InstalledPackageStatus::default(),
Namespace::default(),
)
.await;
assert_eq!(
error.unwrap_err().to_string(),
"General error regarding package: package has diverged".to_string()
);
}
#[tokio::test]
async fn test_no_pull_if_up_to_date() {
let storage = mocks::storage::MockStorage::default();
let remote = mocks::remote::MockRemote::default();
let lineage = PackageLineage {
remote: ManifestUri {
hash: "a".to_string(),
..ManifestUri::default()
},
base_hash: "a".to_string(),
latest_hash: "a".to_string(),
..PackageLineage::default()
};
let error = pull_package(
lineage,
&mut Table::default(),
&DomainPaths::default(),
&storage,
&remote,
PathBuf::default(),
InstalledPackageStatus::default(),
Namespace::default(),
)
.await;
assert_eq!(
error.unwrap_err().to_string(),
"General error regarding package: package is already up-to-date".to_string()
);
}
}