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}