branchless/
util.rs

1//! Utility functions.
2
3use std::num::TryFromIntError;
4use std::path::PathBuf;
5use std::process::ExitStatus;
6
7/// Represents the code to exit the process with.
8#[must_use]
9#[derive(Copy, Clone, Debug)]
10pub struct ExitCode(pub isize);
11
12impl ExitCode {
13    /// Return an exit code corresponding to success.
14    pub fn success() -> Self {
15        Self(0)
16    }
17
18    /// Determine whether or not this exit code represents a successful
19    /// termination.
20    pub fn is_success(&self) -> bool {
21        match self {
22            ExitCode(0) => true,
23            ExitCode(_) => false,
24        }
25    }
26}
27
28impl TryFrom<ExitStatus> for ExitCode {
29    type Error = TryFromIntError;
30
31    fn try_from(status: ExitStatus) -> Result<Self, Self::Error> {
32        let exit_code = status.code().unwrap_or(1);
33        Ok(Self(exit_code.try_into()?))
34    }
35}
36
37/// Encapsulate both an `eyre::Result<T>` and a possible subcommand exit code.
38///
39/// Helper type alias for the common case that we want to run a computation and
40/// return `eyre::Result<T>`, but it's also possible that we run a subcommand
41/// which returns an exit code that we want to propagate. See also `try_exit_code`.
42pub type EyreExitOr<T> = eyre::Result<Result<T, ExitCode>>;
43
44/// Macro to propagate `ExitCode`s in the same way as the `try!` macro/the `?`
45/// operator.
46///
47/// Ideally, we would make `ExitCode` implement `std::ops::Try`, but that's a
48/// nightly API. We could also make `ExitCode` implement `Error`, but this
49/// interacts badly with `eyre::Result`, because all `Error`s are convertible to
50/// `eyre::Error`, so our exit codes get treated at the same as other errors.
51/// So, instead, we have this macro to accomplish the same thing, but for
52/// `Result<T, ExitCode>`s specifically.
53#[macro_export]
54macro_rules! try_exit_code {
55    ($e:expr) => {
56        match $e {
57            Ok(value) => value,
58            Err(exit_code) => {
59                return Ok(Err(exit_code));
60            }
61        }
62    };
63}
64
65/// Returns a path for a given file, searching through PATH to find it.
66pub fn get_from_path(exe_name: &str) -> Option<PathBuf> {
67    std::env::var_os("PATH").and_then(|paths| {
68        std::env::split_paths(&paths).find_map(|dir| {
69            let bash_path = dir.join(exe_name);
70            if bash_path.is_file() {
71                Some(bash_path)
72            } else {
73                None
74            }
75        })
76    })
77}
78
79/// Returns the path to a shell suitable for running hooks.
80pub fn get_sh() -> Option<PathBuf> {
81    let exe_name = if cfg!(target_os = "windows") {
82        "bash.exe"
83    } else {
84        "sh"
85    };
86    // If we are on Windows, first look for git.exe, and try to use it's bash, otherwise it won't
87    // be able to find git-branchless correctly.
88    if cfg!(target_os = "windows") {
89        // Git is typically installed at C:\Program Files\Git\cmd\git.exe with the cmd\ directory
90        // in the path, however git-bash is usually not in PATH and is in bin\ directory:
91        let git_path = get_from_path("git.exe").expect("Couldn't find git.exe");
92        let git_dir = git_path.parent().unwrap().parent().unwrap();
93        let git_bash = git_dir.join("bin").join(exe_name);
94        if git_bash.is_file() {
95            return Some(git_bash);
96        }
97    }
98    get_from_path(exe_name)
99}