From fd6967058448f7aca18beac899e248fbf4067ce7 Mon Sep 17 00:00:00 2001 From: BiagioFesta <15035284+BiagioFesta@users.noreply.github.com> Date: Sun, 28 Sep 2025 16:32:14 +0200 Subject: [PATCH] cargo: retry git fetch (with CLI) when failure is spurious Mark `git fetch` (in the `CARGO_NET_GIT_FETCH_WITH_CLI` path) as a command whose failure may be considered spurious, and enable retry logic when such a failure occurs. This helps reduce intermittent errors in situations where `git fetch` fails due to transient network or server issues. --- src/cargo/sources/git/utils.rs | 8 +++++-- src/cargo/util/errors.rs | 40 +++++++++++++++++++++++++++++++++ src/cargo/util/network/retry.rs | 17 +++++++++++++- tests/testsuite/git.rs | 6 +++++ 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs index e9224df22dc..e9c8dbd846a 100644 --- a/src/cargo/sources/git/utils.rs +++ b/src/cargo/sources/git/utils.rs @@ -6,7 +6,7 @@ use crate::sources::git::fetch::RemoteKind; use crate::sources::git::oxide; use crate::sources::git::oxide::cargo_config_to_gitoxide_overrides; use crate::util::HumanBytes; -use crate::util::errors::CargoResult; +use crate::util::errors::{CargoResult, GitCliError}; use crate::util::{GlobalContext, IntoUrl, MetricsCounter, Progress, network}; use anyhow::{Context as _, anyhow}; use cargo_util::{ProcessBuilder, paths}; @@ -1110,7 +1110,11 @@ fn fetch_with_cli( .cwd(repo.path()); gctx.shell() .verbose(|s| s.status("Running", &cmd.to_string()))?; - cmd.exec()?; + network::retry::with_retry(gctx, || { + cmd.exec() + .map_err(|error| GitCliError::new(error, true).into()) + })?; + Ok(()) } diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index 5752f2370a9..254a61bcf47 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -338,6 +338,46 @@ impl From for CliError { } } +// ============================================================================= +// Git CLI errors + +pub type GitCliResult = Result<(), GitCliError>; + +/// An error that occurred while invoking the `git` command-line tool. +/// +/// This is used for errors from Git operations performed via CLI. +/// It wraps a lower-level error and indicates whether the failure +/// should be considered *spurious*. +/// +/// Spurious failures might involve retry mechanism. +#[derive(Debug)] +pub struct GitCliError { + inner: Error, + is_spurious: bool, +} + +impl GitCliError { + pub fn new(inner: Error, is_spurious: bool) -> GitCliError { + GitCliError { inner, is_spurious } + } + + pub fn is_spurious(&self) -> bool { + self.is_spurious + } +} + +impl std::error::Error for GitCliError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.inner.source() + } +} + +impl fmt::Display for GitCliError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + // ============================================================================= // Construction helpers diff --git a/src/cargo/util/network/retry.rs b/src/cargo/util/network/retry.rs index 5d4e58024d5..539535dd452 100644 --- a/src/cargo/util/network/retry.rs +++ b/src/cargo/util/network/retry.rs @@ -41,7 +41,7 @@ //! - //! - -use crate::util::errors::HttpNotSuccessful; +use crate::util::errors::{GitCliError, HttpNotSuccessful}; use crate::{CargoResult, GlobalContext}; use anyhow::Error; use rand::Rng; @@ -223,6 +223,12 @@ fn maybe_spurious(err: &Error) -> bool { } } + if let Some(err) = err.downcast_ref::() { + if err.is_spurious() { + return true; + } + } + false } @@ -402,3 +408,12 @@ fn retry_after_parsing() { _ => panic!("unexpected non-retry"), } } + +#[test] +fn git_cli_error_spurious() { + let error = GitCliError::new(Error::msg("test-git-cli-error"), false); + assert!(!maybe_spurious(&error.into())); + + let error = GitCliError::new(Error::msg("test-git-cli-error"), true); + assert!(maybe_spurious(&error.into())); +} diff --git a/tests/testsuite/git.rs b/tests/testsuite/git.rs index c4a0dec7435..90acbb15178 100644 --- a/tests/testsuite/git.rs +++ b/tests/testsuite/git.rs @@ -4092,6 +4092,12 @@ fn github_fastpath_error_message() { .with_stderr_data(str![[r#" [UPDATING] git repository `https://github.com/rust-lang/bitflags.git` fatal: remote [ERROR] upload-pack: not our ref 11111b376b93484341c68fbca3ca110ae5cd2790 +[WARNING] spurious network error (3 tries remaining): process didn't exit successfully: `git fetch --no-tags --force --update-head-ok [..] +fatal: remote [ERROR] upload-pack: not our ref 11111b376b93484341c68fbca3ca110ae5cd2790 +[WARNING] spurious network error (2 tries remaining): process didn't exit successfully: `git fetch --no-tags --force --update-head-ok [..] +fatal: remote [ERROR] upload-pack: not our ref 11111b376b93484341c68fbca3ca110ae5cd2790 +[WARNING] spurious network error (1 try remaining): process didn't exit successfully: `git fetch --no-tags --force --update-head-ok [..] +fatal: remote [ERROR] upload-pack: not our ref 11111b376b93484341c68fbca3ca110ae5cd2790 [ERROR] failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` Caused by: