use std::path::Path;
use std::sync::Arc;
use url::Url;
use crate::resolver::DependencyScope;
use crate::resolver::error::ResolverError;
use crate::resolver::policy::ResolverPolicy;
use crate::resolver::versions::RemoteRefs;
pub(crate) struct GitFetcher {
policy: Arc<ResolverPolicy>,
}
impl GitFetcher {
pub fn new(policy: Arc<ResolverPolicy>) -> Self {
Self { policy }
}
pub fn list_tags(
&self,
url: &Url,
scope: DependencyScope,
) -> Result<RemoteRefs, ResolverError> {
let net = self.policy.git_policy(scope);
crate::resolver::versions::discover_remote_tags(
url,
net.max_advertised_refs,
self.policy.fetch_policy(scope, url),
)
.map_err(ResolverError::from)
}
pub fn list_branches(
&self,
url: &Url,
scope: DependencyScope,
) -> Result<RemoteRefs, ResolverError> {
let net = self.policy.git_policy(scope);
crate::resolver::versions::discover_remote_branches(
url,
net.max_advertised_refs,
self.policy.fetch_policy(scope, url),
)
.map_err(ResolverError::from)
}
pub fn default_branch(
&self,
url: &Url,
scope: DependencyScope,
) -> Result<String, ResolverError> {
crate::resolver::git::ops::discover_default_branch(
url,
self.policy.fetch_policy(scope, url),
self.policy.git_policy(scope).max_advertised_refs,
)
.map_err(ResolverError::from)
}
pub fn resolve_commit_prefix(
&self,
url: &Url,
prefix: &str,
scope: DependencyScope,
work_dir: &Path,
) -> Result<String, ResolverError> {
let policy = self.policy.fetch_policy(scope, url);
let refs = crate::resolver::git::ops::list_advertised_refs(
url,
self.policy.git_policy(scope).max_advertised_refs,
policy,
)?;
if let Some(sha) = crate::resolver::git::ops::unique_ref_prefix_match(&refs, prefix) {
return Ok(sha.to_string());
}
crate::resolver::git::ops::resolve_commit_prefix(work_dir, url, prefix, policy)
.map_err(ResolverError::from)
}
pub fn ensure_materialized(
&self,
url: &Url,
commit: &str,
paths: &[&str],
scope: DependencyScope,
cache: crate::resolver::git::ops::CacheLocation<'_>,
) -> Result<bool, ResolverError> {
let fetched = crate::resolver::git::ops::ensure_materialized(
cache,
url,
commit,
paths.iter().copied(),
self.policy.fetch_policy(scope, url),
crate::resolver::git::ops::TreeLimits {
max_files: self.policy.max_materialized_files,
max_bytes: self.policy.max_materialized_bytes,
},
)?;
Ok(fetched)
}
}
#[cfg(test)]
mod tests {
use std::fs;
use std::io;
use git2::Repository;
use git2::Signature;
use tempfile::tempdir;
use super::*;
use crate::resolver::config::ModulesConfig;
#[test]
fn local_remote_operations_materialize_module() -> Result<(), Box<dyn std::error::Error>> {
let upstream = tempdir()?;
let repo = Repository::init(upstream.path())?;
let module = upstream.path().join("module");
fs::create_dir(&module)?;
fs::write(
module.join(crate::MANIFEST_FILENAME),
br#"{"name":"dep","license":"MIT"}"#,
)?;
fs::write(module.join("index.wdl"), b"version 1.3\nworkflow w {}\n")?;
let mut index = repo.index()?;
index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
index.write()?;
let tree = repo.find_tree(index.write_tree()?)?;
let signature = Signature::now("test", "test@example.com")?;
let oid = repo.commit(Some("HEAD"), &signature, &signature, "initial", &tree, &[])?;
repo.tag_lightweight("v1.0.0", &repo.find_object(oid, None)?, false)?;
let branch = repo
.head()?
.shorthand()
.map_err(|source| io::Error::new(io::ErrorKind::InvalidData, source))?
.to_string();
let url = Url::from_file_path(upstream.path())
.map_err(|()| io::Error::other("failed to create file URL"))?;
let policy = ResolverPolicy::try_from(&ModulesConfig {
allowed_schemes: vec!["file".to_string()],
..ModulesConfig::default()
})?;
let fetcher = GitFetcher::new(Arc::new(policy));
let sha = oid.to_string();
let tags = fetcher.list_tags(&url, DependencyScope::TopLevel)?;
assert_eq!(
tags.get("v1.0.0").map(|commit| commit.as_str()),
Some(sha.as_str())
);
let branches = fetcher.list_branches(&url, DependencyScope::TopLevel)?;
assert_eq!(
branches.get(&branch).map(|commit| commit.as_str()),
Some(sha.as_str())
);
assert_eq!(
fetcher.default_branch(&url, DependencyScope::TopLevel)?,
branch
);
let resolution = tempdir()?;
assert_eq!(
fetcher.resolve_commit_prefix(
&url,
&sha[..8],
DependencyScope::TopLevel,
&resolution.path().join("fallback"),
)?,
sha
);
let cache = tempdir()?;
let leaf = cache.path().join(&sha);
let location = crate::resolver::git::ops::CacheLocation {
root: cache.path(),
leaf: &leaf,
};
assert!(fetcher.ensure_materialized(
&url,
&sha,
&["module"],
DependencyScope::TopLevel,
location,
)?);
assert!(leaf.join("module").join(crate::MANIFEST_FILENAME).is_file());
assert!(!fetcher.ensure_materialized(
&url,
&sha,
&["module"],
DependencyScope::TopLevel,
location,
)?);
Ok(())
}
}