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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
use std::collections::BTreeSet;

use crate::{bstr::ByteSlice, config};

/// General Configuration
impl crate::Repository {
    /// Return a snapshot of the configuration as seen upon opening the repository.
    pub fn config_snapshot(&self) -> config::Snapshot<'_> {
        config::Snapshot { repo: self }
    }

    /// Return a mutable snapshot of the configuration as seen upon opening the repository, starting a transaction.
    /// When the returned instance is dropped, it is applied in full, even if the reason for the drop is an error.
    ///
    /// Note that changes to the configuration are in-memory only and are observed only the this instance
    /// of the [`Repository`][crate::Repository].
    pub fn config_snapshot_mut(&mut self) -> config::SnapshotMut<'_> {
        let config = self.config.resolved.as_ref().clone();
        config::SnapshotMut {
            repo: Some(self),
            config,
        }
    }

    /// Return filesystem options as retrieved from the repository configuration.
    ///
    /// Note that these values have not been [probed](gix_fs::Capabilities::probe()).
    pub fn filesystem_options(&self) -> Result<gix_fs::Capabilities, config::boolean::Error> {
        self.config.fs_capabilities()
    }

    /// Return filesystem options on how to perform stat-checks, typically in relation to the index.
    ///
    /// Note that these values have not been [probed](gix_fs::Capabilities::probe()).
    #[cfg(feature = "index")]
    pub fn stat_options(&self) -> Result<gix_index::entry::stat::Options, config::stat_options::Error> {
        self.config.stat_options()
    }

    /// The options used to open the repository.
    pub fn open_options(&self) -> &crate::open::Options {
        &self.options
    }

    /// Obtain options for use when connecting via `ssh`.
    #[cfg(feature = "blocking-network-client")]
    pub fn ssh_connect_options(
        &self,
    ) -> Result<gix_protocol::transport::client::ssh::connect::Options, config::ssh_connect_options::Error> {
        use crate::config::{
            cache::util::ApplyLeniency,
            tree::{gitoxide, Core, Ssh},
        };

        let config = &self.config.resolved;
        let mut trusted = self.filter_config_section();
        let mut fallback_active = false;
        let ssh_command = config
            .string_filter("core", None, Core::SSH_COMMAND.name, &mut trusted)
            .or_else(|| {
                fallback_active = true;
                config.string_filter(
                    "gitoxide",
                    Some("ssh".into()),
                    gitoxide::Ssh::COMMAND_WITHOUT_SHELL_FALLBACK.name,
                    &mut trusted,
                )
            })
            .map(|cmd| gix_path::from_bstr(cmd).into_owned().into());
        let opts = gix_protocol::transport::client::ssh::connect::Options {
            disallow_shell: fallback_active,
            command: ssh_command,
            kind: config
                .string_filter_by_key("ssh.variant", &mut trusted)
                .and_then(|variant| Ssh::VARIANT.try_into_variant(variant).transpose())
                .transpose()
                .with_leniency(self.options.lenient_config)?,
        };
        Ok(opts)
    }

    /// The kind of object hash the repository is configured to use.
    pub fn object_hash(&self) -> gix_hash::Kind {
        self.config.object_hash
    }
}

#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
mod transport;

mod remote {
    use std::{borrow::Cow, collections::BTreeSet};

    use crate::{bstr::ByteSlice, remote};

    impl crate::Repository {
        /// Returns a sorted list unique of symbolic names of remotes that
        /// we deem [trustworthy][crate::open::Options::filter_config_section()].
        // TODO: Use `remote::Name` here
        pub fn remote_names(&self) -> BTreeSet<&str> {
            self.subsection_names_of("remote")
        }

        /// Obtain the branch-independent name for a remote for use in the given `direction`, or `None` if it could not be determined.
        ///
        /// For _fetching_, use the only configured remote, or default to `origin` if it exists.
        /// For _pushing_, use the `remote.pushDefault` trusted configuration key, or fall back to the rules for _fetching_.
        ///
        /// # Notes
        ///
        /// It's up to the caller to determine what to do if the current `head` is unborn or detached.
        // TODO: use remote::Name here
        pub fn remote_default_name(&self, direction: remote::Direction) -> Option<Cow<'_, str>> {
            let name = (direction == remote::Direction::Push)
                .then(|| {
                    self.config
                        .resolved
                        .string_filter("remote", None, "pushDefault", &mut self.filter_config_section())
                        .and_then(|s| match s {
                            Cow::Borrowed(s) => s.to_str().ok().map(Cow::Borrowed),
                            Cow::Owned(s) => s.to_str().ok().map(|s| Cow::Owned(s.into())),
                        })
                })
                .flatten();
            name.or_else(|| {
                let names = self.remote_names();
                match names.len() {
                    0 => None,
                    1 => names.iter().next().copied().map(Cow::Borrowed),
                    _more_than_one => names.get("origin").copied().map(Cow::Borrowed),
                }
            })
        }
    }
}

mod branch {
    use std::{borrow::Cow, collections::BTreeSet, convert::TryInto};

    use gix_ref::FullNameRef;
    use gix_validate::reference::name::Error as ValidateNameError;

    use crate::bstr::BStr;

    impl crate::Repository {
        /// Return a set of unique short branch names for which custom configuration exists in the configuration,
        /// if we deem them [trustworthy][crate::open::Options::filter_config_section()].
        pub fn branch_names(&self) -> BTreeSet<&str> {
            self.subsection_names_of("branch")
        }

        /// Returns the validated reference on the remote associated with the given `short_branch_name`,
        /// always `main` instead of `refs/heads/main`.
        ///
        /// The returned reference is the one we track on the remote side for merging and pushing.
        /// Returns `None` if the remote reference was not found.
        /// May return an error if the reference is invalid.
        pub fn branch_remote_ref<'a>(
            &self,
            short_branch_name: impl Into<&'a BStr>,
        ) -> Option<Result<Cow<'_, FullNameRef>, ValidateNameError>> {
            self.config
                .resolved
                .string("branch", Some(short_branch_name.into()), "merge")
                .map(crate::config::tree::branch::Merge::try_into_fullrefname)
        }

        /// Returns the unvalidated name of the remote associated with the given `short_branch_name`,
        /// typically `main` instead of `refs/heads/main`.
        /// In some cases, the returned name will be an URL.
        /// Returns `None` if the remote was not found or if the name contained illformed UTF-8.
        ///
        /// See also [`Reference::remote_name()`][crate::Reference::remote_name()] for a more typesafe version
        /// to be used when a `Reference` is available.
        pub fn branch_remote_name<'a>(
            &self,
            short_branch_name: impl Into<&'a BStr>,
        ) -> Option<crate::remote::Name<'_>> {
            self.config
                .resolved
                .string("branch", Some(short_branch_name.into()), "remote")
                .and_then(|name| name.try_into().ok())
        }
    }
}

impl crate::Repository {
    pub(crate) fn filter_config_section(&self) -> fn(&gix_config::file::Metadata) -> bool {
        self.options
            .filter_config_section
            .unwrap_or(config::section::is_trusted)
    }

    fn subsection_names_of<'a>(&'a self, header_name: &'a str) -> BTreeSet<&'a str> {
        self.config
            .resolved
            .sections_by_name(header_name)
            .map(|it| {
                let filter = self.filter_config_section();
                it.filter(move |s| filter(s.meta()))
                    .filter_map(|section| section.header().subsection_name().and_then(|b| b.to_str().ok()))
                    .collect()
            })
            .unwrap_or_default()
    }
}