use std::ffi::{c_void, CStr, CString}; use std::path::Path; #[cfg(has_std__ffi__c_char)] use std::ffi::{c_char, c_int}; #[cfg(not(has_std__ffi__c_char))] #[allow(non_camel_case_types)] type c_char = i8; #[cfg(not(has_std__ffi__c_char))] #[allow(non_camel_case_types)] type c_int = i32; use libgit_sys::*; /// A ConfigSet is an in-memory cache for config-like files such as `.gitmodules` or `.gitconfig`. /// It does not support all config directives; notably, it will not process `include` or /// `includeIf` directives (but it will store them so that callers can choose whether and how to /// handle them). pub struct ConfigSet(*mut libgit_config_set); impl ConfigSet { /// Allocate a new ConfigSet pub fn new() -> Self { unsafe { ConfigSet(libgit_configset_alloc()) } } /// Load the given files into the ConfigSet; conflicting directives in later files will /// override those given in earlier files. pub fn add_files(&mut self, files: &[&Path]) { for file in files { let pstr = file.to_str().expect("Invalid UTF-8"); let rs = CString::new(pstr).expect("Couldn't convert to CString"); unsafe { libgit_configset_add_file(self.0, rs.as_ptr()); } } } /// Load the value for the given key and attempt to parse it as an i32. Dies with a fatal error /// if the value cannot be parsed. Returns None if the key is not present. pub fn get_int(&mut self, key: &str) -> Option { let key = CString::new(key).expect("Couldn't convert to CString"); let mut val: c_int = 0; unsafe { if libgit_configset_get_int(self.0, key.as_ptr(), &mut val as *mut c_int) != 0 { return None; } } Some(val.into()) } /// Clones the value for the given key. Dies with a fatal error if the value cannot be /// converted to a String. Returns None if the key is not present. pub fn get_string(&mut self, key: &str) -> Option { let key = CString::new(key).expect("Couldn't convert key to CString"); let mut val: *mut c_char = std::ptr::null_mut(); unsafe { if libgit_configset_get_string(self.0, key.as_ptr(), &mut val as *mut *mut c_char) != 0 { return None; } let borrowed_str = CStr::from_ptr(val); let owned_str = String::from(borrowed_str.to_str().expect("Couldn't convert val to str")); free(val as *mut c_void); // Free the xstrdup()ed pointer from the C side Some(owned_str) } } } impl Default for ConfigSet { fn default() -> Self { Self::new() } } impl Drop for ConfigSet { fn drop(&mut self) { unsafe { libgit_configset_free(self.0); } } } #[cfg(test)] mod tests { use super::*; #[test] fn load_configs_via_configset() { let mut cs = ConfigSet::new(); cs.add_files(&[ Path::new("testdata/config1"), Path::new("testdata/config2"), Path::new("testdata/config3"), ]); // ConfigSet retrieves correct value assert_eq!(cs.get_int("trace2.eventTarget"), Some(1)); // ConfigSet respects last config value set assert_eq!(cs.get_int("trace2.eventNesting"), Some(3)); // ConfigSet returns None for missing key assert_eq!(cs.get_string("foo.bar"), None); } }