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 stack::state::{AttributeMatchGroup, Attributes},
9 PathIdMapping, Stack,
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 {
53 Source::IdMapping
54 } else {
55 self
56 }
57 }
58}
59
60impl Attributes {
62 pub fn new(
70 globals: AttributeMatchGroup,
71 info_attributes: Option<PathBuf>,
72 source: Source,
73 collection: gix_attributes::search::MetadataCollection,
74 ) -> Self {
75 Attributes {
76 globals,
77 stack: Default::default(),
78 info_attributes,
79 source,
80 collection,
81 }
82 }
83}
84
85impl Attributes {
86 pub(crate) fn pop_directory(&mut self) {
87 self.stack.pop_pattern_list().expect("something to pop");
88 }
89
90 #[allow(clippy::too_many_arguments)]
91 pub(crate) fn push_directory(
92 &mut self,
93 root: &Path,
94 dir: &Path,
95 rela_dir: &BStr,
96 buf: &mut Vec<u8>,
97 id_mappings: &[PathIdMapping],
98 objects: &dyn gix_object::Find,
99 stats: &mut Statistics,
100 ) -> std::io::Result<()> {
101 let attr_path_relative = gix_path::join_bstr_unix_pathsep(rela_dir, ".gitattributes");
102 let attr_file_in_index = id_mappings.binary_search_by(|t| t.0.as_bstr().cmp(attr_path_relative.as_ref()));
103 let no_follow_symlinks = false;
105 let read_macros_as_dir_is_root = root == dir;
106
107 let mut added = false;
108 match self.source {
109 Source::IdMapping | Source::IdMappingThenWorktree => {
110 if let Ok(idx) = attr_file_in_index {
111 let blob = objects
112 .find_blob(&id_mappings[idx].1, buf)
113 .map_err(std::io::Error::other)?;
114 let attr_path = gix_path::from_bstring(attr_path_relative.into_owned());
115 self.stack.add_patterns_buffer(
116 blob.data,
117 attr_path,
118 Some(Path::new("")),
119 &mut self.collection,
120 read_macros_as_dir_is_root,
121 );
122 added = true;
123 stats.patterns_buffers += 1;
124 }
125 if !added && matches!(self.source, Source::IdMappingThenWorktree) {
126 added = self.stack.add_patterns_file(
127 dir.join(".gitattributes"),
128 no_follow_symlinks,
129 Some(root),
130 buf,
131 &mut self.collection,
132 read_macros_as_dir_is_root,
133 )?;
134 stats.pattern_files += usize::from(added);
135 stats.tried_pattern_files += 1;
136 }
137 }
138 Source::WorktreeThenIdMapping => {
139 added = self.stack.add_patterns_file(
140 dir.join(".gitattributes"),
141 no_follow_symlinks,
142 Some(root),
143 buf,
144 &mut self.collection,
145 read_macros_as_dir_is_root,
146 )?;
147 stats.pattern_files += usize::from(added);
148 stats.tried_pattern_files += 1;
149 if let Some(idx) = attr_file_in_index.ok().filter(|_| !added) {
150 let blob = objects
151 .find_blob(&id_mappings[idx].1, buf)
152 .map_err(std::io::Error::other)?;
153 let attr_path = gix_path::from_bstring(attr_path_relative.into_owned());
154 self.stack.add_patterns_buffer(
155 blob.data,
156 attr_path,
157 Some(Path::new("")),
158 &mut self.collection,
159 read_macros_as_dir_is_root,
160 );
161 added = true;
162 stats.patterns_buffers += 1;
163 }
164 }
165 }
166
167 if !added && self.info_attributes.is_none() {
169 self.stack
170 .add_patterns_buffer(&[], "<empty dummy>".into(), None, &mut self.collection, true);
171 }
172
173 if let Some(info_attr) = self.info_attributes.take() {
176 let added = self.stack.add_patterns_file(
177 info_attr,
178 true,
179 None,
180 buf,
181 &mut self.collection,
182 true, )?;
184 stats.pattern_files += usize::from(added);
185 stats.tried_pattern_files += 1;
186 }
187
188 Ok(())
189 }
190
191 pub(crate) fn matching_attributes(
192 &self,
193 relative_path: &BStr,
194 case: Case,
195 is_dir: Option<bool>,
196 out: &mut gix_attributes::search::Outcome,
197 ) -> bool {
198 out.initialize(&self.collection);
200
201 let groups = [&self.globals, &self.stack];
202 let mut has_match = false;
203 groups.iter().rev().any(|group| {
204 has_match |= group.pattern_matching_relative_path(relative_path, case, is_dir, out);
205 out.is_done()
206 });
207 has_match
208 }
209}
210
211impl Stack {
213 pub fn attribute_matches(&self) -> gix_attributes::search::Outcome {
219 let mut out = gix_attributes::search::Outcome::default();
220 out.initialize(&self.state.attributes_or_panic().collection);
221 out
222 }
223
224 pub fn selected_attribute_matches<'a>(
230 &self,
231 given: impl IntoIterator<Item = impl Into<&'a str>>,
232 ) -> gix_attributes::search::Outcome {
233 let mut out = gix_attributes::search::Outcome::default();
234 out.initialize_with_selection(
235 &self.state.attributes_or_panic().collection,
236 given.into_iter().map(Into::into),
237 );
238 out
239 }
240
241 pub fn attributes_collection(&self) -> &gix_attributes::search::MetadataCollection {
248 &self.state.attributes_or_panic().collection
249 }
250}