mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Add Git resolver in lieu of static hash map (#3954)
## Summary This PR removes the static resolver map: ```rust static RESOLVED_GIT_REFS: Lazy<Mutex<FxHashMap<RepositoryReference, GitSha>>> = Lazy::new(Mutex::default); ``` With a `GitResolver` struct that we now pass around on the `BuildContext`. There should be no behavior changes here; it's purely an internal refactor with an eye towards making it cleaner for us to "pre-populate" the list of resolved SHAs.
This commit is contained in:
parent
a0652921fc
commit
b7d77c04cc
31 changed files with 475 additions and 384 deletions
|
@ -17,9 +17,11 @@ cache-key = { workspace = true }
|
|||
uv-fs = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
cargo-util = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
fs-err = { workspace = true, features = ["tokio"] }
|
||||
reqwest = { workspace = true, features = ["blocking"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
|
|
@ -2,10 +2,12 @@ use std::str::FromStr;
|
|||
use url::Url;
|
||||
|
||||
pub use crate::git::GitReference;
|
||||
pub use crate::resolver::{GitResolver, GitResolverError, RepositoryReference};
|
||||
pub use crate::sha::{GitOid, GitSha, OidParseError};
|
||||
pub use crate::source::{Fetch, GitSource, Reporter};
|
||||
|
||||
mod git;
|
||||
mod resolver;
|
||||
mod sha;
|
||||
mod source;
|
||||
|
||||
|
|
154
crates/uv-git/src/resolver.rs
Normal file
154
crates/uv-git/src/resolver.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use dashmap::mapref::one::Ref;
|
||||
use dashmap::DashMap;
|
||||
use fs_err::tokio as fs;
|
||||
use tracing::debug;
|
||||
|
||||
use cache_key::RepositoryUrl;
|
||||
use uv_fs::LockedFile;
|
||||
|
||||
use crate::{Fetch, GitReference, GitSha, GitSource, GitUrl, Reporter};
|
||||
|
||||
#[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(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
/// A resolver for Git repositories.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct GitResolver(Arc<DashMap<RepositoryReference, GitSha>>);
|
||||
|
||||
impl GitResolver {
|
||||
/// Returns the [`GitSha`] for the given [`RepositoryReference`], if it exists.
|
||||
pub fn get(&self, reference: &RepositoryReference) -> Option<Ref<RepositoryReference, GitSha>> {
|
||||
self.0.get(reference)
|
||||
}
|
||||
|
||||
/// Inserts a new [`GitSha`] for the given [`RepositoryReference`].
|
||||
pub fn insert(&self, reference: RepositoryReference, sha: GitSha) {
|
||||
self.0.insert(reference, sha);
|
||||
}
|
||||
|
||||
/// Download a source distribution from a Git repository.
|
||||
///
|
||||
/// Assumes that the URL is a precise Git URL, with a full commit hash.
|
||||
pub async fn fetch(
|
||||
&self,
|
||||
url: &GitUrl,
|
||||
cache: PathBuf,
|
||||
reporter: Option<impl Reporter + 'static>,
|
||||
) -> Result<Fetch, GitResolverError> {
|
||||
debug!("Fetching source distribution from Git: {url}");
|
||||
|
||||
// Avoid races between different processes, too.
|
||||
let lock_dir = cache.join("locks");
|
||||
fs::create_dir_all(&lock_dir).await?;
|
||||
let repository_url = RepositoryUrl::new(url.repository());
|
||||
let _lock = LockedFile::acquire(
|
||||
lock_dir.join(cache_key::digest(&repository_url)),
|
||||
&repository_url,
|
||||
)?;
|
||||
|
||||
// Fetch the Git repository.
|
||||
let source = if let Some(reporter) = reporter {
|
||||
GitSource::new(url.clone(), cache).with_reporter(reporter)
|
||||
} else {
|
||||
GitSource::new(url.clone(), cache)
|
||||
};
|
||||
let fetch = tokio::task::spawn_blocking(move || source.fetch())
|
||||
.await?
|
||||
.map_err(GitResolverError::Git)?;
|
||||
|
||||
Ok(fetch)
|
||||
}
|
||||
|
||||
/// Given a remote source distribution, return a precise variant, if possible.
|
||||
///
|
||||
/// For example, given a Git dependency with a reference to a branch or tag, return a URL
|
||||
/// with a precise reference to the current commit of that branch or tag.
|
||||
///
|
||||
/// This method takes into account various normalizations that are independent from the Git
|
||||
/// layer. For example: removing `#subdirectory=pkg_dir`-like fragments, and removing `git+`
|
||||
/// prefix kinds.
|
||||
pub async fn resolve(
|
||||
&self,
|
||||
url: &GitUrl,
|
||||
cache: impl Into<PathBuf>,
|
||||
reporter: Option<impl Reporter + 'static>,
|
||||
) -> Result<Option<GitUrl>, GitResolverError> {
|
||||
// If the Git reference already contains a complete SHA, short-circuit.
|
||||
if url.precise().is_some() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// If the Git reference is in the in-memory cache, return it.
|
||||
{
|
||||
let reference = RepositoryReference::from(url);
|
||||
if let Some(precise) = self.get(&reference) {
|
||||
return Ok(Some(url.clone().with_precise(*precise)));
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the precise SHA of the Git reference (which could be a branch, a tag, a partial
|
||||
// commit, etc.).
|
||||
let source = if let Some(reporter) = reporter {
|
||||
GitSource::new(url.clone(), cache).with_reporter(reporter)
|
||||
} else {
|
||||
GitSource::new(url.clone(), cache)
|
||||
};
|
||||
let fetch = tokio::task::spawn_blocking(move || source.fetch())
|
||||
.await?
|
||||
.map_err(GitResolverError::Git)?;
|
||||
let git = fetch.into_git();
|
||||
|
||||
// Insert the resolved URL into the in-memory cache.
|
||||
if let Some(precise) = git.precise() {
|
||||
let reference = RepositoryReference::from(url);
|
||||
self.insert(reference, precise);
|
||||
}
|
||||
|
||||
Ok(Some(git))
|
||||
}
|
||||
|
||||
/// Given a remote source distribution, return a precise variant, if possible.
|
||||
///
|
||||
/// For example, given a Git dependency with a reference to a branch or tag, return a URL
|
||||
/// with a precise reference to the current commit of that branch or tag.
|
||||
///
|
||||
/// This method takes into account various normalizations that are independent from the Git
|
||||
/// layer. For example: removing `#subdirectory=pkg_dir`-like fragments, and removing `git+`
|
||||
/// prefix kinds.
|
||||
///
|
||||
/// This method will only return precise URLs for URLs that have already been resolved via
|
||||
/// [`resolve_precise`].
|
||||
pub fn precise(&self, url: GitUrl) -> Option<GitUrl> {
|
||||
let reference = RepositoryReference::from(&url);
|
||||
let precise = self.get(&reference)?;
|
||||
Some(url.with_precise(*precise))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RepositoryReference {
|
||||
/// The URL of the Git repository, with any query parameters and fragments removed.
|
||||
pub url: RepositoryUrl,
|
||||
/// The reference to the commit to use, which could be a branch, tag or revision.
|
||||
pub reference: GitReference,
|
||||
}
|
||||
|
||||
impl From<&GitUrl> for RepositoryReference {
|
||||
fn from(git: &GitUrl) -> Self {
|
||||
Self {
|
||||
url: RepositoryUrl::new(git.repository()),
|
||||
reference: git.reference().clone(),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue