ok/jj
1
0
Fork 0
forked from mirrors/jj

cli: handle last-minute ui.write() error

This works if the pager exits instantly and jj is slow enough to notice
EPIPE. If the pager exits late, no error would be reported.

Since the pager process is asynchronous, EPIPE could occur in
handle_command_result(). That's why I made it not panic.
This commit is contained in:
Yuya Nishihara 2023-02-10 17:24:05 +09:00
parent 8eda80fc53
commit d7f64c07e0
2 changed files with 39 additions and 21 deletions

View file

@ -1897,24 +1897,28 @@ pub fn parse_args(
Ok((matches, args)) Ok((matches, args))
} }
#[must_use] const BROKEN_PIPE_EXIT_CODE: u8 = 3;
pub fn handle_command_result(ui: &mut Ui, result: Result<(), CommandError>) -> ExitCode {
pub fn handle_command_result(
ui: &mut Ui,
result: Result<(), CommandError>,
) -> std::io::Result<ExitCode> {
match result { match result {
Ok(()) => ExitCode::SUCCESS, Ok(()) => Ok(ExitCode::SUCCESS),
Err(CommandError::UserError { message, hint }) => { Err(CommandError::UserError { message, hint }) => {
writeln!(ui.error(), "Error: {message}").unwrap(); writeln!(ui.error(), "Error: {message}")?;
if let Some(hint) = hint { if let Some(hint) = hint {
writeln!(ui.hint(), "Hint: {hint}").unwrap(); writeln!(ui.hint(), "Hint: {hint}")?;
} }
ExitCode::from(1) Ok(ExitCode::from(1))
} }
Err(CommandError::ConfigError(message)) => { Err(CommandError::ConfigError(message)) => {
writeln!(ui.error(), "Config error: {message}").unwrap(); writeln!(ui.error(), "Config error: {message}")?;
ExitCode::from(1) Ok(ExitCode::from(1))
} }
Err(CommandError::CliError(message)) => { Err(CommandError::CliError(message)) => {
writeln!(ui.error(), "Error: {message}").unwrap(); writeln!(ui.error(), "Error: {message}")?;
ExitCode::from(2) Ok(ExitCode::from(2))
} }
Err(CommandError::ClapCliError(inner)) => { Err(CommandError::ClapCliError(inner)) => {
let clap_str = if ui.color() { let clap_str = if ui.color() {
@ -1934,19 +1938,24 @@ pub fn handle_command_result(ui: &mut Ui, result: Result<(), CommandError>) -> E
// https://github.com/clap-rs/clap/blob/master/src/error/mod.rs // https://github.com/clap-rs/clap/blob/master/src/error/mod.rs
match inner.kind() { match inner.kind() {
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => { clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
ui.write(&clap_str).unwrap(); ui.write(&clap_str)?;
ExitCode::SUCCESS Ok(ExitCode::SUCCESS)
} }
_ => { _ => {
ui.write_stderr(&clap_str).unwrap(); ui.write_stderr(&clap_str)?;
ExitCode::from(2) Ok(ExitCode::from(2))
} }
} }
} }
Err(CommandError::BrokenPipe) => ExitCode::from(3), Err(CommandError::BrokenPipe) => {
// It's unlikely this write() would succeed, but try anyway to either
// print error message or raise new io::Error.
writeln!(ui.error(), "Error: Broken pipe")?;
Ok(ExitCode::from(BROKEN_PIPE_EXIT_CODE))
}
Err(CommandError::InternalError(message)) => { Err(CommandError::InternalError(message)) => {
writeln!(ui.error(), "Internal error: {message}").unwrap(); writeln!(ui.error(), "Internal error: {message}")?;
ExitCode::from(255) Ok(ExitCode::from(255))
} }
} }
} }
@ -2070,8 +2079,17 @@ impl CliRunner {
let mut ui = Ui::with_config(&layered_configs.merge()) let mut ui = Ui::with_config(&layered_configs.merge())
.expect("default config should be valid, env vars are stringly typed"); .expect("default config should be valid, env vars are stringly typed");
let result = self.run_internal(&mut ui, layered_configs); let result = self.run_internal(&mut ui, layered_configs);
let exit_code = handle_command_result(&mut ui, result); match handle_command_result(&mut ui, result) {
ui.finalize_writes(); Ok(exit_code) => {
ui.finalize_pager();
exit_code exit_code
} }
Err(err) => {
// The error is most likely a BrokenPipe. Close pager and write to stderr.
ui.finalize_pager();
writeln!(ui.error(), "Error: {err}").ok();
ExitCode::from(BROKEN_PIPE_EXIT_CODE)
}
}
}
} }

View file

@ -242,7 +242,7 @@ impl Ui {
} }
} }
pub fn finalize_writes(&mut self) { pub fn finalize_pager(&mut self) {
if let UiOutput::Paged { if let UiOutput::Paged {
mut child, mut child,
child_stdin, child_stdin,