mirror of
				https://github.com/astral-sh/uv.git
				synced 2025-11-03 21:23:54 +00:00 
			
		
		
		
	Add --no-clear to uv venv to disable removal prompts (#15795)
				
					
				
			<!-- Thank you for contributing to uv! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> Closes #15485 --------- Co-authored-by: Aditya-PS-05 <adityapratapsjnhh7654@gmail.com> Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
		
							parent
							
								
									fb71b079d5
								
							
						
					
					
						commit
						2825ee3435
					
				
					 5 changed files with 173 additions and 32 deletions
				
			
		| 
						 | 
				
			
			@ -2754,6 +2754,18 @@ pub struct VenvArgs {
 | 
			
		|||
    #[clap(long, short, overrides_with = "allow_existing", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_VENV_CLEAR)]
 | 
			
		||||
    pub clear: bool,
 | 
			
		||||
 | 
			
		||||
    /// Fail without prompting if any existing files or directories are present at the target path.
 | 
			
		||||
    ///
 | 
			
		||||
    /// By default, when a TTY is available, `uv venv` will prompt to clear a non-empty directory.
 | 
			
		||||
    /// When `--no-clear` is used, the command will exit with an error instead of prompting.
 | 
			
		||||
    #[clap(
 | 
			
		||||
        long,
 | 
			
		||||
        overrides_with = "clear",
 | 
			
		||||
        conflicts_with = "allow_existing",
 | 
			
		||||
        hide = true
 | 
			
		||||
    )]
 | 
			
		||||
    pub no_clear: bool,
 | 
			
		||||
 | 
			
		||||
    /// Preserve any existing files or directories at the target path.
 | 
			
		||||
    ///
 | 
			
		||||
    /// By default, `uv venv` will exit with an error if the given path is non-empty. The
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -116,6 +116,22 @@ pub(crate) fn create(
 | 
			
		|||
            } else {
 | 
			
		||||
                "directory"
 | 
			
		||||
            };
 | 
			
		||||
            let hint = format!(
 | 
			
		||||
                "Use the `{}` flag or set `{}` to replace the existing {name}",
 | 
			
		||||
                "--clear".green(),
 | 
			
		||||
                "UV_VENV_CLEAR=1".green()
 | 
			
		||||
            );
 | 
			
		||||
            // TODO(zanieb): We may want to consider omitting the hint in some of these cases, e.g.,
 | 
			
		||||
            // when `--no-clear` is used do we want to suggest `--clear`?
 | 
			
		||||
            let err = 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(),
 | 
			
		||||
                ),
 | 
			
		||||
            )));
 | 
			
		||||
            match on_existing {
 | 
			
		||||
                OnExisting::Allow => {
 | 
			
		||||
                    debug!("Allowing existing {name} due to `--allow-existing`");
 | 
			
		||||
| 
						 | 
				
			
			@ -131,15 +147,11 @@ pub(crate) fn create(
 | 
			
		|||
                    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 {
 | 
			
		||||
                OnExisting::Fail => return err,
 | 
			
		||||
                // If not a virtual environment, fail without prompting.
 | 
			
		||||
                OnExisting::Prompt if !is_virtualenv => return err,
 | 
			
		||||
                OnExisting::Prompt => {
 | 
			
		||||
                    match confirm_clear(location, name)? {
 | 
			
		||||
                        Some(true) => {
 | 
			
		||||
                            debug!("Removing existing {name} due to confirmation");
 | 
			
		||||
                            // Before removing the virtual environment, we need to canonicalize the
 | 
			
		||||
| 
						 | 
				
			
			@ -151,22 +163,7 @@ pub(crate) fn create(
 | 
			
		|||
                            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(),
 | 
			
		||||
                                ),
 | 
			
		||||
                            )));
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(false) => return err,
 | 
			
		||||
                        // When we don't have a TTY, warn that the behavior will change in the future
 | 
			
		||||
                        None => {
 | 
			
		||||
                            warn_user_once!(
 | 
			
		||||
| 
						 | 
				
			
			@ -637,10 +634,14 @@ pub fn remove_virtualenv(location: &Path) -> Result<(), Error> {
 | 
			
		|||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
 | 
			
		||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
 | 
			
		||||
pub enum OnExisting {
 | 
			
		||||
    /// Fail if the directory already exists and is non-empty.
 | 
			
		||||
    /// Prompt before removing an existing directory.
 | 
			
		||||
    ///
 | 
			
		||||
    /// If a TTY is not available, fail.
 | 
			
		||||
    #[default]
 | 
			
		||||
    Prompt,
 | 
			
		||||
    /// Fail if the directory already exists and is non-empty.
 | 
			
		||||
    Fail,
 | 
			
		||||
    /// Allow an existing directory, overwriting virtual environment files while retaining other
 | 
			
		||||
    /// files in the directory.
 | 
			
		||||
| 
						 | 
				
			
			@ -650,13 +651,15 @@ pub enum OnExisting {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
impl OnExisting {
 | 
			
		||||
    pub fn from_args(allow_existing: bool, clear: bool) -> Self {
 | 
			
		||||
    pub fn from_args(allow_existing: bool, clear: bool, no_clear: bool) -> Self {
 | 
			
		||||
        if allow_existing {
 | 
			
		||||
            Self::Allow
 | 
			
		||||
        } else if clear {
 | 
			
		||||
            Self::Remove
 | 
			
		||||
        } else if no_clear {
 | 
			
		||||
            Self::Fail
 | 
			
		||||
        } else {
 | 
			
		||||
            Self::default()
 | 
			
		||||
            Self::Prompt
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1107,7 +1107,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
 | 
			
		|||
            let python_request: Option<PythonRequest> =
 | 
			
		||||
                args.settings.python.as_deref().map(PythonRequest::parse);
 | 
			
		||||
 | 
			
		||||
            let on_existing = uv_virtualenv::OnExisting::from_args(args.allow_existing, args.clear);
 | 
			
		||||
            let on_existing = uv_virtualenv::OnExisting::from_args(
 | 
			
		||||
                args.allow_existing,
 | 
			
		||||
                args.clear,
 | 
			
		||||
                args.no_clear,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            commands::venv(
 | 
			
		||||
                &project_dir,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2719,6 +2719,7 @@ pub(crate) struct VenvSettings {
 | 
			
		|||
    pub(crate) seed: bool,
 | 
			
		||||
    pub(crate) allow_existing: bool,
 | 
			
		||||
    pub(crate) clear: bool,
 | 
			
		||||
    pub(crate) no_clear: bool,
 | 
			
		||||
    pub(crate) path: Option<PathBuf>,
 | 
			
		||||
    pub(crate) prompt: Option<String>,
 | 
			
		||||
    pub(crate) system_site_packages: bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -2738,6 +2739,7 @@ impl VenvSettings {
 | 
			
		|||
            seed,
 | 
			
		||||
            allow_existing,
 | 
			
		||||
            clear,
 | 
			
		||||
            no_clear,
 | 
			
		||||
            path,
 | 
			
		||||
            prompt,
 | 
			
		||||
            system_site_packages,
 | 
			
		||||
| 
						 | 
				
			
			@ -2757,6 +2759,7 @@ impl VenvSettings {
 | 
			
		|||
            seed,
 | 
			
		||||
            allow_existing,
 | 
			
		||||
            clear,
 | 
			
		||||
            no_clear,
 | 
			
		||||
            path,
 | 
			
		||||
            prompt,
 | 
			
		||||
            system_site_packages,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -978,8 +978,7 @@ fn non_empty_dir_exists() -> Result<()> {
 | 
			
		|||
      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())
 | 
			
		||||
| 
						 | 
				
			
			@ -1675,3 +1674,123 @@ fn create_venv_current_working_directory() {
 | 
			
		|||
    "
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn no_clear_with_existing_directory() {
 | 
			
		||||
    let context = TestContext::new_with_versions(&["3.12"]);
 | 
			
		||||
 | 
			
		||||
    // Create a virtual environment first
 | 
			
		||||
    uv_snapshot!(context.filters(), context.venv()
 | 
			
		||||
        .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
 | 
			
		||||
    "###
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Try to create again with --no-clear (should fail)
 | 
			
		||||
    uv_snapshot!(context.filters(), context.venv()
 | 
			
		||||
        .arg(context.venv.as_os_str())
 | 
			
		||||
        .arg("--no-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: A virtual environment already exists at: .venv
 | 
			
		||||
 | 
			
		||||
    hint: Use the `--clear` flag or set `UV_VENV_CLEAR=1` to replace the existing virtual environment
 | 
			
		||||
    "
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn no_clear_with_non_existent_directory() {
 | 
			
		||||
    let context = TestContext::new_with_versions(&["3.12"]);
 | 
			
		||||
 | 
			
		||||
    // Create with --no-clear on non-existent directory (should succeed)
 | 
			
		||||
    uv_snapshot!(context.filters(), context.venv()
 | 
			
		||||
        .arg(context.venv.as_os_str())
 | 
			
		||||
        .arg("--no-clear")
 | 
			
		||||
        .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
 | 
			
		||||
    "###
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    context.venv.assert(predicates::path::is_dir());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn no_clear_overrides_clear() {
 | 
			
		||||
    let context = TestContext::new_with_versions(&["3.12"]);
 | 
			
		||||
 | 
			
		||||
    // Create a non-empty directory at `.venv`
 | 
			
		||||
    context.venv.create_dir_all().unwrap();
 | 
			
		||||
    context.venv.child("file").touch().unwrap();
 | 
			
		||||
 | 
			
		||||
    // --no-clear should override --clear and fail without prompting
 | 
			
		||||
    uv_snapshot!(context.filters(), context.venv()
 | 
			
		||||
        .arg(context.venv.as_os_str())
 | 
			
		||||
        .arg("--clear")
 | 
			
		||||
        .arg("--no-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: A directory already exists at: .venv
 | 
			
		||||
 | 
			
		||||
    hint: Use the `--clear` flag or set `UV_VENV_CLEAR=1` to replace the existing directory
 | 
			
		||||
    "
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn no_clear_conflicts_with_allow_existing() {
 | 
			
		||||
    let context = TestContext::new_with_versions(&["3.12"]);
 | 
			
		||||
 | 
			
		||||
    // Try to use --no-clear with --allow-existing (should fail)
 | 
			
		||||
    uv_snapshot!(context.filters(), context.venv()
 | 
			
		||||
        .arg(context.venv.as_os_str())
 | 
			
		||||
        .arg("--no-clear")
 | 
			
		||||
        .arg("--allow-existing")
 | 
			
		||||
        .arg("--python")
 | 
			
		||||
        .arg("3.12"), @r"
 | 
			
		||||
    success: false
 | 
			
		||||
    exit_code: 2
 | 
			
		||||
    ----- stdout -----
 | 
			
		||||
 | 
			
		||||
    ----- stderr -----
 | 
			
		||||
    error: the argument '--no-clear' cannot be used with '--allow-existing'
 | 
			
		||||
 | 
			
		||||
    Usage: uv venv --cache-dir [CACHE_DIR] --python <PYTHON> --exclude-newer <EXCLUDE_NEWER> <PATH>
 | 
			
		||||
 | 
			
		||||
    For more information, try '--help'.
 | 
			
		||||
    "
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue