mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Don't panic on Ctrl-C in confirm prompt (#11706)
<!-- 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? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> Resolves #11704 Propagate errors from `uv_console::confirm` up instead of `unwrap`ping them, causing panics. ## Test Plan <!-- How was it tested? --> Regression testing the bug is very difficult, as the behavior of `confirm` changes based on whether `uv` is talking to a `tty`. We can trick it using ptys, but the best rust pty crate I could find only provides blocking reads of the spawned child, which is insufficient to write the regression test. --------- Co-authored-by: konstin <konstin@mailbox.org>
This commit is contained in:
parent
b180fe99b4
commit
6e7ec3274a
3 changed files with 84 additions and 78 deletions
|
@ -10,6 +10,7 @@ pub fn confirm(message: &str, term: &Term, default: bool) -> std::io::Result<boo
|
|||
let result = ctrlc::set_handler(move || {
|
||||
let term = Term::stderr();
|
||||
term.show_cursor().ok();
|
||||
term.write_str("\n").ok();
|
||||
term.flush().ok();
|
||||
|
||||
#[allow(clippy::exit, clippy::cast_possible_wrap)]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use console::Term;
|
||||
|
||||
use uv_fs::Simplified;
|
||||
|
@ -85,7 +86,7 @@ impl RequirementsSource {
|
|||
///
|
||||
/// If the user provided a value that appears to be a `requirements.txt` file or a local
|
||||
/// directory, prompt them to correct it (if the terminal is interactive).
|
||||
pub fn from_package(name: String) -> Self {
|
||||
pub fn from_package(name: String) -> Result<Self> {
|
||||
// If the user provided a `requirements.txt` file without `-r` (as in
|
||||
// `uv pip install requirements.txt`), prompt them to correct it.
|
||||
#[allow(clippy::case_sensitive_file_extension_comparisons)]
|
||||
|
@ -95,9 +96,9 @@ impl RequirementsSource {
|
|||
let prompt = format!(
|
||||
"`{name}` looks like a local requirements file but was passed as a package name. Did you mean `-r {name}`?"
|
||||
);
|
||||
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
|
||||
let confirmation = uv_console::confirm(&prompt, &term, true)?;
|
||||
if confirmation {
|
||||
return Self::from_requirements_file(name.into());
|
||||
return Ok(Self::from_requirements_file(name.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,14 +113,14 @@ impl RequirementsSource {
|
|||
let prompt = format!(
|
||||
"`{name}` looks like a local metadata file but was passed as a package name. Did you mean `-r {name}`?"
|
||||
);
|
||||
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
|
||||
let confirmation = uv_console::confirm(&prompt, &term, true)?;
|
||||
if confirmation {
|
||||
return Self::from_requirements_file(name.into());
|
||||
return Ok(Self::from_requirements_file(name.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::Package(name)
|
||||
Ok(Self::Package(name))
|
||||
}
|
||||
|
||||
/// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a `--with`
|
||||
|
@ -127,7 +128,7 @@ impl RequirementsSource {
|
|||
///
|
||||
/// If the user provided a value that appears to be a `requirements.txt` file or a local
|
||||
/// directory, prompt them to correct it (if the terminal is interactive).
|
||||
pub fn from_with_package(name: String) -> Self {
|
||||
pub fn from_with_package(name: String) -> Result<Self> {
|
||||
// If the user provided a `requirements.txt` file without `--with-requirements` (as in
|
||||
// `uvx --with requirements.txt ruff`), prompt them to correct it.
|
||||
#[allow(clippy::case_sensitive_file_extension_comparisons)]
|
||||
|
@ -137,9 +138,9 @@ impl RequirementsSource {
|
|||
let prompt = format!(
|
||||
"`{name}` looks like a local requirements file but was passed as a package name. Did you mean `--with-requirements {name}`?"
|
||||
);
|
||||
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
|
||||
let confirmation = uv_console::confirm(&prompt, &term, true)?;
|
||||
if confirmation {
|
||||
return Self::from_requirements_file(name.into());
|
||||
return Ok(Self::from_requirements_file(name.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,14 +155,14 @@ impl RequirementsSource {
|
|||
let prompt = format!(
|
||||
"`{name}` looks like a local metadata file but was passed as a package name. Did you mean `--with-requirements {name}`?"
|
||||
);
|
||||
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
|
||||
let confirmation = uv_console::confirm(&prompt, &term, true)?;
|
||||
if confirmation {
|
||||
return Self::from_requirements_file(name.into());
|
||||
return Ok(Self::from_requirements_file(name.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::Package(name)
|
||||
Ok(Self::Package(name))
|
||||
}
|
||||
|
||||
/// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a path to a source
|
||||
|
|
|
@ -537,17 +537,18 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
let requirements = args
|
||||
.package
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_package)
|
||||
.chain(args.editables.into_iter().map(RequirementsSource::Editable))
|
||||
.chain(
|
||||
args.requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let mut requirements = Vec::with_capacity(
|
||||
args.package.len() + args.editables.len() + args.requirements.len(),
|
||||
);
|
||||
for package in args.package {
|
||||
requirements.push(RequirementsSource::from_package(package)?);
|
||||
}
|
||||
requirements.extend(args.editables.into_iter().map(RequirementsSource::Editable));
|
||||
requirements.extend(
|
||||
args.requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
);
|
||||
let constraints = args
|
||||
.constraints
|
||||
.into_iter()
|
||||
|
@ -624,16 +625,15 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
// Initialize the cache.
|
||||
let cache = cache.init()?;
|
||||
|
||||
let sources = args
|
||||
.package
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_package)
|
||||
.chain(
|
||||
args.requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_txt),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let mut sources = Vec::with_capacity(args.package.len() + args.requirements.len());
|
||||
for package in args.package {
|
||||
sources.push(RequirementsSource::from_package(package)?);
|
||||
}
|
||||
sources.extend(
|
||||
args.requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
);
|
||||
commands::pip_uninstall(
|
||||
&sources,
|
||||
args.settings.python,
|
||||
|
@ -985,21 +985,22 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
let requirements = args
|
||||
.with
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_with_package)
|
||||
.chain(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
)
|
||||
.chain(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let mut requirements = Vec::with_capacity(
|
||||
args.with.len() + args.with_editable.len() + args.with_requirements.len(),
|
||||
);
|
||||
for package in args.with {
|
||||
requirements.push(RequirementsSource::from_with_package(package)?);
|
||||
}
|
||||
requirements.extend(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
);
|
||||
requirements.extend(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
);
|
||||
|
||||
Box::pin(commands::tool_run(
|
||||
args.command,
|
||||
|
@ -1038,21 +1039,23 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
let requirements = args
|
||||
.with
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_with_package)
|
||||
.chain(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
)
|
||||
.chain(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let mut requirements = Vec::with_capacity(
|
||||
args.with.len() + args.with_editable.len() + args.with_requirements.len(),
|
||||
);
|
||||
for package in args.with {
|
||||
requirements.push(RequirementsSource::from_with_package(package)?);
|
||||
}
|
||||
requirements.extend(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
);
|
||||
requirements.extend(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
);
|
||||
|
||||
let constraints = args
|
||||
.constraints
|
||||
.into_iter()
|
||||
|
@ -1468,21 +1471,22 @@ async fn run_project(
|
|||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
let requirements = args
|
||||
.with
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_with_package)
|
||||
.chain(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
)
|
||||
.chain(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let mut requirements = Vec::with_capacity(
|
||||
args.with.len() + args.with_editable.len() + args.with_requirements.len(),
|
||||
);
|
||||
for package in args.with {
|
||||
requirements.push(RequirementsSource::from_with_package(package)?);
|
||||
}
|
||||
requirements.extend(
|
||||
args.with_editable
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Editable),
|
||||
);
|
||||
requirements.extend(
|
||||
args.with_requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
);
|
||||
|
||||
Box::pin(commands::run(
|
||||
project_dir,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue