Search code examples
rustgithookstty

stdin read_line does not wait for user input when called from a git hook


I'm an absolute Rust beginner trying to build a simple confirmation function (yes or no), but I can't get the user to type anything, the function just keeps looping without waiting for user input:

""
""
""
etc.  

is the result of the simplified version below.

use std::process;
use std::io;

pub fn confirm() {
  loop {
    let mut answer = String::new();

    io::stdin().read_line(&mut answer)
      .ok()
      .expect("Failed to read line");

    println!("{:?}", answer);
  }
}

I've built my function around the guessing game example, and the rest of my program does nothing much, just reading a file and printing text.

Perhaps is due to the way my program (a git hook) is launched?


Solution

  • Assuming that the problem is that your git commit hook is running in an non-interactive environment, you can follow the advice laid out in that question and directly open /dev/tty. Unlike STDIN, we don't treat it as a magical global variable and instead we pass it into the places we need:

    use std::io::{self, BufRead, BufReader};
    use std::fs::File;
    
    type Tty = BufReader<File>;
    
    fn open_tty() -> io::Result<Tty> {
        let f = try!(File::open("/dev/tty"));
        Ok(BufReader::new(f))
    }
    
    fn confirm(tty: &mut Tty) -> io::Result<String> {
        let mut answer = String::new();
        try!(tty.read_line(&mut answer));
        Ok(answer)
    }
    
    fn inner_main() -> io::Result<()> {
        let mut tty = try!(open_tty());
        let answer = try!(confirm(&mut tty));
    
        println!("The answer was: {}", answer);
    
        Ok(())
    }
    
    fn main() {
        inner_main().unwrap()
    }
    

    Note that this will not be platform independent. Specifically, this is very unlikely to work on Windows!

    I've also gone ahead and allowed the io::Result to propagate throughout the program, only panicking at the outermost shell.