1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use std::path::Path;

pub use tokio::process::Command;

/// Error encountered when shelling out to the git binary for some reason
#[derive(thiserror::Error, Debug)]
pub enum Error {
    /// Program failed to open a subprocess.
    #[error("Unable to run subcommand {command}")]
    IO {
        command: String,
        source: std::io::Error,
    },

    /// The subprocess ran to completion but returned a failure code.
    #[error("Command {command:?} failed with STDERR: {stderr}")]
    CommandError {
        command: String,
        status: std::process::ExitStatus,
        stderr: String,
        stdout: String,
    },
}

impl Error {
    /// return the underlying process status code if applicable
    pub fn status(&self) -> Option<i32> {
        if let Error::CommandError {
            command: _,
            status,
            stderr: _,
            stdout: _,
        } = self
        {
            status.code()
        } else {
            None
        }
    }
}

/// Wrapper trait to generate commands for Lorry
pub trait CommandBuilder {
    fn build(&self, current_dir: &Path) -> Command;
}

/// Build the command and execute it returning its stdout/stderr or an error
pub async fn execute<T>(builder: &T, current_dir: &Path) -> Result<(String, String), Error>
where
    T: CommandBuilder,
{
    let mut cmd = builder.build(current_dir);
    cmd.stdin(std::process::Stdio::null());
    let child_process_output = cmd.output().await.map_err(|e| Error::IO {
        command: format!("{:?}", cmd),
        source: e,
    })?;
    let succeeded = child_process_output.status.success();
    let out = String::from_utf8_lossy(&child_process_output.stdout);
    let err = String::from_utf8_lossy(&child_process_output.stderr);
    tracing::debug!(
        "Command:{:?} \n Exit code:{:?} \n Stdout: {}, Stderr: {} \n",
        cmd,
        child_process_output.status.code(),
        out,
        err,
    );
    if !succeeded {
        tracing::debug!(
            "Failed to run {:?}, Status was: {:?}, Stdout was:\n{:?},Stderr was:\n{:?}",
            cmd,
            child_process_output.status.code(),
            out,
            err
        );
        Err(Error::CommandError {
            status: child_process_output.status,
            stdout: out.to_string(),
            stderr: err.to_string(),
            command: format!("{:?}", cmd),
        })
    } else {
        Ok((out.to_string(), err.to_string()))
    }
}