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}