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