mirror of https://github.com/astral-sh/uv
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
|
|
@ -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)]
|
#[clap(long, short, overrides_with = "allow_existing", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_VENV_CLEAR)]
|
||||||
pub clear: bool,
|
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.
|
/// 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
|
/// 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 {
|
} else {
|
||||||
"directory"
|
"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 {
|
match on_existing {
|
||||||
OnExisting::Allow => {
|
OnExisting::Allow => {
|
||||||
debug!("Allowing existing {name} due to `--allow-existing`");
|
debug!("Allowing existing {name} due to `--allow-existing`");
|
||||||
|
|
@ -131,15 +147,11 @@ pub(crate) fn create(
|
||||||
remove_virtualenv(&location)?;
|
remove_virtualenv(&location)?;
|
||||||
fs::create_dir_all(&location)?;
|
fs::create_dir_all(&location)?;
|
||||||
}
|
}
|
||||||
OnExisting::Fail => {
|
OnExisting::Fail => return err,
|
||||||
let confirmation = if is_virtualenv {
|
// If not a virtual environment, fail without prompting.
|
||||||
confirm_clear(location, name)?
|
OnExisting::Prompt if !is_virtualenv => return err,
|
||||||
} else {
|
OnExisting::Prompt => {
|
||||||
// Refuse to remove a non-virtual environment; don't even prompt.
|
match confirm_clear(location, name)? {
|
||||||
Some(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
match confirmation {
|
|
||||||
Some(true) => {
|
Some(true) => {
|
||||||
debug!("Removing existing {name} due to confirmation");
|
debug!("Removing existing {name} due to confirmation");
|
||||||
// Before removing the virtual environment, we need to canonicalize the
|
// Before removing the virtual environment, we need to canonicalize the
|
||||||
|
|
@ -151,22 +163,7 @@ pub(crate) fn create(
|
||||||
remove_virtualenv(&location)?;
|
remove_virtualenv(&location)?;
|
||||||
fs::create_dir_all(&location)?;
|
fs::create_dir_all(&location)?;
|
||||||
}
|
}
|
||||||
Some(false) => {
|
Some(false) => return err,
|
||||||
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(),
|
|
||||||
),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
// When we don't have a TTY, warn that the behavior will change in the future
|
// When we don't have a TTY, warn that the behavior will change in the future
|
||||||
None => {
|
None => {
|
||||||
warn_user_once!(
|
warn_user_once!(
|
||||||
|
|
@ -637,10 +634,14 @@ pub fn remove_virtualenv(location: &Path) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
||||||
pub enum OnExisting {
|
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]
|
#[default]
|
||||||
|
Prompt,
|
||||||
|
/// Fail if the directory already exists and is non-empty.
|
||||||
Fail,
|
Fail,
|
||||||
/// Allow an existing directory, overwriting virtual environment files while retaining other
|
/// Allow an existing directory, overwriting virtual environment files while retaining other
|
||||||
/// files in the directory.
|
/// files in the directory.
|
||||||
|
|
@ -650,13 +651,15 @@ pub enum OnExisting {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
if allow_existing {
|
||||||
Self::Allow
|
Self::Allow
|
||||||
} else if clear {
|
} else if clear {
|
||||||
Self::Remove
|
Self::Remove
|
||||||
|
} else if no_clear {
|
||||||
|
Self::Fail
|
||||||
} else {
|
} else {
|
||||||
Self::default()
|
Self::Prompt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1107,7 +1107,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||||
let python_request: Option<PythonRequest> =
|
let python_request: Option<PythonRequest> =
|
||||||
args.settings.python.as_deref().map(PythonRequest::parse);
|
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(
|
commands::venv(
|
||||||
&project_dir,
|
&project_dir,
|
||||||
|
|
|
||||||
|
|
@ -2719,6 +2719,7 @@ pub(crate) struct VenvSettings {
|
||||||
pub(crate) seed: bool,
|
pub(crate) seed: bool,
|
||||||
pub(crate) allow_existing: bool,
|
pub(crate) allow_existing: bool,
|
||||||
pub(crate) clear: bool,
|
pub(crate) clear: bool,
|
||||||
|
pub(crate) no_clear: bool,
|
||||||
pub(crate) path: Option<PathBuf>,
|
pub(crate) path: Option<PathBuf>,
|
||||||
pub(crate) prompt: Option<String>,
|
pub(crate) prompt: Option<String>,
|
||||||
pub(crate) system_site_packages: bool,
|
pub(crate) system_site_packages: bool,
|
||||||
|
|
@ -2738,6 +2739,7 @@ impl VenvSettings {
|
||||||
seed,
|
seed,
|
||||||
allow_existing,
|
allow_existing,
|
||||||
clear,
|
clear,
|
||||||
|
no_clear,
|
||||||
path,
|
path,
|
||||||
prompt,
|
prompt,
|
||||||
system_site_packages,
|
system_site_packages,
|
||||||
|
|
@ -2757,6 +2759,7 @@ impl VenvSettings {
|
||||||
seed,
|
seed,
|
||||||
allow_existing,
|
allow_existing,
|
||||||
clear,
|
clear,
|
||||||
|
no_clear,
|
||||||
path,
|
path,
|
||||||
prompt,
|
prompt,
|
||||||
system_site_packages,
|
system_site_packages,
|
||||||
|
|
|
||||||
|
|
@ -978,8 +978,7 @@ fn non_empty_dir_exists() -> Result<()> {
|
||||||
Caused by: A directory already exists at: .venv
|
Caused by: A directory already exists at: .venv
|
||||||
|
|
||||||
hint: Use the `--clear` flag or set `UV_VENV_CLEAR=1` to replace the existing directory
|
hint: Use the `--clear` flag or set `UV_VENV_CLEAR=1` to replace the existing directory
|
||||||
"
|
");
|
||||||
);
|
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.venv()
|
uv_snapshot!(context.filters(), context.venv()
|
||||||
.arg(context.venv.as_os_str())
|
.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…
Reference in New Issue