diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index dc5bdb57e..88ccd9d0c 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -40,7 +40,7 @@ use uv_distribution_types::{ }; use uv_extract::hash::Hasher; use uv_fs::{rename_with_retry, write_atomic, LockedFile}; -use uv_git::{GitHubRepository, GitSha}; +use uv_git::{GitHubRepository, GitOid}; use uv_metadata::read_archive_metadata; use uv_normalize::PackageName; use uv_pep440::{release_specifiers_to_ranges, Version}; @@ -1783,7 +1783,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { /// Attempts to fetch the `pyproject.toml` from the resolved commit using the GitHub API. async fn github_metadata( &self, - commit: GitSha, + commit: GitOid, source: &BuildableSource<'_>, resource: &GitSourceUrl<'_>, client: &ManagedClient<'_>, diff --git a/crates/uv-git/src/git.rs b/crates/uv-git/src/git.rs index 4bb0623e0..7f1a8d5cc 100644 --- a/crates/uv-git/src/git.rs +++ b/crates/uv-git/src/git.rs @@ -18,8 +18,7 @@ use uv_fs::Simplified; use uv_static::EnvVars; use uv_version::version; -use crate::sha::GitOid; -use crate::{GitHubRepository, GitSha}; +use crate::{GitHubRepository, GitOid}; /// A file indicates that if present, `git reset` has been done and a repo /// checkout is ready to go. See [`GitCheckout::reset`] for why we need this. @@ -32,6 +31,7 @@ pub enum GitError { #[error(transparent)] Other(#[from] which::Error), } + /// A global cache of the result of `which git`. pub static GIT: LazyLock> = LazyLock::new(|| { which::which("git").map_err(|e| match e { @@ -123,10 +123,10 @@ impl GitReference { } } - /// Returns the precise [`GitSha`] of this reference, if it's a full commit. - pub(crate) fn as_sha(&self) -> Option { + /// Returns the precise [`GitOid`] of this reference, if it's a full commit. + pub(crate) fn as_sha(&self) -> Option { if let Self::FullCommit(rev) = self { - Some(GitSha::from_str(rev).expect("Full commit should be exactly 40 characters")) + Some(GitOid::from_str(rev).expect("Full commit should be exactly 40 characters")) } else { None } diff --git a/crates/uv-git/src/lib.rs b/crates/uv-git/src/lib.rs index 2392df224..44eaa582e 100644 --- a/crates/uv-git/src/lib.rs +++ b/crates/uv-git/src/lib.rs @@ -3,17 +3,17 @@ use url::Url; pub use crate::credentials::{store_credentials_from_url, GIT_STORE}; pub use crate::git::{GitReference, GIT}; pub use crate::github::GitHubRepository; +pub use crate::oid::{GitOid, OidParseError}; pub use crate::resolver::{ GitResolver, GitResolverError, RepositoryReference, ResolvedRepositoryReference, }; -pub use crate::sha::{GitSha, OidParseError}; pub use crate::source::{Fetch, GitSource, Reporter}; mod credentials; mod git; mod github; +mod oid; mod resolver; -mod sha; mod source; /// A URL reference to a Git repository. @@ -25,7 +25,7 @@ pub struct GitUrl { /// The reference to the commit to use, which could be a branch, tag or revision. reference: GitReference, /// The precise commit to use, if known. - precise: Option, + precise: Option, } impl GitUrl { @@ -40,7 +40,7 @@ impl GitUrl { } /// Create a new [`GitUrl`] from a repository URL and a precise commit. - pub fn from_commit(repository: Url, reference: GitReference, precise: GitSha) -> Self { + pub fn from_commit(repository: Url, reference: GitReference, precise: GitOid) -> Self { Self { repository, reference, @@ -48,9 +48,9 @@ impl GitUrl { } } - /// Set the precise [`GitSha`] to use for this Git URL. + /// Set the precise [`GitOid`] to use for this Git URL. #[must_use] - pub fn with_precise(mut self, precise: GitSha) -> Self { + pub fn with_precise(mut self, precise: GitOid) -> Self { self.precise = Some(precise); self } @@ -73,7 +73,7 @@ impl GitUrl { } /// Return the precise commit, if known. - pub fn precise(&self) -> Option { + pub fn precise(&self) -> Option { self.precise } } diff --git a/crates/uv-git/src/sha.rs b/crates/uv-git/src/oid.rs similarity index 65% rename from crates/uv-git/src/sha.rs rename to crates/uv-git/src/oid.rs index 8adf8d4e7..be417d4d0 100644 --- a/crates/uv-git/src/sha.rs +++ b/crates/uv-git/src/oid.rs @@ -3,81 +3,25 @@ use std::str::{self, FromStr}; use thiserror::Error; -/// A complete Git SHA, i.e., a 40-character hexadecimal representation of a Git commit. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct GitSha(GitOid); - -impl GitSha { - /// Return the Git SHA as a string. - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - /// Return a truncated representation, i.e., the first 16 characters of the SHA. - pub fn as_short_str(&self) -> &str { - &self.0.as_str()[..16] - } -} - -impl From for GitOid { - fn from(value: GitSha) -> Self { - value.0 - } -} - -impl From for GitSha { - fn from(value: GitOid) -> Self { - Self(value) - } -} - -impl Display for GitSha { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl FromStr for GitSha { - type Err = OidParseError; - - fn from_str(value: &str) -> Result { - Ok(Self(GitOid::from_str(value)?)) - } -} - -impl serde::Serialize for GitSha { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.as_str().serialize(serializer) - } -} - -impl<'de> serde::Deserialize<'de> for GitSha { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = String::deserialize(deserializer)?; - GitSha::from_str(&value).map_err(serde::de::Error::custom) - } -} - /// Unique identity of any Git object (commit, tree, blob, tag). /// /// Note this type does not validate whether the input is a valid hash. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct GitOid { +pub struct GitOid { len: usize, bytes: [u8; 40], } impl GitOid { /// Return the string representation of an object ID. - pub(crate) fn as_str(&self) -> &str { + pub fn as_str(&self) -> &str { str::from_utf8(&self.bytes[..self.len]).unwrap() } + + /// Return a truncated representation, i.e., the first 16 characters of the SHA. + pub fn as_short_str(&self) -> &str { + &self.as_str()[..16] + } } #[derive(Debug, Error, PartialEq)] @@ -116,6 +60,25 @@ impl Display for GitOid { } } +impl serde::Serialize for GitOid { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.as_str().serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for GitOid { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + GitOid::from_str(&value).map_err(serde::de::Error::custom) + } +} + #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/crates/uv-git/src/resolver.rs b/crates/uv-git/src/resolver.rs index 4b200160c..4ef519b22 100644 --- a/crates/uv-git/src/resolver.rs +++ b/crates/uv-git/src/resolver.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use tracing::debug; -use crate::{Fetch, GitHubRepository, GitReference, GitSha, GitSource, GitUrl, Reporter}; +use crate::{Fetch, GitHubRepository, GitOid, GitReference, GitSource, GitUrl, Reporter}; use dashmap::mapref::one::Ref; use dashmap::DashMap; use fs_err::tokio as fs; @@ -30,28 +30,28 @@ pub enum GitResolverError { /// A resolver for Git repositories. #[derive(Default, Clone)] -pub struct GitResolver(Arc>); +pub struct GitResolver(Arc>); impl GitResolver { - /// Inserts a new [`GitSha`] for the given [`RepositoryReference`]. - pub fn insert(&self, reference: RepositoryReference, sha: GitSha) { + /// Inserts a new [`GitOid`] for the given [`RepositoryReference`]. + pub fn insert(&self, reference: RepositoryReference, sha: GitOid) { self.0.insert(reference, sha); } - /// Returns the [`GitSha`] for the given [`RepositoryReference`], if it exists. - fn get(&self, reference: &RepositoryReference) -> Option> { + /// Returns the [`GitOid`] for the given [`RepositoryReference`], if it exists. + fn get(&self, reference: &RepositoryReference) -> Option> { self.0.get(reference) } /// Resolve a Git URL to a specific commit without performing any Git operations. /// - /// Returns a [`GitSha`] if the URL has already been resolved (i.e., is available in the cache), + /// Returns a [`GitOid`] if the URL has already been resolved (i.e., is available in the cache), /// or if it can be fetched via the GitHub API. Otherwise, returns `None`. pub async fn github_fast_path( &self, url: &GitUrl, client: ClientWithMiddleware, - ) -> Result, GitResolverError> { + ) -> Result, GitResolverError> { let reference = RepositoryReference::from(url); // If we know the precise commit already, return it. @@ -92,7 +92,7 @@ impl GitResolver { // Parse the response as a Git SHA. let precise = response.text().await?; let precise = - GitSha::from_str(&precise).map_err(|err| GitResolverError::Git(err.into()))?; + GitOid::from_str(&precise).map_err(|err| GitResolverError::Git(err.into()))?; // Insert the resolved URL into the in-memory cache. This ensures that subsequent fetches // resolve to the same precise commit. @@ -207,7 +207,7 @@ pub struct ResolvedRepositoryReference { /// tag, or revision). pub reference: RepositoryReference, /// The precise commit SHA of the reference. - pub sha: GitSha, + pub sha: GitOid, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/uv-git/src/source.rs b/crates/uv-git/src/source.rs index 92d1117e9..efbea29b7 100644 --- a/crates/uv-git/src/source.rs +++ b/crates/uv-git/src/source.rs @@ -14,8 +14,7 @@ use url::Url; use uv_cache_key::{cache_digest, RepositoryUrl}; use crate::git::GitRemote; -use crate::sha::GitOid; -use crate::{GitSha, GitUrl, GIT_STORE}; +use crate::{GitOid, GitUrl, GIT_STORE}; /// A remote Git source that can be checked out locally. pub struct GitSource { @@ -74,7 +73,7 @@ impl GitSource { let (db, actual_rev, task) = match (self.git.precise, remote.db_at(&db_path).ok()) { // If we have a locked revision, and we have a preexisting database // which has that revision, then no update needs to happen. - (Some(rev), Some(db)) if db.contains(rev.into()) => { + (Some(rev), Some(db)) if db.contains(rev) => { debug!("Using existing Git source `{}`", self.git.repository); (db, rev, None) } @@ -99,13 +98,13 @@ impl GitSource { &self.client, )?; - (db, GitSha::from(actual_rev), task) + (db, actual_rev, task) } }; // Don’t use the full hash, in order to contribute less to reaching the // path length limit on Windows. - let short_id = db.to_short_id(actual_rev.into())?; + let short_id = db.to_short_id(actual_rev)?; // Check out `actual_rev` from the database to a scoped location on the // filesystem. This will use hard links and such to ideally make the @@ -116,7 +115,7 @@ impl GitSource { .join(&ident) .join(short_id.as_str()); - db.copy_to(actual_rev.into(), &checkout_path)?; + db.copy_to(actual_rev, &checkout_path)?; // Report the checkout operation to the reporter. if let Some(task) = task { diff --git a/crates/uv-pypi-types/src/parsed_url.rs b/crates/uv-pypi-types/src/parsed_url.rs index 510a61091..d3c9de22e 100644 --- a/crates/uv-pypi-types/src/parsed_url.rs +++ b/crates/uv-pypi-types/src/parsed_url.rs @@ -5,7 +5,7 @@ use thiserror::Error; use url::{ParseError, Url}; use uv_distribution_filename::{DistExtension, ExtensionError}; -use uv_git::{GitReference, GitSha, GitUrl, OidParseError}; +use uv_git::{GitOid, GitReference, GitUrl, OidParseError}; use uv_pep508::{ looks_like_git_repository, Pep508Url, UnnamedRequirementUrl, VerbatimUrl, VerbatimUrlError, }; @@ -23,7 +23,7 @@ pub enum ParsedUrlError { #[error("Invalid path in file URL: `{0}`")] InvalidFileUrl(String), #[error("Failed to parse Git reference from URL: `{0}`")] - GitShaParse(String, #[source] OidParseError), + GitOidParse(String, #[source] OidParseError), #[error("Not a valid URL: `{0}`")] UrlParse(String, #[source] ParseError), #[error(transparent)] @@ -247,7 +247,7 @@ impl ParsedGitUrl { pub fn from_source( repository: Url, reference: GitReference, - precise: Option, + precise: Option, subdirectory: Option, ) -> Self { let url = if let Some(precise) = precise { @@ -275,7 +275,7 @@ impl TryFrom for ParsedGitUrl { .unwrap_or(url_in.as_str()); let url = Url::parse(url).map_err(|err| ParsedUrlError::UrlParse(url.to_string(), err))?; let url = GitUrl::try_from(url) - .map_err(|err| ParsedUrlError::GitShaParse(url_in.to_string(), err))?; + .map_err(|err| ParsedUrlError::GitOidParse(url_in.to_string(), err))?; Ok(Self { url, subdirectory }) } } diff --git a/crates/uv-pypi-types/src/requirement.rs b/crates/uv-pypi-types/src/requirement.rs index 846d5af45..29dc9a3ac 100644 --- a/crates/uv-pypi-types/src/requirement.rs +++ b/crates/uv-pypi-types/src/requirement.rs @@ -8,7 +8,7 @@ use url::Url; use uv_distribution_filename::DistExtension; use uv_fs::{relative_to, PortablePath, PortablePathBuf, CWD}; -use uv_git::{GitReference, GitSha, GitUrl}; +use uv_git::{GitOid, GitReference, GitUrl}; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::VersionSpecifiers; use uv_pep508::{ @@ -406,7 +406,7 @@ pub enum RequirementSource { /// Optionally, the revision, tag, or branch to use. reference: GitReference, /// The precise commit to use, if known. - precise: Option, + precise: Option, /// The path to the source distribution if it is not in the repository root. subdirectory: Option, /// The PEP 508 style url in the format @@ -823,7 +823,7 @@ impl TryFrom for RequirementSource { }; } - let precise = repository.fragment().map(GitSha::from_str).transpose()?; + let precise = repository.fragment().map(GitOid::from_str).transpose()?; // Clear out any existing state. repository.set_fragment(None); diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index f1968c68f..e3d4ca2fb 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -31,7 +31,7 @@ use uv_distribution_types::{ RemoteSource, ResolvedDist, StaticMetadata, ToUrlError, UrlString, }; use uv_fs::{relative_to, PortablePath, PortablePathBuf}; -use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference}; +use uv_git::{GitOid, GitReference, RepositoryReference, ResolvedRepositoryReference}; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::Version; use uv_pep508::{split_scheme, MarkerEnvironment, MarkerTree, VerbatimUrl, VerbatimUrlError}; @@ -3192,7 +3192,7 @@ struct DirectSource { /// canonical ordering of package entries. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] struct GitSource { - precise: GitSha, + precise: GitOid, subdirectory: Option, kind: GitSourceKind, } @@ -3219,7 +3219,7 @@ impl GitSource { _ => continue, }; } - let precise = GitSha::from_str(url.fragment().ok_or(GitSourceError::MissingSha)?) + let precise = GitOid::from_str(url.fragment().ok_or(GitSourceError::MissingSha)?) .map_err(|_| GitSourceError::InvalidSha)?; Ok(GitSource { @@ -3587,7 +3587,7 @@ fn locked_git_url(git_dist: &GitSourceDist) -> Url { .git .precise() .as_ref() - .map(GitSha::to_string) + .map(GitOid::to_string) .as_deref(), );