diff --git a/crates/uv-fs/src/lib.rs b/crates/uv-fs/src/lib.rs index ec7073573..f2492e2de 100644 --- a/crates/uv-fs/src/lib.rs +++ b/crates/uv-fs/src/lib.rs @@ -93,13 +93,12 @@ pub fn replace_symlink(src: impl AsRef, dst: impl AsRef) -> std::io: match std::os::unix::fs::symlink(src.as_ref(), dst.as_ref()) { Ok(()) => Ok(()), Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => { - // Create a symlink to the directory store, using a temporary file to ensure atomicity. - let temp_dir = - tempfile::tempdir_in(dst.as_ref().parent().expect("Cache entry to have parent"))?; + // Create a symlink, using a temporary file to ensure atomicity. + let temp_dir = tempfile::tempdir_in(dst.as_ref().parent().unwrap())?; let temp_file = temp_dir.path().join("link"); std::os::unix::fs::symlink(src, &temp_file)?; - // Move the symlink into the wheel cache. + // Move the symlink into the target location. fs_err::rename(&temp_file, dst.as_ref())?; Ok(()) diff --git a/crates/uv-virtualenv/src/bare.rs b/crates/uv-virtualenv/src/bare.rs index b832ae1af..0fa2fd523 100644 --- a/crates/uv-virtualenv/src/bare.rs +++ b/crates/uv-virtualenv/src/bare.rs @@ -49,7 +49,7 @@ pub fn create_bare_venv( interpreter: &Interpreter, prompt: Prompt, system_site_packages: bool, - force: bool, + allow_existing: bool, ) -> Result { // Determine the base Python executable; that is, the Python executable that should be // considered the "base" for the virtual environment. This is typically the Python executable @@ -91,8 +91,8 @@ pub fn create_bare_venv( format!("File exists at `{}`", location.user_display()), ))); } else if metadata.is_dir() { - if force { - info!("Overwriting existing directory"); + if allow_existing { + info!("Allowing existing directory"); } else if location.join("pyvenv.cfg").is_file() { info!("Removing existing directory"); fs::remove_dir_all(location)?; diff --git a/crates/uv-virtualenv/src/lib.rs b/crates/uv-virtualenv/src/lib.rs index 1ea705f5e..ff6981a23 100644 --- a/crates/uv-virtualenv/src/lib.rs +++ b/crates/uv-virtualenv/src/lib.rs @@ -51,10 +51,16 @@ pub fn create_venv( interpreter: Interpreter, prompt: Prompt, system_site_packages: bool, - force: bool, + allow_existing: bool, ) -> Result { // Create the virtualenv at the given location. - let virtualenv = create_bare_venv(location, &interpreter, prompt, system_site_packages, force)?; + let virtualenv = create_bare_venv( + location, + &interpreter, + prompt, + system_site_packages, + allow_existing, + )?; // Create the corresponding `PythonEnvironment`. let interpreter = interpreter.with_virtualenv(virtualenv); diff --git a/crates/uv/src/cli.rs b/crates/uv/src/cli.rs index 82f8eafb9..66d98885f 100644 --- a/crates/uv/src/cli.rs +++ b/crates/uv/src/cli.rs @@ -1706,14 +1706,17 @@ pub(crate) struct VenvArgs { #[arg(long)] pub(crate) seed: bool, - /// Overwrite the directory at the specified path when creating the virtual environment. + /// Preserve any existing files or directories at the target path. /// /// By default, `uv venv` will remove an existing virtual environment at the given path, and - /// exit with an error if the path is non-empty but _not_ a virtual environment. The `--force` - /// option will instead write to the given path, regardless of its contents, and without - /// clearing it beforehand. + /// exit with an error if the path is non-empty but _not_ a virtual environment. The + /// `--allow-existing` option will instead write to the given path, regardless of its contents, + /// and without clearing it beforehand. + /// + /// WARNING: This option can lead to unexpected behavior if the existing virtual environment + /// and the newly-created virtual environment are linked to different Python interpreters. #[clap(long)] - pub(crate) force: bool, + pub(crate) allow_existing: bool, /// The path to the virtual environment to create. #[arg(default_value = ".venv")] diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 4f192db17..4f4572786 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -44,7 +44,7 @@ pub(crate) async fn venv( system_site_packages: bool, connectivity: Connectivity, seed: bool, - force: bool, + allow_existing: bool, exclude_newer: Option, native_tls: bool, cache: &Cache, @@ -61,7 +61,7 @@ pub(crate) async fn venv( system_site_packages, connectivity, seed, - force, + allow_existing, exclude_newer, native_tls, cache, @@ -109,7 +109,7 @@ async fn venv_impl( system_site_packages: bool, connectivity: Connectivity, seed: bool, - force: bool, + allow_existing: bool, exclude_newer: Option, native_tls: bool, cache: &Cache, @@ -146,8 +146,14 @@ async fn venv_impl( .into_diagnostic()?; // Create the virtual environment. - let venv = uv_virtualenv::create_venv(path, interpreter, prompt, system_site_packages, force) - .map_err(VenvError::Creation)?; + let venv = uv_virtualenv::create_venv( + path, + interpreter, + prompt, + system_site_packages, + allow_existing, + ) + .map_err(VenvError::Creation)?; // Install seed packages. if seed { diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index ace68713c..a4313700f 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -484,7 +484,7 @@ async fn run() -> Result { args.system_site_packages, args.shared.connectivity, args.seed, - args.force, + args.allow_existing, args.shared.exclude_newer, globals.native_tls, &cache, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 1c8e4714a..1a8c003a6 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -747,7 +747,7 @@ impl PipCheckSettings { pub(crate) struct VenvSettings { // CLI-only settings. pub(crate) seed: bool, - pub(crate) force: bool, + pub(crate) allow_existing: bool, pub(crate) name: PathBuf, pub(crate) prompt: Option, pub(crate) system_site_packages: bool, @@ -764,7 +764,7 @@ impl VenvSettings { system, no_system, seed, - force, + allow_existing, name, prompt, system_site_packages, @@ -783,7 +783,7 @@ impl VenvSettings { Self { // CLI-only settings. seed, - force, + allow_existing, name, prompt, system_site_packages, diff --git a/crates/uv/tests/venv.rs b/crates/uv/tests/venv.rs index 5520db5e4..718c16fc5 100644 --- a/crates/uv/tests/venv.rs +++ b/crates/uv/tests/venv.rs @@ -381,11 +381,11 @@ fn non_empty_dir_exists() -> Result<()> { } #[test] -fn non_empty_dir_exists_force() -> Result<()> { +fn non_empty_dir_exists_allow_existing() -> Result<()> { let context = VenvTestContext::new(&["3.12"]); // Create a non-empty directory at `.venv`. Creating a virtualenv at the same path should - // succeed when `--force` is specified, but fail when it is not. + // succeed when `--allow-existing` is specified, but fail when it is not. context.venv.create_dir_all()?; context.venv.child("file").touch()?; @@ -409,7 +409,7 @@ fn non_empty_dir_exists_force() -> Result<()> { uv_snapshot!(context.filters(), context.venv_command() .arg(context.venv.as_os_str()) - .arg("--force") + .arg("--allow-existing") .arg("--python") .arg("3.12"), @r###" success: true @@ -427,7 +427,7 @@ fn non_empty_dir_exists_force() -> Result<()> { // directories. uv_snapshot!(context.filters(), context.venv_command() .arg(context.venv.as_os_str()) - .arg("--force") + .arg("--allow-existing") .arg("--python") .arg("3.12"), @r###" success: true