diff --git a/src/lib.rs b/src/lib.rs index 2a3444855b..76e5e748ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -363,7 +363,7 @@ pub enum BranchType { #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum ConfigLevel { /// System-wide on Windows, for compatibility with portable git - ProgramData, + ProgramData = 1, /// System-wide configuration file, e.g. /etc/gitconfig System, /// XDG-compatible configuration file, e.g. ~/.config/git/config @@ -375,7 +375,7 @@ pub enum ConfigLevel { /// Application specific configuration file App, /// Highest level available - Highest, + Highest = -1, } /// Merge file favor options for `MergeOptions` instruct the file-level diff --git a/src/opts.rs b/src/opts.rs index 71fa4eadf4..ca36804e92 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -1,6 +1,70 @@ //! Bindings to libgit2's git_libgit2_opts function. -use crate::raw; +use std::ffi::CString; + +use crate::util::Binding; +use crate::{call, raw, Buf, ConfigLevel, Error, IntoCString}; + +/// Set the search path for a level of config data. The search path applied to +/// shared attributes and ignore files, too. +/// +/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`], +/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`]. +/// +/// `path` lists directories delimited by `GIT_PATH_LIST_SEPARATOR`. +/// Use magic path `$PATH` to include the old value of the path +/// (if you want to prepend or append, for instance). +/// +/// This function is unsafe as it mutates the global state but cannot guarantee +/// thread-safety. It needs to be externally synchronized with calls to access +/// the global state. +pub unsafe fn set_search_path<P>(level: ConfigLevel, path: P) -> Result<(), Error> +where + P: IntoCString, +{ + call::c_try(raw::git_libgit2_opts( + raw::GIT_OPT_SET_SEARCH_PATH as libc::c_int, + level as libc::c_int, + path.into_c_string()?.as_ptr(), + ))?; + Ok(()) +} + +/// Reset the search path for a given level of config data to the default +/// (generally based on environment variables). +/// +/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`], +/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`]. +/// +/// This function is unsafe as it mutates the global state but cannot guarantee +/// thread-safety. It needs to be externally synchronized with calls to access +/// the global state. +pub unsafe fn reset_search_path(level: ConfigLevel) -> Result<(), Error> { + call::c_try(raw::git_libgit2_opts( + raw::GIT_OPT_SET_SEARCH_PATH as libc::c_int, + level as libc::c_int, + core::ptr::null::<u8>(), + ))?; + Ok(()) +} + +/// Get the search path for a given level of config data. +/// +/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`], +/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`]. +/// +/// This function is unsafe as it mutates the global state but cannot guarantee +/// thread-safety. It needs to be externally synchronized with calls to access +/// the global state. +pub unsafe fn get_search_path(level: ConfigLevel) -> Result<CString, Error> { + let buf = Buf::new(); + call::c_try(raw::git_libgit2_opts( + raw::GIT_OPT_GET_SEARCH_PATH as libc::c_int, + level as libc::c_int, + buf.raw(), + ))?; + buf.into_c_string() +} /// Controls whether or not libgit2 will verify when writing an object that all /// objects it references are valid. Enabled by default, but disabling this can @@ -37,8 +101,10 @@ pub fn strict_hash_verification(enabled: bool) { #[cfg(test)] mod test { + use super::*; + #[test] fn smoke() { - super::strict_hash_verification(false); + strict_hash_verification(false); } } diff --git a/tests/global_state.rs b/tests/global_state.rs new file mode 100644 index 0000000000..192acdbd3a --- /dev/null +++ b/tests/global_state.rs @@ -0,0 +1,47 @@ +//! Test for some global state set up by libgit2's `git_libgit2_init` function +//! that need to be synchronized within a single process. + +use git2::opts; +use git2::{ConfigLevel, IntoCString}; + +// Test for mutating configuration file search path which is set during +// initialization in libgit2's `git_sysdir_global_init` function. +#[test] +fn search_path() -> Result<(), Box<dyn std::error::Error>> { + use std::env::join_paths; + + let path = "fake_path"; + let original = unsafe { opts::get_search_path(ConfigLevel::Global) }; + assert_ne!(original, Ok(path.into_c_string()?)); + + // Set + unsafe { + opts::set_search_path(ConfigLevel::Global, &path)?; + } + assert_eq!( + unsafe { opts::get_search_path(ConfigLevel::Global) }, + Ok(path.into_c_string()?) + ); + + // Append + let paths = join_paths(["$PATH", path].iter())?; + let expected_paths = join_paths([path, path].iter())?.into_c_string()?; + unsafe { + opts::set_search_path(ConfigLevel::Global, paths)?; + } + assert_eq!( + unsafe { opts::get_search_path(ConfigLevel::Global) }, + Ok(expected_paths) + ); + + // Reset + unsafe { + opts::reset_search_path(ConfigLevel::Global)?; + } + assert_eq!( + unsafe { opts::get_search_path(ConfigLevel::Global) }, + original + ); + + Ok(()) +}