use std::path::PathBuf;
use super::Config;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ConfigScope {
System,
Global,
Local,
Worktree,
Environment,
Command,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ConfigFile {
pub path: PathBuf,
pub scope: ConfigScope,
pub optional: bool,
}
impl ConfigFile {
pub fn new(path: PathBuf, scope: ConfigScope) -> Self {
Self {
path,
scope,
optional: false,
}
}
#[must_use]
pub fn with_path(mut self, path: PathBuf) -> Self {
self.path = path;
self
}
#[must_use]
pub fn with_scope(mut self, scope: ConfigScope) -> Self {
self.scope = scope;
self
}
#[must_use]
pub fn with_optional(mut self, optional: bool) -> Self {
self.optional = optional;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceLocation {
pub path: Option<PathBuf>,
pub line: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Origin {
pub scope: ConfigScope,
pub location: SourceLocation,
pub included_from: Vec<SourceLocation>,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct IncludeContext {
pub home: Option<PathBuf>,
pub user_homes: Vec<(Vec<u8>, PathBuf)>,
pub prefix: Option<PathBuf>,
pub git_dirs: Vec<PathBuf>,
pub branch: Option<Vec<u8>>,
}
impl IncludeContext {
#[must_use]
pub fn with_home(mut self, home: Option<PathBuf>) -> Self {
self.home = home;
self
}
#[must_use]
pub fn with_user_homes(mut self, user_homes: Vec<(Vec<u8>, PathBuf)>) -> Self {
self.user_homes = user_homes;
self
}
#[must_use]
pub fn with_prefix(mut self, prefix: Option<PathBuf>) -> Self {
self.prefix = prefix;
self
}
#[must_use]
pub fn with_git_dirs(mut self, git_dirs: Vec<PathBuf>) -> Self {
self.git_dirs = git_dirs;
self
}
#[must_use]
pub fn with_branch(mut self, branch: Option<Vec<u8>>) -> Self {
self.branch = branch;
self
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct ResolveLimits {
pub depth: usize,
pub bytes: usize,
pub entries: usize,
pub match_cells: usize,
}
impl ResolveLimits {
#[must_use]
pub fn with_depth(mut self, depth: usize) -> Self {
self.depth = depth;
self
}
#[must_use]
pub fn with_bytes(mut self, bytes: usize) -> Self {
self.bytes = bytes;
self
}
#[must_use]
pub fn with_entries(mut self, entries: usize) -> Self {
self.entries = entries;
self
}
#[must_use]
pub fn with_match_cells(mut self, match_cells: usize) -> Self {
self.match_cells = match_cells;
self
}
}
impl Default for ResolveLimits {
fn default() -> Self {
Self {
depth: 10,
bytes: 16 * 1024 * 1024,
entries: 100_000,
match_cells: 1_000_000,
}
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct ConfigInputs {
pub files: Vec<ConfigFile>,
pub context: IncludeContext,
pub environment: Option<Config>,
pub command: Option<Config>,
pub limits: ResolveLimits,
}
impl ConfigInputs {
#[must_use]
pub fn with_files(mut self, files: Vec<ConfigFile>) -> Self {
self.files = files;
self
}
#[must_use]
pub fn with_context(mut self, context: IncludeContext) -> Self {
self.context = context;
self
}
#[must_use]
pub fn with_environment(mut self, environment: Option<Config>) -> Self {
self.environment = environment;
self
}
#[must_use]
pub fn with_command(mut self, command: Option<Config>) -> Self {
self.command = command;
self
}
#[must_use]
pub fn with_limits(mut self, limits: ResolveLimits) -> Self {
self.limits = limits;
self
}
}
impl ConfigInputs {
pub fn from_environment(
system: Option<PathBuf>,
mut get: impl FnMut(&str) -> Option<std::ffi::OsString>,
max_pairs: usize,
) -> Result<Self, super::ConfigError> {
let mut inputs = Self::default();
let home = get("HOME").map(PathBuf::from);
inputs.context.home = home.clone();
let no_system = get("GIT_CONFIG_NOSYSTEM");
let no_system = match no_system {
None => false,
Some(value) => {
#[cfg(unix)]
let bytes = {
use std::os::unix::ffi::OsStrExt;
value.as_bytes()
};
#[cfg(not(unix))]
let bytes = value
.to_str()
.ok_or(super::ConfigError {
line: 1,
reason: "invalid GIT_CONFIG_NOSYSTEM",
})?
.as_bytes();
super::boolean(Some(bytes)).ok_or(super::ConfigError {
line: 1,
reason: "invalid GIT_CONFIG_NOSYSTEM",
})?
}
};
let system = get("GIT_CONFIG_SYSTEM").map(PathBuf::from).or(system);
if !no_system && let Some(path) = system {
inputs.files.push(ConfigFile {
path,
scope: ConfigScope::System,
optional: true,
});
}
if let Some(path) = get("GIT_CONFIG_GLOBAL") {
inputs.files.push(ConfigFile {
path: path.into(),
scope: ConfigScope::Global,
optional: true,
});
} else {
let xdg = get("XDG_CONFIG_HOME")
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.or_else(|| home.as_ref().map(|home| home.join(".config")));
if let Some(xdg) = xdg {
inputs.files.push(ConfigFile {
path: xdg.join("git/config"),
scope: ConfigScope::Global,
optional: true,
});
}
if let Some(home) = home {
inputs.files.push(ConfigFile {
path: home.join(".gitconfig"),
scope: ConfigScope::Global,
optional: true,
});
}
}
#[cfg(not(unix))]
let mut invalid_encoding = false;
inputs.environment = Some(Config::from_environment(
|key| {
get(key).map(|value| {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStringExt;
value.into_vec()
}
#[cfg(not(unix))]
{
match value.into_string() {
Ok(value) => value.into_bytes(),
Err(_) => {
invalid_encoding = true;
Vec::new()
}
}
}
})
},
max_pairs,
)?);
#[cfg(not(unix))]
if invalid_encoding {
return Err(super::ConfigError {
line: 1,
reason: "environment pair is not Unicode",
});
}
Ok(inputs)
}
}