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(())
+}