comparison rust/hg-core/src/dirstate_tree/status.rs @ 47335:ed1583a845d2

dirstate-v2: Make more APIs fallible, returning Result When parsing becomes lazy, parse error will potentially happen in more places. This propagates such errors to callers. Differential Revision: https://phab.mercurial-scm.org/D10749
author Simon Sapin <simon.sapin@octobus.net>
date Wed, 19 May 2021 13:15:00 +0200
parents 69530e5d4fe5
children 8d0260d0dbc9
comparison
equal deleted inserted replaced
47334:18b3060fe598 47335:ed1583a845d2
1 use crate::dirstate::status::IgnoreFnType; 1 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate_tree::dirstate_map::ChildNodesRef; 2 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
3 use crate::dirstate_tree::dirstate_map::DirstateMap; 3 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::NodeRef; 4 use crate::dirstate_tree::dirstate_map::NodeRef;
5 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
5 use crate::matchers::get_ignore_function; 6 use crate::matchers::get_ignore_function;
6 use crate::matchers::Matcher; 7 use crate::matchers::Matcher;
7 use crate::utils::files::get_bytes_from_os_string; 8 use crate::utils::files::get_bytes_from_os_string;
8 use crate::utils::hg_path::HgPath; 9 use crate::utils::hg_path::HgPath;
9 use crate::BadMatch; 10 use crate::BadMatch;
58 has_ignored_ancestor, 59 has_ignored_ancestor,
59 dmap.root.as_ref(), 60 dmap.root.as_ref(),
60 hg_path, 61 hg_path,
61 &root_dir, 62 &root_dir,
62 is_at_repo_root, 63 is_at_repo_root,
63 ); 64 )?;
64 Ok((common.outcome.into_inner().unwrap(), warnings)) 65 Ok((common.outcome.into_inner().unwrap(), warnings))
65 } 66 }
66 67
67 /// Bag of random things needed by various parts of the algorithm. Reduces the 68 /// Bag of random things needed by various parts of the algorithm. Reduces the
68 /// number of parameters passed to functions. 69 /// number of parameters passed to functions.
95 has_ignored_ancestor: bool, 96 has_ignored_ancestor: bool,
96 dirstate_nodes: ChildNodesRef<'tree, '_>, 97 dirstate_nodes: ChildNodesRef<'tree, '_>,
97 directory_hg_path: &'tree HgPath, 98 directory_hg_path: &'tree HgPath,
98 directory_fs_path: &Path, 99 directory_fs_path: &Path,
99 is_at_repo_root: bool, 100 is_at_repo_root: bool,
100 ) { 101 ) -> Result<(), DirstateV2ParseError> {
101 let mut fs_entries = if let Ok(entries) = self.read_dir( 102 let mut fs_entries = if let Ok(entries) = self.read_dir(
102 directory_hg_path, 103 directory_hg_path,
103 directory_fs_path, 104 directory_fs_path,
104 is_at_repo_root, 105 is_at_repo_root,
105 ) { 106 ) {
106 entries 107 entries
107 } else { 108 } else {
108 return; 109 return Ok(());
109 }; 110 };
110 111
111 // `merge_join_by` requires both its input iterators to be sorted: 112 // `merge_join_by` requires both its input iterators to be sorted:
112 113
113 let dirstate_nodes = dirstate_nodes.sorted(); 114 let dirstate_nodes = dirstate_nodes.sorted();
114 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value: 115 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
115 // https://github.com/rust-lang/rust/issues/34162 116 // https://github.com/rust-lang/rust/issues/34162
116 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name)); 117 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
117 118
119 // Propagate here any error that would happen inside the comparison
120 // callback below
121 for dirstate_node in &dirstate_nodes {
122 dirstate_node.base_name()?;
123 }
118 itertools::merge_join_by( 124 itertools::merge_join_by(
119 dirstate_nodes, 125 dirstate_nodes,
120 &fs_entries, 126 &fs_entries,
121 |dirstate_node, fs_entry| { 127 |dirstate_node, fs_entry| {
122 dirstate_node.base_name().cmp(&fs_entry.base_name) 128 // This `unwrap` never panics because we already propagated
129 // those errors above
130 dirstate_node.base_name().unwrap().cmp(&fs_entry.base_name)
123 }, 131 },
124 ) 132 )
125 .par_bridge() 133 .par_bridge()
126 .for_each(|pair| { 134 .map(|pair| {
127 use itertools::EitherOrBoth::*; 135 use itertools::EitherOrBoth::*;
128 match pair { 136 match pair {
129 Both(dirstate_node, fs_entry) => { 137 Both(dirstate_node, fs_entry) => self
130 self.traverse_fs_and_dirstate( 138 .traverse_fs_and_dirstate(
131 fs_entry, 139 fs_entry,
132 dirstate_node, 140 dirstate_node,
133 has_ignored_ancestor, 141 has_ignored_ancestor,
134 ); 142 ),
135 }
136 Left(dirstate_node) => { 143 Left(dirstate_node) => {
137 self.traverse_dirstate_only(dirstate_node) 144 self.traverse_dirstate_only(dirstate_node)
138 } 145 }
139 Right(fs_entry) => self.traverse_fs_only( 146 Right(fs_entry) => Ok(self.traverse_fs_only(
140 has_ignored_ancestor, 147 has_ignored_ancestor,
141 directory_hg_path, 148 directory_hg_path,
142 fs_entry, 149 fs_entry,
143 ), 150 )),
144 } 151 }
145 }) 152 })
153 .collect()
146 } 154 }
147 155
148 fn traverse_fs_and_dirstate( 156 fn traverse_fs_and_dirstate(
149 &self, 157 &self,
150 fs_entry: &DirEntry, 158 fs_entry: &DirEntry,
151 dirstate_node: NodeRef<'tree, '_>, 159 dirstate_node: NodeRef<'tree, '_>,
152 has_ignored_ancestor: bool, 160 has_ignored_ancestor: bool,
153 ) { 161 ) -> Result<(), DirstateV2ParseError> {
154 let hg_path = dirstate_node.full_path(); 162 let hg_path = dirstate_node.full_path()?;
155 let file_type = fs_entry.metadata.file_type(); 163 let file_type = fs_entry.metadata.file_type();
156 let file_or_symlink = file_type.is_file() || file_type.is_symlink(); 164 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
157 if !file_or_symlink { 165 if !file_or_symlink {
158 // If we previously had a file here, it was removed (with 166 // If we previously had a file here, it was removed (with
159 // `hg rm` or similar) or deleted before it could be 167 // `hg rm` or similar) or deleted before it could be
160 // replaced by a directory or something else. 168 // replaced by a directory or something else.
161 self.mark_removed_or_deleted_if_file( 169 self.mark_removed_or_deleted_if_file(
162 dirstate_node.full_path(), 170 hg_path,
163 dirstate_node.state(), 171 dirstate_node.state()?,
164 ); 172 );
165 } 173 }
166 if file_type.is_dir() { 174 if file_type.is_dir() {
167 if self.options.collect_traversed_dirs { 175 if self.options.collect_traversed_dirs {
168 self.outcome.lock().unwrap().traversed.push(hg_path.into()) 176 self.outcome.lock().unwrap().traversed.push(hg_path.into())
169 } 177 }
170 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path); 178 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
171 let is_at_repo_root = false; 179 let is_at_repo_root = false;
172 self.traverse_fs_directory_and_dirstate( 180 self.traverse_fs_directory_and_dirstate(
173 is_ignored, 181 is_ignored,
174 dirstate_node.children(), 182 dirstate_node.children()?,
175 hg_path, 183 hg_path,
176 &fs_entry.full_path, 184 &fs_entry.full_path,
177 is_at_repo_root, 185 is_at_repo_root,
178 ); 186 )?
179 } else { 187 } else {
180 if file_or_symlink && self.matcher.matches(hg_path) { 188 if file_or_symlink && self.matcher.matches(hg_path) {
181 let full_path = Cow::from(hg_path); 189 let full_path = Cow::from(hg_path);
182 if let Some(state) = dirstate_node.state() { 190 if let Some(state) = dirstate_node.state()? {
183 match state { 191 match state {
184 EntryState::Added => { 192 EntryState::Added => {
185 self.outcome.lock().unwrap().added.push(full_path) 193 self.outcome.lock().unwrap().added.push(full_path)
186 } 194 }
187 EntryState::Removed => self 195 EntryState::Removed => self
195 .lock() 203 .lock()
196 .unwrap() 204 .unwrap()
197 .modified 205 .modified
198 .push(full_path), 206 .push(full_path),
199 EntryState::Normal => { 207 EntryState::Normal => {
200 self.handle_normal_file(&dirstate_node, fs_entry); 208 self.handle_normal_file(&dirstate_node, fs_entry)?
201 } 209 }
202 // This variant is not used in DirstateMap 210 // This variant is not used in DirstateMap
203 // nodes 211 // nodes
204 EntryState::Unknown => unreachable!(), 212 EntryState::Unknown => unreachable!(),
205 } 213 }
211 full_path, 219 full_path,
212 ) 220 )
213 } 221 }
214 } 222 }
215 223
216 for child_node in dirstate_node.children().iter() { 224 for child_node in dirstate_node.children()?.iter() {
217 self.traverse_dirstate_only(child_node) 225 self.traverse_dirstate_only(child_node)?
218 } 226 }
219 } 227 }
228 Ok(())
220 } 229 }
221 230
222 /// A file with `EntryState::Normal` in the dirstate was found in the 231 /// A file with `EntryState::Normal` in the dirstate was found in the
223 /// filesystem 232 /// filesystem
224 fn handle_normal_file( 233 fn handle_normal_file(
225 &self, 234 &self,
226 dirstate_node: &NodeRef<'tree, '_>, 235 dirstate_node: &NodeRef<'tree, '_>,
227 fs_entry: &DirEntry, 236 fs_entry: &DirEntry,
228 ) { 237 ) -> Result<(), DirstateV2ParseError> {
229 // Keep the low 31 bits 238 // Keep the low 31 bits
230 fn truncate_u64(value: u64) -> i32 { 239 fn truncate_u64(value: u64) -> i32 {
231 (value & 0x7FFF_FFFF) as i32 240 (value & 0x7FFF_FFFF) as i32
232 } 241 }
233 fn truncate_i64(value: i64) -> i32 { 242 fn truncate_i64(value: i64) -> i32 {
234 (value & 0x7FFF_FFFF) as i32 243 (value & 0x7FFF_FFFF) as i32
235 } 244 }
236 245
237 let entry = dirstate_node 246 let entry = dirstate_node
238 .entry() 247 .entry()?
239 .expect("handle_normal_file called with entry-less node"); 248 .expect("handle_normal_file called with entry-less node");
240 let full_path = Cow::from(dirstate_node.full_path()); 249 let full_path = Cow::from(dirstate_node.full_path()?);
241 let mode_changed = || { 250 let mode_changed = || {
242 self.options.check_exec && entry.mode_changed(&fs_entry.metadata) 251 self.options.check_exec && entry.mode_changed(&fs_entry.metadata)
243 }; 252 };
244 let size_changed = entry.size != truncate_u64(fs_entry.metadata.len()); 253 let size_changed = entry.size != truncate_u64(fs_entry.metadata.len());
245 if entry.size >= 0 254 if entry.size >= 0
247 && fs_entry.metadata.file_type().is_symlink() 256 && fs_entry.metadata.file_type().is_symlink()
248 { 257 {
249 // issue6456: Size returned may be longer due to encryption 258 // issue6456: Size returned may be longer due to encryption
250 // on EXT-4 fscrypt. TODO maybe only do it on EXT4? 259 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
251 self.outcome.lock().unwrap().unsure.push(full_path) 260 self.outcome.lock().unwrap().unsure.push(full_path)
252 } else if dirstate_node.copy_source().is_some() 261 } else if dirstate_node.has_copy_source()
253 || entry.is_from_other_parent() 262 || entry.is_from_other_parent()
254 || (entry.size >= 0 && (size_changed || mode_changed())) 263 || (entry.size >= 0 && (size_changed || mode_changed()))
255 { 264 {
256 self.outcome.lock().unwrap().modified.push(full_path) 265 self.outcome.lock().unwrap().modified.push(full_path)
257 } else { 266 } else {
262 self.outcome.lock().unwrap().unsure.push(full_path) 271 self.outcome.lock().unwrap().unsure.push(full_path)
263 } else if self.options.list_clean { 272 } else if self.options.list_clean {
264 self.outcome.lock().unwrap().clean.push(full_path) 273 self.outcome.lock().unwrap().clean.push(full_path)
265 } 274 }
266 } 275 }
276 Ok(())
267 } 277 }
268 278
269 /// A node in the dirstate tree has no corresponding filesystem entry 279 /// A node in the dirstate tree has no corresponding filesystem entry
270 fn traverse_dirstate_only(&self, dirstate_node: NodeRef<'tree, '_>) { 280 fn traverse_dirstate_only(
281 &self,
282 dirstate_node: NodeRef<'tree, '_>,
283 ) -> Result<(), DirstateV2ParseError> {
271 self.mark_removed_or_deleted_if_file( 284 self.mark_removed_or_deleted_if_file(
272 dirstate_node.full_path(), 285 dirstate_node.full_path()?,
273 dirstate_node.state(), 286 dirstate_node.state()?,
274 ); 287 );
275 dirstate_node 288 dirstate_node
276 .children() 289 .children()?
277 .par_iter() 290 .par_iter()
278 .for_each(|child_node| self.traverse_dirstate_only(child_node)) 291 .map(|child_node| self.traverse_dirstate_only(child_node))
292 .collect()
279 } 293 }
280 294
281 /// A node in the dirstate tree has no corresponding *file* on the 295 /// A node in the dirstate tree has no corresponding *file* on the
282 /// filesystem 296 /// filesystem
283 /// 297 ///