find_git/
lib.rs

1//! A cross-platform library which returns the full path to the Git binary if it can be found.
2
3#![forbid(warnings)]
4#![warn(
5    missing_copy_implementations,
6    trivial_casts,
7    trivial_numeric_casts,
8    unsafe_code,
9    unused_extern_crates,
10    unused_import_braces,
11    unused_qualifications,
12    unused_results,
13    variant_size_differences
14)]
15
16#[cfg(windows)]
17extern crate winapi;
18#[cfg(windows)]
19extern crate winreg;
20
21use std::ffi::OsStr;
22use std::path::PathBuf;
23use std::process::{Command, Stdio};
24use std::str;
25
26#[cfg(windows)]
27const LOCATE_COMMAND: &'static str = "where";
28#[cfg(not(windows))]
29const LOCATE_COMMAND: &str = "which";
30
31fn git_ran_ok<S: AsRef<OsStr>>(binary_path: S) -> bool {
32    Command::new(binary_path)
33        .arg("--version")
34        .stdout(Stdio::null())
35        .stderr(Stdio::null())
36        .status()
37        .is_ok()
38}
39
40fn try_git_with_no_path() -> Option<::std::path::PathBuf> {
41    if let Ok(output) = Command::new(LOCATE_COMMAND).arg("git").output() {
42        let git = str::from_utf8(&output.stdout)
43            .unwrap_or_else(|_| panic!("Non-UTF8 output when running `{} git`.", LOCATE_COMMAND))
44            .trim()
45            .lines()
46            .next()
47            .unwrap_or_else(|| {
48                panic!(
49                    "Should have had at least one line of text when running `{} git`.",
50                    LOCATE_COMMAND
51                )
52            });
53        if git_ran_ok(git) {
54            return Some(PathBuf::from(git));
55        }
56    }
57    None
58}
59
60#[cfg(windows)]
61mod find_git {
62    use super::{git_ran_ok, try_git_with_no_path};
63    use std::ffi::OsStr;
64    use std::io::Result;
65    use std::path::PathBuf;
66    use winapi::shared::minwindef::HKEY;
67    use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ};
68    use winreg::RegKey;
69
70    fn try_full_path_to_git<P: AsRef<OsStr>>(
71        predefined_key: HKEY,
72        subkey_path: P,
73        value: P,
74    ) -> Option<PathBuf> {
75        let root = RegKey::predef(predefined_key);
76        if let Ok(subkey) = root.open_subkey_with_flags(subkey_path, KEY_READ) {
77            let subkey_value: Result<String> = subkey.get_value(value);
78            if let Ok(install_path) = subkey_value {
79                let binary_path = PathBuf::from(&install_path).join("bin").join("git.exe");
80                if git_ran_ok(binary_path.as_os_str()) {
81                    return Some(binary_path);
82                }
83            }
84        }
85        None
86    }
87
88    /// Tries to find Git.  On Windows, if it is not currently available in `%PATH%`, tries using
89    /// several known registry values.
90    #[rustfmt::skip]
91    pub fn git_path() -> Option<PathBuf> {
92        const SUBKEY_RECENT: &'static str = "Software\\GitForWindows";
93        const SUBKEY_32_BIT: &'static str =
94            "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1";
95        const SUBKEY_64_BIT: &'static str =
96            "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1";
97        try_git_with_no_path()
98            .or_else(|| try_full_path_to_git(HKEY_LOCAL_MACHINE, SUBKEY_RECENT, "InstallPath"))
99            .or_else(|| try_full_path_to_git(HKEY_CURRENT_USER, SUBKEY_32_BIT, "InstallLocation"))
100            .or_else(|| try_full_path_to_git(HKEY_CURRENT_USER, SUBKEY_64_BIT, "InstallLocation"))
101            .or_else(|| try_full_path_to_git(HKEY_LOCAL_MACHINE, SUBKEY_32_BIT, "InstallLocation"))
102            .or_else(|| try_full_path_to_git(HKEY_LOCAL_MACHINE, SUBKEY_64_BIT, "InstallLocation"))
103    }
104}
105
106#[cfg(not(windows))]
107mod find_git {
108    pub fn git_path() -> Option<::std::path::PathBuf> {
109        super::try_git_with_no_path()
110    }
111}
112
113pub use find_git::git_path;