Skip to content

Commit c94f634

Browse files
committed
Refuse to remove non-virtual environments in uv venv
1 parent 0c67461 commit c94f634

File tree

3 files changed

+61
-13
lines changed

3 files changed

+61
-13
lines changed

crates/uv-virtualenv/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::io;
2-
use std::path::Path;
2+
use std::path::{Path, PathBuf};
33

44
use thiserror::Error;
55

6+
use uv_fs::Simplified;
67
use uv_preview::Preview;
78
use uv_python::{Interpreter, PythonEnvironment};
89

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

2528
/// The value to use for the shell prompt when inside a virtual environment.

crates/uv-virtualenv/src/virtualenv.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ pub(crate) fn create(
110110
);
111111
}
112112
Ok(metadata) if metadata.is_dir() => {
113-
let name = if uv_fs::is_virtualenv_base(location) {
113+
let is_virtualenv = uv_fs::is_virtualenv_base(location);
114+
let name = if is_virtualenv {
114115
"virtual environment"
115116
} else {
116117
"directory"
@@ -120,6 +121,11 @@ pub(crate) fn create(
120121
debug!("Allowing existing {name} due to `--allow-existing`");
121122
}
122123
OnExisting::Remove => {
124+
// Refuse to remove a non-virtual environment, even if `--clear` is provided.
125+
if !is_virtualenv {
126+
return Err(Error::NonVirtualEnvironment(location.to_path_buf()));
127+
}
128+
123129
debug!("Removing existing {name} due to `--clear`");
124130
// Before removing the virtual environment, we need to canonicalize the path
125131
// because `Path::metadata` will follow the symlink but we're still operating on
@@ -131,6 +137,11 @@ pub(crate) fn create(
131137
fs::create_dir_all(&location)?;
132138
}
133139
OnExisting::Fail => {
140+
// Refuse to remove a non-virtual environment.
141+
if !is_virtualenv {
142+
return Err(Error::NonVirtualEnvironment(location.to_path_buf()));
143+
}
144+
134145
match confirm_clear(location, name)? {
135146
Some(true) => {
136147
debug!("Removing existing {name} due to confirmation");

crates/uv/tests/it/venv.rs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -958,23 +958,41 @@ fn empty_dir_exists() -> Result<()> {
958958
fn non_empty_dir_exists() -> Result<()> {
959959
let context = TestContext::new_with_versions(&["3.12"]);
960960

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

965966
uv_snapshot!(context.filters(), context.venv()
966967
.arg(context.venv.as_os_str())
967968
.arg("--python")
968969
.arg("3.12"), @r"
969-
success: true
970-
exit_code: 0
970+
success: false
971+
exit_code: 2
971972
----- stdout -----
972973
973974
----- stderr -----
974975
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
975976
Creating virtual environment at: .venv
976-
warning: A directory already exists at `.venv`. In the future, uv will require `--clear` to replace it
977-
Activate with: source .venv/[BIN]/activate
977+
error: Failed to create virtual environment
978+
Caused by: The directory at `.venv` is not a virtual environment; remove it manually to proceed
979+
"
980+
);
981+
982+
uv_snapshot!(context.filters(), context.venv()
983+
.arg(context.venv.as_os_str())
984+
.arg("--clear")
985+
.arg("--python")
986+
.arg("3.12"), @r"
987+
success: false
988+
exit_code: 2
989+
----- stdout -----
990+
991+
----- stderr -----
992+
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
993+
Creating virtual environment at: .venv
994+
error: Failed to create virtual environment
995+
Caused by: The directory at `.venv` is not a virtual environment; remove it manually to proceed
978996
"
979997
);
980998

@@ -994,15 +1012,15 @@ fn non_empty_dir_exists_allow_existing() -> Result<()> {
9941012
.arg(context.venv.as_os_str())
9951013
.arg("--python")
9961014
.arg("3.12"), @r"
997-
success: true
998-
exit_code: 0
1015+
success: false
1016+
exit_code: 2
9991017
----- stdout -----
10001018
10011019
----- stderr -----
10021020
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
10031021
Creating virtual environment at: .venv
1004-
warning: A directory already exists at `.venv`. In the future, uv will require `--clear` to replace it
1005-
Activate with: source .venv/[BIN]/activate
1022+
error: Failed to create virtual environment
1023+
Caused by: The directory at `.venv` is not a virtual environment; remove it manually to proceed
10061024
"
10071025
);
10081026

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

15801598
uv_snapshot!(context.filters(), context.venv()
1581-
.arg(".")
1599+
.arg(context.venv.as_os_str())
15821600
.arg("--python")
15831601
.arg("3.12"), @r"
15841602
success: true
15851603
exit_code: 0
15861604
----- stdout -----
15871605
1606+
----- stderr -----
1607+
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
1608+
Creating virtual environment at: .venv
1609+
Activate with: source .venv/[BIN]/activate
1610+
"
1611+
);
1612+
1613+
uv_snapshot!(context.filters(), context.venv()
1614+
.arg(".")
1615+
.arg("--clear")
1616+
.arg("--python")
1617+
.arg("3.12")
1618+
.current_dir(&context.venv), @r"
1619+
success: true
1620+
exit_code: 0
1621+
----- stdout -----
1622+
15881623
----- stderr -----
15891624
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
15901625
Creating virtual environment at: .
1591-
warning: A directory already exists at `.`. In the future, uv will require `--clear` to replace it
15921626
Activate with: source bin/activate
15931627
"
15941628
);

0 commit comments

Comments
 (0)