gitoxide_core/repository/attributes/
query.rs1use gix::worktree::IndexPersistedOrInMemory;
2
3use crate::OutputFormat;
4
5pub struct Options {
6 pub format: OutputFormat,
7 pub statistics: bool,
8}
9
10pub(crate) mod function {
11 use anyhow::bail;
12 use gix::bstr::BStr;
13 use std::borrow::Cow;
14 use std::{io, path::Path};
15
16 use crate::{
17 is_dir_to_mode,
18 repository::{
19 attributes::query::{attributes_cache, Options},
20 PathsOrPatterns,
21 },
22 OutputFormat,
23 };
24
25 pub fn query(
26 repo: gix::Repository,
27 input: PathsOrPatterns,
28 mut out: impl io::Write,
29 mut err: impl io::Write,
30 Options { format, statistics }: Options,
31 ) -> anyhow::Result<()> {
32 if format != OutputFormat::Human {
33 bail!("JSON output isn't implemented yet");
34 }
35
36 let (mut cache, index) = attributes_cache(&repo)?;
37 let mut matches = cache.attribute_matches();
38
39 match input {
40 PathsOrPatterns::Paths(paths) => {
41 for path in paths {
42 let mode = gix::path::from_bstr(Cow::Borrowed(path.as_ref()))
43 .metadata()
44 .ok()
45 .map(|m| is_dir_to_mode(m.is_dir()));
46
47 let entry = cache.at_entry(&path, mode)?;
48 if !entry.matching_attributes(&mut matches) {
49 continue;
50 }
51 print_match(&matches, path.as_ref(), &mut out)?;
52 }
53 }
54 PathsOrPatterns::Patterns(patterns) => {
55 let mut pathspec = repo.pathspec(
56 true,
57 patterns.iter(),
58 true,
59 &index,
60 gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping
61 .adjust_for_bare(repo.is_bare()),
62 )?;
63 let mut pathspec_matched_entry = false;
64 if let Some(it) = pathspec.index_entries_with_paths(&index) {
65 for (path, entry) in it {
66 pathspec_matched_entry = true;
67 let entry = cache.at_entry(path, entry.mode.into())?;
68 if !entry.matching_attributes(&mut matches) {
69 continue;
70 }
71 print_match(&matches, path, &mut out)?;
72 }
73 }
74
75 if !pathspec_matched_entry {
76 let pathspec = repo.pathspec(
79 true,
80 patterns.iter(),
81 true,
82 &index,
83 gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping
84 .adjust_for_bare(repo.is_bare()),
85 )?;
86 let workdir = repo.workdir();
87 for pattern in pathspec.search().patterns() {
88 let path = pattern.path();
89 let entry = cache.at_entry(
90 path,
91 Some(is_dir_to_mode(
92 workdir.is_some_and(|wd| wd.join(gix::path::from_bstr(path)).is_dir())
93 || pattern.signature.contains(gix::pathspec::MagicSignature::MUST_BE_DIR),
94 )),
95 )?;
96 if !entry.matching_attributes(&mut matches) {
97 continue;
98 }
99 print_match(&matches, path, &mut out)?;
100 }
101 }
102 }
103 }
104
105 if let Some(stats) = statistics.then(|| cache.take_statistics()) {
106 out.flush()?;
107 writeln!(err, "{stats:#?}").ok();
108 }
109 Ok(())
110 }
111
112 fn print_match(
113 matches: &gix::attrs::search::Outcome,
114 path: &BStr,
115 mut out: impl std::io::Write,
116 ) -> std::io::Result<()> {
117 for m in matches.iter() {
118 writeln!(
119 out,
120 "{}:{}:{}\t{}\t{}",
121 m.location.source.map(Path::to_string_lossy).unwrap_or_default(),
122 m.location.sequence_number,
123 m.pattern,
124 path,
125 m.assignment
126 )?;
127 }
128 Ok(())
129 }
130}
131
132pub(crate) fn attributes_cache(
133 repo: &gix::Repository,
134) -> anyhow::Result<(gix::AttributeStack<'_>, IndexPersistedOrInMemory)> {
135 let index = repo.index_or_load_from_head()?;
136 let cache = repo.attributes(
137 &index,
138 if repo.is_bare() {
139 gix::worktree::stack::state::attributes::Source::IdMapping
140 } else {
141 gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping
142 },
143 gix::worktree::stack::state::ignore::Source::IdMapping,
144 None,
145 )?;
146 Ok((cache, index))
147}