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