Refuse to remove non-virtual environments in `uv venv` (#15538)

## Summary

Closes https://github.com/astral-sh/uv/issues/15474.
This commit is contained in:
Charlie Marsh 2025-08-26 13:26:20 -04:00 committed by GitHub
parent 0c674619b2
commit 9eb5fc240c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 10 deletions

View File

@ -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"
@ -131,7 +132,14 @@ pub(crate) fn create(
fs::create_dir_all(&location)?;
}
OnExisting::Fail => {
match confirm_clear(location, name)? {
let confirmation = if is_virtualenv {
confirm_clear(location, name)?
} else {
// Refuse to remove a non-virtual environment; don't even prompt.
Some(false)
};
match confirmation {
Some(true) => {
debug!("Removing existing {name} due to confirmation");
// Before removing the virtual environment, we need to canonicalize the

View File

@ -958,7 +958,8 @@ 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,
// unless `--clear` is specified.
context.venv.create_dir_all()?;
context.venv.child("file").touch()?;
@ -966,6 +967,25 @@ fn non_empty_dir_exists() -> Result<()> {
.arg(context.venv.as_os_str())
.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: A directory already exists at: .venv
hint: Use the `--clear` flag or set `UV_VENV_CLEAR=1` to replace the existing directory
"
);
uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str())
.arg("--clear")
.arg("--python")
.arg("3.12"), @r"
success: true
exit_code: 0
----- stdout -----
@ -973,7 +993,6 @@ fn non_empty_dir_exists() -> Result<()> {
----- 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
"
);
@ -994,15 +1013,17 @@ 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: A directory already exists at: .venv
hint: Use the `--clear` flag or set `UV_VENV_CLEAR=1` to replace the existing directory
"
);
@ -1578,17 +1599,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
"
);