gitoxide_core/repository/
config.rs

1use anyhow::{bail, Result};
2use gix::{bstr::BString, config::AsKey};
3
4use crate::OutputFormat;
5
6pub fn list(
7    repo: gix::Repository,
8    filters: Vec<BString>,
9    overrides: Vec<BString>,
10    format: OutputFormat,
11    mut out: impl std::io::Write,
12) -> Result<()> {
13    if format != OutputFormat::Human {
14        bail!("Only human output format is supported at the moment");
15    }
16    let repo = gix::open_opts(repo.git_dir(), repo.open_options().clone().cli_overrides(overrides))?;
17    let config = repo.config_snapshot();
18    if let Some(frontmatter) = config.frontmatter() {
19        for event in frontmatter {
20            event.write_to(&mut out)?;
21        }
22    }
23    let filters: Vec<_> = filters.into_iter().map(Filter::new).collect();
24    let mut last_meta = None;
25    let mut it = config.sections_and_postmatter().peekable();
26    while let Some((section, matter)) = it.next() {
27        if !filters.is_empty() && !filters.iter().any(|filter| filter.matches_section(section)) {
28            continue;
29        }
30
31        let meta = section.meta();
32        if last_meta != Some(meta) {
33            write_meta(meta, &mut out)?;
34        }
35        last_meta = Some(meta);
36
37        section.write_to(&mut out)?;
38        for event in matter {
39            event.write_to(&mut out)?;
40        }
41        if it
42            .peek()
43            .is_some_and(|(next_section, _)| next_section.header().name() != section.header().name())
44        {
45            writeln!(&mut out)?;
46        }
47    }
48    Ok(())
49}
50
51struct Filter {
52    name: String,
53    subsection: Option<BString>,
54}
55
56impl Filter {
57    fn new(input: BString) -> Self {
58        match input.try_as_key() {
59            Some(key) => Filter {
60                name: key.section_name.into(),
61                subsection: key.subsection_name.map(ToOwned::to_owned),
62            },
63            None => Filter {
64                name: input.to_string(),
65                subsection: None,
66            },
67        }
68    }
69
70    fn matches_section(&self, section: &gix::config::file::Section<'_>) -> bool {
71        let ignore_case = gix::glob::wildmatch::Mode::IGNORE_CASE;
72
73        if !gix::glob::wildmatch(self.name.as_bytes().into(), section.header().name(), ignore_case) {
74            return false;
75        }
76        match (self.subsection.as_deref(), section.header().subsection_name()) {
77            (Some(filter), Some(name)) => {
78                if !gix::glob::wildmatch(filter.as_slice().into(), name, ignore_case) {
79                    return false;
80                }
81            }
82            (None, _) => {}
83            (Some(_), None) => return false,
84        }
85        true
86    }
87}
88
89fn write_meta(meta: &gix::config::file::Metadata, out: &mut impl std::io::Write) -> std::io::Result<()> {
90    writeln!(
91        out,
92        "# From '{}' ({:?}{}{})",
93        meta.path
94            .as_deref()
95            .map_or_else(|| "memory".into(), |p| p.display().to_string()),
96        meta.source,
97        (meta.level != 0)
98            .then(|| format!(", include level {}", meta.level))
99            .unwrap_or_default(),
100        (meta.trust != gix::sec::Trust::Full)
101            .then_some(", untrusted")
102            .unwrap_or_default()
103    )
104}