Mercurial > public > mercurial-scm > hg
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 /// |