Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/uv-virtualenv/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::io;
use std::path::Path;
use std::path::{Path, PathBuf};

use thiserror::Error;

use uv_fs::Simplified;
use uv_preview::Preview;
use uv_python::{Interpreter, PythonEnvironment};

Expand All @@ -20,6 +21,8 @@ pub enum Error {
NotFound(String),
#[error(transparent)]
Python(#[from] uv_python::managed::Error),
#[error("The directory at `{}` is not a virtual environment; remove it manually to proceed", _0.user_display())]
NonVirtualEnvironment(PathBuf),
}

/// The value to use for the shell prompt when inside a virtual environment.
Expand Down
13 changes: 12 additions & 1 deletion crates/uv-virtualenv/src/virtualenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ pub(crate) fn create(
);
}
Ok(metadata) if metadata.is_dir() => {
let name = if uv_fs::is_virtualenv_base(location) {
let is_virtualenv = uv_fs::is_virtualenv_base(location);
let name = if is_virtualenv {
"virtual environment"
} else {
"directory"
Expand All @@ -120,6 +121,11 @@ pub(crate) fn create(
debug!("Allowing existing {name} due to `--allow-existing`");
}
OnExisting::Remove => {
// Refuse to remove a non-virtual environment, even if `--clear` is provided.
if !is_virtualenv {
return Err(Error::NonVirtualEnvironment(location.to_path_buf()));
}

debug!("Removing existing {name} due to `--clear`");
// Before removing the virtual environment, we need to canonicalize the path
// because `Path::metadata` will follow the symlink but we're still operating on
Expand All @@ -131,6 +137,11 @@ pub(crate) fn create(
fs::create_dir_all(&location)?;
}
OnExisting::Fail => {
// Refuse to remove a non-virtual environment.
if !is_virtualenv {
return Err(Error::NonVirtualEnvironment(location.to_path_buf()));
}

match confirm_clear(location, name)? {
Some(true) => {
debug!("Removing existing {name} due to confirmation");
Expand Down
56 changes: 45 additions & 11 deletions crates/uv/tests/it/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -958,23 +958,41 @@ fn empty_dir_exists() -> Result<()> {
fn non_empty_dir_exists() -> Result<()> {
let context = TestContext::new_with_versions(&["3.12"]);

// Create a non-empty directory at `.venv`. Creating a virtualenv at the same path should fail.
// Create a non-empty directory at `.venv`. Creating a virtualenv at the same path should fail,
// even if `--clear` is specified.
context.venv.create_dir_all()?;
context.venv.child("file").touch()?;

uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str())
.arg("--python")
.arg("3.12"), @r"
success: true
exit_code: 0
success: false
exit_code: 2
----- stdout -----

----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
warning: A directory already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
error: Failed to create virtual environment
Caused by: The directory at `.venv` is not a virtual environment; remove it manually to proceed
"
);

uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str())
.arg("--clear")
.arg("--python")
.arg("3.12"), @r"
success: false
exit_code: 2
----- stdout -----

----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
error: Failed to create virtual environment
Caused by: The directory at `.venv` is not a virtual environment; remove it manually to proceed
"
);

Expand All @@ -994,15 +1012,15 @@ fn non_empty_dir_exists_allow_existing() -> Result<()> {
.arg(context.venv.as_os_str())
.arg("--python")
.arg("3.12"), @r"
success: true
exit_code: 0
success: false
exit_code: 2
----- stdout -----

----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
warning: A directory already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
error: Failed to create virtual environment
Caused by: The directory at `.venv` is not a virtual environment; remove it manually to proceed
"
);

Expand Down Expand Up @@ -1578,17 +1596,33 @@ fn create_venv_current_working_directory() {
let context = TestContext::new_with_versions(&["3.12"]);

uv_snapshot!(context.filters(), context.venv()
.arg(".")
.arg(context.venv.as_os_str())
.arg("--python")
.arg("3.12"), @r"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
Activate with: source .venv/[BIN]/activate
"
);

uv_snapshot!(context.filters(), context.venv()
.arg(".")
.arg("--clear")
.arg("--python")
.arg("3.12")
.current_dir(&context.venv), @r"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .
warning: A directory already exists at `.`. In the future, uv will require `--clear` to replace it
Activate with: source bin/activate
"
);
Expand Down
Loading