gitoxide_core/repository/
exclude.rs1use std::{borrow::Cow, io};
2
3use anyhow::bail;
4use gix::bstr::BStr;
5
6use crate::{is_dir_to_mode, repository::PathsOrPatterns, OutputFormat};
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(overrides.into_iter())),
41 Default::default(),
42 )?;
43
44 match input {
45 PathsOrPatterns::Paths(paths) => {
46 for path in paths {
47 let mode = gix::path::from_bstr(Cow::Borrowed(path.as_ref()))
48 .metadata()
49 .ok()
50 .map(|m| is_dir_to_mode(m.is_dir()));
51 let entry = cache.at_entry(&path, mode)?;
52 let match_ = entry
53 .matching_exclude_pattern()
54 .and_then(|m| (show_ignore_patterns || !m.pattern.is_negative()).then_some(m));
55 print_match(match_, path.as_ref(), &mut out)?;
56 }
57 }
58 PathsOrPatterns::Patterns(patterns) => {
59 let mut pathspec_matched_something = false;
60 let mut pathspec = repo.pathspec(
61 true,
62 patterns.iter(),
63 repo.workdir().is_some(),
64 &index,
65 gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping.adjust_for_bare(repo.is_bare()),
66 )?;
67
68 if let Some(it) = pathspec.index_entries_with_paths(&index) {
69 for (path, entry) in it {
70 pathspec_matched_something = true;
71 let entry = cache.at_entry(path, entry.mode.into())?;
72 let match_ = entry
73 .matching_exclude_pattern()
74 .and_then(|m| (show_ignore_patterns || !m.pattern.is_negative()).then_some(m));
75 print_match(match_, path, &mut out)?;
76 }
77 }
78
79 if !pathspec_matched_something {
80 let pathspec = repo.pathspec(
83 true,
84 patterns.iter(),
85 repo.workdir().is_some(),
86 &index,
87 gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping
88 .adjust_for_bare(repo.is_bare()),
89 )?;
90 let workdir = repo.workdir();
91 for pattern in pathspec.search().patterns() {
92 let path = pattern.path();
93 let entry = cache.at_entry(
94 path,
95 Some(is_dir_to_mode(
96 workdir.is_some_and(|wd| wd.join(gix::path::from_bstr(path)).is_dir())
97 || pattern.signature.contains(gix::pathspec::MagicSignature::MUST_BE_DIR),
98 )),
99 )?;
100 let match_ = entry
101 .matching_exclude_pattern()
102 .and_then(|m| (show_ignore_patterns || !m.pattern.is_negative()).then_some(m));
103 print_match(match_, path, &mut out)?;
104 }
105 }
106 }
107 }
108
109 if let Some(stats) = statistics.then(|| cache.take_statistics()) {
110 out.flush()?;
111 writeln!(err, "{stats:#?}").ok();
112 }
113 Ok(())
114}
115
116fn print_match(
117 m: Option<gix::ignore::search::Match<'_>>,
118 path: &BStr,
119 mut out: impl std::io::Write,
120) -> std::io::Result<()> {
121 match m {
122 Some(m) => writeln!(
123 out,
124 "{}:{}:{}\t{}",
125 m.source.map(std::path::Path::to_string_lossy).unwrap_or_default(),
126 m.sequence_number,
127 m.pattern,
128 path
129 ),
130 None => writeln!(out, "::\t{path}"),
131 }
132}