Skip to main content

gitoxide_core/repository/
exclude.rs

1use std::{borrow::Cow, io};
2
3use anyhow::bail;
4use gix::bstr::{BStr, ByteSlice};
5
6use crate::{OutputFormat, is_dir_to_mode, repository::PathsOrPatterns};
7
8pub mod query {
9    use std::ffi::OsString;
10
11    use crate::OutputFormat;
12
13    pub struct Options {
14        pub format: OutputFormat,
15        pub overrides: Vec<OsString>,
16        pub show_ignore_patterns: bool,
17        pub statistics: bool,
18    }
19}
20
21pub fn query(
22    repo: gix::Repository,
23    input: PathsOrPatterns,
24    mut out: impl io::Write,
25    mut err: impl io::Write,
26    query::Options {
27        overrides,
28        format,
29        show_ignore_patterns,
30        statistics,
31    }: query::Options,
32) -> anyhow::Result<()> {
33    if format != OutputFormat::Human {
34        bail!("JSON output isn't implemented yet");
35    }
36
37    let index = repo.index()?;
38    let mut cache = repo.excludes(
39        &index,
40        Some(gix::ignore::Search::from_overrides(
41            overrides,
42            repo.ignore_pattern_parser()?,
43        )),
44        Default::default(),
45    )?;
46
47    let paths: Box<dyn Iterator<Item = gix::bstr::BString>> = match input {
48        PathsOrPatterns::Paths(paths) => paths,
49        PathsOrPatterns::Patterns(paths) => Box::new(paths.into_iter()),
50    };
51    for path in paths {
52        let mode = gix::path::from_bstr(Cow::Borrowed(path.as_ref()))
53            .metadata()
54            .ok()
55            .map(|m| is_dir_to_mode(m.is_dir()))
56            .or_else(|| path.ends_with(b"/").then_some(gix::index::entry::Mode::DIR));
57        let query_path = repo.normalize_path(&path)?;
58        let entry = cache.at_entry(query_path.as_bstr(), mode)?;
59        let match_ = entry
60            .matching_exclude_pattern()
61            .filter(|m| show_ignore_patterns || !m.pattern.is_negative());
62        print_match_unless_tracked(match_, &index, query_path.as_bstr(), path.as_ref(), &mut out)?;
63    }
64
65    if let Some(stats) = statistics.then(|| cache.take_statistics()) {
66        out.flush()?;
67        writeln!(err, "{stats:#?}").ok();
68    }
69    Ok(())
70}
71
72fn print_match_unless_tracked(
73    match_: Option<gix::ignore::search::Match<'_>>,
74    index: &gix::index::State,
75    query_path: &BStr,
76    display_path: &BStr,
77    out: impl std::io::Write,
78) -> std::io::Result<()> {
79    print_match(match_.filter(|_| !is_tracked(index, query_path)), display_path, out)
80}
81
82fn is_tracked(index: &gix::index::State, path: &BStr) -> bool {
83    let path = path.trim_end_with(|b| b == '/').as_bstr();
84    index.entry_by_path(path).is_some() || index.path_is_directory(path)
85}
86
87fn print_match(
88    m: Option<gix::ignore::search::Match<'_>>,
89    path: &BStr,
90    mut out: impl std::io::Write,
91) -> std::io::Result<()> {
92    match m {
93        Some(m) => writeln!(
94            out,
95            "{}:{}:{}\t{}",
96            m.source.map(std::path::Path::to_string_lossy).unwrap_or_default(),
97            m.sequence_number,
98            m.pattern,
99            path
100        ),
101        None => writeln!(out, "::\t{path}"),
102    }
103}