diff --git a/crates/pypi-types/src/parsed_url.rs b/crates/pypi-types/src/parsed_url.rs index 16396b912..f562886b7 100644 --- a/crates/pypi-types/src/parsed_url.rs +++ b/crates/pypi-types/src/parsed_url.rs @@ -347,6 +347,11 @@ impl TryFrom for ParsedUrl { message: "Unknown scheme", }), } + } else if Path::new(url.path()) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("git")) + { + Ok(Self::Git(ParsedGitUrl::try_from(url)?)) } else if url.scheme().eq_ignore_ascii_case("file") { let path = url .to_file_path() diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index b0e1c97b7..6f5ffafa0 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -106,7 +106,7 @@ pub(crate) async fn add( // Read the requirements. let RequirementsSpecification { requirements, .. } = - RequirementsSpecification::from_sources(&requirements, &[], &[], &client_builder).await?; + RequirementsSpecification::from_simple_sources(&requirements, &client_builder).await?; // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 8c2f005c0..0a08d2359 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -491,6 +491,64 @@ fn add_git_raw() -> Result<()> { Ok(()) } +/// Add a Git requirement without the `git+` prefix. +#[test] +fn add_git_implicit() -> 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 = ["anyio==3.7.0"] + "#})?; + + 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 4 packages 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 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==3.7.0 + + idna==3.6 + + project==0.1.0 (from file://[TEMP_DIR]/) + + sniffio==1.3.1 + "###); + + // Omit the `git+` prefix. + uv_snapshot!(context.filters(), context.add(&["uv-public-pypackage @ https://github.com/astral-test/uv-public-pypackage.git"]).arg("--preview"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 2 packages in [TIME] + Uninstalled 1 package in [TIME] + Installed 2 packages in [TIME] + - project==0.1.0 (from file://[TEMP_DIR]/) + + project==0.1.0 (from file://[TEMP_DIR]/) + + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage.git@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389#b270df1a2fb5d012294e9aaf05e7e0bab1e6a389) + "###); + + Ok(()) +} + /// `--raw-sources` should be considered conflicting with sources-specific arguments, like `--tag`. #[test] fn add_raw_error() -> Result<()> { diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 6f0160103..fd0fa7a55 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -1357,6 +1357,31 @@ fn install_git_public_https() { context.assert_installed("uv_public_pypackage", "0.1.0"); } +/// Install a package from a public GitHub repository, omitting the `git+` prefix +#[test] +#[cfg(feature = "git")] +fn install_implicit_git_public_https() { + let context = TestContext::new("3.8"); + + uv_snapshot!( + context + .pip_install() + .arg("uv-public-pypackage @ https://github.com/astral-test/uv-public-pypackage.git"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage.git@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389) + "###); + + context.assert_installed("uv_public_pypackage", "0.1.0"); +} + /// Install and update a package from a public GitHub repository #[test] #[cfg(feature = "git")]