diff --git a/crates/uv-git/src/git.rs b/crates/uv-git/src/git.rs index cc72ca7a4..b4d2b75df 100644 --- a/crates/uv-git/src/git.rs +++ b/crates/uv-git/src/git.rs @@ -44,6 +44,7 @@ pub(crate) enum GitReference { } /// Strategy when fetching refspecs for a [`GitReference`] +#[derive(Debug, Clone, Copy)] enum RefspecStrategy { // All refspecs should be fetched, if any fail then the fetch will fail All, @@ -151,7 +152,7 @@ impl GitRemote { db: Option, reference: &GitReference, locked_rev: Option, - strategy: FetchStrategy, + strategy: Option, client: &Client, ) -> Result<(GitDatabase, git2::Oid)> { let locked_ref = locked_rev.map(|oid| GitReference::FullCommit(oid.to_string())); @@ -211,7 +212,7 @@ impl GitDatabase { &self, rev: git2::Oid, destination: &Path, - strategy: FetchStrategy, + strategy: Option, client: &Client, ) -> Result> { // If the existing checkout exists, and it is fresh, use it. @@ -437,7 +438,7 @@ impl<'a> GitCheckout<'a> { /// Submodules set to `none` won't be fetched. /// /// [^1]: - fn update_submodules(&self, strategy: FetchStrategy, client: &Client) -> Result<()> { + fn update_submodules(&self, strategy: Option, client: &Client) -> Result<()> { /// Like `Cow`, but without a requirement on `Clone`. enum Repo<'a> { Borrowed(&'a git2::Repository), @@ -891,15 +892,17 @@ pub(crate) fn with_fetch_options( /// * Dispatches `git fetch` using libgit2 or git CLI. /// /// The `remote_url` argument is the git remote URL where we want to fetch from. +/// +/// Returns the successful fetch strategy used, if any. pub(crate) fn fetch( repo: &mut git2::Repository, remote_url: &str, reference: &GitReference, - strategy: FetchStrategy, + strategy: Option, client: &Client, -) -> Result<()> { +) -> Result> { let oid_to_fetch = match github_fast_path(repo, remote_url, reference, client) { - Ok(FastPathRev::UpToDate) => return Ok(()), + Ok(FastPathRev::UpToDate) => return Ok(None), Ok(FastPathRev::NeedsFetch(rev)) => Some(rev), Ok(FastPathRev::Indeterminate) => None, Err(e) => { @@ -977,11 +980,53 @@ pub(crate) fn fetch( } } + if let Some(strategy) = strategy { + fetch_with_strategy( + repo, + remote_url, + strategy, + refspecs.as_slice(), + refspec_strategy, + tags, + ) + } else { + fetch_with_strategy( + repo, + remote_url, + FetchStrategy::Libgit2, + refspecs.as_slice(), + refspec_strategy, + tags, + ) + .or_else(|_| { + fetch_with_strategy( + repo, + remote_url, + FetchStrategy::Cli, + refspecs.as_slice(), + refspec_strategy, + tags, + ) + }) + } +} + +fn fetch_with_strategy( + repo: &mut git2::Repository, + remote_url: &str, + strategy: FetchStrategy, + refspecs: &[String], + refspec_strategy: RefspecStrategy, + tags: bool, +) -> Result> { debug!("Performing a Git fetch for: {remote_url}"); match strategy { FetchStrategy::Cli => { match refspec_strategy { - RefspecStrategy::All => fetch_with_cli(repo, remote_url, refspecs.as_slice(), tags), + RefspecStrategy::All => { + fetch_with_cli(repo, remote_url, refspecs, tags)?; + Ok(Some(strategy)) + } RefspecStrategy::First => { let num_refspecs = refspecs.len(); @@ -991,7 +1036,7 @@ pub(crate) fn fetch( .map(|refspec| { ( refspec.clone(), - fetch_with_cli(repo, remote_url, &[refspec], tags), + fetch_with_cli(repo, remote_url, &[refspec.clone()], tags), ) }) // Stop after the first success @@ -1007,7 +1052,7 @@ pub(crate) fn fetch( } Err(anyhow!("failed to fetch all refspecs")) } else { - Ok(()) + Ok(Some(strategy)) } } } @@ -1060,7 +1105,8 @@ pub(crate) fn fetch( return Err(err.into()); } Ok(()) - }) + })?; + Ok(Some(strategy)) } } } diff --git a/crates/uv-git/src/source.rs b/crates/uv-git/src/source.rs index 95e89e4fe..ad1162960 100644 --- a/crates/uv-git/src/source.rs +++ b/crates/uv-git/src/source.rs @@ -19,6 +19,8 @@ pub struct GitSource { git: GitUrl, /// The HTTP client to use for fetching. client: Client, + /// The fetch strategy to use when cloning. + strategy: Option, /// The path to the Git source database. cache: PathBuf, /// The reporter to use for this source. @@ -31,6 +33,7 @@ impl GitSource { Self { git, client: Client::new(), + strategy: None, cache: cache.into(), reporter: None, } @@ -46,25 +49,7 @@ impl GitSource { } /// Fetch the underlying Git repository at the given revision. - /// - /// Uses the Libgit2 backend first, then falls back to the CLI which supports - /// more authentication schemes. pub fn fetch(self) -> Result { - self.fetch_with_strategy(FetchStrategy::Libgit2) - .or_else(|_| { - debug!("fetch with libgit2 failed, trying git cli"); - self.fetch_with_strategy(FetchStrategy::Cli) - }) - .map(|(actual_rev, checkout_path)| Fetch { - git: self.git.with_precise(actual_rev), - path: checkout_path, - }) - } - - /// Fetch the underlying Git repository at the given revision. - /// - /// Callers **should** update `self.git.with_precise` with the given SHA. - fn fetch_with_strategy(&self, strategy: FetchStrategy) -> Result<(GitSha, PathBuf)> { // The path to the repo, within the Git database. let ident = digest(&RepositoryUrl::new(&self.git.repository)); let db_path = self.cache.join("db").join(&ident); @@ -92,7 +77,7 @@ impl GitSource { db, &self.git.reference, locked_rev.map(git2::Oid::from), - strategy, + self.strategy, &self.client, )?; @@ -112,7 +97,12 @@ impl GitSource { .join("checkouts") .join(&ident) .join(short_id.as_str()); - db.copy_to(actual_rev.into(), &checkout_path, strategy, &self.client)?; + db.copy_to( + actual_rev.into(), + &checkout_path, + self.strategy, + &self.client, + )?; // Report the checkout operation to the reporter. if let Some(task) = task { @@ -121,7 +111,10 @@ impl GitSource { } } - Ok((actual_rev, checkout_path)) + Ok(Fetch { + git: self.git.with_precise(actual_rev), + path: checkout_path, + }) } }