use std::path::PathBuf;
use std::sync::Arc;
use rattler_prefix_guard::AsyncPrefixGuard;
use tracing::debug;
use crate::LazyClient;
use crate::{
GitError, GitUrl, Reporter,
git::{CheckoutOptions, GitReference},
sha::GitSha,
source::{Fetch, GitSource, cache_digest},
url::RepositoryUrl,
};
use dashmap::DashMap;
use dashmap::mapref::one::Ref;
use serde::Serialize;
#[derive(Debug, thiserror::Error)]
pub enum GitResolverError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Join(#[from] tokio::task::JoinError),
#[error("Git operation failed")]
Git(String),
}
#[derive(Default, Clone)]
pub struct GitResolver(Arc<DashMap<RepositoryReference, GitSha>>);
impl GitResolver {
pub fn insert(&self, reference: RepositoryReference, sha: GitSha) {
self.0.insert(reference, sha);
}
fn get(&self, reference: &RepositoryReference) -> Option<Ref<'_, RepositoryReference, GitSha>> {
self.0.get(reference)
}
pub async fn fetch(
&self,
url: GitUrl,
client: impl Into<LazyClient>,
cache: PathBuf,
reporter: Option<Arc<dyn Reporter>>,
checkout_options: CheckoutOptions,
) -> Result<Fetch, GitError> {
debug!("Fetching source distribution from Git: {url}");
let reference = RepositoryReference::from(&url);
let url = {
if let Some(precise) = self.get(&reference) {
url.with_precise(*precise)
} else {
url
}
};
let lock_dir = cache.join("locks");
let repository_url = RepositoryUrl::new(url.repository());
let write_guard_path = lock_dir.join(cache_digest(&repository_url));
let guard = AsyncPrefixGuard::new(&write_guard_path).await?;
let mut write_guard = guard.write().await?;
write_guard.begin().await?;
let source =
GitSource::new(url.clone(), client, cache).with_checkout_options(checkout_options);
let source = if let Some(reporter) = reporter {
source.with_reporter(reporter)
} else {
source
};
let fetch = tokio::task::spawn_blocking(move || source.fetch())
.await?
.inspect_err(|err| tracing::error!("Error fetching Git repository: {err}"))?;
self.insert(reference, fetch.commit());
write_guard.finish().await?;
tracing::debug!("Fetched source distribution from Git: {url}");
Ok(fetch)
}
pub fn precise(&self, url: GitUrl) -> Option<GitUrl> {
let reference = RepositoryReference::from(&url);
let precise = self.get(&reference)?;
Some(url.with_precise(*precise))
}
pub fn same_ref(&self, a: &GitUrl, b: &GitUrl) -> bool {
let a_ref = RepositoryReference::from(a);
let b_ref = RepositoryReference::from(b);
if a_ref.url != b_ref.url {
return false;
}
if a_ref.reference == b_ref.reference {
return true;
}
let Some(a_precise) = a.precise().or_else(|| self.get(&a_ref).map(|sha| *sha)) else {
return false;
};
let Some(b_precise) = b.precise().or_else(|| self.get(&b_ref).map(|sha| *sha)) else {
return false;
};
a_precise == b_precise
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ResolvedRepositoryReference {
pub reference: RepositoryReference,
pub sha: GitSha,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct RepositoryReference {
pub url: RepositoryUrl,
#[serde(skip_serializing_if = "GitReference::is_default")]
pub reference: GitReference,
}
impl From<&GitUrl> for RepositoryReference {
fn from(git: &GitUrl) -> Self {
Self {
url: RepositoryUrl::new(git.repository()),
reference: git.reference().clone(),
}
}
}