##// END OF EJS Templates
rust: clippy pass...
Raphaël Gomès -
r46181:2a169781 default
parent child Browse files
Show More
@@ -1,392 +1,392 b''
1 // iter.rs
1 // iter.rs
2 //
2 //
3 // Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use super::node::{Node, NodeKind};
8 use super::node::{Node, NodeKind};
9 use super::tree::Tree;
9 use super::tree::Tree;
10 use crate::dirstate::dirstate_tree::node::Directory;
10 use crate::dirstate::dirstate_tree::node::Directory;
11 use crate::dirstate::status::Dispatch;
11 use crate::dirstate::status::Dispatch;
12 use crate::utils::hg_path::{hg_path_to_path_buf, HgPath, HgPathBuf};
12 use crate::utils::hg_path::{hg_path_to_path_buf, HgPath, HgPathBuf};
13 use crate::DirstateEntry;
13 use crate::DirstateEntry;
14 use std::borrow::Cow;
14 use std::borrow::Cow;
15 use std::collections::VecDeque;
15 use std::collections::VecDeque;
16 use std::iter::{FromIterator, FusedIterator};
16 use std::iter::{FromIterator, FusedIterator};
17 use std::path::PathBuf;
17 use std::path::PathBuf;
18
18
19 impl FromIterator<(HgPathBuf, DirstateEntry)> for Tree {
19 impl FromIterator<(HgPathBuf, DirstateEntry)> for Tree {
20 fn from_iter<T: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
20 fn from_iter<T: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
21 iter: T,
21 iter: T,
22 ) -> Self {
22 ) -> Self {
23 let mut tree = Self::new();
23 let mut tree = Self::new();
24 for (path, entry) in iter {
24 for (path, entry) in iter {
25 tree.insert(path, entry);
25 tree.insert(path, entry);
26 }
26 }
27 tree
27 tree
28 }
28 }
29 }
29 }
30
30
31 /// Iterator of all entries in the dirstate tree.
31 /// Iterator of all entries in the dirstate tree.
32 ///
32 ///
33 /// It has no particular ordering.
33 /// It has no particular ordering.
34 pub struct Iter<'a> {
34 pub struct Iter<'a> {
35 to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>,
35 to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>,
36 }
36 }
37
37
38 impl<'a> Iter<'a> {
38 impl<'a> Iter<'a> {
39 pub fn new(node: &'a Node) -> Iter<'a> {
39 pub fn new(node: &'a Node) -> Iter<'a> {
40 let mut to_visit = VecDeque::new();
40 let mut to_visit = VecDeque::new();
41 to_visit.push_back((Cow::Borrowed(&b""[..]), node));
41 to_visit.push_back((Cow::Borrowed(&b""[..]), node));
42 Self { to_visit }
42 Self { to_visit }
43 }
43 }
44 }
44 }
45
45
46 impl<'a> Iterator for Iter<'a> {
46 impl<'a> Iterator for Iter<'a> {
47 type Item = (HgPathBuf, DirstateEntry);
47 type Item = (HgPathBuf, DirstateEntry);
48
48
49 fn next(&mut self) -> Option<Self::Item> {
49 fn next(&mut self) -> Option<Self::Item> {
50 while let Some((base_path, node)) = self.to_visit.pop_front() {
50 while let Some((base_path, node)) = self.to_visit.pop_front() {
51 match &node.kind {
51 match &node.kind {
52 NodeKind::Directory(dir) => {
52 NodeKind::Directory(dir) => {
53 add_children_to_visit(
53 add_children_to_visit(
54 &mut self.to_visit,
54 &mut self.to_visit,
55 &base_path,
55 &base_path,
56 &dir,
56 &dir,
57 );
57 );
58 if let Some(file) = &dir.was_file {
58 if let Some(file) = &dir.was_file {
59 return Some((
59 return Some((
60 HgPathBuf::from_bytes(&base_path),
60 HgPathBuf::from_bytes(&base_path),
61 file.entry,
61 file.entry,
62 ));
62 ));
63 }
63 }
64 }
64 }
65 NodeKind::File(file) => {
65 NodeKind::File(file) => {
66 if let Some(dir) = &file.was_directory {
66 if let Some(dir) = &file.was_directory {
67 add_children_to_visit(
67 add_children_to_visit(
68 &mut self.to_visit,
68 &mut self.to_visit,
69 &base_path,
69 &base_path,
70 &dir,
70 &dir,
71 );
71 );
72 }
72 }
73 return Some((
73 return Some((
74 HgPathBuf::from_bytes(&base_path),
74 HgPathBuf::from_bytes(&base_path),
75 file.entry,
75 file.entry,
76 ));
76 ));
77 }
77 }
78 }
78 }
79 }
79 }
80 None
80 None
81 }
81 }
82 }
82 }
83
83
84 impl<'a> FusedIterator for Iter<'a> {}
84 impl<'a> FusedIterator for Iter<'a> {}
85
85
86 /// Iterator of all entries in the dirstate tree, with a special filesystem
86 /// Iterator of all entries in the dirstate tree, with a special filesystem
87 /// handling for the directories containing said entries.
87 /// handling for the directories containing said entries.
88 ///
88 ///
89 /// It checks every directory on-disk to see if it has become a symlink, to
89 /// It checks every directory on-disk to see if it has become a symlink, to
90 /// prevent a potential security issue.
90 /// prevent a potential security issue.
91 /// Using this information, it may dispatch `status` information early: it
91 /// Using this information, it may dispatch `status` information early: it
92 /// returns canonical paths along with `Shortcut`s, which are either a
92 /// returns canonical paths along with `Shortcut`s, which are either a
93 /// `DirstateEntry` or a `Dispatch`, if the fate of said path has already been
93 /// `DirstateEntry` or a `Dispatch`, if the fate of said path has already been
94 /// determined.
94 /// determined.
95 ///
95 ///
96 /// Like `Iter`, it has no particular ordering.
96 /// Like `Iter`, it has no particular ordering.
97 pub struct FsIter<'a> {
97 pub struct FsIter<'a> {
98 root_dir: PathBuf,
98 root_dir: PathBuf,
99 to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>,
99 to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>,
100 shortcuts: VecDeque<(HgPathBuf, StatusShortcut)>,
100 shortcuts: VecDeque<(HgPathBuf, StatusShortcut)>,
101 }
101 }
102
102
103 impl<'a> FsIter<'a> {
103 impl<'a> FsIter<'a> {
104 pub fn new(node: &'a Node, root_dir: PathBuf) -> FsIter<'a> {
104 pub fn new(node: &'a Node, root_dir: PathBuf) -> FsIter<'a> {
105 let mut to_visit = VecDeque::new();
105 let mut to_visit = VecDeque::new();
106 to_visit.push_back((Cow::Borrowed(&b""[..]), node));
106 to_visit.push_back((Cow::Borrowed(&b""[..]), node));
107 Self {
107 Self {
108 root_dir,
108 root_dir,
109 to_visit,
109 to_visit,
110 shortcuts: Default::default(),
110 shortcuts: Default::default(),
111 }
111 }
112 }
112 }
113
113
114 /// Mercurial tracks symlinks but *not* what they point to.
114 /// Mercurial tracks symlinks but *not* what they point to.
115 /// If a directory is moved and symlinked:
115 /// If a directory is moved and symlinked:
116 ///
116 ///
117 /// ```bash
117 /// ```bash
118 /// $ mkdir foo
118 /// $ mkdir foo
119 /// $ touch foo/a
119 /// $ touch foo/a
120 /// $ # commit...
120 /// $ # commit...
121 /// $ mv foo bar
121 /// $ mv foo bar
122 /// $ ln -s bar foo
122 /// $ ln -s bar foo
123 /// ```
123 /// ```
124 /// We need to dispatch the new symlink as `Unknown` and all the
124 /// We need to dispatch the new symlink as `Unknown` and all the
125 /// descendents of the directory it replace as `Deleted`.
125 /// descendents of the directory it replace as `Deleted`.
126 fn dispatch_symlinked_directory(
126 fn dispatch_symlinked_directory(
127 &mut self,
127 &mut self,
128 path: impl AsRef<HgPath>,
128 path: impl AsRef<HgPath>,
129 node: &Node,
129 node: &Node,
130 ) {
130 ) {
131 let path = path.as_ref();
131 let path = path.as_ref();
132 self.shortcuts.push_back((
132 self.shortcuts.push_back((
133 path.to_owned(),
133 path.to_owned(),
134 StatusShortcut::Dispatch(Dispatch::Unknown),
134 StatusShortcut::Dispatch(Dispatch::Unknown),
135 ));
135 ));
136 for (file, _) in node.iter() {
136 for (file, _) in node.iter() {
137 self.shortcuts.push_back((
137 self.shortcuts.push_back((
138 path.join(&file),
138 path.join(&file),
139 StatusShortcut::Dispatch(Dispatch::Deleted),
139 StatusShortcut::Dispatch(Dispatch::Deleted),
140 ));
140 ));
141 }
141 }
142 }
142 }
143
143
144 /// Returns `true` if the canonical `path` of a directory corresponds to a
144 /// Returns `true` if the canonical `path` of a directory corresponds to a
145 /// symlink on disk. It means it was moved and symlinked after the last
145 /// symlink on disk. It means it was moved and symlinked after the last
146 /// dirstate update.
146 /// dirstate update.
147 ///
147 ///
148 /// # Special cases
148 /// # Special cases
149 ///
149 ///
150 /// Returns `false` for the repository root.
150 /// Returns `false` for the repository root.
151 /// Returns `false` on io error, error handling is outside of the iterator.
151 /// Returns `false` on io error, error handling is outside of the iterator.
152 fn directory_became_symlink(&mut self, path: &HgPath) -> bool {
152 fn directory_became_symlink(&mut self, path: &HgPath) -> bool {
153 if path.is_empty() {
153 if path.is_empty() {
154 return false;
154 return false;
155 }
155 }
156 let filename_as_path = match hg_path_to_path_buf(&path) {
156 let filename_as_path = match hg_path_to_path_buf(&path) {
157 Ok(p) => p,
157 Ok(p) => p,
158 _ => return false,
158 _ => return false,
159 };
159 };
160 let meta = self.root_dir.join(filename_as_path).symlink_metadata();
160 let meta = self.root_dir.join(filename_as_path).symlink_metadata();
161 match meta {
161 match meta {
162 Ok(ref m) if m.file_type().is_symlink() => true,
162 Ok(ref m) if m.file_type().is_symlink() => true,
163 _ => return false,
163 _ => false,
164 }
164 }
165 }
165 }
166 }
166 }
167
167
168 /// Returned by `FsIter`, since the `Dispatch` of any given entry may already
168 /// Returned by `FsIter`, since the `Dispatch` of any given entry may already
169 /// be determined during the iteration. This is necessary for performance
169 /// be determined during the iteration. This is necessary for performance
170 /// reasons, since hierarchical information is needed to `Dispatch` an entire
170 /// reasons, since hierarchical information is needed to `Dispatch` an entire
171 /// subtree efficiently.
171 /// subtree efficiently.
172 #[derive(Debug, Copy, Clone)]
172 #[derive(Debug, Copy, Clone)]
173 pub enum StatusShortcut {
173 pub enum StatusShortcut {
174 /// A entry in the dirstate for further inspection
174 /// A entry in the dirstate for further inspection
175 Entry(DirstateEntry),
175 Entry(DirstateEntry),
176 /// The result of the status of the corresponding file
176 /// The result of the status of the corresponding file
177 Dispatch(Dispatch),
177 Dispatch(Dispatch),
178 }
178 }
179
179
180 impl<'a> Iterator for FsIter<'a> {
180 impl<'a> Iterator for FsIter<'a> {
181 type Item = (HgPathBuf, StatusShortcut);
181 type Item = (HgPathBuf, StatusShortcut);
182
182
183 fn next(&mut self) -> Option<Self::Item> {
183 fn next(&mut self) -> Option<Self::Item> {
184 // If any paths have already been `Dispatch`-ed, return them
184 // If any paths have already been `Dispatch`-ed, return them
185 while let Some(res) = self.shortcuts.pop_front() {
185 if let Some(res) = self.shortcuts.pop_front() {
186 return Some(res);
186 return Some(res);
187 }
187 }
188
188
189 while let Some((base_path, node)) = self.to_visit.pop_front() {
189 while let Some((base_path, node)) = self.to_visit.pop_front() {
190 match &node.kind {
190 match &node.kind {
191 NodeKind::Directory(dir) => {
191 NodeKind::Directory(dir) => {
192 let canonical_path = HgPath::new(&base_path);
192 let canonical_path = HgPath::new(&base_path);
193 if self.directory_became_symlink(canonical_path) {
193 if self.directory_became_symlink(canonical_path) {
194 // Potential security issue, don't do a normal
194 // Potential security issue, don't do a normal
195 // traversal, force the results.
195 // traversal, force the results.
196 self.dispatch_symlinked_directory(
196 self.dispatch_symlinked_directory(
197 canonical_path,
197 canonical_path,
198 &node,
198 &node,
199 );
199 );
200 continue;
200 continue;
201 }
201 }
202 add_children_to_visit(
202 add_children_to_visit(
203 &mut self.to_visit,
203 &mut self.to_visit,
204 &base_path,
204 &base_path,
205 &dir,
205 &dir,
206 );
206 );
207 if let Some(file) = &dir.was_file {
207 if let Some(file) = &dir.was_file {
208 return Some((
208 return Some((
209 HgPathBuf::from_bytes(&base_path),
209 HgPathBuf::from_bytes(&base_path),
210 StatusShortcut::Entry(file.entry),
210 StatusShortcut::Entry(file.entry),
211 ));
211 ));
212 }
212 }
213 }
213 }
214 NodeKind::File(file) => {
214 NodeKind::File(file) => {
215 if let Some(dir) = &file.was_directory {
215 if let Some(dir) = &file.was_directory {
216 add_children_to_visit(
216 add_children_to_visit(
217 &mut self.to_visit,
217 &mut self.to_visit,
218 &base_path,
218 &base_path,
219 &dir,
219 &dir,
220 );
220 );
221 }
221 }
222 return Some((
222 return Some((
223 HgPathBuf::from_bytes(&base_path),
223 HgPathBuf::from_bytes(&base_path),
224 StatusShortcut::Entry(file.entry),
224 StatusShortcut::Entry(file.entry),
225 ));
225 ));
226 }
226 }
227 }
227 }
228 }
228 }
229
229
230 None
230 None
231 }
231 }
232 }
232 }
233
233
234 impl<'a> FusedIterator for FsIter<'a> {}
234 impl<'a> FusedIterator for FsIter<'a> {}
235
235
236 fn join_path<'a, 'b>(path: &'a [u8], other: &'b [u8]) -> Cow<'b, [u8]> {
236 fn join_path<'a, 'b>(path: &'a [u8], other: &'b [u8]) -> Cow<'b, [u8]> {
237 if path.is_empty() {
237 if path.is_empty() {
238 other.into()
238 other.into()
239 } else {
239 } else {
240 [path, &b"/"[..], other].concat().into()
240 [path, &b"/"[..], other].concat().into()
241 }
241 }
242 }
242 }
243
243
244 /// Adds all children of a given directory `dir` to the visit queue `to_visit`
244 /// Adds all children of a given directory `dir` to the visit queue `to_visit`
245 /// prefixed by a `base_path`.
245 /// prefixed by a `base_path`.
246 fn add_children_to_visit<'a>(
246 fn add_children_to_visit<'a>(
247 to_visit: &mut VecDeque<(Cow<'a, [u8]>, &'a Node)>,
247 to_visit: &mut VecDeque<(Cow<'a, [u8]>, &'a Node)>,
248 base_path: &[u8],
248 base_path: &[u8],
249 dir: &'a Directory,
249 dir: &'a Directory,
250 ) {
250 ) {
251 to_visit.extend(dir.children.iter().map(|(path, child)| {
251 to_visit.extend(dir.children.iter().map(|(path, child)| {
252 let full_path = join_path(&base_path, &path);
252 let full_path = join_path(&base_path, &path);
253 (Cow::from(full_path), child)
253 (full_path, child)
254 }));
254 }));
255 }
255 }
256
256
257 #[cfg(test)]
257 #[cfg(test)]
258 mod tests {
258 mod tests {
259 use super::*;
259 use super::*;
260 use crate::utils::hg_path::HgPath;
260 use crate::utils::hg_path::HgPath;
261 use crate::{EntryState, FastHashMap};
261 use crate::{EntryState, FastHashMap};
262 use std::collections::HashSet;
262 use std::collections::HashSet;
263
263
264 #[test]
264 #[test]
265 fn test_iteration() {
265 fn test_iteration() {
266 let mut tree = Tree::new();
266 let mut tree = Tree::new();
267
267
268 assert_eq!(
268 assert_eq!(
269 tree.insert(
269 tree.insert(
270 HgPathBuf::from_bytes(b"foo/bar"),
270 HgPathBuf::from_bytes(b"foo/bar"),
271 DirstateEntry {
271 DirstateEntry {
272 state: EntryState::Merged,
272 state: EntryState::Merged,
273 mode: 41,
273 mode: 41,
274 mtime: 42,
274 mtime: 42,
275 size: 43,
275 size: 43,
276 }
276 }
277 ),
277 ),
278 None
278 None
279 );
279 );
280
280
281 assert_eq!(
281 assert_eq!(
282 tree.insert(
282 tree.insert(
283 HgPathBuf::from_bytes(b"foo2"),
283 HgPathBuf::from_bytes(b"foo2"),
284 DirstateEntry {
284 DirstateEntry {
285 state: EntryState::Merged,
285 state: EntryState::Merged,
286 mode: 40,
286 mode: 40,
287 mtime: 41,
287 mtime: 41,
288 size: 42,
288 size: 42,
289 }
289 }
290 ),
290 ),
291 None
291 None
292 );
292 );
293
293
294 assert_eq!(
294 assert_eq!(
295 tree.insert(
295 tree.insert(
296 HgPathBuf::from_bytes(b"foo/baz"),
296 HgPathBuf::from_bytes(b"foo/baz"),
297 DirstateEntry {
297 DirstateEntry {
298 state: EntryState::Normal,
298 state: EntryState::Normal,
299 mode: 0,
299 mode: 0,
300 mtime: 0,
300 mtime: 0,
301 size: 0,
301 size: 0,
302 }
302 }
303 ),
303 ),
304 None
304 None
305 );
305 );
306
306
307 assert_eq!(
307 assert_eq!(
308 tree.insert(
308 tree.insert(
309 HgPathBuf::from_bytes(b"foo/bap/nested"),
309 HgPathBuf::from_bytes(b"foo/bap/nested"),
310 DirstateEntry {
310 DirstateEntry {
311 state: EntryState::Normal,
311 state: EntryState::Normal,
312 mode: 0,
312 mode: 0,
313 mtime: 0,
313 mtime: 0,
314 size: 0,
314 size: 0,
315 }
315 }
316 ),
316 ),
317 None
317 None
318 );
318 );
319
319
320 assert_eq!(tree.len(), 4);
320 assert_eq!(tree.len(), 4);
321
321
322 let results: HashSet<_> =
322 let results: HashSet<_> =
323 tree.iter().map(|(c, _)| c.to_owned()).collect();
323 tree.iter().map(|(c, _)| c.to_owned()).collect();
324 dbg!(&results);
324 dbg!(&results);
325 assert!(results.contains(HgPath::new(b"foo2")));
325 assert!(results.contains(HgPath::new(b"foo2")));
326 assert!(results.contains(HgPath::new(b"foo/bar")));
326 assert!(results.contains(HgPath::new(b"foo/bar")));
327 assert!(results.contains(HgPath::new(b"foo/baz")));
327 assert!(results.contains(HgPath::new(b"foo/baz")));
328 assert!(results.contains(HgPath::new(b"foo/bap/nested")));
328 assert!(results.contains(HgPath::new(b"foo/bap/nested")));
329
329
330 let mut iter = tree.iter();
330 let mut iter = tree.iter();
331 assert!(iter.next().is_some());
331 assert!(iter.next().is_some());
332 assert!(iter.next().is_some());
332 assert!(iter.next().is_some());
333 assert!(iter.next().is_some());
333 assert!(iter.next().is_some());
334 assert!(iter.next().is_some());
334 assert!(iter.next().is_some());
335 assert_eq!(None, iter.next());
335 assert_eq!(None, iter.next());
336 assert_eq!(None, iter.next());
336 assert_eq!(None, iter.next());
337 drop(iter);
337 drop(iter);
338
338
339 assert_eq!(
339 assert_eq!(
340 tree.insert(
340 tree.insert(
341 HgPathBuf::from_bytes(b"foo/bap/nested/a"),
341 HgPathBuf::from_bytes(b"foo/bap/nested/a"),
342 DirstateEntry {
342 DirstateEntry {
343 state: EntryState::Normal,
343 state: EntryState::Normal,
344 mode: 0,
344 mode: 0,
345 mtime: 0,
345 mtime: 0,
346 size: 0,
346 size: 0,
347 }
347 }
348 ),
348 ),
349 None
349 None
350 );
350 );
351
351
352 let results: FastHashMap<_, _> = tree.iter().collect();
352 let results: FastHashMap<_, _> = tree.iter().collect();
353 assert!(results.contains_key(HgPath::new(b"foo2")));
353 assert!(results.contains_key(HgPath::new(b"foo2")));
354 assert!(results.contains_key(HgPath::new(b"foo/bar")));
354 assert!(results.contains_key(HgPath::new(b"foo/bar")));
355 assert!(results.contains_key(HgPath::new(b"foo/baz")));
355 assert!(results.contains_key(HgPath::new(b"foo/baz")));
356 // Is a dir but `was_file`, so it's listed as a removed file
356 // Is a dir but `was_file`, so it's listed as a removed file
357 assert!(results.contains_key(HgPath::new(b"foo/bap/nested")));
357 assert!(results.contains_key(HgPath::new(b"foo/bap/nested")));
358 assert!(results.contains_key(HgPath::new(b"foo/bap/nested/a")));
358 assert!(results.contains_key(HgPath::new(b"foo/bap/nested/a")));
359
359
360 // insert removed file (now directory) after nested file
360 // insert removed file (now directory) after nested file
361 assert_eq!(
361 assert_eq!(
362 tree.insert(
362 tree.insert(
363 HgPathBuf::from_bytes(b"a/a"),
363 HgPathBuf::from_bytes(b"a/a"),
364 DirstateEntry {
364 DirstateEntry {
365 state: EntryState::Normal,
365 state: EntryState::Normal,
366 mode: 0,
366 mode: 0,
367 mtime: 0,
367 mtime: 0,
368 size: 0,
368 size: 0,
369 }
369 }
370 ),
370 ),
371 None
371 None
372 );
372 );
373
373
374 // `insert` returns `None` for a directory
374 // `insert` returns `None` for a directory
375 assert_eq!(
375 assert_eq!(
376 tree.insert(
376 tree.insert(
377 HgPathBuf::from_bytes(b"a"),
377 HgPathBuf::from_bytes(b"a"),
378 DirstateEntry {
378 DirstateEntry {
379 state: EntryState::Removed,
379 state: EntryState::Removed,
380 mode: 0,
380 mode: 0,
381 mtime: 0,
381 mtime: 0,
382 size: 0,
382 size: 0,
383 }
383 }
384 ),
384 ),
385 None
385 None
386 );
386 );
387
387
388 let results: FastHashMap<_, _> = tree.iter().collect();
388 let results: FastHashMap<_, _> = tree.iter().collect();
389 assert!(results.contains_key(HgPath::new(b"a")));
389 assert!(results.contains_key(HgPath::new(b"a")));
390 assert!(results.contains_key(HgPath::new(b"a/a")));
390 assert!(results.contains_key(HgPath::new(b"a/a")));
391 }
391 }
392 }
392 }
@@ -1,395 +1,395 b''
1 // node.rs
1 // node.rs
2 //
2 //
3 // Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use super::iter::Iter;
8 use super::iter::Iter;
9 use crate::utils::hg_path::HgPathBuf;
9 use crate::utils::hg_path::HgPathBuf;
10 use crate::{DirstateEntry, EntryState, FastHashMap};
10 use crate::{DirstateEntry, EntryState, FastHashMap};
11
11
12 /// Represents a filesystem directory in the dirstate tree
12 /// Represents a filesystem directory in the dirstate tree
13 #[derive(Debug, Default, Clone, PartialEq)]
13 #[derive(Debug, Default, Clone, PartialEq)]
14 pub struct Directory {
14 pub struct Directory {
15 /// Contains the old file information if it existed between changesets.
15 /// Contains the old file information if it existed between changesets.
16 /// Happens if a file `foo` is marked as removed, removed from the
16 /// Happens if a file `foo` is marked as removed, removed from the
17 /// filesystem then a directory `foo` is created and at least one of its
17 /// filesystem then a directory `foo` is created and at least one of its
18 /// descendents is added to Mercurial.
18 /// descendents is added to Mercurial.
19 pub(super) was_file: Option<Box<File>>,
19 pub(super) was_file: Option<Box<File>>,
20 pub(super) children: FastHashMap<Vec<u8>, Node>,
20 pub(super) children: FastHashMap<Vec<u8>, Node>,
21 }
21 }
22
22
23 /// Represents a filesystem file (or symlink) in the dirstate tree
23 /// Represents a filesystem file (or symlink) in the dirstate tree
24 #[derive(Debug, Clone, PartialEq)]
24 #[derive(Debug, Clone, PartialEq)]
25 pub struct File {
25 pub struct File {
26 /// Contains the old structure if it existed between changesets.
26 /// Contains the old structure if it existed between changesets.
27 /// Happens all descendents of `foo` marked as removed and removed from
27 /// Happens all descendents of `foo` marked as removed and removed from
28 /// the filesystem, then a file `foo` is created and added to Mercurial.
28 /// the filesystem, then a file `foo` is created and added to Mercurial.
29 pub(super) was_directory: Option<Box<Directory>>,
29 pub(super) was_directory: Option<Box<Directory>>,
30 pub(super) entry: DirstateEntry,
30 pub(super) entry: DirstateEntry,
31 }
31 }
32
32
33 #[derive(Debug, Clone, PartialEq)]
33 #[derive(Debug, Clone, PartialEq)]
34 pub enum NodeKind {
34 pub enum NodeKind {
35 Directory(Directory),
35 Directory(Directory),
36 File(File),
36 File(File),
37 }
37 }
38
38
39 #[derive(Debug, Default, Clone, PartialEq)]
39 #[derive(Debug, Default, Clone, PartialEq)]
40 pub struct Node {
40 pub struct Node {
41 pub kind: NodeKind,
41 pub kind: NodeKind,
42 }
42 }
43
43
44 impl Default for NodeKind {
44 impl Default for NodeKind {
45 fn default() -> Self {
45 fn default() -> Self {
46 NodeKind::Directory(Default::default())
46 NodeKind::Directory(Default::default())
47 }
47 }
48 }
48 }
49
49
50 impl Node {
50 impl Node {
51 pub fn insert(
51 pub fn insert(
52 &mut self,
52 &mut self,
53 path: &[u8],
53 path: &[u8],
54 new_entry: DirstateEntry,
54 new_entry: DirstateEntry,
55 ) -> InsertResult {
55 ) -> InsertResult {
56 let mut split = path.splitn(2, |&c| c == b'/');
56 let mut split = path.splitn(2, |&c| c == b'/');
57 let head = split.next().unwrap_or(b"");
57 let head = split.next().unwrap_or(b"");
58 let tail = split.next().unwrap_or(b"");
58 let tail = split.next().unwrap_or(b"");
59
59
60 if let NodeKind::File(file) = &mut self.kind {
60 if let NodeKind::File(file) = &mut self.kind {
61 if tail.is_empty() && head.is_empty() {
61 if tail.is_empty() && head.is_empty() {
62 // We're modifying the current file
62 // We're modifying the current file
63 let new = Self {
63 let new = Self {
64 kind: NodeKind::File(File {
64 kind: NodeKind::File(File {
65 entry: new_entry,
65 entry: new_entry,
66 ..file.clone()
66 ..file.clone()
67 }),
67 }),
68 };
68 };
69 return InsertResult {
69 return InsertResult {
70 did_insert: false,
70 did_insert: false,
71 old_entry: Some(std::mem::replace(self, new)),
71 old_entry: Some(std::mem::replace(self, new)),
72 };
72 };
73 } else {
73 } else {
74 match file.entry.state {
74 match file.entry.state {
75 // Only replace the current file with a directory if it's
75 // Only replace the current file with a directory if it's
76 // marked as `Removed`
76 // marked as `Removed`
77 EntryState::Removed => {
77 EntryState::Removed => {
78 self.kind = NodeKind::Directory(Directory {
78 self.kind = NodeKind::Directory(Directory {
79 was_file: Some(Box::from(file.clone())),
79 was_file: Some(Box::from(file.clone())),
80 children: Default::default(),
80 children: Default::default(),
81 })
81 })
82 }
82 }
83 _ => {
83 _ => {
84 return Node::insert_in_file(
84 return Node::insert_in_file(
85 file, new_entry, head, tail,
85 file, new_entry, head, tail,
86 )
86 )
87 }
87 }
88 }
88 }
89 }
89 }
90 }
90 }
91
91
92 match &mut self.kind {
92 match &mut self.kind {
93 NodeKind::Directory(directory) => {
93 NodeKind::Directory(directory) => {
94 return Node::insert_in_directory(
94 Node::insert_in_directory(
95 directory, new_entry, head, tail,
95 directory, new_entry, head, tail,
96 );
96 )
97 }
97 }
98 NodeKind::File(_) => {
98 NodeKind::File(_) => {
99 unreachable!("The file case has already been handled")
99 unreachable!("The file case has already been handled")
100 }
100 }
101 }
101 }
102 }
102 }
103
103
104 /// The current file still exists and is not marked as `Removed`.
104 /// The current file still exists and is not marked as `Removed`.
105 /// Insert the entry in its `was_directory`.
105 /// Insert the entry in its `was_directory`.
106 fn insert_in_file(
106 fn insert_in_file(
107 file: &mut File,
107 file: &mut File,
108 new_entry: DirstateEntry,
108 new_entry: DirstateEntry,
109 head: &[u8],
109 head: &[u8],
110 tail: &[u8],
110 tail: &[u8],
111 ) -> InsertResult {
111 ) -> InsertResult {
112 if let Some(d) = &mut file.was_directory {
112 if let Some(d) = &mut file.was_directory {
113 Node::insert_in_directory(d, new_entry, head, tail)
113 Node::insert_in_directory(d, new_entry, head, tail)
114 } else {
114 } else {
115 let mut dir = Directory {
115 let mut dir = Directory {
116 was_file: None,
116 was_file: None,
117 children: FastHashMap::default(),
117 children: FastHashMap::default(),
118 };
118 };
119 let res =
119 let res =
120 Node::insert_in_directory(&mut dir, new_entry, head, tail);
120 Node::insert_in_directory(&mut dir, new_entry, head, tail);
121 file.was_directory = Some(Box::new(dir));
121 file.was_directory = Some(Box::new(dir));
122 res
122 res
123 }
123 }
124 }
124 }
125
125
126 /// Insert an entry in the subtree of `directory`
126 /// Insert an entry in the subtree of `directory`
127 fn insert_in_directory(
127 fn insert_in_directory(
128 directory: &mut Directory,
128 directory: &mut Directory,
129 new_entry: DirstateEntry,
129 new_entry: DirstateEntry,
130 head: &[u8],
130 head: &[u8],
131 tail: &[u8],
131 tail: &[u8],
132 ) -> InsertResult {
132 ) -> InsertResult {
133 let mut res = InsertResult::default();
133 let mut res = InsertResult::default();
134
134
135 if let Some(node) = directory.children.get_mut(head) {
135 if let Some(node) = directory.children.get_mut(head) {
136 // Node exists
136 // Node exists
137 match &mut node.kind {
137 match &mut node.kind {
138 NodeKind::Directory(subdir) => {
138 NodeKind::Directory(subdir) => {
139 if tail.is_empty() {
139 if tail.is_empty() {
140 let becomes_file = Self {
140 let becomes_file = Self {
141 kind: NodeKind::File(File {
141 kind: NodeKind::File(File {
142 was_directory: Some(Box::from(subdir.clone())),
142 was_directory: Some(Box::from(subdir.clone())),
143 entry: new_entry,
143 entry: new_entry,
144 }),
144 }),
145 };
145 };
146 let old_entry = directory
146 let old_entry = directory
147 .children
147 .children
148 .insert(head.to_owned(), becomes_file);
148 .insert(head.to_owned(), becomes_file);
149 return InsertResult {
149 return InsertResult {
150 did_insert: true,
150 did_insert: true,
151 old_entry,
151 old_entry,
152 };
152 };
153 } else {
153 } else {
154 res = node.insert(tail, new_entry);
154 res = node.insert(tail, new_entry);
155 }
155 }
156 }
156 }
157 NodeKind::File(_) => {
157 NodeKind::File(_) => {
158 res = node.insert(tail, new_entry);
158 res = node.insert(tail, new_entry);
159 }
159 }
160 }
160 }
161 } else if tail.is_empty() {
161 } else if tail.is_empty() {
162 // File does not already exist
162 // File does not already exist
163 directory.children.insert(
163 directory.children.insert(
164 head.to_owned(),
164 head.to_owned(),
165 Self {
165 Self {
166 kind: NodeKind::File(File {
166 kind: NodeKind::File(File {
167 was_directory: None,
167 was_directory: None,
168 entry: new_entry,
168 entry: new_entry,
169 }),
169 }),
170 },
170 },
171 );
171 );
172 res.did_insert = true;
172 res.did_insert = true;
173 } else {
173 } else {
174 // Directory does not already exist
174 // Directory does not already exist
175 let mut nested = Self {
175 let mut nested = Self {
176 kind: NodeKind::Directory(Directory {
176 kind: NodeKind::Directory(Directory {
177 was_file: None,
177 was_file: None,
178 children: Default::default(),
178 children: Default::default(),
179 }),
179 }),
180 };
180 };
181 res = nested.insert(tail, new_entry);
181 res = nested.insert(tail, new_entry);
182 directory.children.insert(head.to_owned(), nested);
182 directory.children.insert(head.to_owned(), nested);
183 }
183 }
184 res
184 res
185 }
185 }
186
186
187 /// Removes an entry from the tree, returns a `RemoveResult`.
187 /// Removes an entry from the tree, returns a `RemoveResult`.
188 pub fn remove(&mut self, path: &[u8]) -> RemoveResult {
188 pub fn remove(&mut self, path: &[u8]) -> RemoveResult {
189 let empty_result = RemoveResult::default();
189 let empty_result = RemoveResult::default();
190 if path.is_empty() {
190 if path.is_empty() {
191 return empty_result;
191 return empty_result;
192 }
192 }
193 let mut split = path.splitn(2, |&c| c == b'/');
193 let mut split = path.splitn(2, |&c| c == b'/');
194 let head = split.next();
194 let head = split.next();
195 let tail = split.next().unwrap_or(b"");
195 let tail = split.next().unwrap_or(b"");
196
196
197 let head = match head {
197 let head = match head {
198 None => {
198 None => {
199 return empty_result;
199 return empty_result;
200 }
200 }
201 Some(h) => h,
201 Some(h) => h,
202 };
202 };
203 if head == path {
203 if head == path {
204 match &mut self.kind {
204 match &mut self.kind {
205 NodeKind::Directory(d) => {
205 NodeKind::Directory(d) => {
206 return Node::remove_from_directory(head, d);
206 return Node::remove_from_directory(head, d);
207 }
207 }
208 NodeKind::File(f) => {
208 NodeKind::File(f) => {
209 if let Some(d) = &mut f.was_directory {
209 if let Some(d) = &mut f.was_directory {
210 let RemoveResult { old_entry, .. } =
210 let RemoveResult { old_entry, .. } =
211 Node::remove_from_directory(head, d);
211 Node::remove_from_directory(head, d);
212 return RemoveResult {
212 return RemoveResult {
213 cleanup: false,
213 cleanup: false,
214 old_entry,
214 old_entry,
215 };
215 };
216 }
216 }
217 }
217 }
218 }
218 }
219 empty_result
219 empty_result
220 } else {
220 } else {
221 // Look into the dirs
221 // Look into the dirs
222 match &mut self.kind {
222 match &mut self.kind {
223 NodeKind::Directory(d) => {
223 NodeKind::Directory(d) => {
224 if let Some(child) = d.children.get_mut(head) {
224 if let Some(child) = d.children.get_mut(head) {
225 let mut res = child.remove(tail);
225 let mut res = child.remove(tail);
226 if res.cleanup {
226 if res.cleanup {
227 d.children.remove(head);
227 d.children.remove(head);
228 }
228 }
229 res.cleanup =
229 res.cleanup =
230 d.children.len() == 0 && d.was_file.is_none();
230 d.children.is_empty() && d.was_file.is_none();
231 res
231 res
232 } else {
232 } else {
233 empty_result
233 empty_result
234 }
234 }
235 }
235 }
236 NodeKind::File(f) => {
236 NodeKind::File(f) => {
237 if let Some(d) = &mut f.was_directory {
237 if let Some(d) = &mut f.was_directory {
238 if let Some(child) = d.children.get_mut(head) {
238 if let Some(child) = d.children.get_mut(head) {
239 let RemoveResult { cleanup, old_entry } =
239 let RemoveResult { cleanup, old_entry } =
240 child.remove(tail);
240 child.remove(tail);
241 if cleanup {
241 if cleanup {
242 d.children.remove(head);
242 d.children.remove(head);
243 }
243 }
244 if d.children.len() == 0 && d.was_file.is_none() {
244 if d.children.is_empty() && d.was_file.is_none() {
245 f.was_directory = None;
245 f.was_directory = None;
246 }
246 }
247
247
248 return RemoveResult {
248 return RemoveResult {
249 cleanup: false,
249 cleanup: false,
250 old_entry,
250 old_entry,
251 };
251 };
252 }
252 }
253 }
253 }
254 empty_result
254 empty_result
255 }
255 }
256 }
256 }
257 }
257 }
258 }
258 }
259
259
260 fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult {
260 fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult {
261 if let Some(node) = d.children.get_mut(head) {
261 if let Some(node) = d.children.get_mut(head) {
262 return match &mut node.kind {
262 return match &mut node.kind {
263 NodeKind::Directory(d) => {
263 NodeKind::Directory(d) => {
264 if let Some(f) = &mut d.was_file {
264 if let Some(f) = &mut d.was_file {
265 let entry = f.entry;
265 let entry = f.entry;
266 d.was_file = None;
266 d.was_file = None;
267 RemoveResult {
267 RemoveResult {
268 cleanup: false,
268 cleanup: false,
269 old_entry: Some(entry),
269 old_entry: Some(entry),
270 }
270 }
271 } else {
271 } else {
272 RemoveResult::default()
272 RemoveResult::default()
273 }
273 }
274 }
274 }
275 NodeKind::File(f) => {
275 NodeKind::File(f) => {
276 let entry = f.entry;
276 let entry = f.entry;
277 let mut cleanup = false;
277 let mut cleanup = false;
278 match &f.was_directory {
278 match &f.was_directory {
279 None => {
279 None => {
280 if d.children.len() == 1 {
280 if d.children.len() == 1 {
281 cleanup = true;
281 cleanup = true;
282 }
282 }
283 d.children.remove(head);
283 d.children.remove(head);
284 }
284 }
285 Some(dir) => {
285 Some(dir) => {
286 node.kind = NodeKind::Directory(*dir.clone());
286 node.kind = NodeKind::Directory(*dir.clone());
287 }
287 }
288 }
288 }
289
289
290 RemoveResult {
290 RemoveResult {
291 cleanup: cleanup,
291 cleanup,
292 old_entry: Some(entry),
292 old_entry: Some(entry),
293 }
293 }
294 }
294 }
295 };
295 };
296 }
296 }
297 RemoveResult::default()
297 RemoveResult::default()
298 }
298 }
299
299
300 pub fn get(&self, path: &[u8]) -> Option<&Node> {
300 pub fn get(&self, path: &[u8]) -> Option<&Node> {
301 if path.is_empty() {
301 if path.is_empty() {
302 return Some(&self);
302 return Some(&self);
303 }
303 }
304 let mut split = path.splitn(2, |&c| c == b'/');
304 let mut split = path.splitn(2, |&c| c == b'/');
305 let head = split.next();
305 let head = split.next();
306 let tail = split.next().unwrap_or(b"");
306 let tail = split.next().unwrap_or(b"");
307
307
308 let head = match head {
308 let head = match head {
309 None => {
309 None => {
310 return Some(&self);
310 return Some(&self);
311 }
311 }
312 Some(h) => h,
312 Some(h) => h,
313 };
313 };
314 match &self.kind {
314 match &self.kind {
315 NodeKind::Directory(d) => {
315 NodeKind::Directory(d) => {
316 if let Some(child) = d.children.get(head) {
316 if let Some(child) = d.children.get(head) {
317 return child.get(tail);
317 return child.get(tail);
318 }
318 }
319 }
319 }
320 NodeKind::File(f) => {
320 NodeKind::File(f) => {
321 if let Some(d) = &f.was_directory {
321 if let Some(d) = &f.was_directory {
322 if let Some(child) = d.children.get(head) {
322 if let Some(child) = d.children.get(head) {
323 return child.get(tail);
323 return child.get(tail);
324 }
324 }
325 }
325 }
326 }
326 }
327 }
327 }
328
328
329 None
329 None
330 }
330 }
331
331
332 pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> {
332 pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> {
333 if path.is_empty() {
333 if path.is_empty() {
334 return Some(&mut self.kind);
334 return Some(&mut self.kind);
335 }
335 }
336 let mut split = path.splitn(2, |&c| c == b'/');
336 let mut split = path.splitn(2, |&c| c == b'/');
337 let head = split.next();
337 let head = split.next();
338 let tail = split.next().unwrap_or(b"");
338 let tail = split.next().unwrap_or(b"");
339
339
340 let head = match head {
340 let head = match head {
341 None => {
341 None => {
342 return Some(&mut self.kind);
342 return Some(&mut self.kind);
343 }
343 }
344 Some(h) => h,
344 Some(h) => h,
345 };
345 };
346 match &mut self.kind {
346 match &mut self.kind {
347 NodeKind::Directory(d) => {
347 NodeKind::Directory(d) => {
348 if let Some(child) = d.children.get_mut(head) {
348 if let Some(child) = d.children.get_mut(head) {
349 return child.get_mut(tail);
349 return child.get_mut(tail);
350 }
350 }
351 }
351 }
352 NodeKind::File(f) => {
352 NodeKind::File(f) => {
353 if let Some(d) = &mut f.was_directory {
353 if let Some(d) = &mut f.was_directory {
354 if let Some(child) = d.children.get_mut(head) {
354 if let Some(child) = d.children.get_mut(head) {
355 return child.get_mut(tail);
355 return child.get_mut(tail);
356 }
356 }
357 }
357 }
358 }
358 }
359 }
359 }
360
360
361 None
361 None
362 }
362 }
363
363
364 pub fn iter(&self) -> Iter {
364 pub fn iter(&self) -> Iter {
365 Iter::new(self)
365 Iter::new(self)
366 }
366 }
367 }
367 }
368
368
369 /// Information returned to the caller of an `insert` operation for integrity.
369 /// Information returned to the caller of an `insert` operation for integrity.
370 #[derive(Debug, Default)]
370 #[derive(Debug, Default)]
371 pub struct InsertResult {
371 pub struct InsertResult {
372 /// Whether the insertion resulted in an actual insertion and not an
372 /// Whether the insertion resulted in an actual insertion and not an
373 /// update
373 /// update
374 pub(super) did_insert: bool,
374 pub(super) did_insert: bool,
375 /// The entry that was replaced, if it exists
375 /// The entry that was replaced, if it exists
376 pub(super) old_entry: Option<Node>,
376 pub(super) old_entry: Option<Node>,
377 }
377 }
378
378
379 /// Information returned to the caller of a `remove` operation integrity.
379 /// Information returned to the caller of a `remove` operation integrity.
380 #[derive(Debug, Default)]
380 #[derive(Debug, Default)]
381 pub struct RemoveResult {
381 pub struct RemoveResult {
382 /// If the caller needs to remove the current node
382 /// If the caller needs to remove the current node
383 pub(super) cleanup: bool,
383 pub(super) cleanup: bool,
384 /// The entry that was replaced, if it exists
384 /// The entry that was replaced, if it exists
385 pub(super) old_entry: Option<DirstateEntry>,
385 pub(super) old_entry: Option<DirstateEntry>,
386 }
386 }
387
387
388 impl<'a> IntoIterator for &'a Node {
388 impl<'a> IntoIterator for &'a Node {
389 type Item = (HgPathBuf, DirstateEntry);
389 type Item = (HgPathBuf, DirstateEntry);
390 type IntoIter = Iter<'a>;
390 type IntoIter = Iter<'a>;
391
391
392 fn into_iter(self) -> Self::IntoIter {
392 fn into_iter(self) -> Self::IntoIter {
393 self.iter()
393 self.iter()
394 }
394 }
395 }
395 }
General Comments 0
You need to be logged in to leave comments. Login now