]> git.proxmox.com Git - rustc.git/blame - vendor/gix-worktree/src/cache/state/attributes.rs
New upstream version 1.73.0+dfsg1
[rustc.git] / vendor / gix-worktree / src / cache / state / attributes.rs
CommitLineData
49aad941
FG
1use std::path::{Path, PathBuf};
2
fe692bf9 3use bstr::{BStr, ByteSlice};
49aad941
FG
4use gix_glob::pattern::Case;
5
fe692bf9
FG
6use crate::{
7 cache::state::{AttributeMatchGroup, Attributes},
8 Cache, PathIdMapping,
9};
49aad941
FG
10
11/// Various aggregate numbers related [`Attributes`].
12#[derive(Default, Clone, Copy, Debug)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Statistics {
15 /// Amount of patterns buffers read from the index.
16 pub patterns_buffers: usize,
17 /// Amount of pattern files read from disk.
18 pub pattern_files: usize,
19 /// Amount of pattern files we tried to find on disk.
20 pub tried_pattern_files: usize,
21}
22
23/// Decide where to read `.gitattributes` files from.
24#[derive(Default, Debug, Clone, Copy)]
25pub enum Source {
26 /// Retrieve attribute files from id mappings, see
27 /// [State::id_mappings_from_index()][crate::cache::State::id_mappings_from_index()].
28 ///
29 /// These mappings are typically produced from an index.
30 /// If a tree should be the source, build an attribute list from a tree instead, or convert a tree to an index.
31 ///
32 /// Use this when no worktree checkout is available, like in bare repositories or when accessing blobs from other parts
33 /// of the history which aren't checked out.
34 #[default]
35 IdMapping,
36 /// Read from an id mappings and if not present, read from the worktree.
37 ///
38 /// This us typically used when *checking out* files.
39 IdMappingThenWorktree,
40 /// Read from the worktree and if not present, read them from the id mappings.
41 ///
42 /// This is typically used when *checking in* files, and it's possible for sparse worktrees not to have a `.gitattribute` file
43 /// checked out even though it's available in the index.
44 WorktreeThenIdMapping,
45}
46
47/// Initialization
48impl Attributes {
49 /// Create a new instance from an attribute match group that represents `globals`. It can more easily be created with
fe692bf9 50 /// [`AttributeMatchGroup::new_globals()`].
49aad941
FG
51 ///
52 /// * `globals` contribute first and consist of all globally available, static files.
53 /// * `info_attributes` is a path that should refer to `.git/info/attributes`, and it's not an error if the file doesn't exist.
54 /// * `case` is used to control case-sensitivity during matching.
55 /// * `source` specifies from where the directory-based attribute files should be loaded from.
56 pub fn new(
57 globals: AttributeMatchGroup,
58 info_attributes: Option<PathBuf>,
59 source: Source,
60 collection: gix_attributes::search::MetadataCollection,
61 ) -> Self {
62 Attributes {
63 globals,
64 stack: Default::default(),
65 info_attributes,
66 source,
67 collection,
68 }
69 }
70}
71
72impl Attributes {
73 pub(crate) fn pop_directory(&mut self) {
74 self.stack.pop_pattern_list().expect("something to pop");
75 }
76
77 #[allow(clippy::too_many_arguments)]
78 pub(crate) fn push_directory<Find, E>(
79 &mut self,
80 root: &Path,
81 dir: &Path,
82 rela_dir: &BStr,
83 buf: &mut Vec<u8>,
84 id_mappings: &[PathIdMapping],
85 mut find: Find,
86 stats: &mut Statistics,
87 ) -> std::io::Result<()>
88 where
89 Find: for<'b> FnMut(&gix_hash::oid, &'b mut Vec<u8>) -> Result<gix_object::BlobRef<'b>, E>,
90 E: std::error::Error + Send + Sync + 'static,
91 {
92 let attr_path_relative =
93 gix_path::to_unix_separators_on_windows(gix_path::join_bstr_unix_pathsep(rela_dir, ".gitattributes"));
94 let attr_file_in_index = id_mappings.binary_search_by(|t| t.0.as_bstr().cmp(attr_path_relative.as_ref()));
95 // Git does not follow symbolic links as per documentation.
96 let no_follow_symlinks = false;
fe692bf9 97 let read_macros_as_dir_is_root = root == dir;
49aad941
FG
98
99 let mut added = false;
100 match self.source {
101 Source::IdMapping | Source::IdMappingThenWorktree => {
102 if let Ok(idx) = attr_file_in_index {
103 let blob = find(&id_mappings[idx].1, buf)
104 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
105 let attr_path = gix_path::from_bstring(attr_path_relative.into_owned());
fe692bf9
FG
106 self.stack.add_patterns_buffer(
107 blob.data,
108 attr_path,
109 Some(Path::new("")),
110 &mut self.collection,
111 read_macros_as_dir_is_root,
112 );
49aad941
FG
113 added = true;
114 stats.patterns_buffers += 1;
115 }
116 if !added && matches!(self.source, Source::IdMappingThenWorktree) {
117 added = self.stack.add_patterns_file(
118 dir.join(".gitattributes"),
119 no_follow_symlinks,
120 Some(root),
121 buf,
122 &mut self.collection,
fe692bf9 123 read_macros_as_dir_is_root,
49aad941
FG
124 )?;
125 stats.pattern_files += usize::from(added);
126 stats.tried_pattern_files += 1;
127 }
128 }
129 Source::WorktreeThenIdMapping => {
130 added = self.stack.add_patterns_file(
131 dir.join(".gitattributes"),
132 no_follow_symlinks,
133 Some(root),
134 buf,
135 &mut self.collection,
fe692bf9 136 read_macros_as_dir_is_root,
49aad941
FG
137 )?;
138 stats.pattern_files += usize::from(added);
139 stats.tried_pattern_files += 1;
140 if let Some(idx) = attr_file_in_index.ok().filter(|_| !added) {
141 let blob = find(&id_mappings[idx].1, buf)
142 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
143 let attr_path = gix_path::from_bstring(attr_path_relative.into_owned());
fe692bf9
FG
144 self.stack.add_patterns_buffer(
145 blob.data,
146 attr_path,
147 Some(Path::new("")),
148 &mut self.collection,
149 read_macros_as_dir_is_root,
150 );
49aad941
FG
151 added = true;
152 stats.patterns_buffers += 1;
153 }
154 }
155 }
156
157 // Need one stack level per component so push and pop matches, but only if this isn't the root level which is never popped.
158 if !added && self.info_attributes.is_none() {
159 self.stack
fe692bf9 160 .add_patterns_buffer(&[], Path::new("<empty dummy>"), None, &mut self.collection, true)
49aad941
FG
161 }
162
163 // When reading the root, always the first call, we can try to also read the `.git/info/attributes` file which is
164 // by nature never popped, and follows the root, as global.
165 if let Some(info_attr) = self.info_attributes.take() {
fe692bf9
FG
166 let added = self.stack.add_patterns_file(
167 info_attr,
168 true,
169 None,
170 buf,
171 &mut self.collection,
172 true, /* read macros */
173 )?;
49aad941
FG
174 stats.pattern_files += usize::from(added);
175 stats.tried_pattern_files += 1;
176 }
177
178 Ok(())
179 }
180
181 pub(crate) fn matching_attributes(
182 &self,
183 relative_path: &BStr,
184 case: Case,
185 out: &mut gix_attributes::search::Outcome,
186 ) -> bool {
187 // assure `out` is ready to deal with possibly changed collections (append-only)
188 out.initialize(&self.collection);
189
190 let groups = [&self.globals, &self.stack];
191 let mut has_match = false;
192 groups.iter().rev().any(|group| {
193 has_match |= group.pattern_matching_relative_path(relative_path, case, out);
194 out.is_done()
195 });
196 has_match
197 }
198}
199
200/// Attribute matching specific methods
201impl Cache {
202 /// Creates a new container to store match outcomes for all attribute matches.
203 pub fn attribute_matches(&self) -> gix_attributes::search::Outcome {
204 let mut out = gix_attributes::search::Outcome::default();
205 out.initialize(&self.state.attributes_or_panic().collection);
206 out
207 }
208
209 /// Creates a new container to store match outcomes for the given attributes.
210 pub fn selected_attribute_matches<'a>(
211 &self,
212 given: impl IntoIterator<Item = impl Into<&'a str>>,
213 ) -> gix_attributes::search::Outcome {
214 let mut out = gix_attributes::search::Outcome::default();
215 out.initialize_with_selection(
216 &self.state.attributes_or_panic().collection,
217 given.into_iter().map(|n| n.into()),
218 );
219 out
220 }
221}