1use std::ffi::CString;
2use std::marker;
3use std::path::{Path, PathBuf};
4use std::ptr;
5use std::str;
6
7use crate::util::{self, Binding};
8use crate::{raw, Buf, ConfigLevel, Error, IntoCString};
9
10pub struct Config {
12    raw: *mut raw::git_config,
13}
14
15pub struct ConfigEntry<'cfg> {
19    raw: *mut raw::git_config_entry,
20    _marker: marker::PhantomData<&'cfg Config>,
21    owned: bool,
22}
23
24pub struct ConfigEntries<'cfg> {
56    raw: *mut raw::git_config_iterator,
57    current: Option<ConfigEntry<'cfg>>,
58    _marker: marker::PhantomData<&'cfg Config>,
59}
60
61impl Config {
62    pub fn new() -> Result<Config, Error> {
67        crate::init();
68        let mut raw = ptr::null_mut();
69        unsafe {
70            try_call!(raw::git_config_new(&mut raw));
71            Ok(Binding::from_raw(raw))
72        }
73    }
74
75    pub fn open(path: &Path) -> Result<Config, Error> {
77        crate::init();
78        let mut raw = ptr::null_mut();
79        let path = path.into_c_string()?;
81        unsafe {
82            try_call!(raw::git_config_open_ondisk(&mut raw, path));
83            Ok(Binding::from_raw(raw))
84        }
85    }
86
87    pub fn open_default() -> Result<Config, Error> {
93        crate::init();
94        let mut raw = ptr::null_mut();
95        unsafe {
96            try_call!(raw::git_config_open_default(&mut raw));
97            Ok(Binding::from_raw(raw))
98        }
99    }
100
101    pub fn find_global() -> Result<PathBuf, Error> {
113        crate::init();
114        let buf = Buf::new();
115        unsafe {
116            try_call!(raw::git_config_find_global(buf.raw()));
117        }
118        Ok(util::bytes2path(&buf).to_path_buf())
119    }
120
121    pub fn find_system() -> Result<PathBuf, Error> {
125        crate::init();
126        let buf = Buf::new();
127        unsafe {
128            try_call!(raw::git_config_find_system(buf.raw()));
129        }
130        Ok(util::bytes2path(&buf).to_path_buf())
131    }
132
133    pub fn find_xdg() -> Result<PathBuf, Error> {
138        crate::init();
139        let buf = Buf::new();
140        unsafe {
141            try_call!(raw::git_config_find_xdg(buf.raw()));
142        }
143        Ok(util::bytes2path(&buf).to_path_buf())
144    }
145
146    pub fn add_file(&mut self, path: &Path, level: ConfigLevel, force: bool) -> Result<(), Error> {
156        let path = path.into_c_string()?;
158        unsafe {
159            try_call!(raw::git_config_add_file_ondisk(
160                self.raw,
161                path,
162                level,
163                ptr::null(),
164                force
165            ));
166            Ok(())
167        }
168    }
169
170    pub fn remove(&mut self, name: &str) -> Result<(), Error> {
173        let name = CString::new(name)?;
174        unsafe {
175            try_call!(raw::git_config_delete_entry(self.raw, name));
176            Ok(())
177        }
178    }
179
180    pub fn remove_multivar(&mut self, name: &str, regexp: &str) -> Result<(), Error> {
185        let name = CString::new(name)?;
186        let regexp = CString::new(regexp)?;
187        unsafe {
188            try_call!(raw::git_config_delete_multivar(self.raw, name, regexp));
189        }
190        Ok(())
191    }
192
193    pub fn get_bool(&self, name: &str) -> Result<bool, Error> {
199        let mut out = 0 as libc::c_int;
200        let name = CString::new(name)?;
201        unsafe {
202            try_call!(raw::git_config_get_bool(&mut out, &*self.raw, name));
203        }
204        Ok(out != 0)
205    }
206
207    pub fn get_i32(&self, name: &str) -> Result<i32, Error> {
213        let mut out = 0i32;
214        let name = CString::new(name)?;
215        unsafe {
216            try_call!(raw::git_config_get_int32(&mut out, &*self.raw, name));
217        }
218        Ok(out)
219    }
220
221    pub fn get_i64(&self, name: &str) -> Result<i64, Error> {
227        let mut out = 0i64;
228        let name = CString::new(name)?;
229        unsafe {
230            try_call!(raw::git_config_get_int64(&mut out, &*self.raw, name));
231        }
232        Ok(out)
233    }
234
235    pub fn get_str(&self, name: &str) -> Result<&str, Error> {
245        str::from_utf8(self.get_bytes(name)?)
246            .map_err(|_| Error::from_str("configuration value is not valid utf8"))
247    }
248
249    pub fn get_bytes(&self, name: &str) -> Result<&[u8], Error> {
256        let mut ret = ptr::null();
257        let name = CString::new(name)?;
258        unsafe {
259            try_call!(raw::git_config_get_string(&mut ret, &*self.raw, name));
260            Ok(crate::opt_bytes(self, ret).unwrap())
261        }
262    }
263
264    pub fn get_string(&self, name: &str) -> Result<String, Error> {
272        let ret = Buf::new();
273        let name = CString::new(name)?;
274        unsafe {
275            try_call!(raw::git_config_get_string_buf(ret.raw(), self.raw, name));
276        }
277        str::from_utf8(&ret)
278            .map(|s| s.to_string())
279            .map_err(|_| Error::from_str("configuration value is not valid utf8"))
280    }
281
282    pub fn get_path(&self, name: &str) -> Result<PathBuf, Error> {
292        let ret = Buf::new();
293        let name = CString::new(name)?;
294        unsafe {
295            try_call!(raw::git_config_get_path(ret.raw(), self.raw, name));
296        }
297        Ok(crate::util::bytes2path(&ret).to_path_buf())
298    }
299
300    pub fn get_entry(&self, name: &str) -> Result<ConfigEntry<'_>, Error> {
302        let mut ret = ptr::null_mut();
303        let name = CString::new(name)?;
304        unsafe {
305            try_call!(raw::git_config_get_entry(&mut ret, self.raw, name));
306            Ok(Binding::from_raw(ret))
307        }
308    }
309
310    pub fn entries(&self, glob: Option<&str>) -> Result<ConfigEntries<'_>, Error> {
336        let mut ret = ptr::null_mut();
337        unsafe {
338            match glob {
339                Some(s) => {
340                    let s = CString::new(s)?;
341                    try_call!(raw::git_config_iterator_glob_new(&mut ret, &*self.raw, s));
342                }
343                None => {
344                    try_call!(raw::git_config_iterator_new(&mut ret, &*self.raw));
345                }
346            }
347            Ok(Binding::from_raw(ret))
348        }
349    }
350
351    pub fn multivar(&self, name: &str, regexp: Option<&str>) -> Result<ConfigEntries<'_>, Error> {
363        let mut ret = ptr::null_mut();
364        let name = CString::new(name)?;
365        let regexp = regexp.map(CString::new).transpose()?;
366        unsafe {
367            try_call!(raw::git_config_multivar_iterator_new(
368                &mut ret, &*self.raw, name, regexp
369            ));
370            Ok(Binding::from_raw(ret))
371        }
372    }
373
374    pub fn open_global(&mut self) -> Result<Config, Error> {
381        let mut raw = ptr::null_mut();
382        unsafe {
383            try_call!(raw::git_config_open_global(&mut raw, self.raw));
384            Ok(Binding::from_raw(raw))
385        }
386    }
387
388    pub fn open_level(&self, level: ConfigLevel) -> Result<Config, Error> {
393        let mut raw = ptr::null_mut();
394        unsafe {
395            try_call!(raw::git_config_open_level(&mut raw, &*self.raw, level));
396            Ok(Binding::from_raw(raw))
397        }
398    }
399
400    pub fn set_bool(&mut self, name: &str, value: bool) -> Result<(), Error> {
403        let name = CString::new(name)?;
404        unsafe {
405            try_call!(raw::git_config_set_bool(self.raw, name, value));
406        }
407        Ok(())
408    }
409
410    pub fn set_i32(&mut self, name: &str, value: i32) -> Result<(), Error> {
413        let name = CString::new(name)?;
414        unsafe {
415            try_call!(raw::git_config_set_int32(self.raw, name, value));
416        }
417        Ok(())
418    }
419
420    pub fn set_i64(&mut self, name: &str, value: i64) -> Result<(), Error> {
423        let name = CString::new(name)?;
424        unsafe {
425            try_call!(raw::git_config_set_int64(self.raw, name, value));
426        }
427        Ok(())
428    }
429
430    pub fn set_multivar(&mut self, name: &str, regexp: &str, value: &str) -> Result<(), Error> {
435        let name = CString::new(name)?;
436        let regexp = CString::new(regexp)?;
437        let value = CString::new(value)?;
438        unsafe {
439            try_call!(raw::git_config_set_multivar(self.raw, name, regexp, value));
440        }
441        Ok(())
442    }
443
444    pub fn set_str(&mut self, name: &str, value: &str) -> Result<(), Error> {
447        let name = CString::new(name)?;
448        let value = CString::new(value)?;
449        unsafe {
450            try_call!(raw::git_config_set_string(self.raw, name, value));
451        }
452        Ok(())
453    }
454
455    pub fn snapshot(&mut self) -> Result<Config, Error> {
461        let mut ret = ptr::null_mut();
462        unsafe {
463            try_call!(raw::git_config_snapshot(&mut ret, self.raw));
464            Ok(Binding::from_raw(ret))
465        }
466    }
467
468    pub fn parse_bool<S: IntoCString>(s: S) -> Result<bool, Error> {
473        let s = s.into_c_string()?;
474        let mut out = 0;
475        crate::init();
476        unsafe {
477            try_call!(raw::git_config_parse_bool(&mut out, s));
478        }
479        Ok(out != 0)
480    }
481
482    pub fn parse_i32<S: IntoCString>(s: S) -> Result<i32, Error> {
485        let s = s.into_c_string()?;
486        let mut out = 0;
487        crate::init();
488        unsafe {
489            try_call!(raw::git_config_parse_int32(&mut out, s));
490        }
491        Ok(out)
492    }
493
494    pub fn parse_i64<S: IntoCString>(s: S) -> Result<i64, Error> {
497        let s = s.into_c_string()?;
498        let mut out = 0;
499        crate::init();
500        unsafe {
501            try_call!(raw::git_config_parse_int64(&mut out, s));
502        }
503        Ok(out)
504    }
505}
506
507impl Binding for Config {
508    type Raw = *mut raw::git_config;
509    unsafe fn from_raw(raw: *mut raw::git_config) -> Config {
510        Config { raw }
511    }
512    fn raw(&self) -> *mut raw::git_config {
513        self.raw
514    }
515}
516
517impl Drop for Config {
518    fn drop(&mut self) {
519        unsafe { raw::git_config_free(self.raw) }
520    }
521}
522
523impl<'cfg> ConfigEntry<'cfg> {
524    pub fn name(&self) -> Option<&str> {
528        str::from_utf8(self.name_bytes()).ok()
529    }
530
531    pub fn name_bytes(&self) -> &[u8] {
533        unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() }
534    }
535
536    pub fn value(&self) -> Option<&str> {
544        str::from_utf8(self.value_bytes()).ok()
545    }
546
547    pub fn value_bytes(&self) -> &[u8] {
553        unsafe { crate::opt_bytes(self, (*self.raw).value).unwrap() }
554    }
555
556    pub fn has_value(&self) -> bool {
560        unsafe { !(*self.raw).value.is_null() }
561    }
562
563    pub fn level(&self) -> ConfigLevel {
565        unsafe { ConfigLevel::from_raw((*self.raw).level) }
566    }
567
568    pub fn include_depth(&self) -> u32 {
570        unsafe { (*self.raw).include_depth as u32 }
571    }
572}
573
574impl<'cfg> Binding for ConfigEntry<'cfg> {
575    type Raw = *mut raw::git_config_entry;
576
577    unsafe fn from_raw(raw: *mut raw::git_config_entry) -> ConfigEntry<'cfg> {
578        ConfigEntry {
579            raw,
580            _marker: marker::PhantomData,
581            owned: true,
582        }
583    }
584    fn raw(&self) -> *mut raw::git_config_entry {
585        self.raw
586    }
587}
588
589impl<'cfg> Binding for ConfigEntries<'cfg> {
590    type Raw = *mut raw::git_config_iterator;
591
592    unsafe fn from_raw(raw: *mut raw::git_config_iterator) -> ConfigEntries<'cfg> {
593        ConfigEntries {
594            raw,
595            current: None,
596            _marker: marker::PhantomData,
597        }
598    }
599    fn raw(&self) -> *mut raw::git_config_iterator {
600        self.raw
601    }
602}
603
604impl<'cfg> ConfigEntries<'cfg> {
605    pub fn next(&mut self) -> Option<Result<&ConfigEntry<'cfg>, Error>> {
609        let mut raw = ptr::null_mut();
610        drop(self.current.take());
611        unsafe {
612            try_call_iter!(raw::git_config_next(&mut raw, self.raw));
613            let entry = ConfigEntry {
614                owned: false,
615                raw,
616                _marker: marker::PhantomData,
617            };
618            self.current = Some(entry);
619            Some(Ok(self.current.as_ref().unwrap()))
620        }
621    }
622
623    pub fn for_each<F: FnMut(&ConfigEntry<'cfg>)>(mut self, mut f: F) -> Result<(), Error> {
625        while let Some(entry) = self.next() {
626            let entry = entry?;
627            f(entry);
628        }
629        Ok(())
630    }
631}
632
633impl<'cfg> Drop for ConfigEntries<'cfg> {
634    fn drop(&mut self) {
635        unsafe { raw::git_config_iterator_free(self.raw) }
636    }
637}
638
639impl<'cfg> Drop for ConfigEntry<'cfg> {
640    fn drop(&mut self) {
641        if self.owned {
642            unsafe { raw::git_config_entry_free(self.raw) }
643        }
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use std::fs::File;
650    use tempfile::TempDir;
651
652    use crate::Config;
653
654    #[test]
655    fn smoke() {
656        let _cfg = Config::new().unwrap();
657        let _ = Config::find_global();
658        let _ = Config::find_system();
659        let _ = Config::find_xdg();
660    }
661
662    #[test]
663    fn persisted() {
664        let td = TempDir::new().unwrap();
665        let path = td.path().join("foo");
666        File::create(&path).unwrap();
667
668        let mut cfg = Config::open(&path).unwrap();
669        assert!(cfg.get_bool("foo.bar").is_err());
670        cfg.set_bool("foo.k1", true).unwrap();
671        cfg.set_i32("foo.k2", 1).unwrap();
672        cfg.set_i64("foo.k3", 2).unwrap();
673        cfg.set_str("foo.k4", "bar").unwrap();
674        cfg.snapshot().unwrap();
675        drop(cfg);
676
677        let cfg = Config::open(&path).unwrap().snapshot().unwrap();
678        assert_eq!(cfg.get_bool("foo.k1").unwrap(), true);
679        assert_eq!(cfg.get_i32("foo.k2").unwrap(), 1);
680        assert_eq!(cfg.get_i64("foo.k3").unwrap(), 2);
681        assert_eq!(cfg.get_str("foo.k4").unwrap(), "bar");
682
683        let mut entries = cfg.entries(None).unwrap();
684        while let Some(entry) = entries.next() {
685            let entry = entry.unwrap();
686            entry.name();
687            entry.value();
688            entry.level();
689        }
690    }
691
692    #[test]
693    fn multivar() {
694        let td = TempDir::new().unwrap();
695        let path = td.path().join("foo");
696        File::create(&path).unwrap();
697
698        let mut cfg = Config::open(&path).unwrap();
699        cfg.set_multivar("foo.bar", "^$", "baz").unwrap();
700        cfg.set_multivar("foo.bar", "^$", "qux").unwrap();
701        cfg.set_multivar("foo.bar", "^$", "quux").unwrap();
702        cfg.set_multivar("foo.baz", "^$", "oki").unwrap();
703
704        let mut entries: Vec<String> = Vec::new();
706        cfg.entries(Some("foo.bar"))
707            .unwrap()
708            .for_each(|entry| entries.push(entry.value().unwrap().to_string()))
709            .unwrap();
710        entries.sort();
711        assert_eq!(entries, ["baz", "quux", "qux"]);
712
713        let mut multivals = Vec::new();
715        cfg.multivar("foo.bar", None)
716            .unwrap()
717            .for_each(|entry| multivals.push(entry.value().unwrap().to_string()))
718            .unwrap();
719        multivals.sort();
720        assert_eq!(multivals, entries);
721
722        let mut quxish = Vec::new();
724        cfg.multivar("foo.bar", Some("qu.*x"))
725            .unwrap()
726            .for_each(|entry| quxish.push(entry.value().unwrap().to_string()))
727            .unwrap();
728        quxish.sort();
729        assert_eq!(quxish, ["quux", "qux"]);
730
731        cfg.remove_multivar("foo.bar", ".*").unwrap();
732
733        let count = |entries: super::ConfigEntries<'_>| -> usize {
734            let mut c = 0;
735            entries.for_each(|_| c += 1).unwrap();
736            c
737        };
738
739        assert_eq!(count(cfg.entries(Some("foo.bar")).unwrap()), 0);
740        assert_eq!(count(cfg.multivar("foo.bar", None).unwrap()), 0);
741    }
742
743    #[test]
744    fn parse() {
745        assert_eq!(Config::parse_bool("").unwrap(), false);
746        assert_eq!(Config::parse_bool("false").unwrap(), false);
747        assert_eq!(Config::parse_bool("no").unwrap(), false);
748        assert_eq!(Config::parse_bool("off").unwrap(), false);
749        assert_eq!(Config::parse_bool("0").unwrap(), false);
750
751        assert_eq!(Config::parse_bool("true").unwrap(), true);
752        assert_eq!(Config::parse_bool("yes").unwrap(), true);
753        assert_eq!(Config::parse_bool("on").unwrap(), true);
754        assert_eq!(Config::parse_bool("1").unwrap(), true);
755        assert_eq!(Config::parse_bool("42").unwrap(), true);
756
757        assert!(Config::parse_bool(" ").is_err());
758        assert!(Config::parse_bool("some-string").is_err());
759        assert!(Config::parse_bool("-").is_err());
760
761        assert_eq!(Config::parse_i32("0").unwrap(), 0);
762        assert_eq!(Config::parse_i32("1").unwrap(), 1);
763        assert_eq!(Config::parse_i32("100").unwrap(), 100);
764        assert_eq!(Config::parse_i32("-1").unwrap(), -1);
765        assert_eq!(Config::parse_i32("-100").unwrap(), -100);
766        assert_eq!(Config::parse_i32("1k").unwrap(), 1024);
767        assert_eq!(Config::parse_i32("4k").unwrap(), 4096);
768        assert_eq!(Config::parse_i32("1M").unwrap(), 1048576);
769        assert_eq!(Config::parse_i32("1G").unwrap(), 1024 * 1024 * 1024);
770
771        assert_eq!(Config::parse_i64("0").unwrap(), 0);
772        assert_eq!(Config::parse_i64("1").unwrap(), 1);
773        assert_eq!(Config::parse_i64("100").unwrap(), 100);
774        assert_eq!(Config::parse_i64("-1").unwrap(), -1);
775        assert_eq!(Config::parse_i64("-100").unwrap(), -100);
776        assert_eq!(Config::parse_i64("1k").unwrap(), 1024);
777        assert_eq!(Config::parse_i64("4k").unwrap(), 4096);
778        assert_eq!(Config::parse_i64("1M").unwrap(), 1048576);
779        assert_eq!(Config::parse_i64("1G").unwrap(), 1024 * 1024 * 1024);
780        assert_eq!(Config::parse_i64("100G").unwrap(), 100 * 1024 * 1024 * 1024);
781    }
782}