gix_worktree/stack/state/
attributes.rs1use std::path::{Path, PathBuf};
2
3use bstr::{BStr, ByteSlice};
4use gix_glob::pattern::Case;
5use gix_object::FindExt;
6
7use crate::{
8 PathIdMapping, Stack,
9 stack::state::{AttributeMatchGroup, Attributes},
10};
11
12#[derive(Default, Clone, Copy, Debug)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct Statistics {
16 pub patterns_buffers: usize,
18 pub pattern_files: usize,
20 pub tried_pattern_files: usize,
22}
23
24#[derive(Default, Debug, Clone, Copy)]
33pub enum Source {
34 #[default]
37 IdMapping,
38 IdMappingThenWorktree,
42 WorktreeThenIdMapping,
47}
48
49impl Source {
50 pub fn adjust_for_bare(self, is_bare: bool) -> Self {
52 if is_bare { Source::IdMapping } else { self }
53 }
54}
55
56impl Attributes {
58 pub fn new(
66 globals: AttributeMatchGroup,
67 info_attributes: Option<PathBuf>,
68 source: Source,
69 collection: gix_attributes::search::MetadataCollection,
70 ) -> Self {
71 Attributes {
72 globals,
73 stack: Default::default(),
74 info_attributes,
75 source,
76 collection,
77 }
78 }
79}
80
81impl Attributes {
82 pub(crate) fn pop_directory(&mut self) {
83 self.stack.pop_pattern_list().expect("something to pop");
84 }
85
86 #[expect(clippy::too_many_arguments)]
87 pub(crate) fn push_directory(
88 &mut self,
89 root: &Path,
90 dir: &Path,
91 rela_dir: &BStr,
92 buf: &mut Vec<u8>,
93 id_mappings: &[PathIdMapping],
94 objects: &dyn gix_object::Find,
95 stats: &mut Statistics,
96 ) -> std::io::Result<()> {
97 let attr_path_relative = gix_path::join_bstr_unix_pathsep(rela_dir, ".gitattributes");
98 let attr_file_in_index = id_mappings.binary_search_by(|t| t.0.as_bstr().cmp(attr_path_relative.as_ref()));
99 let no_follow_symlinks = false;
101 let read_macros_as_dir_is_root = root == dir;
102
103 let mut added = false;
104 match self.source {
105 Source::IdMapping | Source::IdMappingThenWorktree => {
106 if let Ok(idx) = attr_file_in_index {
107 let blob = objects
108 .find_blob(&id_mappings[idx].1, buf)
109 .map_err(std::io::Error::other)?;
110 let attr_path =
111 gix_path::from_bstring(attr_path_relative.into_owned()).map_err(std::io::Error::other)?;
112 self.stack.add_patterns_buffer(
113 blob.data,
114 attr_path,
115 Some(Path::new("")),
116 &mut self.collection,
117 read_macros_as_dir_is_root,
118 )?;
119 added = true;
120 stats.patterns_buffers += 1;
121 }
122 if !added && matches!(self.source, Source::IdMappingThenWorktree) {
123 added = self.stack.add_patterns_file(
124 dir.join(".gitattributes"),
125 no_follow_symlinks,
126 Some(root),
127 buf,
128 &mut self.collection,
129 read_macros_as_dir_is_root,
130 )?;
131 stats.pattern_files += usize::from(added);
132 stats.tried_pattern_files += 1;
133 }
134 }
135 Source::WorktreeThenIdMapping => {
136 added = self.stack.add_patterns_file(
137 dir.join(".gitattributes"),
138 no_follow_symlinks,
139 Some(root),
140 buf,
141 &mut self.collection,
142 read_macros_as_dir_is_root,
143 )?;
144 stats.pattern_files += usize::from(added);
145 stats.tried_pattern_files += 1;
146 if let Some(idx) = attr_file_in_index.ok().filter(|_| !added) {
147 let blob = objects
148 .find_blob(&id_mappings[idx].1, buf)
149 .map_err(std::io::Error::other)?;
150 let attr_path =
151 gix_path::from_bstring(attr_path_relative.into_owned()).map_err(std::io::Error::other)?;
152 self.stack.add_patterns_buffer(
153 blob.data,
154 attr_path,
155 Some(Path::new("")),
156 &mut self.collection,
157 read_macros_as_dir_is_root,
158 )?;
159 added = true;
160 stats.patterns_buffers += 1;
161 }
162 }
163 }
164
165 if !added && self.info_attributes.is_none() {
167 self.stack
168 .add_patterns_buffer(&[], "<empty dummy>".into(), None, &mut self.collection, true)?;
169 }
170
171 if let Some(info_attr) = self.info_attributes.take() {
174 let added = self.stack.add_patterns_file(
175 info_attr,
176 true,
177 None,
178 buf,
179 &mut self.collection,
180 true, )?;
182 stats.pattern_files += usize::from(added);
183 stats.tried_pattern_files += 1;
184 }
185
186 Ok(())
187 }
188
189 pub(crate) fn matching_attributes(
190 &self,
191 relative_path: &BStr,
192 case: Case,
193 is_dir: Option<bool>,
194 out: &mut gix_attributes::search::Outcome,
195 ) -> bool {
196 out.initialize(&self.collection);
198
199 let groups = [&self.globals, &self.stack];
200 let mut has_match = false;
201 groups.iter().rev().any(|group| {
202 has_match |= group.pattern_matching_relative_path(relative_path, case, is_dir, out);
203 out.is_done()
204 });
205 has_match
206 }
207}
208
209impl Stack {
211 pub fn attribute_matches(&self) -> gix_attributes::search::Outcome {
217 let mut out = gix_attributes::search::Outcome::default();
218 out.initialize(&self.state.attributes_or_panic().collection);
219 out
220 }
221
222 pub fn selected_attribute_matches<'a>(
228 &self,
229 given: impl IntoIterator<Item = impl Into<&'a str>>,
230 ) -> gix_attributes::search::Outcome {
231 let mut out = gix_attributes::search::Outcome::default();
232 out.initialize_with_selection(
233 &self.state.attributes_or_panic().collection,
234 given.into_iter().map(Into::into),
235 );
236 out
237 }
238
239 pub fn attributes_collection(&self) -> &gix_attributes::search::MetadataCollection {
246 &self.state.attributes_or_panic().collection
247 }
248}