186 lines · 6.2 KB · Rust
RawDownload
use anyhow::{Context, Result, bail};use gix::{bstr::BString, config::AsKey};use std::io::Write as _; use crate::OutputFormat; /// List all files which contributed sections to the resolved configuration, in precedence order.pub fn list_files(    repo: gix::Repository,    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 = gix::open_opts(repo.git_dir(), repo.open_options().clone().cli_overrides(overrides))?;    let config = repo.config_snapshot();    let mut seen = std::collections::BTreeSet::new();    for meta in config.sections().map(|section| section.meta()).chain([config.meta()]) {        let Some(path) = meta.path.as_ref() else {            continue;        };        if seen.insert(path) {            if meta.level == 0 {                writeln!(out, "{}\t{{ source={:?} }}", path.display(), meta.source)?;            } else {                writeln!(                    out,                    "{}\t{{ source={:?}, include-level={} }}",                    path.display(),                    meta.source,                    meta.level                )?;            }        }    }    Ok(())} pub fn show(    repo: gix::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 = gix::open_opts(repo.git_dir(), repo.open_options().clone().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 != Some(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()            .is_some_and(|(next_section, _)| next_section.header().name() != section.header().name())        {            writeln!(&mut out)?;        }    }    Ok(())} /// Format the git configuration file at `in_file`, or the repository-local configuration if `in_file`/// is `None`, writing the result back in place, to `out_file`, or to `out` (stdout) respectively.pub fn fmt(    repo: Option<gix::Repository>,    in_file: Option<std::path::PathBuf>,    out_file: Option<std::path::PathBuf>,    in_place: bool,    mut out: impl std::io::Write,) -> Result<()> {    if in_place && out_file.is_some() {        bail!("Cannot combine --in-place with an explicit output file");    }    let source = match in_file {        Some(path) => path,        None => repo            .context("Formatting the repository-local configuration requires being in a repository")?            .common_dir()            .join("config"),    };    let lock = in_place        .then(|| {            gix::lock::File::acquire_to_update_resource(&source, gix::lock::acquire::Fail::Immediately, None)                .with_context(|| format!("Could not lock configuration file at '{}'", source.display()))        })        .transpose()?;    let input = std::fs::read(&source)        .with_context(|| format!("Could not read configuration file at '{}'", source.display()))?;    let formatted = gix::config::format::normalize(&input, Default::default())?;    match (lock, out_file) {        (Some(mut lock), _) => {            lock.write_all(&formatted)                .with_context(|| format!("Could not write formatted configuration to '{}.lock'", source.display()))?;            lock.commit()                .map_err(|err| err.error)                .with_context(|| format!("Could not commit formatted configuration to '{}'", source.display()))?;        }        (None, Some(path)) => std::fs::write(&path, &formatted)            .with_context(|| format!("Could not write formatted configuration to '{}'", path.display()))?,        (None, None) => out.write_all(&formatted)?,    }    Ok(())} struct Filter {    name: String,    subsection: Option<BString>,} impl Filter {    fn new(input: BString) -> Self {        match (&input).try_as_key() {            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: &gix::config::file::SectionRef<'_>) -> bool {        let ignore_case = gix::glob::wildmatch::Mode::IGNORE_CASE;         if !gix::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 !gix::glob::wildmatch(filter.as_slice().into(), name, ignore_case) {                    return false;                }            }            (None, _) => {}            (Some(_), None) => return false,        }        true    }} fn write_meta(meta: &gix::config::file::Metadata, out: &mut impl std::io::Write) -> std::io::Result<()> {    writeln!(        out,        "# From '{}' ({:?}{}{})",        meta.path            .as_deref()            .map_or_else(|| "memory".into(), |p| p.display().to_string()),        meta.source,        if meta.level != 0 {            format!(", include level {}", meta.level)        } else {            Default::default()        },        if meta.trust != gix::sec::Trust::Full {            ", untrusted"        } else {            Default::default()        }    )}