1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use anyhow::{bail, Result};
use git::bstr::{BStr, BString};
use git_repository as git;

use crate::OutputFormat;

pub fn list(
    repo: git::Repository,
    filters: Vec<BString>,
    overrides: Vec<BString>,
    format: OutputFormat,
    mut out: impl std::io::Write,
) -> Result<()> {
    if format != OutputFormat::Human {
        bail!("Only human output format is supported at the moment");
    }
    let repo = git::open_opts(
        repo.git_dir(),
        repo.open_options().clone().lossy_config(false).cli_overrides(overrides),
    )?;
    let config = repo.config_snapshot();
    if let Some(frontmatter) = config.frontmatter() {
        for event in frontmatter {
            event.write_to(&mut out)?;
        }
    }
    let filters: Vec<_> = filters.into_iter().map(Filter::new).collect();
    let mut last_meta = None;
    let mut it = config.sections_and_postmatter().peekable();
    while let Some((section, matter)) = it.next() {
        if !filters.is_empty() && !filters.iter().any(|filter| filter.matches_section(section)) {
            continue;
        }

        let meta = section.meta();
        if last_meta.map_or(true, |last| last != meta) {
            write_meta(meta, &mut out)?;
        }
        last_meta = Some(meta);

        section.write_to(&mut out)?;
        for event in matter {
            event.write_to(&mut out)?;
        }
        if it.peek().map_or(false, |(next_section, _)| {
            next_section.header().name() != section.header().name()
        }) {
            writeln!(&mut out)?;
        }
    }
    Ok(())
}

struct Filter {
    name: String,
    subsection: Option<BString>,
}

impl Filter {
    fn new(input: BString) -> Self {
        match git::config::parse::key(<_ as AsRef<BStr>>::as_ref(&input)) {
            Some(key) => Filter {
                name: key.section_name.into(),
                subsection: key.subsection_name.map(ToOwned::to_owned),
            },
            None => Filter {
                name: input.to_string(),
                subsection: None,
            },
        }
    }

    fn matches_section(&self, section: &git::config::file::Section<'_>) -> bool {
        let ignore_case = git::glob::wildmatch::Mode::IGNORE_CASE;

        if !git::glob::wildmatch(self.name.as_bytes().into(), section.header().name(), ignore_case) {
            return false;
        }
        match (self.subsection.as_deref(), section.header().subsection_name()) {
            (Some(filter), Some(name)) => {
                if !git::glob::wildmatch(filter.as_slice().into(), name, ignore_case) {
                    return false;
                }
            }
            (None, None) | (None, Some(_)) => {}
            _ => return false,
        };
        true
    }
}

fn write_meta(meta: &git::config::file::Metadata, out: &mut impl std::io::Write) -> std::io::Result<()> {
    writeln!(
        out,
        "# From '{}' ({:?}{}{})",
        meta.path
            .as_deref()
            .map(|p| p.display().to_string())
            .unwrap_or_else(|| "memory".into()),
        meta.source,
        (meta.level != 0)
            .then(|| format!(", include level {}", meta.level))
            .unwrap_or_default(),
        (meta.trust != git::sec::Trust::Full)
            .then_some(", untrusted")
            .unwrap_or_default()
    )
}