gix_path/env/
mod.rs

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
use std::ffi::OsString;
use std::path::{Path, PathBuf};

use bstr::{BString, ByteSlice};
use once_cell::sync::Lazy;

use crate::env::git::EXE_NAME;

mod git;

/// Return the location at which installation specific git configuration file can be found, or `None`
/// if the binary could not be executed or its results could not be parsed.
///
/// ### Performance
///
/// This invokes the git binary which is slow on windows.
pub fn installation_config() -> Option<&'static Path> {
    git::install_config_path().and_then(|p| crate::try_from_byte_slice(p).ok())
}

/// Return the location at which git installation specific configuration files are located, or `None` if the binary
/// could not be executed or its results could not be parsed.
///
/// ### Performance
///
/// This invokes the git binary which is slow on windows.
pub fn installation_config_prefix() -> Option<&'static Path> {
    installation_config().map(git::config_to_base_path)
}

/// Return the name of the Git executable to invoke it.
/// If it's in the `PATH`, it will always be a short name.
///
/// Note that on Windows, we will find the executable in the `PATH` if it exists there, or search it
/// in alternative locations which when found yields the full path to it.
pub fn exe_invocation() -> &'static Path {
    if cfg!(windows) {
        /// The path to the Git executable as located in the `PATH` or in other locations that it's known to be installed to.
        /// It's `None` if environment variables couldn't be read or if no executable could be found.
        static EXECUTABLE_PATH: Lazy<Option<PathBuf>> = Lazy::new(|| {
            std::env::split_paths(&std::env::var_os("PATH")?)
                .chain(git::ALTERNATIVE_LOCATIONS.iter().map(Into::into))
                .find_map(|prefix| {
                    let full_path = prefix.join(EXE_NAME);
                    full_path.is_file().then_some(full_path)
                })
                .map(|exe_path| {
                    let is_in_alternate_location = git::ALTERNATIVE_LOCATIONS
                        .iter()
                        .any(|prefix| exe_path.strip_prefix(prefix).is_ok());
                    if is_in_alternate_location {
                        exe_path
                    } else {
                        EXE_NAME.into()
                    }
                })
        });
        EXECUTABLE_PATH.as_deref().unwrap_or(Path::new(git::EXE_NAME))
    } else {
        Path::new("git")
    }
}

/// Returns the fully qualified path in the *xdg-home* directory (or equivalent in the home dir) to `file`,
/// accessing `env_var(<name>)` to learn where these bases are.
///
/// Note that the `HOME` directory should ultimately come from [`home_dir()`] as it handles windows correctly.
/// The same can be achieved by using [`var()`] as `env_var`.
pub fn xdg_config(file: &str, env_var: &mut dyn FnMut(&str) -> Option<OsString>) -> Option<PathBuf> {
    env_var("XDG_CONFIG_HOME")
        .map(|home| {
            let mut p = PathBuf::from(home);
            p.push("git");
            p.push(file);
            p
        })
        .or_else(|| {
            env_var("HOME").map(|home| {
                let mut p = PathBuf::from(home);
                p.push(".config");
                p.push("git");
                p.push(file);
                p
            })
        })
}

/// Returns the platform dependent system prefix or `None` if it cannot be found (right now only on windows).
///
/// ### Performance
///
/// On windows, the slowest part is the launch of the Git executable in the PATH, which only happens when launched
/// from outside of the `msys2` shell.
///
/// ### When `None` is returned
///
/// This happens only windows if the git binary can't be found at all for obtaining its executable path, or if the git binary
/// wasn't built with a well-known directory structure or environment.
pub fn system_prefix() -> Option<&'static Path> {
    if cfg!(windows) {
        static PREFIX: Lazy<Option<PathBuf>> = Lazy::new(|| {
            if let Some(root) = std::env::var_os("EXEPATH").map(PathBuf::from) {
                for candidate in ["mingw64", "mingw32"] {
                    let candidate = root.join(candidate);
                    if candidate.is_dir() {
                        return Some(candidate);
                    }
                }
            }

            let mut cmd = std::process::Command::new(exe_invocation());
            #[cfg(windows)]
            {
                use std::os::windows::process::CommandExt;
                const CREATE_NO_WINDOW: u32 = 0x08000000;
                cmd.creation_flags(CREATE_NO_WINDOW);
            }
            cmd.arg("--exec-path").stderr(std::process::Stdio::null());
            gix_trace::debug!(cmd = ?cmd, "invoking git to get system prefix/exec path");
            let path = cmd.output().ok()?.stdout;
            let path = BString::new(path)
                .trim_with(|b| b.is_ascii_whitespace())
                .to_path()
                .ok()?
                .to_owned();

            let one_past_prefix = path.components().enumerate().find_map(|(idx, c)| {
                matches!(c,std::path::Component::Normal(name) if name.to_str() == Some("libexec")).then_some(idx)
            })?;
            Some(path.components().take(one_past_prefix.checked_sub(1)?).collect())
        });
        PREFIX.as_deref()
    } else {
        Path::new("/").into()
    }
}

/// Returns `$HOME` or `None` if it cannot be found.
#[cfg(target_family = "wasm")]
pub fn home_dir() -> Option<PathBuf> {
    std::env::var("HOME").map(PathBuf::from).ok()
}

/// Tries to obtain the home directory from `HOME` on all platforms, but falls back to [`home::home_dir()`] for
/// more complex ways of obtaining a home directory, particularly useful on Windows.
///
/// The reason `HOME` is tried first is to allow Windows users to have a custom location for their linux-style
/// home, as otherwise they would have to accumulate dot files in a directory these are inconvenient and perceived
/// as clutter.
#[cfg(not(target_family = "wasm"))]
pub fn home_dir() -> Option<PathBuf> {
    std::env::var_os("HOME").map(Into::into).or_else(home::home_dir)
}

/// Returns the contents of an environment variable of `name` with some special handling
/// for certain environment variables (like `HOME`) for platform compatibility.
pub fn var(name: &str) -> Option<OsString> {
    if name == "HOME" {
        home_dir().map(PathBuf::into_os_string)
    } else {
        std::env::var_os(name)
    }
}