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}