comparison rust/hg-core/src/dirstate_tree/status.rs @ 47333:69530e5d4fe5

dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums They are used instead of `&Node` and `&ChildNodes` respectively. The `ChildNodes` type alias also becomes a similar enum. For now they only have one variant each, to be extended later. Adding enums now forces various use sites go through new methods instead of manipulating the underlying data structure directly. Differential Revision: https://phab.mercurial-scm.org/D10747
author Simon Sapin <simon.sapin@octobus.net>
date Wed, 19 May 2021 13:15:00 +0200
parents 0252600fd1cf
children ed1583a845d2
comparison
equal deleted inserted replaced
47332:4ee9f419c52e 47333:69530e5d4fe5
1 use crate::dirstate::status::IgnoreFnType; 1 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate_tree::dirstate_map::ChildNodes; 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::Node; 4 use crate::dirstate_tree::dirstate_map::NodeRef;
5 use crate::matchers::get_ignore_function; 5 use crate::matchers::get_ignore_function;
6 use crate::matchers::Matcher; 6 use crate::matchers::Matcher;
7 use crate::utils::files::get_bytes_from_os_string; 7 use crate::utils::files::get_bytes_from_os_string;
8 use crate::utils::hg_path::HgPath; 8 use crate::utils::hg_path::HgPath;
9 use crate::BadMatch; 9 use crate::BadMatch;
54 let is_at_repo_root = true; 54 let is_at_repo_root = true;
55 let hg_path = HgPath::new(""); 55 let hg_path = HgPath::new("");
56 let has_ignored_ancestor = false; 56 let has_ignored_ancestor = false;
57 common.traverse_fs_directory_and_dirstate( 57 common.traverse_fs_directory_and_dirstate(
58 has_ignored_ancestor, 58 has_ignored_ancestor,
59 &dmap.root, 59 dmap.root.as_ref(),
60 hg_path, 60 hg_path,
61 &root_dir, 61 &root_dir,
62 is_at_repo_root, 62 is_at_repo_root,
63 ); 63 );
64 Ok((common.outcome.into_inner().unwrap(), warnings)) 64 Ok((common.outcome.into_inner().unwrap(), warnings))
91 } 91 }
92 92
93 fn traverse_fs_directory_and_dirstate( 93 fn traverse_fs_directory_and_dirstate(
94 &self, 94 &self,
95 has_ignored_ancestor: bool, 95 has_ignored_ancestor: bool,
96 dirstate_nodes: &'tree ChildNodes, 96 dirstate_nodes: ChildNodesRef<'tree, '_>,
97 directory_hg_path: &'tree HgPath, 97 directory_hg_path: &'tree HgPath,
98 directory_fs_path: &Path, 98 directory_fs_path: &Path,
99 is_at_repo_root: bool, 99 is_at_repo_root: bool,
100 ) { 100 ) {
101 let mut fs_entries = if let Ok(entries) = self.read_dir( 101 let mut fs_entries = if let Ok(entries) = self.read_dir(
108 return; 108 return;
109 }; 109 };
110 110
111 // `merge_join_by` requires both its input iterators to be sorted: 111 // `merge_join_by` requires both its input iterators to be sorted:
112 112
113 let dirstate_nodes = Node::sorted(dirstate_nodes); 113 let dirstate_nodes = dirstate_nodes.sorted();
114 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value: 114 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
115 // https://github.com/rust-lang/rust/issues/34162 115 // https://github.com/rust-lang/rust/issues/34162
116 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name)); 116 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
117 117
118 itertools::merge_join_by( 118 itertools::merge_join_by(
119 dirstate_nodes, 119 dirstate_nodes,
120 &fs_entries, 120 &fs_entries,
121 |(full_path, _node), fs_entry| { 121 |dirstate_node, fs_entry| {
122 full_path.base_name().cmp(&fs_entry.base_name) 122 dirstate_node.base_name().cmp(&fs_entry.base_name)
123 }, 123 },
124 ) 124 )
125 .par_bridge() 125 .par_bridge()
126 .for_each(|pair| { 126 .for_each(|pair| {
127 use itertools::EitherOrBoth::*; 127 use itertools::EitherOrBoth::*;
128 match pair { 128 match pair {
129 Both((hg_path, dirstate_node), fs_entry) => { 129 Both(dirstate_node, fs_entry) => {
130 self.traverse_fs_and_dirstate( 130 self.traverse_fs_and_dirstate(
131 fs_entry, 131 fs_entry,
132 hg_path.full_path(),
133 dirstate_node, 132 dirstate_node,
134 has_ignored_ancestor, 133 has_ignored_ancestor,
135 ); 134 );
136 } 135 }
137 Left((hg_path, dirstate_node)) => self.traverse_dirstate_only( 136 Left(dirstate_node) => {
138 hg_path.full_path(), 137 self.traverse_dirstate_only(dirstate_node)
139 dirstate_node, 138 }
140 ),
141 Right(fs_entry) => self.traverse_fs_only( 139 Right(fs_entry) => self.traverse_fs_only(
142 has_ignored_ancestor, 140 has_ignored_ancestor,
143 directory_hg_path, 141 directory_hg_path,
144 fs_entry, 142 fs_entry,
145 ), 143 ),
148 } 146 }
149 147
150 fn traverse_fs_and_dirstate( 148 fn traverse_fs_and_dirstate(
151 &self, 149 &self,
152 fs_entry: &DirEntry, 150 fs_entry: &DirEntry,
153 hg_path: &'tree HgPath, 151 dirstate_node: NodeRef<'tree, '_>,
154 dirstate_node: &'tree Node,
155 has_ignored_ancestor: bool, 152 has_ignored_ancestor: bool,
156 ) { 153 ) {
154 let hg_path = dirstate_node.full_path();
157 let file_type = fs_entry.metadata.file_type(); 155 let file_type = fs_entry.metadata.file_type();
158 let file_or_symlink = file_type.is_file() || file_type.is_symlink(); 156 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
159 if !file_or_symlink { 157 if !file_or_symlink {
160 // If we previously had a file here, it was removed (with 158 // If we previously had a file here, it was removed (with
161 // `hg rm` or similar) or deleted before it could be 159 // `hg rm` or similar) or deleted before it could be
162 // replaced by a directory or something else. 160 // replaced by a directory or something else.
163 self.mark_removed_or_deleted_if_file( 161 self.mark_removed_or_deleted_if_file(
164 hg_path, 162 dirstate_node.full_path(),
165 dirstate_node.state(), 163 dirstate_node.state(),
166 ); 164 );
167 } 165 }
168 if file_type.is_dir() { 166 if file_type.is_dir() {
169 if self.options.collect_traversed_dirs { 167 if self.options.collect_traversed_dirs {
171 } 169 }
172 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path); 170 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
173 let is_at_repo_root = false; 171 let is_at_repo_root = false;
174 self.traverse_fs_directory_and_dirstate( 172 self.traverse_fs_directory_and_dirstate(
175 is_ignored, 173 is_ignored,
176 &dirstate_node.children, 174 dirstate_node.children(),
177 hg_path, 175 hg_path,
178 &fs_entry.full_path, 176 &fs_entry.full_path,
179 is_at_repo_root, 177 is_at_repo_root,
180 ); 178 );
181 } else { 179 } else {
182 if file_or_symlink && self.matcher.matches(hg_path) { 180 if file_or_symlink && self.matcher.matches(hg_path) {
183 let full_path = Cow::from(hg_path); 181 let full_path = Cow::from(hg_path);
184 if let Some(entry) = &dirstate_node.entry { 182 if let Some(state) = dirstate_node.state() {
185 match entry.state { 183 match state {
186 EntryState::Added => { 184 EntryState::Added => {
187 self.outcome.lock().unwrap().added.push(full_path) 185 self.outcome.lock().unwrap().added.push(full_path)
188 } 186 }
189 EntryState::Removed => self 187 EntryState::Removed => self
190 .outcome 188 .outcome
197 .lock() 195 .lock()
198 .unwrap() 196 .unwrap()
199 .modified 197 .modified
200 .push(full_path), 198 .push(full_path),
201 EntryState::Normal => { 199 EntryState::Normal => {
202 self.handle_normal_file( 200 self.handle_normal_file(&dirstate_node, fs_entry);
203 full_path,
204 dirstate_node,
205 entry,
206 fs_entry,
207 );
208 } 201 }
209 // This variant is not used in DirstateMap 202 // This variant is not used in DirstateMap
210 // nodes 203 // nodes
211 EntryState::Unknown => unreachable!(), 204 EntryState::Unknown => unreachable!(),
212 } 205 }
218 full_path, 211 full_path,
219 ) 212 )
220 } 213 }
221 } 214 }
222 215
223 for (child_hg_path, child_node) in &dirstate_node.children { 216 for child_node in dirstate_node.children().iter() {
224 self.traverse_dirstate_only( 217 self.traverse_dirstate_only(child_node)
225 child_hg_path.full_path(),
226 child_node,
227 )
228 } 218 }
229 } 219 }
230 } 220 }
231 221
232 /// A file with `EntryState::Normal` in the dirstate was found in the 222 /// A file with `EntryState::Normal` in the dirstate was found in the
233 /// filesystem 223 /// filesystem
234 fn handle_normal_file( 224 fn handle_normal_file(
235 &self, 225 &self,
236 full_path: Cow<'tree, HgPath>, 226 dirstate_node: &NodeRef<'tree, '_>,
237 dirstate_node: &Node,
238 entry: &crate::DirstateEntry,
239 fs_entry: &DirEntry, 227 fs_entry: &DirEntry,
240 ) { 228 ) {
241 // Keep the low 31 bits 229 // Keep the low 31 bits
242 fn truncate_u64(value: u64) -> i32 { 230 fn truncate_u64(value: u64) -> i32 {
243 (value & 0x7FFF_FFFF) as i32 231 (value & 0x7FFF_FFFF) as i32
244 } 232 }
245 fn truncate_i64(value: i64) -> i32 { 233 fn truncate_i64(value: i64) -> i32 {
246 (value & 0x7FFF_FFFF) as i32 234 (value & 0x7FFF_FFFF) as i32
247 } 235 }
248 236
237 let entry = dirstate_node
238 .entry()
239 .expect("handle_normal_file called with entry-less node");
240 let full_path = Cow::from(dirstate_node.full_path());
249 let mode_changed = || { 241 let mode_changed = || {
250 self.options.check_exec && entry.mode_changed(&fs_entry.metadata) 242 self.options.check_exec && entry.mode_changed(&fs_entry.metadata)
251 }; 243 };
252 let size_changed = entry.size != truncate_u64(fs_entry.metadata.len()); 244 let size_changed = entry.size != truncate_u64(fs_entry.metadata.len());
253 if entry.size >= 0 245 if entry.size >= 0
255 && fs_entry.metadata.file_type().is_symlink() 247 && fs_entry.metadata.file_type().is_symlink()
256 { 248 {
257 // issue6456: Size returned may be longer due to encryption 249 // issue6456: Size returned may be longer due to encryption
258 // on EXT-4 fscrypt. TODO maybe only do it on EXT4? 250 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
259 self.outcome.lock().unwrap().unsure.push(full_path) 251 self.outcome.lock().unwrap().unsure.push(full_path)
260 } else if dirstate_node.copy_source.is_some() 252 } else if dirstate_node.copy_source().is_some()
261 || entry.is_from_other_parent() 253 || entry.is_from_other_parent()
262 || (entry.size >= 0 && (size_changed || mode_changed())) 254 || (entry.size >= 0 && (size_changed || mode_changed()))
263 { 255 {
264 self.outcome.lock().unwrap().modified.push(full_path) 256 self.outcome.lock().unwrap().modified.push(full_path)
265 } else { 257 } else {
273 } 265 }
274 } 266 }
275 } 267 }
276 268
277 /// A node in the dirstate tree has no corresponding filesystem entry 269 /// A node in the dirstate tree has no corresponding filesystem entry
278 fn traverse_dirstate_only( 270 fn traverse_dirstate_only(&self, dirstate_node: NodeRef<'tree, '_>) {
279 &self, 271 self.mark_removed_or_deleted_if_file(
280 hg_path: &'tree HgPath, 272 dirstate_node.full_path(),
281 dirstate_node: &'tree Node, 273 dirstate_node.state(),
282 ) { 274 );
283 self.mark_removed_or_deleted_if_file(hg_path, dirstate_node.state()); 275 dirstate_node
284 dirstate_node.children.par_iter().for_each( 276 .children()
285 |(child_hg_path, child_node)| { 277 .par_iter()
286 self.traverse_dirstate_only( 278 .for_each(|child_node| self.traverse_dirstate_only(child_node))
287 child_hg_path.full_path(),
288 child_node,
289 )
290 },
291 )
292 } 279 }
293 280
294 /// A node in the dirstate tree has no corresponding *file* on the 281 /// A node in the dirstate tree has no corresponding *file* on the
295 /// filesystem 282 /// filesystem
296 /// 283 ///