mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-02 18:12:17 +00:00
Redact Git credentials from pyproject.toml
(#6074)
## Summary We retain them if you use `--raw-sources`, but otherwise they're removed. We still respect them in the subsequent `uv.lock` via an in-process store. Closes #6056.
This commit is contained in:
parent
92263108cc
commit
8fac63d4ce
10 changed files with 301 additions and 23 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4885,6 +4885,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
"uv-auth",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::io::Write;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub(crate) struct Credentials {
|
pub struct Credentials {
|
||||||
/// The name of the user for authentication.
|
/// The name of the user for authentication.
|
||||||
username: Username,
|
username: Username,
|
||||||
/// The password to use for authentication.
|
/// The password to use for authentication.
|
||||||
|
@ -114,7 +114,7 @@ impl Credentials {
|
||||||
/// Parse [`Credentials`] from a URL, if any.
|
/// Parse [`Credentials`] from a URL, if any.
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if both [`Url::username`] and [`Url::password`] are not populated.
|
/// Returns [`None`] if both [`Url::username`] and [`Url::password`] are not populated.
|
||||||
pub(crate) fn from_url(url: &Url) -> Option<Self> {
|
pub fn from_url(url: &Url) -> Option<Self> {
|
||||||
if url.username().is_empty() && url.password().is_none() {
|
if url.username().is_empty() && url.password().is_none() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -203,6 +203,20 @@ impl Credentials {
|
||||||
header
|
header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply the credentials to the given URL.
|
||||||
|
///
|
||||||
|
/// Any existing credentials will be overridden.
|
||||||
|
#[must_use]
|
||||||
|
pub fn apply(&self, mut url: Url) -> Url {
|
||||||
|
if let Some(username) = self.username() {
|
||||||
|
let _ = url.set_username(username);
|
||||||
|
}
|
||||||
|
if let Some(password) = self.password() {
|
||||||
|
let _ = url.set_password(Some(password));
|
||||||
|
}
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
/// Attach the credentials to the given request.
|
/// Attach the credentials to the given request.
|
||||||
///
|
///
|
||||||
/// Any existing credentials will be overridden.
|
/// Any existing credentials will be overridden.
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
|
use std::sync::{Arc, LazyLock};
|
||||||
|
|
||||||
|
use tracing::trace;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use cache::CredentialsCache;
|
||||||
|
pub use credentials::Credentials;
|
||||||
|
pub use keyring::KeyringProvider;
|
||||||
|
pub use middleware::AuthMiddleware;
|
||||||
|
use realm::Realm;
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
mod credentials;
|
mod credentials;
|
||||||
mod keyring;
|
mod keyring;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
mod realm;
|
mod realm;
|
||||||
|
|
||||||
use std::sync::{Arc, LazyLock};
|
|
||||||
|
|
||||||
use cache::CredentialsCache;
|
|
||||||
use credentials::Credentials;
|
|
||||||
|
|
||||||
pub use keyring::KeyringProvider;
|
|
||||||
pub use middleware::AuthMiddleware;
|
|
||||||
use realm::Realm;
|
|
||||||
use tracing::trace;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
// TODO(zanieb): Consider passing a cache explicitly throughout
|
// TODO(zanieb): Consider passing a cache explicitly throughout
|
||||||
|
|
||||||
/// Global authentication cache for a uv invocation
|
/// Global authentication cache for a uv invocation
|
||||||
|
|
|
@ -15,6 +15,7 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cache-key = { workspace = true }
|
cache-key = { workspace = true }
|
||||||
uv-fs = { workspace = true }
|
uv-fs = { workspace = true }
|
||||||
|
uv-auth = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
cargo-util = { workspace = true }
|
cargo-util = { workspace = true }
|
||||||
|
|
21
crates/uv-git/src/credentials.rs
Normal file
21
crates/uv-git/src/credentials.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use cache_key::RepositoryUrl;
|
||||||
|
use uv_auth::Credentials;
|
||||||
|
|
||||||
|
/// A store for Git credentials.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct GitStore(RwLock<HashMap<RepositoryUrl, Arc<Credentials>>>);
|
||||||
|
|
||||||
|
impl GitStore {
|
||||||
|
/// Insert [`Credentials`] for the given URL into the store.
|
||||||
|
pub fn insert(&self, url: RepositoryUrl, credentials: Credentials) -> Option<Arc<Credentials>> {
|
||||||
|
self.0.write().unwrap().insert(url, Arc::new(credentials))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the [`Credentials`] for the given URL, if they exist.
|
||||||
|
pub fn get(&self, url: &RepositoryUrl) -> Option<Arc<Credentials>> {
|
||||||
|
self.0.read().unwrap().get(url).cloned()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::credentials::GitStore;
|
||||||
pub use crate::git::GitReference;
|
pub use crate::git::GitReference;
|
||||||
pub use crate::resolver::{
|
pub use crate::resolver::{
|
||||||
GitResolver, GitResolverError, RepositoryReference, ResolvedRepositoryReference,
|
GitResolver, GitResolverError, RepositoryReference, ResolvedRepositoryReference,
|
||||||
|
@ -7,11 +10,17 @@ pub use crate::resolver::{
|
||||||
pub use crate::sha::{GitOid, GitSha, OidParseError};
|
pub use crate::sha::{GitOid, GitSha, OidParseError};
|
||||||
pub use crate::source::{Fetch, GitSource, Reporter};
|
pub use crate::source::{Fetch, GitSource, Reporter};
|
||||||
|
|
||||||
|
mod credentials;
|
||||||
mod git;
|
mod git;
|
||||||
mod resolver;
|
mod resolver;
|
||||||
mod sha;
|
mod sha;
|
||||||
mod source;
|
mod source;
|
||||||
|
|
||||||
|
/// Global authentication cache for a uv invocation.
|
||||||
|
///
|
||||||
|
/// This is used to share Git credentials within a single process.
|
||||||
|
pub static GIT_STORE: LazyLock<GitStore> = LazyLock::new(GitStore::default);
|
||||||
|
|
||||||
/// A URL reference to a Git repository.
|
/// A URL reference to a Git repository.
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Hash, Ord)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Hash, Ord)]
|
||||||
pub struct GitUrl {
|
pub struct GitUrl {
|
||||||
|
@ -44,6 +53,7 @@ impl GitUrl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the precise [`GitSha`] to use for this Git URL.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_precise(mut self, precise: GitSha) -> Self {
|
pub fn with_precise(mut self, precise: GitSha) -> Self {
|
||||||
self.precise = Some(precise);
|
self.precise = Some(precise);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
//! Git support is derived from Cargo's implementation.
|
//! Git support is derived from Cargo's implementation.
|
||||||
//! Cargo is dual-licensed under either Apache 2.0 or MIT, at the user's choice.
|
//! Cargo is dual-licensed under either Apache 2.0 or MIT, at the user's choice.
|
||||||
//! Source: <https://github.com/rust-lang/cargo/blob/23eb492cf920ce051abfc56bbaf838514dc8365c/src/cargo/sources/git/source.rs>
|
//! Source: <https://github.com/rust-lang/cargo/blob/23eb492cf920ce051abfc56bbaf838514dc8365c/src/cargo/sources/git/source.rs>
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -11,7 +13,7 @@ use url::Url;
|
||||||
use cache_key::{cache_digest, RepositoryUrl};
|
use cache_key::{cache_digest, RepositoryUrl};
|
||||||
|
|
||||||
use crate::git::GitRemote;
|
use crate::git::GitRemote;
|
||||||
use crate::{GitOid, GitSha, GitUrl};
|
use crate::{GitOid, GitSha, GitUrl, GIT_STORE};
|
||||||
|
|
||||||
/// A remote Git source that can be checked out locally.
|
/// A remote Git source that can be checked out locally.
|
||||||
pub struct GitSource {
|
pub struct GitSource {
|
||||||
|
@ -52,11 +54,21 @@ impl GitSource {
|
||||||
/// Fetch the underlying Git repository at the given revision.
|
/// Fetch the underlying Git repository at the given revision.
|
||||||
#[instrument(skip(self), fields(repository = %self.git.repository, rev = ?self.git.precise))]
|
#[instrument(skip(self), fields(repository = %self.git.repository, rev = ?self.git.precise))]
|
||||||
pub fn fetch(self) -> Result<Fetch> {
|
pub fn fetch(self) -> Result<Fetch> {
|
||||||
|
// Compute the canonical URL for the repository.
|
||||||
|
let canonical = RepositoryUrl::new(&self.git.repository);
|
||||||
|
|
||||||
// The path to the repo, within the Git database.
|
// The path to the repo, within the Git database.
|
||||||
let ident = cache_digest(&RepositoryUrl::new(&self.git.repository));
|
let ident = cache_digest(&canonical);
|
||||||
let db_path = self.cache.join("db").join(&ident);
|
let db_path = self.cache.join("db").join(&ident);
|
||||||
|
|
||||||
let remote = GitRemote::new(&self.git.repository);
|
// Authenticate the URL, if necessary.
|
||||||
|
let remote = if let Some(credentials) = GIT_STORE.get(&canonical) {
|
||||||
|
Cow::Owned(credentials.apply(self.git.repository.clone()))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(&self.git.repository)
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote = GitRemote::new(&remote);
|
||||||
let (db, actual_rev, task) = match (self.git.precise, remote.db_at(&db_path).ok()) {
|
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
|
// If we have a locked revision, and we have a preexisting database
|
||||||
// which has that revision, then no update needs to happen.
|
// which has that revision, then no update needs to happen.
|
||||||
|
|
|
@ -2,12 +2,12 @@ use std::collections::hash_map::Entry;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use cache_key::RepositoryUrl;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
use pep508_rs::{ExtraName, Requirement, VersionOrUrl};
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
use uv_auth::{store_credentials_from_url, Credentials};
|
||||||
use pep508_rs::{ExtraName, Requirement, VersionOrUrl};
|
|
||||||
use uv_auth::store_credentials_from_url;
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
||||||
use uv_configuration::{
|
use uv_configuration::{
|
||||||
|
@ -16,6 +16,7 @@ use uv_configuration::{
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
use uv_distribution::DistributionDatabase;
|
use uv_distribution::DistributionDatabase;
|
||||||
use uv_fs::CWD;
|
use uv_fs::CWD;
|
||||||
|
use uv_git::GIT_STORE;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_python::{
|
use uv_python::{
|
||||||
request_from_version_file, EnvironmentPreference, Interpreter, PythonDownloads,
|
request_from_version_file, EnvironmentPreference, Interpreter, PythonDownloads,
|
||||||
|
@ -330,6 +331,37 @@ pub(crate) async fn add(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Redact any credentials. By default, we avoid writing sensitive credentials to files that
|
||||||
|
// will be checked into version control (e.g., `pyproject.toml` and `uv.lock`). Instead,
|
||||||
|
// we store the credentials in a global store, and reuse them during resolution. The
|
||||||
|
// expectation is that subsequent resolutions steps will succeed by reading from (e.g.) the
|
||||||
|
// user's credentials store, rather than by reading from the `pyproject.toml` file.
|
||||||
|
let source = match source {
|
||||||
|
Some(Source::Git {
|
||||||
|
mut git,
|
||||||
|
subdirectory,
|
||||||
|
rev,
|
||||||
|
tag,
|
||||||
|
branch,
|
||||||
|
}) => {
|
||||||
|
let credentials = Credentials::from_url(&git);
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
Some(Source::Git {
|
||||||
|
git,
|
||||||
|
subdirectory,
|
||||||
|
rev,
|
||||||
|
tag,
|
||||||
|
branch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => source,
|
||||||
|
};
|
||||||
|
|
||||||
// Update the `pyproject.toml`.
|
// Update the `pyproject.toml`.
|
||||||
let edit = match dependency_type {
|
let edit = match dependency_type {
|
||||||
DependencyType::Production => toml.add_dependency(&requirement, source.as_ref())?,
|
DependencyType::Production => toml.add_dependency(&requirement, source.as_ref())?,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use assert_fs::prelude::*;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::common::packse_index_url;
|
use crate::common::{decode_token, packse_index_url};
|
||||||
use common::{uv_snapshot, TestContext};
|
use common::{uv_snapshot, TestContext};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
@ -291,6 +291,196 @@ fn add_git() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a Git requirement from a private repository, with credentials. The resolution should
|
||||||
|
/// succeed, but the `pyproject.toml` should omit the credentials.
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "git")]
|
||||||
|
fn add_git_private_source() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.add(&[&format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")]).arg("--preview"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Prepared 2 packages in [TIME]
|
||||||
|
Installed 2 packages in [TIME]
|
||||||
|
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||||
|
+ uv-private-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071)
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
assert_snapshot!(
|
||||||
|
pyproject_toml, @r###"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"uv-private-pypackage",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
uv-private-pypackage = { git = "https://github.com/astral-test/uv-private-pypackage" }
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
assert_snapshot!(
|
||||||
|
lock, @r###"
|
||||||
|
version = 1
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[options]
|
||||||
|
exclude-newer = "2024-03-25 00:00:00 UTC"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "uv-private-pypackage" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uv-private-pypackage"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { git = "https://github.com/astral-test/uv-private-pypackage#d780faf0ac91257d4d5a4f0c5a0e4509608c0071" }
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Install from the lockfile.
|
||||||
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv sync` is experimental and may change without warning
|
||||||
|
Audited 2 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a Git requirement from a private repository, with credentials. Since `--raw-sources` is
|
||||||
|
/// specified, the `pyproject.toml` should retain the credentials.
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "git")]
|
||||||
|
fn add_git_private_raw() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.add(&[&format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")]).arg("--raw-sources").arg("--preview"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Prepared 2 packages in [TIME]
|
||||||
|
Installed 2 packages in [TIME]
|
||||||
|
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||||
|
+ uv-private-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071)
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
|
||||||
|
|
||||||
|
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||||
|
.into_iter()
|
||||||
|
.chain(context.filters())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => filters
|
||||||
|
}, {
|
||||||
|
assert_snapshot!(
|
||||||
|
pyproject_toml, @r###"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"uv-private-pypackage @ git+https://***@github.com/astral-test/uv-private-pypackage",
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
assert_snapshot!(
|
||||||
|
lock, @r###"
|
||||||
|
version = 1
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[options]
|
||||||
|
exclude-newer = "2024-03-25 00:00:00 UTC"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "uv-private-pypackage" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uv-private-pypackage"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { git = "https://github.com/astral-test/uv-private-pypackage#d780faf0ac91257d4d5a4f0c5a0e4509608c0071" }
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Install from the lockfile.
|
||||||
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv sync` is experimental and may change without warning
|
||||||
|
Audited 2 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "git")]
|
#[cfg(feature = "git")]
|
||||||
fn add_git_error() -> Result<()> {
|
fn add_git_error() -> Result<()> {
|
||||||
|
|
|
@ -5271,9 +5271,6 @@ fn lock_redact_git() -> Result<()> {
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = ["uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage"]
|
dependencies = ["uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage"]
|
||||||
|
|
||||||
[tool.uv]
|
|
||||||
index-url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/simple"
|
|
||||||
"#,
|
"#,
|
||||||
token = token,
|
token = token,
|
||||||
})?;
|
})?;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue