Skip to content

Commit ebb63fb

Browse files
committed
feat: Print command stderr to screen as they run (e.g. git hooks)
1 parent 1cc3340 commit ebb63fb

1 file changed

Lines changed: 47 additions & 5 deletions

File tree

src/state.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::io::Read;
2+
use std::io::Write;
23
use std::ops::DerefMut;
34
use std::process::Child;
45
use std::process::Command;
@@ -315,6 +316,11 @@ impl State {
315316

316317
cmd.current_dir(self.repo.workdir().ok_or(Error::NoRepoWorkdir)?);
317318

319+
self.current_cmd_log.push_cmd_with_output(&cmd, "\n".into());
320+
self.update(term, &[GituEvent::Refresh])?;
321+
322+
eprint!("\r");
323+
318324
// Redirect stderr so we can capture it via `Child::wait_with_output()`
319325
cmd.stderr(Stdio::piped());
320326

@@ -326,13 +332,27 @@ impl State {
326332
// state), the cursor may be missing in $EDITOR.
327333
term.show_cursor().map_err(Error::Term)?;
328334

329-
let child = cmd.spawn().map_err(Error::SpawnCmd)?;
335+
let mut child = cmd.spawn().map_err(Error::SpawnCmd)?;
336+
337+
// Drop stdin as `Child::wait_with_output` would
338+
drop(child.stdin.take());
339+
340+
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
330341

331-
let out = child.wait_with_output().map_err(Error::CouldntAwaitCmd)?;
332-
let out_utf8 = String::from_utf8(strip_ansi_escapes::strip(out.stderr.clone()))
342+
tee(child.stdout.as_mut(), &mut [&mut stdout]).map_err(Error::Term)?;
343+
344+
tee(
345+
child.stderr.as_mut(),
346+
&mut [&mut std::io::stderr(), &mut stderr],
347+
)
348+
.map_err(Error::Term)?;
349+
350+
let status = child.wait().map_err(Error::CouldntAwaitCmd)?;
351+
let out_utf8 = String::from_utf8(strip_ansi_escapes::strip(stderr.clone()))
333352
.expect("Error turning command output to String")
334353
.into();
335354

355+
self.current_cmd_log.clear();
336356
self.current_cmd_log.push_cmd_with_output(&cmd, out_utf8);
337357

338358
// restore the raw mode
@@ -347,7 +367,7 @@ impl State {
347367
term.clear().map_err(Error::Term)?;
348368
self.screen_mut().update()?;
349369

350-
if !out.status.success() {
370+
if !status.success() {
351371
return Err(Error::CmdBadExit(
352372
format!(
353373
"{} {}",
@@ -356,7 +376,7 @@ impl State {
356376
.map(|arg| arg.to_string_lossy())
357377
.collect::<String>()
358378
),
359-
out.status.code(),
379+
status.code(),
360380
));
361381
}
362382

@@ -376,6 +396,28 @@ impl State {
376396
}
377397
}
378398

399+
fn tee(maybe_input: Option<&mut impl Read>, outputs: &mut [&mut dyn Write]) -> std::io::Result<()> {
400+
let Some(input) = maybe_input else {
401+
return Ok(());
402+
};
403+
404+
let mut buf = [0u8; 1024];
405+
406+
loop {
407+
let num_read = input.read(&mut buf)?;
408+
if num_read == 0 {
409+
break;
410+
}
411+
412+
let buf = &buf[..num_read];
413+
for output in &mut *outputs {
414+
output.write_all(buf)?;
415+
}
416+
}
417+
418+
Ok(())
419+
}
420+
379421
pub(crate) fn root_menu(config: &Config) -> Option<Menu> {
380422
if config.general.always_show_help.enabled {
381423
Some(Menu::Help)

0 commit comments

Comments
 (0)