gix/
discover.rs

1#![allow(clippy::result_large_err)]
2use std::path::Path;
3
4pub use gix_discover::*;
5
6use crate::{bstr::BString, ThreadSafeRepository};
7
8/// The error returned by [`crate::discover()`].
9#[derive(Debug, thiserror::Error)]
10#[allow(missing_docs)]
11pub enum Error {
12    #[error(transparent)]
13    Discover(#[from] upwards::Error),
14    #[error(transparent)]
15    Open(#[from] crate::open::Error),
16}
17
18impl ThreadSafeRepository {
19    /// Try to open a git repository in `directory` and search upwards through its parents until one is found,
20    /// using default trust options which matters in case the found repository isn't owned by the current user.
21    pub fn discover(directory: impl AsRef<Path>) -> Result<Self, Error> {
22        Self::discover_opts(directory, Default::default(), Default::default())
23    }
24
25    /// Try to open a git repository in `directory` and search upwards through its parents until one is found,
26    /// while applying `options`. Then use the `trust_map` to determine which of our own repository options to use
27    /// for instantiations.
28    ///
29    /// Note that [trust overrides](crate::open::Options::with()) in the `trust_map` are not effective here and we will
30    /// always override it with the determined trust value. This is a precaution as the API user is unable to actually know
31    /// if the directory that is discovered can indeed be trusted (or else they'd have to implement the discovery themselves
32    /// and be sure that no attacker ever gets access to a directory structure. The cost of this is a permission check, which
33    /// seems acceptable).
34    pub fn discover_opts(
35        directory: impl AsRef<Path>,
36        options: upwards::Options<'_>,
37        trust_map: gix_sec::trust::Mapping<crate::open::Options>,
38    ) -> Result<Self, Error> {
39        let _span = gix_trace::coarse!("ThreadSafeRepository::discover()");
40        let (path, trust) = upwards_opts(directory.as_ref(), options)?;
41        let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories();
42        let mut options = trust_map.into_value_by_level(trust);
43        options.git_dir_trust = trust.into();
44        // Note that we will adjust the `current_dir` later so it matches the value of `core.precomposeUnicode`.
45        options.current_dir = Some(gix_fs::current_dir(false).map_err(upwards::Error::CurrentDir)?);
46        Self::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into)
47    }
48
49    /// Try to open a git repository directly from the environment.
50    /// If that fails, discover upwards from `directory` until one is found,
51    /// while applying discovery options from the environment.
52    pub fn discover_with_environment_overrides(directory: impl AsRef<Path>) -> Result<Self, Error> {
53        Self::discover_with_environment_overrides_opts(directory, Default::default(), Default::default())
54    }
55
56    /// Try to open a git repository directly from the environment, which reads `GIT_DIR`
57    /// if it is set. If unset, discover upwards from `directory` until one is found,
58    /// while applying `options` with overrides from the environment which includes:
59    ///
60    /// - `GIT_DISCOVERY_ACROSS_FILESYSTEM`
61    /// - `GIT_CEILING_DIRECTORIES`
62    ///
63    /// Finally, use the `trust_map` to determine which of our own repository options to use
64    /// based on the trust level of the effective repository directory.
65    ///
66    /// ### Note
67    ///
68    /// Consider to set [`match_ceiling_dir_or_error = false`](gix_discover::upwards::Options::match_ceiling_dir_or_error)
69    /// to allow discovery if an outside environment variable sets non-matching ceiling directories for greater
70    /// compatibility with Git.
71    pub fn discover_with_environment_overrides_opts(
72        directory: impl AsRef<Path>,
73        mut options: upwards::Options<'_>,
74        trust_map: gix_sec::trust::Mapping<crate::open::Options>,
75    ) -> Result<Self, Error> {
76        fn apply_additional_environment(mut opts: upwards::Options<'_>) -> upwards::Options<'_> {
77            use crate::bstr::ByteVec;
78
79            if let Some(cross_fs) = std::env::var_os("GIT_DISCOVERY_ACROSS_FILESYSTEM")
80                .and_then(|v| Vec::from_os_string(v).ok().map(BString::from))
81            {
82                if let Ok(b) = gix_config::Boolean::try_from(cross_fs.as_ref()) {
83                    opts.cross_fs = b.into();
84                }
85            }
86            opts
87        }
88
89        if std::env::var_os("GIT_DIR").is_some() {
90            return Self::open_with_environment_overrides(directory.as_ref(), trust_map).map_err(Error::Open);
91        }
92
93        options = apply_additional_environment(options.apply_environment());
94        Self::discover_opts(directory, options, trust_map)
95    }
96}