From 76566b09becb4d44e4843d70ca8dc912ee5c11b8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 23 Jul 2024 17:48:50 -0400 Subject: [PATCH] Reject Git CLI arguments with non-Git sources (#5377) ## Summary Closes https://github.com/astral-sh/uv/issues/5335. --- crates/uv-cli/src/lib.rs | 2 +- crates/uv-workspace/src/pyproject.rs | 22 ++++++++++- crates/uv/tests/edit.rs | 58 ++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 6595f7d556f9..82f7b81eebfa 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2012,7 +2012,7 @@ pub struct AddArgs { /// Add source requirements to the `project.dependencies` section of the `pyproject.toml`. /// - /// Without this flag uv will try to use `tool.uv.sources` for any sources. + /// Without this flag, uv will try to use `tool.uv.sources` for any sources. #[arg(long)] pub raw_sources: bool, diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 2fbe27b842be..501761fa049a 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -233,7 +233,7 @@ pub enum Source { #[derive(Error, Debug)] pub enum SourceError { - #[error("Cannot resolve git reference `{0}`")] + #[error("Failed to resolve Git reference: `{0}`")] UnresolvedReference(String), #[error("Workspace dependency `{0}` must refer to local directory, not a Git repository")] WorkspacePackageGit(String), @@ -241,6 +241,12 @@ pub enum SourceError { WorkspacePackageUrl(String), #[error("Workspace dependency `{0}` must refer to local directory, not a file")] WorkspacePackageFile(String), + #[error("`{0}` did not resolve to a Git repository, but a Git reference (`--rev {1}`) was provided.")] + UnusedRev(String, String), + #[error("`{0}` did not resolve to a Git repository, but a Git reference (`--tag {1}`) was provided.")] + UnusedTag(String, String), + #[error("`{0}` did not resolve to a Git repository, but a Git reference (`--branch {1}`) was provided.")] + UnusedBranch(String, String), } impl Source { @@ -253,6 +259,20 @@ impl Source { tag: Option, branch: Option, ) -> Result, SourceError> { + // If we resolved to a non-Git source, and the user specified a Git reference, error. + if !matches!(source, RequirementSource::Git { .. }) { + if let Some(rev) = rev { + return Err(SourceError::UnusedRev(name.to_string(), rev)); + } + if let Some(tag) = tag { + return Err(SourceError::UnusedTag(name.to_string(), tag)); + } + if let Some(branch) = branch { + return Err(SourceError::UnusedBranch(name.to_string(), branch)); + } + } + + // If the source is a workspace package, error if the user tried to specify a source. if workspace { return match source { RequirementSource::Registry { .. } | RequirementSource::Directory { .. } => { diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 249369c313a2..5f5b43b019d3 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -285,6 +285,64 @@ fn add_git() -> Result<()> { Ok(()) } +#[test] +fn add_git_error() -> Result<()> { + let context = TestContext::new("3.12"); + + 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.lock(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning + Resolved 1 package in [TIME] + "###); + + 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 + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + project==0.1.0 (from file://[TEMP_DIR]/) + "###); + + // Provide a tag without a Git source. + uv_snapshot!(context.filters(), context.add(&[]).arg("flask").arg("--tag").arg("0.0.1").arg("--preview"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `flask` did not resolve to a Git repository, but a Git reference (`--tag 0.0.1`) was provided. + "###); + + // Provide a tag with a non-Git source. + uv_snapshot!(context.filters(), context.add(&[]).arg("flask @ https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl").arg("--branch").arg("0.0.1").arg("--preview"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `flask` did not resolve to a Git repository, but a Git reference (`--branch 0.0.1`) was provided. + "###); + + Ok(()) +} + /// Add a Git requirement using the `--raw-sources` API. #[test] fn add_git_raw() -> Result<()> {