use std::collections::HashMap;
use std::ffi::OsString;
use std::path::PathBuf;
use std::sync::Arc;
use time::{OffsetDateTime, UtcOffset};
use crate::change_id::{ChangeIdGenerator, OsChangeIdGenerator};
use crate::command_runner::{system_command_runner, CommandRunner};
use crate::diagnostics::{DiagnosticsHandle, NullDiagnostics};
use crate::git_date::tm::{local_tzoffset_with_tz, TzHhmm};
use crate::ident_resolve::IdentityEnv;
#[derive(Debug, Clone)]
pub struct Environment {
pub cwd: PathBuf,
pub git_dir: Option<String>,
pub git_work_tree: Option<String>,
pub git_ceiling_directories: Option<String>,
pub git_index_file: Option<String>,
pub git_namespace: Option<String>,
pub git_replace_ref_base: Option<String>,
pub git_no_replace_objects: bool,
pub git_alternate_object_directories: Option<String>,
pub git_object_directory: Option<String>,
pub git_prefix: Option<String>,
pub git_config_nosystem: Option<String>,
pub git_config_system: Option<String>,
pub git_config_global: Option<String>,
pub git_config: Option<String>,
pub git_config_parameters: Option<String>,
pub git_config_count: Option<String>,
pub git_config_pairs: Vec<(String, String)>,
pub home: Option<OsString>,
pub xdg_config_home: Option<String>,
pub userprofile: Option<OsString>,
pub homedrive: Option<OsString>,
pub homepath: Option<OsString>,
pub git_install_root: Option<OsString>,
pub git_exec_path: Option<OsString>,
pub pwd: Option<String>,
pub git_test_assume_different_owner: Option<String>,
pub grit_debug_safe_dir: bool,
pub git_trace_setup: Option<String>,
pub git_trace2_perf: Option<String>,
pub grit_invocation_cwd: Option<String>,
pub sudo_uid: Option<String>,
pub git_test_utf8_nfd_to_nfc: Option<String>,
pub git_test_no_write_rev_index: Option<String>,
pub git_test_reftable_autocompaction: Option<String>,
pub git_author_name: Option<String>,
pub git_author_email: Option<String>,
pub git_author_date: Option<String>,
pub git_committer_name: Option<String>,
pub git_committer_email: Option<String>,
pub git_committer_date: Option<String>,
pub tz: Option<String>,
pub user: Option<String>,
pub username: Option<String>,
pub program_files: Option<String>,
pub program_files_x86: Option<String>,
pub(crate) captured_vars: HashMap<String, OsString>,
}
impl Environment {
#[must_use]
pub fn empty() -> Self {
Self {
cwd: PathBuf::from("."),
git_dir: None,
git_work_tree: None,
git_ceiling_directories: None,
git_index_file: None,
git_namespace: None,
git_replace_ref_base: None,
git_no_replace_objects: false,
git_alternate_object_directories: None,
git_object_directory: None,
git_prefix: None,
git_config_nosystem: None,
git_config_system: None,
git_config_global: None,
git_config: None,
git_config_parameters: None,
git_config_count: None,
git_config_pairs: Vec::new(),
home: None,
xdg_config_home: None,
userprofile: None,
homedrive: None,
homepath: None,
git_install_root: None,
git_exec_path: None,
pwd: None,
git_test_assume_different_owner: None,
grit_debug_safe_dir: false,
git_trace_setup: None,
git_trace2_perf: None,
grit_invocation_cwd: None,
sudo_uid: None,
git_test_utf8_nfd_to_nfc: None,
git_test_no_write_rev_index: None,
git_test_reftable_autocompaction: None,
git_author_name: None,
git_author_email: None,
git_author_date: None,
git_committer_name: None,
git_committer_email: None,
git_committer_date: None,
tz: None,
user: None,
username: None,
program_files: None,
program_files_x86: None,
captured_vars: HashMap::new(),
}
}
#[must_use]
pub fn is_fetch_options_default(&self) -> bool {
let e = Self::empty();
self.cwd == e.cwd
&& self.git_dir == e.git_dir
&& self.git_work_tree == e.git_work_tree
&& self.git_ceiling_directories == e.git_ceiling_directories
&& self.git_index_file == e.git_index_file
&& self.git_namespace == e.git_namespace
&& self.git_replace_ref_base == e.git_replace_ref_base
&& self.git_no_replace_objects == e.git_no_replace_objects
&& self.git_alternate_object_directories == e.git_alternate_object_directories
&& self.git_object_directory == e.git_object_directory
&& self.git_prefix == e.git_prefix
&& self.git_config_nosystem == e.git_config_nosystem
&& self.git_config_system == e.git_config_system
&& self.git_config_global == e.git_config_global
&& self.git_config == e.git_config
&& self.git_config_parameters == e.git_config_parameters
&& self.git_config_count == e.git_config_count
&& self.git_config_pairs == e.git_config_pairs
&& self.home == e.home
&& self.xdg_config_home == e.xdg_config_home
&& self.userprofile == e.userprofile
&& self.homedrive == e.homedrive
&& self.homepath == e.homepath
&& self.git_install_root == e.git_install_root
&& self.git_exec_path == e.git_exec_path
&& self.pwd == e.pwd
&& self.git_test_assume_different_owner == e.git_test_assume_different_owner
&& self.grit_debug_safe_dir == e.grit_debug_safe_dir
&& self.git_trace_setup == e.git_trace_setup
&& self.git_trace2_perf == e.git_trace2_perf
&& self.grit_invocation_cwd == e.grit_invocation_cwd
&& self.sudo_uid == e.sudo_uid
&& self.git_test_utf8_nfd_to_nfc == e.git_test_utf8_nfd_to_nfc
&& self.git_test_no_write_rev_index == e.git_test_no_write_rev_index
&& self.git_test_reftable_autocompaction == e.git_test_reftable_autocompaction
&& self.git_author_name == e.git_author_name
&& self.git_author_email == e.git_author_email
&& self.git_author_date == e.git_author_date
&& self.git_committer_name == e.git_committer_name
&& self.git_committer_email == e.git_committer_email
&& self.git_committer_date == e.git_committer_date
&& self.tz == e.tz
&& self.user == e.user
&& self.username == e.username
&& self.program_files == e.program_files
&& self.program_files_x86 == e.program_files_x86
&& self.captured_vars == e.captured_vars
}
pub fn from_vars(vars: impl IntoIterator<Item = (OsString, OsString)>, cwd: PathBuf) -> Self {
let map: HashMap<String, OsString> = vars
.into_iter()
.map(|(k, v)| (k.to_string_lossy().into_owned(), v))
.collect();
let get = |key: &str| -> Option<String> {
map.get(key).and_then(|v| v.to_str().map(str::to_owned))
};
let get_os = |key: &str| -> Option<OsString> { map.get(key).cloned() };
let mut git_config_pairs = Vec::new();
if let Some(count_str) = get("GIT_CONFIG_COUNT") {
if let Ok(count) = count_str.parse::<usize>() {
for i in 0..count {
let key_var = format!("GIT_CONFIG_KEY_{i}");
let value_var = format!("GIT_CONFIG_VALUE_{i}");
if let (Some(k), Some(v)) = (get(&key_var), get(&value_var)) {
git_config_pairs.push((k, v));
}
}
}
}
Self {
cwd,
git_dir: get("GIT_DIR"),
git_work_tree: get("GIT_WORK_TREE"),
git_ceiling_directories: get("GIT_CEILING_DIRECTORIES"),
git_index_file: get("GIT_INDEX_FILE"),
git_namespace: get("GIT_NAMESPACE"),
git_replace_ref_base: get("GIT_REPLACE_REF_BASE"),
git_no_replace_objects: map.contains_key("GIT_NO_REPLACE_OBJECTS"),
git_alternate_object_directories: get("GIT_ALTERNATE_OBJECT_DIRECTORIES"),
git_object_directory: get("GIT_OBJECT_DIRECTORY"),
git_prefix: get("GIT_PREFIX"),
git_config_nosystem: get("GIT_CONFIG_NOSYSTEM"),
git_config_system: get("GIT_CONFIG_SYSTEM"),
git_config_global: get("GIT_CONFIG_GLOBAL"),
git_config: get("GIT_CONFIG"),
git_config_parameters: get("GIT_CONFIG_PARAMETERS"),
git_config_count: get("GIT_CONFIG_COUNT"),
git_config_pairs,
home: get_os("HOME"),
xdg_config_home: get("XDG_CONFIG_HOME"),
userprofile: get_os("USERPROFILE"),
homedrive: get_os("HOMEDRIVE"),
homepath: get_os("HOMEPATH"),
git_install_root: get_os("GIT_INSTALL_ROOT"),
git_exec_path: get_os("GIT_EXEC_PATH"),
pwd: get("PWD"),
git_test_assume_different_owner: get("GIT_TEST_ASSUME_DIFFERENT_OWNER"),
grit_debug_safe_dir: map.contains_key("GRIT_DEBUG_SAFE_DIR"),
git_trace_setup: get("GIT_TRACE_SETUP"),
git_trace2_perf: get("GIT_TRACE2_PERF"),
grit_invocation_cwd: get("GRIT_INVOCATION_CWD"),
sudo_uid: get("SUDO_UID"),
git_test_utf8_nfd_to_nfc: get("GIT_TEST_UTF8_NFD_TO_NFC"),
git_test_no_write_rev_index: get("GIT_TEST_NO_WRITE_REV_INDEX"),
git_test_reftable_autocompaction: get("GIT_TEST_REFTABLE_AUTOCOMPACTION"),
git_author_name: get("GIT_AUTHOR_NAME"),
git_author_email: get("GIT_AUTHOR_EMAIL"),
git_author_date: get("GIT_AUTHOR_DATE"),
git_committer_name: get("GIT_COMMITTER_NAME"),
git_committer_email: get("GIT_COMMITTER_EMAIL"),
git_committer_date: get("GIT_COMMITTER_DATE"),
tz: get("TZ"),
user: get("USER"),
username: get("USERNAME"),
program_files: get("ProgramFiles"),
program_files_x86: get("ProgramFiles(x86)"),
captured_vars: map,
}
}
#[must_use]
pub fn git_now_date_override(&self) -> Option<i64> {
let raw = self
.git_committer_date
.as_deref()
.or(self.git_author_date.as_deref())?;
raw.split_whitespace()
.next()
.and_then(|p| p.parse::<i64>().ok())
}
#[must_use]
pub fn reftable_autocompaction_enabled(&self) -> bool {
self.git_test_reftable_autocompaction
.as_deref()
.map(|value| value != "false")
.unwrap_or(true)
}
#[must_use]
pub fn test_assume_different_owner(&self) -> bool {
self.git_test_assume_different_owner
.as_deref()
.map(|v| {
let lower = v.to_ascii_lowercase();
v == "1" || lower == "true" || lower == "yes" || lower == "on"
})
.unwrap_or(false)
}
#[must_use]
pub fn capture_process() -> Self {
Self::from_vars(std::env::vars_os(), process_cwd_fallback())
}
#[must_use]
pub fn config_fingerprint(&self) -> Option<Vec<(String, Option<String>)>> {
const VARS: [&str; 8] = [
"GIT_CONFIG_NOSYSTEM",
"GIT_CONFIG_SYSTEM",
"GIT_CONFIG_GLOBAL",
"XDG_CONFIG_HOME",
"HOME",
"GIT_CONFIG",
"GIT_CONFIG_PARAMETERS",
"GIT_CONFIG_COUNT",
];
let mut fp: Vec<(String, Option<String>)> = VARS
.iter()
.map(|name| {
let val = match *name {
"GIT_CONFIG_NOSYSTEM" => self.git_config_nosystem.clone(),
"GIT_CONFIG_SYSTEM" => self.git_config_system.clone(),
"GIT_CONFIG_GLOBAL" => self.git_config_global.clone(),
"XDG_CONFIG_HOME" => self.xdg_config_home.clone(),
"HOME" => self
.home
.as_ref()
.and_then(|h| h.to_str().map(str::to_owned)),
"GIT_CONFIG" => self.git_config.clone(),
"GIT_CONFIG_PARAMETERS" => self.git_config_parameters.clone(),
"GIT_CONFIG_COUNT" => self.git_config_count.clone(),
_ => None,
};
((*name).to_owned(), val)
})
.collect();
if let Some(count_str) = &self.git_config_count {
const MAX_TRACKED: usize = 256;
match count_str.parse::<usize>() {
Ok(n) if n <= MAX_TRACKED => {
for i in 0..n {
fp.push((
format!("GIT_CONFIG_KEY_{i}"),
self.git_config_pairs.get(i).map(|p| p.0.clone()),
));
fp.push((
format!("GIT_CONFIG_VALUE_{i}"),
self.git_config_pairs.get(i).map(|p| p.1.clone()),
));
}
}
Ok(_) => return None,
Err(_) => {}
}
}
fp.push((
"GRIT_ENV_CWD".to_owned(),
self.cwd.to_str().map(str::to_owned),
));
fp.push(("GRIT_ENV_PWD".to_owned(), self.pwd.clone()));
Some(fp)
}
#[must_use]
pub fn var(&self, key: &str) -> Option<String> {
match key {
"GIT_DIR" => self.git_dir.clone(),
"GIT_WORK_TREE" => self.git_work_tree.clone(),
"GIT_CEILING_DIRECTORIES" => self.git_ceiling_directories.clone(),
"GIT_INDEX_FILE" => self.git_index_file.clone(),
"GIT_NAMESPACE" => self.git_namespace.clone(),
"GIT_REPLACE_REF_BASE" => self.git_replace_ref_base.clone(),
"GIT_CONFIG_NOSYSTEM" => self.git_config_nosystem.clone(),
"GIT_CONFIG_SYSTEM" => self.git_config_system.clone(),
"GIT_CONFIG_GLOBAL" => self.git_config_global.clone(),
"GIT_CONFIG" => self.git_config.clone(),
"GIT_CONFIG_PARAMETERS" => self.git_config_parameters.clone(),
"GIT_CONFIG_COUNT" => self.git_config_count.clone(),
"GIT_PREFIX" => self.git_prefix.clone(),
"GIT_TEST_UTF8_NFD_TO_NFC" => self.git_test_utf8_nfd_to_nfc.clone(),
"GIT_TEST_REFTABLE_AUTOCOMPACTION" => self.git_test_reftable_autocompaction.clone(),
"GIT_AUTHOR_NAME" => self.git_author_name.clone(),
"GIT_AUTHOR_EMAIL" => self.git_author_email.clone(),
"GIT_AUTHOR_DATE" => self.git_author_date.clone(),
"GIT_COMMITTER_NAME" => self.git_committer_name.clone(),
"GIT_COMMITTER_EMAIL" => self.git_committer_email.clone(),
"GIT_COMMITTER_DATE" => self.git_committer_date.clone(),
"TZ" => self.tz.clone(),
"USER" => self.user.clone(),
"USERNAME" => self.username.clone(),
"XDG_CONFIG_HOME" => self.xdg_config_home.clone(),
"HOME" => self
.home
.as_ref()
.and_then(|h| h.to_str().map(str::to_owned)),
"USERPROFILE" => self
.userprofile
.as_ref()
.and_then(|h| h.to_str().map(str::to_owned)),
"HOMEDRIVE" => self
.homedrive
.as_ref()
.and_then(|h| h.to_str().map(str::to_owned)),
"HOMEPATH" => self
.homepath
.as_ref()
.and_then(|h| h.to_str().map(str::to_owned)),
key if key.starts_with("GIT_CONFIG_KEY_") => {
let idx: usize = key["GIT_CONFIG_KEY_".len()..].parse().ok()?;
self.git_config_pairs.get(idx).map(|p| p.0.clone())
}
key if key.starts_with("GIT_CONFIG_VALUE_") => {
let idx: usize = key["GIT_CONFIG_VALUE_".len()..].parse().ok()?;
self.git_config_pairs.get(idx).map(|p| p.1.clone())
}
_ => self
.captured_vars
.get(key)
.and_then(|v| v.to_str().map(str::to_owned)),
}
}
#[must_use]
pub fn var_os(&self, key: &str) -> Option<OsString> {
if key == "HOME" {
return self.home.clone();
}
if key == "GIT_NO_REPLACE_OBJECTS" && self.git_no_replace_objects {
return Some(OsString::new());
}
if key == "GIT_INSTALL_ROOT" {
return self.git_install_root.clone();
}
if key == "GIT_EXEC_PATH" {
return self.git_exec_path.clone();
}
if let Some(v) = self.var(key) {
return Some(OsString::from(v));
}
self.captured_vars.get(key).cloned()
}
#[must_use]
pub fn discovery_cwd(&self) -> PathBuf {
self.cwd.canonicalize().unwrap_or_else(|_| self.cwd.clone())
}
#[must_use]
pub fn subprocess_environment(&self) -> Vec<(OsString, OsString)> {
let mut set = Vec::new();
subprocess_push_str(&mut set, "GIT_DIR", self.git_dir.as_deref());
subprocess_push_str(&mut set, "GIT_WORK_TREE", self.git_work_tree.as_deref());
subprocess_push_str(
&mut set,
"GIT_CEILING_DIRECTORIES",
self.git_ceiling_directories.as_deref(),
);
subprocess_push_str(&mut set, "GIT_INDEX_FILE", self.git_index_file.as_deref());
subprocess_push_str(&mut set, "GIT_NAMESPACE", self.git_namespace.as_deref());
subprocess_push_str(
&mut set,
"GIT_REPLACE_REF_BASE",
self.git_replace_ref_base.as_deref(),
);
subprocess_push_str(
&mut set,
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
self.git_alternate_object_directories.as_deref(),
);
subprocess_push_str(
&mut set,
"GIT_OBJECT_DIRECTORY",
self.git_object_directory.as_deref(),
);
subprocess_push_str(&mut set, "GIT_PREFIX", self.git_prefix.as_deref());
subprocess_push_str(
&mut set,
"GIT_CONFIG_NOSYSTEM",
self.git_config_nosystem.as_deref(),
);
subprocess_push_str(
&mut set,
"GIT_CONFIG_SYSTEM",
self.git_config_system.as_deref(),
);
subprocess_push_str(
&mut set,
"GIT_CONFIG_GLOBAL",
self.git_config_global.as_deref(),
);
subprocess_push_str(&mut set, "GIT_CONFIG", self.git_config.as_deref());
subprocess_push_str(
&mut set,
"GIT_CONFIG_PARAMETERS",
self.git_config_parameters.as_deref(),
);
subprocess_push_str(
&mut set,
"GIT_CONFIG_COUNT",
self.git_config_count.as_deref(),
);
for (i, (k, v)) in self.git_config_pairs.iter().enumerate() {
set.push((
OsString::from(format!("GIT_CONFIG_KEY_{i}")),
OsString::from(k),
));
set.push((
OsString::from(format!("GIT_CONFIG_VALUE_{i}")),
OsString::from(v),
));
}
subprocess_push_os(&mut set, "HOME", self.home.as_ref());
subprocess_push_str(&mut set, "XDG_CONFIG_HOME", self.xdg_config_home.as_deref());
subprocess_push_os(&mut set, "USERPROFILE", self.userprofile.as_ref());
subprocess_push_os(&mut set, "HOMEDRIVE", self.homedrive.as_ref());
subprocess_push_os(&mut set, "HOMEPATH", self.homepath.as_ref());
subprocess_push_os(&mut set, "GIT_INSTALL_ROOT", self.git_install_root.as_ref());
subprocess_push_os(&mut set, "GIT_EXEC_PATH", self.git_exec_path.as_ref());
subprocess_push_str(&mut set, "PWD", self.pwd.as_deref());
subprocess_push_str(
&mut set,
"GIT_TEST_ASSUME_DIFFERENT_OWNER",
self.git_test_assume_different_owner.as_deref(),
);
subprocess_push_str(&mut set, "GIT_TRACE_SETUP", self.git_trace_setup.as_deref());
subprocess_push_str(&mut set, "GIT_TRACE2_PERF", self.git_trace2_perf.as_deref());
subprocess_push_str(
&mut set,
"GRIT_INVOCATION_CWD",
self.grit_invocation_cwd.as_deref(),
);
subprocess_push_str(&mut set, "SUDO_UID", self.sudo_uid.as_deref());
subprocess_push_str(
&mut set,
"GIT_TEST_UTF8_NFD_TO_NFC",
self.git_test_utf8_nfd_to_nfc.as_deref(),
);
subprocess_push_str(
&mut set,
"GIT_TEST_NO_WRITE_REV_INDEX",
self.git_test_no_write_rev_index.as_deref(),
);
subprocess_push_str(&mut set, "ProgramFiles", self.program_files.as_deref());
subprocess_push_str(
&mut set,
"ProgramFiles(x86)",
self.program_files_x86.as_deref(),
);
if self.git_no_replace_objects {
set.push((OsString::from("GIT_NO_REPLACE_OBJECTS"), OsString::new()));
}
if self.grit_debug_safe_dir {
set.push((OsString::from("GRIT_DEBUG_SAFE_DIR"), OsString::new()));
}
set
}
#[must_use]
pub fn home_dir(&self) -> Option<PathBuf> {
if let Some(home) = self.home.as_ref().filter(|h| !h.is_empty()) {
return Some(PathBuf::from(home));
}
#[cfg(windows)]
{
if let (Some(drive), Some(path)) = (
self.homedrive.as_ref().filter(|d| !d.is_empty()),
self.homepath.as_ref().filter(|p| !p.is_empty()),
) {
let mut combined = drive.clone();
combined.push(path);
return Some(PathBuf::from(combined));
}
if let Some(profile) = self.userprofile.as_ref().filter(|p| !p.is_empty()) {
return Some(PathBuf::from(profile));
}
}
None
}
#[must_use]
pub fn default_xdg_config_git_file(&self, name: &str) -> Option<PathBuf> {
if let Some(xdg) = self.xdg_config_home.as_deref().filter(|s| !s.is_empty()) {
return Some(PathBuf::from(xdg).join("git").join(name));
}
self.home_dir()
.map(|home| home.join(".config").join("git").join(name))
}
}
impl IdentityEnv for Environment {
fn var(&self, key: &str) -> Option<String> {
Environment::var(self, key)
}
fn var_os(&self, key: &str) -> Option<OsString> {
Environment::var_os(self, key)
}
}
#[must_use]
pub fn wall_clock_unix_timestamp_nanos() -> u64 {
u64::try_from(OffsetDateTime::now_utc().unix_timestamp_nanos()).unwrap_or(u64::MAX)
}
#[must_use]
pub fn offset_from_epoch_and_tz(epoch: i64, tz: Option<&str>) -> OffsetDateTime {
let tz_hhmm = local_tzoffset_with_tz(epoch as u64, tz);
let offset = utc_offset_from_tz_hhmm(tz_hhmm).unwrap_or(UtcOffset::UTC);
OffsetDateTime::from_unix_timestamp(epoch)
.unwrap_or(OffsetDateTime::UNIX_EPOCH)
.to_offset(offset)
}
fn utc_offset_from_tz_hhmm(tz: TzHhmm) -> Option<UtcOffset> {
let sign: i32 = if tz < 0 { -1 } else { 1 };
let abs = tz.unsigned_abs();
let hours = (abs / 100) as i32;
let minutes = (abs % 100) as i32;
let seconds = sign * (hours * 3600 + minutes * 60);
UtcOffset::from_whole_seconds(seconds).ok()
}
fn subprocess_push_str(out: &mut Vec<(OsString, OsString)>, key: &str, val: Option<&str>) {
if let Some(v) = val {
out.push((OsString::from(key), OsString::from(v)));
}
}
fn subprocess_push_os(out: &mut Vec<(OsString, OsString)>, key: &str, val: Option<&OsString>) {
if let Some(v) = val {
out.push((OsString::from(key), v.clone()));
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct RepositoryOptions {
pub environment: Environment,
pub command_runner: Arc<dyn CommandRunner>,
pub change_id_generator: Arc<dyn ChangeIdGenerator>,
pub diagnostics: DiagnosticsHandle,
pub network_trace: bool,
pub test_assume_different_owner: bool,
pub force_split_index: bool,
pub reference_unix_time: Option<i64>,
pub thread_policy: crate::parallel::ThreadPolicy,
pub work_tree: Option<std::path::PathBuf>,
pub validate_repository_format: bool,
pub open_for_serving: bool,
}
impl std::fmt::Debug for RepositoryOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RepositoryOptions")
.field("environment", &self.environment)
.field("command_runner", &"<CommandRunner>")
.field("network_trace", &self.network_trace)
.finish_non_exhaustive()
}
}
impl Default for RepositoryOptions {
fn default() -> Self {
Self {
environment: Environment::empty(),
command_runner: system_command_runner(),
change_id_generator: Arc::new(OsChangeIdGenerator),
diagnostics: Arc::new(NullDiagnostics),
network_trace: false,
test_assume_different_owner: false,
force_split_index: false,
reference_unix_time: None,
thread_policy: crate::parallel::ThreadPolicy::default(),
work_tree: None,
validate_repository_format: true,
open_for_serving: false,
}
}
}
impl RepositoryOptions {
#[must_use]
pub fn empty() -> Self {
Self::default()
}
#[must_use]
pub fn with_environment(environment: Environment) -> Self {
Self {
environment,
..Self::default()
}
}
#[must_use]
pub fn with_command_runner(mut self, runner: Arc<dyn CommandRunner>) -> Self {
self.command_runner = runner;
self
}
#[must_use]
pub fn with_change_id_generator(mut self, generator: Arc<dyn ChangeIdGenerator>) -> Self {
self.change_id_generator = generator;
self
}
#[must_use]
pub fn with_work_tree(mut self, work_tree: impl Into<std::path::PathBuf>) -> Self {
self.work_tree = Some(work_tree.into());
self
}
#[must_use]
pub fn without_format_validation(mut self) -> Self {
self.validate_repository_format = false;
self
}
#[must_use]
pub fn for_serving(mut self) -> Self {
self.open_for_serving = true;
self
}
#[must_use]
pub fn with_diagnostics(mut self, diagnostics: DiagnosticsHandle) -> Self {
self.diagnostics = diagnostics;
self
}
#[must_use]
pub fn with_network_trace(mut self, network_trace: bool) -> Self {
self.network_trace = network_trace;
self
}
}
fn process_cwd_fallback() -> PathBuf {
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_fetch_options_default_matches_empty() {
assert!(Environment::empty().is_fetch_options_default());
let mut env = Environment::from_vars(
[(
OsString::from("GIT_SSH_COMMAND"),
OsString::from("/bin/wrap"),
)],
PathBuf::from("."),
);
assert!(!env.is_fetch_options_default());
env.captured_vars.clear();
assert!(env.is_fetch_options_default());
}
#[test]
fn from_vars_parses_config_count_pairs() {
let vars = [
("GIT_CONFIG_COUNT", "1"),
("GIT_CONFIG_KEY_0", "user.name"),
("GIT_CONFIG_VALUE_0", "Ada"),
]
.into_iter()
.map(|(k, v)| (OsString::from(k), OsString::from(v)));
let env = Environment::from_vars(vars, PathBuf::from("/tmp"));
assert_eq!(env.git_config_count.as_deref(), Some("1"));
assert_eq!(
env.git_config_pairs,
vec![("user.name".to_owned(), "Ada".to_owned())]
);
}
#[test]
fn config_fingerprint_tracks_global_override() {
let mut env = Environment::empty();
env.git_config_global = Some("/tmp/global.cfg".to_owned());
let fp = env.config_fingerprint().expect("fingerprint");
assert!(fp
.iter()
.any(|(k, v)| k == "GIT_CONFIG_GLOBAL" && v.as_deref() == Some("/tmp/global.cfg")));
}
#[test]
fn default_xdg_config_git_file_prefers_xdg() {
let mut env = Environment::empty();
env.home = Some(OsString::from("/home/u"));
env.xdg_config_home = Some("/xdg".to_owned());
assert_eq!(
env.default_xdg_config_git_file("ignore"),
Some(PathBuf::from("/xdg/git/ignore"))
);
}
#[test]
fn default_xdg_config_git_file_falls_back_to_home_config() {
let mut env = Environment::empty();
env.home = Some(OsString::from("/home/u"));
assert_eq!(
env.default_xdg_config_git_file("ignore"),
Some(PathBuf::from("/home/u/.config/git/ignore"))
);
}
}