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 #[allow(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 = gix_path::from_bstring(attr_path_relative.into_owned());
111 self.stack.add_patterns_buffer(
112 blob.data,
113 attr_path,
114 Some(Path::new("")),
115 &mut self.collection,
116 read_macros_as_dir_is_root,
117 );
118 added = true;
119 stats.patterns_buffers += 1;
120 }
121 if !added && matches!(self.source, Source::IdMappingThenWorktree) {
122 added = self.stack.add_patterns_file(
123 dir.join(".gitattributes"),
124 no_follow_symlinks,
125 Some(root),
126 buf,
127 &mut self.collection,
128 read_macros_as_dir_is_root,
129 )?;
130 stats.pattern_files += usize::from(added);
131 stats.tried_pattern_files += 1;
132 }
133 }
134 Source::WorktreeThenIdMapping => {
135 added = self.stack.add_patterns_file(
136 dir.join(".gitattributes"),
137 no_follow_symlinks,
138 Some(root),
139 buf,
140 &mut self.collection,
141 read_macros_as_dir_is_root,
142 )?;
143 stats.pattern_files += usize::from(added);
144 stats.tried_pattern_files += 1;
145 if let Some(idx) = attr_file_in_index.ok().filter(|_| !added) {
146 let blob = objects
147 .find_blob(&id_mappings[idx].1, buf)
148 .map_err(std::io::Error::other)?;
149 let attr_path = gix_path::from_bstring(attr_path_relative.into_owned());
150 self.stack.add_patterns_buffer(
151 blob.data,
152 attr_path,
153 Some(Path::new("")),
154 &mut self.collection,
155 read_macros_as_dir_is_root,
156 );
157 added = true;
158 stats.patterns_buffers += 1;
159 }
160 }
161 }
162
163 if !added && self.info_attributes.is_none() {
165 self.stack
166 .add_patterns_buffer(&[], "<empty dummy>".into(), None, &mut self.collection, true);
167 }
168
169 if let Some(info_attr) = self.info_attributes.take() {
172 let added = self.stack.add_patterns_file(
173 info_attr,
174 true,
175 None,
176 buf,
177 &mut self.collection,
178 true, )?;
180 stats.pattern_files += usize::from(added);
181 stats.tried_pattern_files += 1;
182 }
183
184 Ok(())
185 }
186
187 pub(crate) fn matching_attributes(
188 &self,
189 relative_path: &BStr,
190 case: Case,
191 is_dir: Option<bool>,
192 out: &mut gix_attributes::search::Outcome,
193 ) -> bool {
194 out.initialize(&self.collection);
196
197 let groups = [&self.globals, &self.stack];
198 let mut has_match = false;
199 groups.iter().rev().any(|group| {
200 has_match |= group.pattern_matching_relative_path(relative_path, case, is_dir, out);
201 out.is_done()
202 });
203 has_match
204 }
205}
206
207impl Stack {
209 pub fn attribute_matches(&self) -> gix_attributes::search::Outcome {
215 let mut out = gix_attributes::search::Outcome::default();
216 out.initialize(&self.state.attributes_or_panic().collection);
217 out
218 }
219
220 pub fn selected_attribute_matches<'a>(
226 &self,
227 given: impl IntoIterator<Item = impl Into<&'a str>>,
228 ) -> gix_attributes::search::Outcome {
229 let mut out = gix_attributes::search::Outcome::default();
230 out.initialize_with_selection(
231 &self.state.attributes_or_panic().collection,
232 given.into_iter().map(Into::into),
233 );
234 out
235 }
236
237 pub fn attributes_collection(&self) -> &gix_attributes::search::MetadataCollection {
244 &self.state.attributes_or_panic().collection
245 }
246}