diff --git a/crates/pypi-types/src/requirement.rs b/crates/pypi-types/src/requirement.rs index 77b4d4c58..b0d1c079d 100644 --- a/crates/pypi-types/src/requirement.rs +++ b/crates/pypi-types/src/requirement.rs @@ -80,14 +80,12 @@ impl Requirement { subdirectory, url, } => { - // Redact the repository URL. - let _ = repository.set_password(None); - let _ = repository.set_username(""); + // Redact the repository URL, but allow `git@`. + redact_git_credentials(&mut repository); // Redact the PEP 508 URL. let mut url = url.to_url(); - let _ = url.set_password(None); - let _ = url.set_username(""); + redact_git_credentials(&mut url); let url = VerbatimUrl::from_url(url); Self { @@ -598,8 +596,7 @@ impl From for RequirementSourceWire { let mut url = repository; // Redact the credentials. - let _ = url.set_username(""); - let _ = url.set_password(None); + redact_git_credentials(&mut url); // Clear out any existing state. url.set_fragment(None); @@ -699,8 +696,7 @@ impl TryFrom for RequirementSource { repository.set_query(None); // Redact the credentials. - let _ = repository.set_username(""); - let _ = repository.set_password(None); + redact_git_credentials(&mut repository); // Create a PEP 508-compatible URL. let mut url = Url::parse(&format!("git+{repository}"))?; @@ -765,6 +761,18 @@ impl TryFrom for RequirementSource { } } +/// Remove the credentials from a Git URL, allowing the generic `git` username (without a password) +/// in SSH URLs, as in, `ssh://git@github.com/...`. +pub fn redact_git_credentials(url: &mut Url) { + // For URLs that use the `git` convention (i.e., `ssh://git@github.com/...`), avoid dropping the + // username. + if url.scheme() == "ssh" && url.username() == "git" && url.password().is_none() { + return; + } + let _ = url.set_password(None); + let _ = url.set_username(""); +} + #[cfg(test)] mod tests { use std::path::{Path, PathBuf}; diff --git a/crates/uv-resolver/src/lock.rs b/crates/uv-resolver/src/lock.rs index 768cf6e35..0d435d933 100644 --- a/crates/uv-resolver/src/lock.rs +++ b/crates/uv-resolver/src/lock.rs @@ -25,7 +25,10 @@ use distribution_types::{ use pep440_rs::Version; use pep508_rs::{MarkerEnvironment, MarkerTree, VerbatimUrl, VerbatimUrlError}; use platform_tags::{TagCompatibility, TagPriority, Tags}; -use pypi_types::{HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement, RequirementSource}; +use pypi_types::{ + redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement, + RequirementSource, +}; use uv_configuration::ExtrasSpecification; use uv_distribution::DistributionDatabase; use uv_fs::{relative_to, PortablePath, PortablePathBuf, Simplified}; @@ -2367,8 +2370,7 @@ fn locked_git_url(git_dist: &GitSourceDist) -> Url { let mut url = git_dist.git.repository().clone(); // Redact the credentials. - let _ = url.set_username(""); - let _ = url.set_password(None); + redact_git_credentials(&mut url); // Clear out any existing state. url.set_fragment(None); @@ -2830,14 +2832,12 @@ fn normalize_requirement( subdirectory, url, } => { - // Redact the repository URL. - let _ = repository.set_password(None); - let _ = repository.set_username(""); + // Redact the credentials. + redact_git_credentials(&mut repository); // Redact the PEP 508 URL. let mut url = url.to_url(); - let _ = url.set_password(None); - let _ = url.set_username(""); + redact_git_credentials(&mut url); let url = VerbatimUrl::from_url(url); Ok(Requirement { diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index c2dc96340..2bcd2a28a 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -6,6 +6,7 @@ use anyhow::{bail, Context, Result}; use cache_key::RepositoryUrl; use owo_colors::OwoColorize; use pep508_rs::{ExtraName, Requirement, VersionOrUrl}; +use pypi_types::redact_git_credentials; use rustc_hash::{FxBuildHasher, FxHashMap}; use tracing::debug; use uv_auth::{store_credentials_from_url, Credentials}; @@ -360,8 +361,9 @@ pub(crate) async fn add( if let Some(credentials) = credentials { debug!("Caching credentials for: {git}"); GIT_STORE.insert(RepositoryUrl::new(&git), credentials); - let _ = git.set_username(""); - let _ = git.set_password(None); + + // Redact the credentials. + redact_git_credentials(&mut git); }; Some(Source::Git { git,