Repository: astral-sh/uv
File: crates/uv-virtualenv/src/virtualenv.rs (Rust)

Code before:
            };
            match on_existing {
                OnExisting::Allow => {
                    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
                    // the unresolved path and will remove the symlink itself.
                    let location = location
                        .canonicalize()
                        .unwrap_or_else(|_| location.to_path_buf());
                    remove_virtualenv(&location)?;
                    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");
                            // Before removing the virtual environment, we need to canonicalize the
                            // path because `Path::metadata` will follow the symlink but we're still
                            // operating on the unresolved path and will remove the symlink itself.
                            let location = location
                                .canonicalize()
                                .unwrap_or_else(|_| location.to_path_buf());
                            remove_virtualenv(&location)?;
                            fs::create_dir_all(&location)?;
                        }
                        Some(false) => {
                            let hint = format!(
                                "Use the `{}` flag or set `{}` to replace the existing {name}",
                                "--clear".green(),
                                "UV_VENV_CLEAR=1".green()
                            );
                            return Err(Error::Io(io::Error::new(
                                io::ErrorKind::AlreadyExists,
                                format!(
                                    "A {name} already exists at: {}\n\n{}{} {hint}",
                                    location.user_display(),
                                    "hint".bold().cyan(),

Review comment (bug):
I'm okay with this being breaking since it was a 0.8 regression and I consider it a significant bug.

Code after:
            };
            match on_existing {
                OnExisting::Allow => {
                    debug!("Allowing existing {name} due to `--allow-existing`");
                }
                OnExisting::Remove => {
                    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
                    // the unresolved path and will remove the symlink itself.
                    let location = location
                        .canonicalize()
                        .unwrap_or_else(|_| location.to_path_buf());
                    remove_virtualenv(&location)?;
                    fs::create_dir_all(&location)?;
                }
                OnExisting::Fail => {
                    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
                            // path because `Path::metadata` will follow the symlink but we're still
                            // operating on the unresolved path and will remove the symlink itself.
                            let location = location
                                .canonicalize()
                                .unwrap_or_else(|_| location.to_path_buf());
                            remove_virtualenv(&location)?;
                            fs::create_dir_all(&location)?;
                        }
                        Some(false) => {
                            let hint = format!(
                                "Use the `{}` flag or set `{}` to replace the existing {name}",
                                "--clear".green(),
                                "UV_VENV_CLEAR=1".green()
                            );
                            return Err(Error::Io(io::Error::new(
                                io::ErrorKind::AlreadyExists,
                                format!(
                                    "A {name} already exists at: {}\n\n{}{} {hint}",
                                    location.user_display(),
                                    "hint".bold().cyan(),
                                    ":".bold(),
                                ),
                            )));