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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
use std::{collections::BTreeSet, ffi::OsString};
use crate::{bstr::ByteSlice, config, config::tree::Core};
/// General Configuration
impl crate::Repository {
/// Return the compression level used when writing loose objects.
pub fn loose_compression(&self) -> gix_zlib::Compression {
self.config.loose_compression
}
/// Return the effective compression level used when writing pack entries.
pub fn pack_compression(&self) -> Result<gix_zlib::Compression, config::Error> {
config::cache::access::pack_compression(
&self.config.resolved,
self.config.lenient_config,
self.filter_config_section(),
)
}
/// Return a snapshot of the configuration as seen upon opening the repository.
///
/// Use [`reload()`](Self::reload()) to refresh it from disk.
pub fn config_snapshot(&self) -> config::Snapshot<'_> {
config::Snapshot { repo: self }
}
/// Return the editor program selected by Git's precedence rules.
///
/// `GIT_EDITOR` takes precedence over `core.editor`. If the terminal isn't dumb, `VISUAL` is considered next,
/// followed by `EDITOR`. If none are set, `vi` is returned unless `TERM` is unset or `dumb`, in which case there
/// is no usable editor.
pub fn editor(&self) -> Option<OsString> {
if let Some(editor) = self.config_snapshot().trusted_program(Core::EDITOR) {
return Some(editor);
}
let terminal_is_dumb = std::env::var_os("TERM").is_none_or(|terminal| terminal == "dumb");
if !terminal_is_dumb {
if let Some(editor) = std::env::var_os("VISUAL") {
return Some(editor);
}
}
if let Some(editor) = std::env::var_os("EDITOR") {
return Some(editor);
}
(!terminal_is_dumb).then(|| "vi".into())
}
/// Resolve all Git configuration needed to sign a commit with [`gix_object::Commit::sign()`].
///
/// The returned plumbing options may be adjusted before use, for example to disable GPG pinentry by adding
/// `--pinentry-mode=error` to `program_arguments`.
#[cfg(feature = "command")]
pub fn commit_signing_options(
&self,
) -> Result<gix_object::signature::sign::Options, crate::commit::sign::options::Error> {
crate::commit::sign::signing_options(self)
}
/// Resolve all Git configuration needed to sign a commit if `commit.gpgSign` enables signing.
///
/// If signing is disabled, signer-specific configuration isn't resolved or validated.
#[cfg(feature = "command")]
pub fn commit_signing_options_if_enabled(
&self,
) -> Result<Option<gix_object::signature::sign::Options>, crate::commit::sign::options::Error> {
crate::commit::sign::signing_options_if_enabled(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 this instance
/// of the [`Repository`](crate::Repository). Use [`reload()`](Self::reload()) to discard them and
/// refresh the snapshot from disk.
///
/// Values used to locate repository files are fixed when the repository is opened and aren't reapplied by
/// committing changes here. This includes `core.worktree` and `gitoxide.core.indexFile`; `GIT_DIR` likewise
/// cannot retarget an existing repository. Reload after changing persisted configuration or open another
/// repository to change these locations.
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
}
/// Return the big-file threshold above which Git will not perform a diff anymore or try to delta-diff packs,
/// as configured by `core.bigFileThreshold`, or the default value.
pub fn big_file_threshold(&self) -> Result<u64, config::unsigned_integer::Error> {
self.config.big_file_threshold()
}
/// Create a low-level parser for ignore patterns, for instance for use in [`excludes()`](crate::Repository::excludes()).
///
/// Depending on the configuration, precious-file parsing in `.gitignore-files` is supported.
/// This means that `$` prefixed files will be interpreted as precious, which is a backwards-incompatible change.
#[cfg(feature = "excludes")]
pub fn ignore_pattern_parser(&self) -> Result<gix_ignore::search::Ignore, config::boolean::Error> {
self.config.ignore_pattern_parser()
}
/// Obtain options for use when connecting via `ssh`.
#[cfg(feature = "blocking-network-client")]
pub fn ssh_connect_options(
&self,
) -> Result<gix_protocol::transport::client::blocking_io::ssh::connect::Options, config::ssh_connect_options::Error>
{
use crate::config::{
cache::util::ApplyLeniency,
tree::{Core, Ssh, gitoxide},
};
let config = &self.config.resolved;
let mut trusted = self.filter_config_section();
let mut fallback_active = false;
let ssh_command = config
.string_filter(Core::SSH_COMMAND, &mut trusted)
.or_else(|| {
fallback_active = true;
config.string_filter(gitoxide::Ssh::COMMAND_WITHOUT_SHELL_FALLBACK, &mut trusted)
})
.map(|cmd| gix_path::from_bstr(cmd).into_owned().into());
let opts = gix_protocol::transport::client::blocking_io::ssh::connect::Options {
disallow_shell: fallback_active,
command: ssh_command,
kind: config
.string_filter("ssh.variant", &mut trusted)
.and_then(|variant| Ssh::VARIANT.try_into_variant(variant).transpose())
.transpose()
.with_leniency(self.options.lenient_config)?,
};
Ok(opts)
}
/// Return the context to be passed to any spawned program that is supposed to interact with the repository, like
/// hooks or filters.
#[cfg(feature = "attributes")]
pub fn command_context(&self) -> Result<gix_command::Context, config::command_context::Error> {
use crate::config::{cache::util::ApplyLeniency, tree::gitoxide};
let pathspec_boolean = |key: &'static config::tree::keys::Boolean| {
key.enrich_error(self.config.resolved.boolean(key))
.with_leniency(self.config.lenient_config)
};
Ok(gix_command::Context {
stderr: {
gitoxide::Core::EXTERNAL_COMMAND_STDERR
.enrich_error(self.config.resolved.boolean(gitoxide::Core::EXTERNAL_COMMAND_STDERR))
.with_leniency(self.config.lenient_config)?
.unwrap_or(true)
.into()
},
git_dir: self.git_dir().to_owned().into(),
worktree_dir: self.workdir().map(ToOwned::to_owned),
no_replace_objects: config::shared::is_replace_refs_enabled(
&self.config.resolved,
self.config.lenient_config,
self.filter_config_section(),
)?
.map(|enabled| !enabled),
ref_namespace: self.refs.namespace.as_ref().map(|ns| ns.as_bstr().to_owned()),
literal_pathspecs: pathspec_boolean(&gitoxide::Pathspec::LITERAL)?,
glob_pathspecs: pathspec_boolean(&gitoxide::Pathspec::GLOB)?
.or(pathspec_boolean(&gitoxide::Pathspec::NOGLOB)?),
icase_pathspecs: pathspec_boolean(&gitoxide::Pathspec::ICASE)?,
})
}
/// The kind of object hash the repository is configured to use.
pub fn object_hash(&self) -> gix_hash::Kind {
self.config.object_hash
}
/// Return the algorithm to perform diffs or merges with.
///
/// In case of merges, a diff is performed under the hood in order to learn which hunks need merging.
#[cfg(feature = "blob-diff")]
pub fn diff_algorithm(&self) -> Result<gix_diff::blob::Algorithm, config::diff::algorithm::Error> {
self.config.diff_algorithm()
}
}
mod branch;
mod remote;
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
mod transport;
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_str_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()
}
}