Show More
@@ -1,682 +1,682 b'' | |||||
1 | // tree.rs |
|
1 | // tree.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 super::node::{Directory, Node, NodeKind}; |
|
9 | use super::node::{Directory, Node, NodeKind}; | |
10 | use crate::dirstate::dirstate_tree::iter::FsIter; |
|
10 | use crate::dirstate::dirstate_tree::iter::FsIter; | |
11 | use crate::dirstate::dirstate_tree::node::{InsertResult, RemoveResult}; |
|
11 | use crate::dirstate::dirstate_tree::node::{InsertResult, RemoveResult}; | |
12 | use crate::utils::hg_path::{HgPath, HgPathBuf}; |
|
12 | use crate::utils::hg_path::{HgPath, HgPathBuf}; | |
13 | use crate::DirstateEntry; |
|
13 | use crate::DirstateEntry; | |
14 | use std::path::PathBuf; |
|
14 | use std::path::PathBuf; | |
15 |
|
15 | |||
16 | /// A specialized tree to represent the Mercurial dirstate. |
|
16 | /// A specialized tree to represent the Mercurial dirstate. | |
17 | /// |
|
17 | /// | |
18 | /// # Advantages over a flat structure |
|
18 | /// # Advantages over a flat structure | |
19 | /// |
|
19 | /// | |
20 | /// The dirstate is inherently hierarchical, since it's a representation of the |
|
20 | /// The dirstate is inherently hierarchical, since it's a representation of the | |
21 | /// file structure of the project. The current dirstate format is flat, and |
|
21 | /// file structure of the project. The current dirstate format is flat, and | |
22 | /// while that affords us potentially great (unordered) iteration speeds, the |
|
22 | /// while that affords us potentially great (unordered) iteration speeds, the | |
23 | /// need to retrieve a given path is great enough that you need some kind of |
|
23 | /// need to retrieve a given path is great enough that you need some kind of | |
24 | /// hashmap or tree in a lot of cases anyway. |
|
24 | /// hashmap or tree in a lot of cases anyway. | |
25 | /// |
|
25 | /// | |
26 | /// Going with a tree allows us to be smarter: |
|
26 | /// Going with a tree allows us to be smarter: | |
27 | /// - Skipping an ignored directory means we don't visit its entire subtree |
|
27 | /// - Skipping an ignored directory means we don't visit its entire subtree | |
28 | /// - Security auditing does not need to reconstruct paths backwards to check |
|
28 | /// - Security auditing does not need to reconstruct paths backwards to check | |
29 | /// for symlinked directories, this can be done during the iteration in a |
|
29 | /// for symlinked directories, this can be done during the iteration in a | |
30 | /// very efficient fashion |
|
30 | /// very efficient fashion | |
31 | /// - We don't need to build the directory information in another struct, |
|
31 | /// - We don't need to build the directory information in another struct, | |
32 | /// simplifying the code a lot, reducing the memory footprint and |
|
32 | /// simplifying the code a lot, reducing the memory footprint and | |
33 | /// potentially going faster depending on the implementation. |
|
33 | /// potentially going faster depending on the implementation. | |
34 | /// - We can use it to store a (platform-dependent) caching mechanism [1] |
|
34 | /// - We can use it to store a (platform-dependent) caching mechanism [1] | |
35 | /// - And probably other types of optimizations. |
|
35 | /// - And probably other types of optimizations. | |
36 | /// |
|
36 | /// | |
37 | /// Only the first two items in this list are implemented as of this commit. |
|
37 | /// Only the first two items in this list are implemented as of this commit. | |
38 | /// |
|
38 | /// | |
39 | /// [1]: https://www.mercurial-scm.org/wiki/DirsCachePlan |
|
39 | /// [1]: https://www.mercurial-scm.org/wiki/DirsCachePlan | |
40 | /// |
|
40 | /// | |
41 | /// |
|
41 | /// | |
42 | /// # Structure |
|
42 | /// # Structure | |
43 | /// |
|
43 | /// | |
44 | /// It's a prefix (radix) tree with no fixed arity, with a granularity of a |
|
44 | /// It's a prefix (radix) tree with no fixed arity, with a granularity of a | |
45 | /// folder, allowing it to mimic a filesystem hierarchy: |
|
45 | /// folder, allowing it to mimic a filesystem hierarchy: | |
46 | /// |
|
46 | /// | |
47 | /// ```text |
|
47 | /// ```text | |
48 | /// foo/bar |
|
48 | /// foo/bar | |
49 | /// foo/baz |
|
49 | /// foo/baz | |
50 | /// test |
|
50 | /// test | |
51 | /// ``` |
|
51 | /// ``` | |
52 | /// Will be represented (simplified) by: |
|
52 | /// Will be represented (simplified) by: | |
53 | /// |
|
53 | /// | |
54 | /// ```text |
|
54 | /// ```text | |
55 | /// Directory(root): |
|
55 | /// Directory(root): | |
56 | /// - File("test") |
|
56 | /// - File("test") | |
57 | /// - Directory("foo"): |
|
57 | /// - Directory("foo"): | |
58 | /// - File("bar") |
|
58 | /// - File("bar") | |
59 | /// - File("baz") |
|
59 | /// - File("baz") | |
60 | /// ``` |
|
60 | /// ``` | |
61 | /// |
|
61 | /// | |
62 | /// Moreover, it is special-cased for storing the dirstate and as such handles |
|
62 | /// Moreover, it is special-cased for storing the dirstate and as such handles | |
63 | /// cases that a simple `HashMap` would handle, but while preserving the |
|
63 | /// cases that a simple `HashMap` would handle, but while preserving the | |
64 | /// hierarchy. |
|
64 | /// hierarchy. | |
65 | /// For example: |
|
65 | /// For example: | |
66 | /// |
|
66 | /// | |
67 | /// ```shell |
|
67 | /// ```shell | |
68 | /// $ touch foo |
|
68 | /// $ touch foo | |
69 | /// $ hg add foo |
|
69 | /// $ hg add foo | |
70 | /// $ hg commit -m "foo" |
|
70 | /// $ hg commit -m "foo" | |
71 | /// $ hg remove foo |
|
71 | /// $ hg remove foo | |
72 | /// $ rm foo |
|
72 | /// $ rm foo | |
73 | /// $ mkdir foo |
|
73 | /// $ mkdir foo | |
74 | /// $ touch foo/a |
|
74 | /// $ touch foo/a | |
75 | /// $ hg add foo/a |
|
75 | /// $ hg add foo/a | |
76 | /// $ hg status |
|
76 | /// $ hg status | |
77 | /// R foo |
|
77 | /// R foo | |
78 | /// A foo/a |
|
78 | /// A foo/a | |
79 | /// ``` |
|
79 | /// ``` | |
80 | /// To represent this in a tree, one needs to keep track of whether any given |
|
80 | /// To represent this in a tree, one needs to keep track of whether any given | |
81 | /// file was a directory and whether any given directory was a file at the last |
|
81 | /// file was a directory and whether any given directory was a file at the last | |
82 | /// dirstate update. This tree stores that information, but only in the right |
|
82 | /// dirstate update. This tree stores that information, but only in the right | |
83 | /// circumstances by respecting the high-level rules that prevent nonsensical |
|
83 | /// circumstances by respecting the high-level rules that prevent nonsensical | |
84 | /// structures to exist: |
|
84 | /// structures to exist: | |
85 | /// - a file can only be added as a child of another file if the latter is |
|
85 | /// - a file can only be added as a child of another file if the latter is | |
86 | /// marked as `Removed` |
|
86 | /// marked as `Removed` | |
87 | /// - a file cannot replace a folder unless all its descendents are removed |
|
87 | /// - a file cannot replace a folder unless all its descendents are removed | |
88 | /// |
|
88 | /// | |
89 | /// This second rule is not checked by the tree for performance reasons, and |
|
89 | /// This second rule is not checked by the tree for performance reasons, and | |
90 | /// because high-level logic already prevents that state from happening. |
|
90 | /// because high-level logic already prevents that state from happening. | |
91 | /// |
|
91 | /// | |
92 | /// # Ordering |
|
92 | /// # Ordering | |
93 | /// |
|
93 | /// | |
94 | /// It makes no guarantee of ordering for now. |
|
94 | /// It makes no guarantee of ordering for now. | |
95 | #[derive(Debug, Default, Clone, PartialEq)] |
|
95 | #[derive(Debug, Default, Clone, PartialEq)] | |
96 | pub struct Tree { |
|
96 | pub struct Tree { | |
97 | pub root: Node, |
|
97 | pub root: Node, | |
98 | files_count: usize, |
|
98 | files_count: usize, | |
99 | } |
|
99 | } | |
100 |
|
100 | |||
101 | impl Tree { |
|
101 | impl Tree { | |
102 | pub fn new() -> Self { |
|
102 | pub fn new() -> Self { | |
103 | Self { |
|
103 | Self { | |
104 | root: Node { |
|
104 | root: Node { | |
105 | kind: NodeKind::Directory(Directory { |
|
105 | kind: NodeKind::Directory(Directory { | |
106 | was_file: None, |
|
106 | was_file: None, | |
107 | children: Default::default(), |
|
107 | children: Default::default(), | |
108 | }), |
|
108 | }), | |
109 | }, |
|
109 | }, | |
110 | files_count: 0, |
|
110 | files_count: 0, | |
111 | } |
|
111 | } | |
112 | } |
|
112 | } | |
113 |
|
113 | |||
114 | /// How many files (not directories) are stored in the tree, including ones |
|
114 | /// How many files (not directories) are stored in the tree, including ones | |
115 | /// marked as `Removed`. |
|
115 | /// marked as `Removed`. | |
116 | pub fn len(&self) -> usize { |
|
116 | pub fn len(&self) -> usize { | |
117 | self.files_count |
|
117 | self.files_count | |
118 | } |
|
118 | } | |
119 |
|
119 | |||
120 | pub fn is_empty(&self) -> bool { |
|
120 | pub fn is_empty(&self) -> bool { | |
121 | self.len() == 0 |
|
121 | self.len() == 0 | |
122 | } |
|
122 | } | |
123 |
|
123 | |||
124 | /// Inserts a file in the tree and returns the previous entry if any. |
|
124 | /// Inserts a file in the tree and returns the previous entry if any. | |
125 | pub fn insert( |
|
125 | pub fn insert( | |
126 | &mut self, |
|
126 | &mut self, | |
127 | path: impl AsRef<HgPath>, |
|
127 | path: impl AsRef<HgPath>, | |
128 | kind: DirstateEntry, |
|
128 | kind: DirstateEntry, | |
129 | ) -> Option<DirstateEntry> { |
|
129 | ) -> Option<DirstateEntry> { | |
130 | let old = self.insert_node(path, kind); |
|
130 | let old = self.insert_node(path, kind); | |
131 | match old?.kind { |
|
131 | match old?.kind { | |
132 | NodeKind::Directory(_) => None, |
|
132 | NodeKind::Directory(_) => None, | |
133 | NodeKind::File(f) => Some(f.entry), |
|
133 | NodeKind::File(f) => Some(f.entry), | |
134 | } |
|
134 | } | |
135 | } |
|
135 | } | |
136 |
|
136 | |||
137 | /// Low-level insertion method that returns the previous node (directories |
|
137 | /// Low-level insertion method that returns the previous node (directories | |
138 | /// included). |
|
138 | /// included). | |
139 | fn insert_node( |
|
139 | fn insert_node( | |
140 | &mut self, |
|
140 | &mut self, | |
141 | path: impl AsRef<HgPath>, |
|
141 | path: impl AsRef<HgPath>, | |
142 | kind: DirstateEntry, |
|
142 | kind: DirstateEntry, | |
143 | ) -> Option<Node> { |
|
143 | ) -> Option<Node> { | |
144 | let InsertResult { |
|
144 | let InsertResult { | |
145 | did_insert, |
|
145 | did_insert, | |
146 | old_entry, |
|
146 | old_entry, | |
147 | } = self.root.insert(path.as_ref().as_bytes(), kind); |
|
147 | } = self.root.insert(path.as_ref().as_bytes(), kind); | |
148 | self.files_count += if did_insert { 1 } else { 0 }; |
|
148 | self.files_count += if did_insert { 1 } else { 0 }; | |
149 | old_entry |
|
149 | old_entry | |
150 | } |
|
150 | } | |
151 |
|
151 | |||
152 | /// Returns a reference to a node if it exists. |
|
152 | /// Returns a reference to a node if it exists. | |
153 | pub fn get_node(&self, path: impl AsRef<HgPath>) -> Option<&Node> { |
|
153 | pub fn get_node(&self, path: impl AsRef<HgPath>) -> Option<&Node> { | |
154 | self.root.get(path.as_ref().as_bytes()) |
|
154 | self.root.get(path.as_ref().as_bytes()) | |
155 | } |
|
155 | } | |
156 |
|
156 | |||
157 | /// Returns a reference to the entry corresponding to `path` if it exists. |
|
157 | /// Returns a reference to the entry corresponding to `path` if it exists. | |
158 | pub fn get(&self, path: impl AsRef<HgPath>) -> Option<&DirstateEntry> { |
|
158 | pub fn get(&self, path: impl AsRef<HgPath>) -> Option<&DirstateEntry> { | |
159 | if let Some(node) = self.get_node(&path) { |
|
159 | if let Some(node) = self.get_node(&path) { | |
160 | return match &node.kind { |
|
160 | return match &node.kind { | |
161 | NodeKind::Directory(d) => { |
|
161 | NodeKind::Directory(d) => { | |
162 | d.was_file.as_ref().map(|f| &f.entry) |
|
162 | d.was_file.as_ref().map(|f| &f.entry) | |
163 | } |
|
163 | } | |
164 | NodeKind::File(f) => Some(&f.entry), |
|
164 | NodeKind::File(f) => Some(&f.entry), | |
165 | }; |
|
165 | }; | |
166 | } |
|
166 | } | |
167 | None |
|
167 | None | |
168 | } |
|
168 | } | |
169 |
|
169 | |||
170 | /// Returns `true` if an entry is found for the given `path`. |
|
170 | /// Returns `true` if an entry is found for the given `path`. | |
171 | pub fn contains_key(&self, path: impl AsRef<HgPath>) -> bool { |
|
171 | pub fn contains_key(&self, path: impl AsRef<HgPath>) -> bool { | |
172 | self.get(path).is_some() |
|
172 | self.get(path).is_some() | |
173 | } |
|
173 | } | |
174 |
|
174 | |||
175 | /// Returns a mutable reference to the entry corresponding to `path` if it |
|
175 | /// Returns a mutable reference to the entry corresponding to `path` if it | |
176 | /// exists. |
|
176 | /// exists. | |
177 | pub fn get_mut( |
|
177 | pub fn get_mut( | |
178 | &mut self, |
|
178 | &mut self, | |
179 | path: impl AsRef<HgPath>, |
|
179 | path: impl AsRef<HgPath>, | |
180 | ) -> Option<&mut DirstateEntry> { |
|
180 | ) -> Option<&mut DirstateEntry> { | |
181 | if let Some(kind) = self.root.get_mut(path.as_ref().as_bytes()) { |
|
181 | if let Some(kind) = self.root.get_mut(path.as_ref().as_bytes()) { | |
182 | return match kind { |
|
182 | return match kind { | |
183 | NodeKind::Directory(d) => { |
|
183 | NodeKind::Directory(d) => { | |
184 | d.was_file.as_mut().map(|f| &mut f.entry) |
|
184 | d.was_file.as_mut().map(|f| &mut f.entry) | |
185 | } |
|
185 | } | |
186 | NodeKind::File(f) => Some(&mut f.entry), |
|
186 | NodeKind::File(f) => Some(&mut f.entry), | |
187 | }; |
|
187 | }; | |
188 | } |
|
188 | } | |
189 | None |
|
189 | None | |
190 | } |
|
190 | } | |
191 |
|
191 | |||
192 | /// Returns an iterator over the paths and corresponding entries in the |
|
192 | /// Returns an iterator over the paths and corresponding entries in the | |
193 | /// tree. |
|
193 | /// tree. | |
194 | pub fn iter(&self) -> Iter { |
|
194 | pub fn iter(&self) -> Iter { | |
195 | Iter::new(&self.root) |
|
195 | Iter::new(&self.root) | |
196 | } |
|
196 | } | |
197 |
|
197 | |||
198 | /// Returns an iterator of all entries in the tree, with a special |
|
198 | /// Returns an iterator of all entries in the tree, with a special | |
199 | /// filesystem handling for the directories containing said entries. See |
|
199 | /// filesystem handling for the directories containing said entries. See | |
200 | /// the documentation of `FsIter` for more. |
|
200 | /// the documentation of `FsIter` for more. | |
201 | pub fn fs_iter(&self, root_dir: PathBuf) -> FsIter { |
|
201 | pub fn fs_iter(&self, root_dir: PathBuf) -> FsIter { | |
202 | FsIter::new(&self.root, root_dir) |
|
202 | FsIter::new(&self.root, root_dir) | |
203 | } |
|
203 | } | |
204 |
|
204 | |||
205 | /// Remove the entry at `path` and returns it, if it exists. |
|
205 | /// Remove the entry at `path` and returns it, if it exists. | |
206 | pub fn remove( |
|
206 | pub fn remove( | |
207 | &mut self, |
|
207 | &mut self, | |
208 | path: impl AsRef<HgPath>, |
|
208 | path: impl AsRef<HgPath>, | |
209 | ) -> Option<DirstateEntry> { |
|
209 | ) -> Option<DirstateEntry> { | |
210 | let RemoveResult { old_entry, .. } = |
|
210 | let RemoveResult { old_entry, .. } = | |
211 | self.root.remove(path.as_ref().as_bytes()); |
|
211 | self.root.remove(path.as_ref().as_bytes()); | |
212 | self.files_count = self |
|
212 | self.files_count = self | |
213 | .files_count |
|
213 | .files_count | |
214 | .checked_sub(if old_entry.is_some() { 1 } else { 0 }) |
|
214 | .checked_sub(if old_entry.is_some() { 1 } else { 0 }) | |
215 | .expect("removed too many files"); |
|
215 | .expect("removed too many files"); | |
216 | old_entry |
|
216 | old_entry | |
217 | } |
|
217 | } | |
218 | } |
|
218 | } | |
219 |
|
219 | |||
220 | impl<P: AsRef<HgPath>> Extend<(P, DirstateEntry)> for Tree { |
|
220 | impl<P: AsRef<HgPath>> Extend<(P, DirstateEntry)> for Tree { | |
221 | fn extend<T: IntoIterator<Item = (P, DirstateEntry)>>(&mut self, iter: T) { |
|
221 | fn extend<T: IntoIterator<Item = (P, DirstateEntry)>>(&mut self, iter: T) { | |
222 | for (path, entry) in iter { |
|
222 | for (path, entry) in iter { | |
223 | self.insert(path, entry); |
|
223 | self.insert(path, entry); | |
224 | } |
|
224 | } | |
225 | } |
|
225 | } | |
226 | } |
|
226 | } | |
227 |
|
227 | |||
228 | impl<'a> IntoIterator for &'a Tree { |
|
228 | impl<'a> IntoIterator for &'a Tree { | |
229 | type Item = (HgPathBuf, DirstateEntry); |
|
229 | type Item = (HgPathBuf, DirstateEntry); | |
230 | type IntoIter = Iter<'a>; |
|
230 | type IntoIter = Iter<'a>; | |
231 |
|
231 | |||
232 | fn into_iter(self) -> Self::IntoIter { |
|
232 | fn into_iter(self) -> Self::IntoIter { | |
233 | self.iter() |
|
233 | self.iter() | |
234 | } |
|
234 | } | |
235 | } |
|
235 | } | |
236 |
|
236 | |||
237 | #[cfg(test)] |
|
237 | #[cfg(test)] | |
238 | mod tests { |
|
238 | mod tests { | |
239 | use super::*; |
|
239 | use super::*; | |
240 | use crate::dirstate::dirstate_tree::node::File; |
|
240 | use crate::dirstate::dirstate_tree::node::File; | |
241 | use crate::{EntryState, FastHashMap}; |
|
241 | use crate::{EntryState, FastHashMap}; | |
242 | use pretty_assertions::assert_eq; |
|
242 | use pretty_assertions::assert_eq; | |
243 |
|
243 | |||
244 | impl Node { |
|
244 | impl Node { | |
245 | /// Shortcut for getting children of a node in tests. |
|
245 | /// Shortcut for getting children of a node in tests. | |
246 | fn children(&self) -> Option<&FastHashMap<Vec<u8>, Node>> { |
|
246 | fn children(&self) -> Option<&FastHashMap<Vec<u8>, Node>> { | |
247 | match &self.kind { |
|
247 | match &self.kind { | |
248 | NodeKind::Directory(d) => Some(&d.children), |
|
248 | NodeKind::Directory(d) => Some(&d.children), | |
249 | NodeKind::File(_) => None, |
|
249 | NodeKind::File(_) => None, | |
250 | } |
|
250 | } | |
251 | } |
|
251 | } | |
252 | } |
|
252 | } | |
253 |
|
253 | |||
254 | #[test] |
|
254 | #[test] | |
255 | fn test_dirstate_tree() { |
|
255 | fn test_dirstate_tree() { | |
256 | let mut tree = Tree::new(); |
|
256 | let mut tree = Tree::new(); | |
257 |
|
257 | |||
258 | assert_eq!( |
|
258 | assert_eq!( | |
259 | tree.insert_node( |
|
259 | tree.insert_node( | |
260 | HgPath::new(b"we/p"), |
|
260 | HgPath::new(b"we/p"), | |
261 | DirstateEntry { |
|
261 | DirstateEntry { | |
262 | state: EntryState::Normal, |
|
262 | state: EntryState::Normal, | |
263 | mode: 0, |
|
263 | mode: 0, | |
264 | mtime: 0, |
|
264 | mtime: 0, | |
265 | size: 0 |
|
265 | size: 0 | |
266 | } |
|
266 | } | |
267 | ), |
|
267 | ), | |
268 | None |
|
268 | None | |
269 | ); |
|
269 | ); | |
270 | dbg!(&tree); |
|
270 | dbg!(&tree); | |
271 | assert!(tree.get_node(HgPath::new(b"we")).is_some()); |
|
271 | assert!(tree.get_node(HgPath::new(b"we")).is_some()); | |
272 | let entry = DirstateEntry { |
|
272 | let entry = DirstateEntry { | |
273 | state: EntryState::Merged, |
|
273 | state: EntryState::Merged, | |
274 | mode: 41, |
|
274 | mode: 41, | |
275 | mtime: 42, |
|
275 | mtime: 42, | |
276 | size: 43, |
|
276 | size: 43, | |
277 | }; |
|
277 | }; | |
278 | assert_eq!(tree.insert_node(HgPath::new(b"foo/bar"), entry), None); |
|
278 | assert_eq!(tree.insert_node(HgPath::new(b"foo/bar"), entry), None); | |
279 | assert_eq!( |
|
279 | assert_eq!( | |
280 | tree.get_node(HgPath::new(b"foo/bar")), |
|
280 | tree.get_node(HgPath::new(b"foo/bar")), | |
281 | Some(&Node { |
|
281 | Some(&Node { | |
282 | kind: NodeKind::File(File { |
|
282 | kind: NodeKind::File(File { | |
283 | was_directory: None, |
|
283 | was_directory: None, | |
284 | entry |
|
284 | entry | |
285 | }) |
|
285 | }) | |
286 | }) |
|
286 | }) | |
287 | ); |
|
287 | ); | |
288 | // We didn't override the first entry we made |
|
288 | // We didn't override the first entry we made | |
289 | assert!(tree.get_node(HgPath::new(b"we")).is_some(),); |
|
289 | assert!(tree.get_node(HgPath::new(b"we")).is_some(),); | |
290 | // Inserting the same key again |
|
290 | // Inserting the same key again | |
291 | assert_eq!( |
|
291 | assert_eq!( | |
292 | tree.insert_node(HgPath::new(b"foo/bar"), entry), |
|
292 | tree.insert_node(HgPath::new(b"foo/bar"), entry), | |
293 | Some(Node { |
|
293 | Some(Node { | |
294 | kind: NodeKind::File(File { |
|
294 | kind: NodeKind::File(File { | |
295 | was_directory: None, |
|
295 | was_directory: None, | |
296 | entry |
|
296 | entry | |
297 | }), |
|
297 | }), | |
298 | }) |
|
298 | }) | |
299 | ); |
|
299 | ); | |
300 | // Inserting the two levels deep |
|
300 | // Inserting the two levels deep | |
301 | assert_eq!(tree.insert_node(HgPath::new(b"foo/bar/baz"), entry), None); |
|
301 | assert_eq!(tree.insert_node(HgPath::new(b"foo/bar/baz"), entry), None); | |
302 | // Getting a file "inside a file" should return `None` |
|
302 | // Getting a file "inside a file" should return `None` | |
303 | assert_eq!(tree.get_node(HgPath::new(b"foo/bar/baz/bap"),), None); |
|
303 | assert_eq!(tree.get_node(HgPath::new(b"foo/bar/baz/bap"),), None); | |
304 |
|
304 | |||
305 | assert_eq!( |
|
305 | assert_eq!( | |
306 | tree.insert_node(HgPath::new(b"wasdir/subfile"), entry), |
|
306 | tree.insert_node(HgPath::new(b"wasdir/subfile"), entry), | |
307 | None, |
|
307 | None, | |
308 | ); |
|
308 | ); | |
309 | let removed_entry = DirstateEntry { |
|
309 | let removed_entry = DirstateEntry { | |
310 | state: EntryState::Removed, |
|
310 | state: EntryState::Removed, | |
311 | mode: 0, |
|
311 | mode: 0, | |
312 | mtime: 0, |
|
312 | mtime: 0, | |
313 | size: 0, |
|
313 | size: 0, | |
314 | }; |
|
314 | }; | |
315 | assert!(tree |
|
315 | assert!(tree | |
316 | .insert_node(HgPath::new(b"wasdir"), removed_entry) |
|
316 | .insert_node(HgPath::new(b"wasdir"), removed_entry) | |
317 | .is_some()); |
|
317 | .is_some()); | |
318 |
|
318 | |||
319 | assert_eq!( |
|
319 | assert_eq!( | |
320 | tree.get_node(HgPath::new(b"wasdir")), |
|
320 | tree.get_node(HgPath::new(b"wasdir")), | |
321 | Some(&Node { |
|
321 | Some(&Node { | |
322 | kind: NodeKind::File(File { |
|
322 | kind: NodeKind::File(File { | |
323 | was_directory: Some(Box::new(Directory { |
|
323 | was_directory: Some(Box::new(Directory { | |
324 | was_file: None, |
|
324 | was_file: None, | |
325 | children: [( |
|
325 | children: [( | |
326 | b"subfile".to_vec(), |
|
326 | b"subfile".to_vec(), | |
327 | Node { |
|
327 | Node { | |
328 | kind: NodeKind::File(File { |
|
328 | kind: NodeKind::File(File { | |
329 | was_directory: None, |
|
329 | was_directory: None, | |
330 | entry, |
|
330 | entry, | |
331 | }) |
|
331 | }) | |
332 | } |
|
332 | } | |
333 | )] |
|
333 | )] | |
334 | .to_vec() |
|
334 | .to_vec() | |
335 | .into_iter() |
|
335 | .into_iter() | |
336 | .collect() |
|
336 | .collect() | |
337 | })), |
|
337 | })), | |
338 | entry: removed_entry |
|
338 | entry: removed_entry | |
339 | }) |
|
339 | }) | |
340 | }) |
|
340 | }) | |
341 | ); |
|
341 | ); | |
342 |
|
342 | |||
343 | assert!(tree.get(HgPath::new(b"wasdir/subfile")).is_some()) |
|
343 | assert!(tree.get(HgPath::new(b"wasdir/subfile")).is_some()) | |
344 | } |
|
344 | } | |
345 |
|
345 | |||
346 | #[test] |
|
346 | #[test] | |
347 | fn test_insert_removed() { |
|
347 | fn test_insert_removed() { | |
348 | let mut tree = Tree::new(); |
|
348 | let mut tree = Tree::new(); | |
349 | let entry = DirstateEntry { |
|
349 | let entry = DirstateEntry { | |
350 | state: EntryState::Merged, |
|
350 | state: EntryState::Merged, | |
351 | mode: 1, |
|
351 | mode: 1, | |
352 | mtime: 2, |
|
352 | mtime: 2, | |
353 | size: 3, |
|
353 | size: 3, | |
354 | }; |
|
354 | }; | |
355 | let removed_entry = DirstateEntry { |
|
355 | let removed_entry = DirstateEntry { | |
356 | state: EntryState::Removed, |
|
356 | state: EntryState::Removed, | |
357 | mode: 10, |
|
357 | mode: 10, | |
358 | mtime: 20, |
|
358 | mtime: 20, | |
359 | size: 30, |
|
359 | size: 30, | |
360 | }; |
|
360 | }; | |
361 | assert_eq!(tree.insert_node(HgPath::new(b"foo"), entry), None); |
|
361 | assert_eq!(tree.insert_node(HgPath::new(b"foo"), entry), None); | |
362 | assert_eq!( |
|
362 | assert_eq!( | |
363 | tree.insert_node(HgPath::new(b"foo/a"), removed_entry), |
|
363 | tree.insert_node(HgPath::new(b"foo/a"), removed_entry), | |
364 | None |
|
364 | None | |
365 | ); |
|
365 | ); | |
366 | // The insert should not turn `foo` into a directory as `foo` is not |
|
366 | // The insert should not turn `foo` into a directory as `foo` is not | |
367 | // `Removed`. |
|
367 | // `Removed`. | |
368 | match tree.get_node(HgPath::new(b"foo")).unwrap().kind { |
|
368 | match tree.get_node(HgPath::new(b"foo")).unwrap().kind { | |
369 | NodeKind::Directory(_) => panic!("should be a file"), |
|
369 | NodeKind::Directory(_) => panic!("should be a file"), | |
370 | NodeKind::File(_) => {} |
|
370 | NodeKind::File(_) => {} | |
371 | } |
|
371 | } | |
372 |
|
372 | |||
373 | let mut tree = Tree::new(); |
|
373 | let mut tree = Tree::new(); | |
374 | let entry = DirstateEntry { |
|
374 | let entry = DirstateEntry { | |
375 | state: EntryState::Merged, |
|
375 | state: EntryState::Merged, | |
376 | mode: 1, |
|
376 | mode: 1, | |
377 | mtime: 2, |
|
377 | mtime: 2, | |
378 | size: 3, |
|
378 | size: 3, | |
379 | }; |
|
379 | }; | |
380 | let removed_entry = DirstateEntry { |
|
380 | let removed_entry = DirstateEntry { | |
381 | state: EntryState::Removed, |
|
381 | state: EntryState::Removed, | |
382 | mode: 10, |
|
382 | mode: 10, | |
383 | mtime: 20, |
|
383 | mtime: 20, | |
384 | size: 30, |
|
384 | size: 30, | |
385 | }; |
|
385 | }; | |
386 | // The insert *should* turn `foo` into a directory as it is `Removed`. |
|
386 | // The insert *should* turn `foo` into a directory as it is `Removed`. | |
387 | assert_eq!(tree.insert_node(HgPath::new(b"foo"), removed_entry), None); |
|
387 | assert_eq!(tree.insert_node(HgPath::new(b"foo"), removed_entry), None); | |
388 | assert_eq!(tree.insert_node(HgPath::new(b"foo/a"), entry), None); |
|
388 | assert_eq!(tree.insert_node(HgPath::new(b"foo/a"), entry), None); | |
389 | match tree.get_node(HgPath::new(b"foo")).unwrap().kind { |
|
389 | match tree.get_node(HgPath::new(b"foo")).unwrap().kind { | |
390 | NodeKind::Directory(_) => {} |
|
390 | NodeKind::Directory(_) => {} | |
391 | NodeKind::File(_) => panic!("should be a directory"), |
|
391 | NodeKind::File(_) => panic!("should be a directory"), | |
392 | } |
|
392 | } | |
393 | } |
|
393 | } | |
394 |
|
394 | |||
395 | #[test] |
|
395 | #[test] | |
396 | fn test_get() { |
|
396 | fn test_get() { | |
397 | let mut tree = Tree::new(); |
|
397 | let mut tree = Tree::new(); | |
398 | let entry = DirstateEntry { |
|
398 | let entry = DirstateEntry { | |
399 | state: EntryState::Merged, |
|
399 | state: EntryState::Merged, | |
400 | mode: 1, |
|
400 | mode: 1, | |
401 | mtime: 2, |
|
401 | mtime: 2, | |
402 | size: 3, |
|
402 | size: 3, | |
403 | }; |
|
403 | }; | |
404 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); |
|
404 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); | |
405 | assert_eq!(tree.files_count, 1); |
|
405 | assert_eq!(tree.files_count, 1); | |
406 | assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry)); |
|
406 | assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry)); | |
407 | assert_eq!(tree.get(HgPath::new(b"a/b")), None); |
|
407 | assert_eq!(tree.get(HgPath::new(b"a/b")), None); | |
408 | assert_eq!(tree.get(HgPath::new(b"a")), None); |
|
408 | assert_eq!(tree.get(HgPath::new(b"a")), None); | |
409 | assert_eq!(tree.get(HgPath::new(b"a/b/c/d")), None); |
|
409 | assert_eq!(tree.get(HgPath::new(b"a/b/c/d")), None); | |
410 | let entry2 = DirstateEntry { |
|
410 | let entry2 = DirstateEntry { | |
411 | state: EntryState::Removed, |
|
411 | state: EntryState::Removed, | |
412 | mode: 0, |
|
412 | mode: 0, | |
413 | mtime: 5, |
|
413 | mtime: 5, | |
414 | size: 1, |
|
414 | size: 1, | |
415 | }; |
|
415 | }; | |
416 | // was_directory |
|
416 | // was_directory | |
417 | assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None); |
|
417 | assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None); | |
418 | assert_eq!(tree.files_count, 2); |
|
418 | assert_eq!(tree.files_count, 2); | |
419 | assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2)); |
|
419 | assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2)); | |
420 | assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry)); |
|
420 | assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry)); | |
421 |
|
421 | |||
422 | let mut tree = Tree::new(); |
|
422 | let mut tree = Tree::new(); | |
423 |
|
423 | |||
424 | // was_file |
|
424 | // was_file | |
425 | assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None); |
|
425 | assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None); | |
426 | assert_eq!(tree.files_count, 1); |
|
426 | assert_eq!(tree.files_count, 1); | |
427 | assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None); |
|
427 | assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None); | |
428 | assert_eq!(tree.files_count, 2); |
|
428 | assert_eq!(tree.files_count, 2); | |
429 | assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2)); |
|
429 | assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2)); | |
430 | } |
|
430 | } | |
431 |
|
431 | |||
432 | #[test] |
|
432 | #[test] | |
433 | fn test_get_mut() { |
|
433 | fn test_get_mut() { | |
434 | let mut tree = Tree::new(); |
|
434 | let mut tree = Tree::new(); | |
435 | let mut entry = DirstateEntry { |
|
435 | let mut entry = DirstateEntry { | |
436 | state: EntryState::Merged, |
|
436 | state: EntryState::Merged, | |
437 | mode: 1, |
|
437 | mode: 1, | |
438 | mtime: 2, |
|
438 | mtime: 2, | |
439 | size: 3, |
|
439 | size: 3, | |
440 | }; |
|
440 | }; | |
441 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); |
|
441 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); | |
442 | assert_eq!(tree.files_count, 1); |
|
442 | assert_eq!(tree.files_count, 1); | |
443 | assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry)); |
|
443 | assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry)); | |
444 | assert_eq!(tree.get_mut(HgPath::new(b"a/b")), None); |
|
444 | assert_eq!(tree.get_mut(HgPath::new(b"a/b")), None); | |
445 | assert_eq!(tree.get_mut(HgPath::new(b"a")), None); |
|
445 | assert_eq!(tree.get_mut(HgPath::new(b"a")), None); | |
446 | assert_eq!(tree.get_mut(HgPath::new(b"a/b/c/d")), None); |
|
446 | assert_eq!(tree.get_mut(HgPath::new(b"a/b/c/d")), None); | |
447 | let mut entry2 = DirstateEntry { |
|
447 | let mut entry2 = DirstateEntry { | |
448 | state: EntryState::Removed, |
|
448 | state: EntryState::Removed, | |
449 | mode: 0, |
|
449 | mode: 0, | |
450 | mtime: 5, |
|
450 | mtime: 5, | |
451 | size: 1, |
|
451 | size: 1, | |
452 | }; |
|
452 | }; | |
453 | // was_directory |
|
453 | // was_directory | |
454 | assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None); |
|
454 | assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None); | |
455 | assert_eq!(tree.files_count, 2); |
|
455 | assert_eq!(tree.files_count, 2); | |
456 | assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2)); |
|
456 | assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2)); | |
457 | assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry)); |
|
457 | assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry)); | |
458 |
|
458 | |||
459 | let mut tree = Tree::new(); |
|
459 | let mut tree = Tree::new(); | |
460 |
|
460 | |||
461 | // was_file |
|
461 | // was_file | |
462 | assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None); |
|
462 | assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None); | |
463 | assert_eq!(tree.files_count, 1); |
|
463 | assert_eq!(tree.files_count, 1); | |
464 | assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None); |
|
464 | assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None); | |
465 | assert_eq!(tree.files_count, 2); |
|
465 | assert_eq!(tree.files_count, 2); | |
466 | assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2)); |
|
466 | assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2)); | |
467 | } |
|
467 | } | |
468 |
|
468 | |||
469 | #[test] |
|
469 | #[test] | |
470 | fn test_remove() { |
|
470 | fn test_remove() { | |
471 | let mut tree = Tree::new(); |
|
471 | let mut tree = Tree::new(); | |
472 | assert_eq!(tree.files_count, 0); |
|
472 | assert_eq!(tree.files_count, 0); | |
473 | assert_eq!(tree.remove(HgPath::new(b"foo")), None); |
|
473 | assert_eq!(tree.remove(HgPath::new(b"foo")), None); | |
474 | assert_eq!(tree.files_count, 0); |
|
474 | assert_eq!(tree.files_count, 0); | |
475 |
|
475 | |||
476 | let entry = DirstateEntry { |
|
476 | let entry = DirstateEntry { | |
477 | state: EntryState::Normal, |
|
477 | state: EntryState::Normal, | |
478 | mode: 0, |
|
478 | mode: 0, | |
479 | mtime: 0, |
|
479 | mtime: 0, | |
480 | size: 0, |
|
480 | size: 0, | |
481 | }; |
|
481 | }; | |
482 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); |
|
482 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); | |
483 | assert_eq!(tree.files_count, 1); |
|
483 | assert_eq!(tree.files_count, 1); | |
484 |
|
484 | |||
485 | assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry)); |
|
485 | assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry)); | |
486 | assert_eq!(tree.files_count, 0); |
|
486 | assert_eq!(tree.files_count, 0); | |
487 |
|
487 | |||
488 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None); |
|
488 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None); | |
489 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/y"), entry), None); |
|
489 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/y"), entry), None); | |
490 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/z"), entry), None); |
|
490 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/z"), entry), None); | |
491 | assert_eq!(tree.insert_node(HgPath::new(b"x"), entry), None); |
|
491 | assert_eq!(tree.insert_node(HgPath::new(b"x"), entry), None); | |
492 | assert_eq!(tree.insert_node(HgPath::new(b"y"), entry), None); |
|
492 | assert_eq!(tree.insert_node(HgPath::new(b"y"), entry), None); | |
493 | assert_eq!(tree.files_count, 5); |
|
493 | assert_eq!(tree.files_count, 5); | |
494 |
|
494 | |||
495 | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry)); |
|
495 | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry)); | |
496 | assert_eq!(tree.files_count, 4); |
|
496 | assert_eq!(tree.files_count, 4); | |
497 | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), None); |
|
497 | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), None); | |
498 | assert_eq!(tree.files_count, 4); |
|
498 | assert_eq!(tree.files_count, 4); | |
499 | assert_eq!(tree.remove(HgPath::new(b"a/b/y")), Some(entry)); |
|
499 | assert_eq!(tree.remove(HgPath::new(b"a/b/y")), Some(entry)); | |
500 | assert_eq!(tree.files_count, 3); |
|
500 | assert_eq!(tree.files_count, 3); | |
501 | assert_eq!(tree.remove(HgPath::new(b"a/b/z")), Some(entry)); |
|
501 | assert_eq!(tree.remove(HgPath::new(b"a/b/z")), Some(entry)); | |
502 | assert_eq!(tree.files_count, 2); |
|
502 | assert_eq!(tree.files_count, 2); | |
503 |
|
503 | |||
504 | assert_eq!(tree.remove(HgPath::new(b"x")), Some(entry)); |
|
504 | assert_eq!(tree.remove(HgPath::new(b"x")), Some(entry)); | |
505 | assert_eq!(tree.files_count, 1); |
|
505 | assert_eq!(tree.files_count, 1); | |
506 | assert_eq!(tree.remove(HgPath::new(b"y")), Some(entry)); |
|
506 | assert_eq!(tree.remove(HgPath::new(b"y")), Some(entry)); | |
507 | assert_eq!(tree.files_count, 0); |
|
507 | assert_eq!(tree.files_count, 0); | |
508 |
|
508 | |||
509 | // `a` should have been cleaned up, no more files anywhere in its |
|
509 | // `a` should have been cleaned up, no more files anywhere in its | |
510 | // descendents |
|
510 | // descendents | |
511 | assert_eq!(tree.get_node(HgPath::new(b"a")), None); |
|
511 | assert_eq!(tree.get_node(HgPath::new(b"a")), None); | |
512 | assert_eq!(tree.root.children().unwrap().len(), 0); |
|
512 | assert_eq!(tree.root.children().unwrap().len(), 0); | |
513 |
|
513 | |||
514 | let removed_entry = DirstateEntry { |
|
514 | let removed_entry = DirstateEntry { | |
515 | state: EntryState::Removed, |
|
515 | state: EntryState::Removed, | |
516 | ..entry |
|
516 | ..entry | |
517 | }; |
|
517 | }; | |
518 | assert_eq!(tree.insert(HgPath::new(b"a"), removed_entry), None); |
|
518 | assert_eq!(tree.insert(HgPath::new(b"a"), removed_entry), None); | |
519 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None); |
|
519 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None); | |
520 | assert_eq!(tree.files_count, 2); |
|
520 | assert_eq!(tree.files_count, 2); | |
521 | dbg!(&tree); |
|
521 | dbg!(&tree); | |
522 | assert_eq!(tree.remove(HgPath::new(b"a")), Some(removed_entry)); |
|
522 | assert_eq!(tree.remove(HgPath::new(b"a")), Some(removed_entry)); | |
523 | assert_eq!(tree.files_count, 1); |
|
523 | assert_eq!(tree.files_count, 1); | |
524 | dbg!(&tree); |
|
524 | dbg!(&tree); | |
525 | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry)); |
|
525 | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry)); | |
526 | assert_eq!(tree.files_count, 0); |
|
526 | assert_eq!(tree.files_count, 0); | |
527 |
|
527 | |||
528 | // The entire tree should have been cleaned up, no more files anywhere |
|
528 | // The entire tree should have been cleaned up, no more files anywhere | |
529 | // in its descendents |
|
529 | // in its descendents | |
530 | assert_eq!(tree.root.children().unwrap().len(), 0); |
|
530 | assert_eq!(tree.root.children().unwrap().len(), 0); | |
531 |
|
531 | |||
532 | let removed_entry = DirstateEntry { |
|
532 | let removed_entry = DirstateEntry { | |
533 | state: EntryState::Removed, |
|
533 | state: EntryState::Removed, | |
534 | ..entry |
|
534 | ..entry | |
535 | }; |
|
535 | }; | |
536 | assert_eq!(tree.insert(HgPath::new(b"a"), entry), None); |
|
536 | assert_eq!(tree.insert(HgPath::new(b"a"), entry), None); | |
537 | assert_eq!( |
|
537 | assert_eq!( | |
538 | tree.insert_node(HgPath::new(b"a/b/x"), removed_entry), |
|
538 | tree.insert_node(HgPath::new(b"a/b/x"), removed_entry), | |
539 | None |
|
539 | None | |
540 | ); |
|
540 | ); | |
541 | assert_eq!(tree.files_count, 2); |
|
541 | assert_eq!(tree.files_count, 2); | |
542 | dbg!(&tree); |
|
542 | dbg!(&tree); | |
543 | assert_eq!(tree.remove(HgPath::new(b"a")), Some(entry)); |
|
543 | assert_eq!(tree.remove(HgPath::new(b"a")), Some(entry)); | |
544 | assert_eq!(tree.files_count, 1); |
|
544 | assert_eq!(tree.files_count, 1); | |
545 | dbg!(&tree); |
|
545 | dbg!(&tree); | |
546 | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(removed_entry)); |
|
546 | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(removed_entry)); | |
547 | assert_eq!(tree.files_count, 0); |
|
547 | assert_eq!(tree.files_count, 0); | |
548 |
|
548 | |||
549 | dbg!(&tree); |
|
549 | dbg!(&tree); | |
550 | // The entire tree should have been cleaned up, no more files anywhere |
|
550 | // The entire tree should have been cleaned up, no more files anywhere | |
551 | // in its descendents |
|
551 | // in its descendents | |
552 | assert_eq!(tree.root.children().unwrap().len(), 0); |
|
552 | assert_eq!(tree.root.children().unwrap().len(), 0); | |
553 |
|
553 | |||
554 | assert_eq!(tree.insert(HgPath::new(b"d"), entry), None); |
|
554 | assert_eq!(tree.insert(HgPath::new(b"d"), entry), None); | |
555 | assert_eq!(tree.insert(HgPath::new(b"d/d/d"), entry), None); |
|
555 | assert_eq!(tree.insert(HgPath::new(b"d/d/d"), entry), None); | |
556 | assert_eq!(tree.files_count, 2); |
|
556 | assert_eq!(tree.files_count, 2); | |
557 |
|
557 | |||
558 | // Deleting the nested file should not delete the top directory as it |
|
558 | // Deleting the nested file should not delete the top directory as it | |
559 | // used to be a file |
|
559 | // used to be a file | |
560 | assert_eq!(tree.remove(HgPath::new(b"d/d/d")), Some(entry)); |
|
560 | assert_eq!(tree.remove(HgPath::new(b"d/d/d")), Some(entry)); | |
561 | assert_eq!(tree.files_count, 1); |
|
561 | assert_eq!(tree.files_count, 1); | |
562 | assert!(tree.get_node(HgPath::new(b"d")).is_some()); |
|
562 | assert!(tree.get_node(HgPath::new(b"d")).is_some()); | |
563 | assert!(tree.remove(HgPath::new(b"d")).is_some()); |
|
563 | assert!(tree.remove(HgPath::new(b"d")).is_some()); | |
564 | assert_eq!(tree.files_count, 0); |
|
564 | assert_eq!(tree.files_count, 0); | |
565 |
|
565 | |||
566 | // Deleting the nested file should not delete the top file (other way |
|
566 | // Deleting the nested file should not delete the top file (other way | |
567 | // around from the last case) |
|
567 | // around from the last case) | |
568 | assert_eq!(tree.insert(HgPath::new(b"a/a"), entry), None); |
|
568 | assert_eq!(tree.insert(HgPath::new(b"a/a"), entry), None); | |
569 | assert_eq!(tree.files_count, 1); |
|
569 | assert_eq!(tree.files_count, 1); | |
570 | assert_eq!(tree.insert(HgPath::new(b"a"), entry), None); |
|
570 | assert_eq!(tree.insert(HgPath::new(b"a"), entry), None); | |
571 | assert_eq!(tree.files_count, 2); |
|
571 | assert_eq!(tree.files_count, 2); | |
572 | dbg!(&tree); |
|
572 | dbg!(&tree); | |
573 | assert_eq!(tree.remove(HgPath::new(b"a/a")), Some(entry)); |
|
573 | assert_eq!(tree.remove(HgPath::new(b"a/a")), Some(entry)); | |
574 | assert_eq!(tree.files_count, 1); |
|
574 | assert_eq!(tree.files_count, 1); | |
575 | dbg!(&tree); |
|
575 | dbg!(&tree); | |
576 | assert!(tree.get_node(HgPath::new(b"a")).is_some()); |
|
576 | assert!(tree.get_node(HgPath::new(b"a")).is_some()); | |
577 | assert!(tree.get_node(HgPath::new(b"a/a")).is_none()); |
|
577 | assert!(tree.get_node(HgPath::new(b"a/a")).is_none()); | |
578 | } |
|
578 | } | |
579 |
|
579 | |||
580 | #[test] |
|
580 | #[test] | |
581 | fn test_was_directory() { |
|
581 | fn test_was_directory() { | |
582 | let mut tree = Tree::new(); |
|
582 | let mut tree = Tree::new(); | |
583 |
|
583 | |||
584 | let entry = DirstateEntry { |
|
584 | let entry = DirstateEntry { | |
585 | state: EntryState::Removed, |
|
585 | state: EntryState::Removed, | |
586 | mode: 0, |
|
586 | mode: 0, | |
587 | mtime: 0, |
|
587 | mtime: 0, | |
588 | size: 0, |
|
588 | size: 0, | |
589 | }; |
|
589 | }; | |
590 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); |
|
590 | assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); | |
591 | assert_eq!(tree.files_count, 1); |
|
591 | assert_eq!(tree.files_count, 1); | |
592 |
|
592 | |||
593 | assert!(tree.insert_node(HgPath::new(b"a"), entry).is_some()); |
|
593 | assert!(tree.insert_node(HgPath::new(b"a"), entry).is_some()); | |
594 | let new_a = tree.root.children().unwrap().get(&b"a".to_vec()).unwrap(); |
|
594 | let new_a = tree.root.children().unwrap().get(&b"a".to_vec()).unwrap(); | |
595 |
|
595 | |||
596 | match &new_a.kind { |
|
596 | match &new_a.kind { | |
597 | NodeKind::Directory(_) => panic!(), |
|
597 | NodeKind::Directory(_) => panic!(), | |
598 | NodeKind::File(f) => { |
|
598 | NodeKind::File(f) => { | |
599 | let dir = f.was_directory.clone().unwrap(); |
|
599 | let dir = f.was_directory.clone().unwrap(); | |
600 | let c = dir |
|
600 | let c = dir | |
601 | .children |
|
601 | .children | |
602 | .get(&b"b".to_vec()) |
|
602 | .get(&b"b".to_vec()) | |
603 | .unwrap() |
|
603 | .unwrap() | |
604 | .children() |
|
604 | .children() | |
605 | .unwrap() |
|
605 | .unwrap() | |
606 | .get(&b"c".to_vec()) |
|
606 | .get(&b"c".to_vec()) | |
607 | .unwrap(); |
|
607 | .unwrap(); | |
608 |
|
608 | |||
609 | assert_eq!( |
|
609 | assert_eq!( | |
610 | match &c.kind { |
|
610 | match &c.kind { | |
611 | NodeKind::Directory(_) => panic!(), |
|
611 | NodeKind::Directory(_) => panic!(), | |
612 | NodeKind::File(f) => f.entry, |
|
612 | NodeKind::File(f) => f.entry, | |
613 | }, |
|
613 | }, | |
614 | entry |
|
614 | entry | |
615 | ); |
|
615 | ); | |
616 | } |
|
616 | } | |
617 | } |
|
617 | } | |
618 | assert_eq!(tree.files_count, 2); |
|
618 | assert_eq!(tree.files_count, 2); | |
619 | dbg!(&tree); |
|
619 | dbg!(&tree); | |
620 | assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry)); |
|
620 | assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry)); | |
621 | assert_eq!(tree.files_count, 1); |
|
621 | assert_eq!(tree.files_count, 1); | |
622 | dbg!(&tree); |
|
622 | dbg!(&tree); | |
623 | let a = tree.get_node(HgPath::new(b"a")).unwrap(); |
|
623 | let a = tree.get_node(HgPath::new(b"a")).unwrap(); | |
624 | match &a.kind { |
|
624 | match &a.kind { | |
625 | NodeKind::Directory(_) => panic!(), |
|
625 | NodeKind::Directory(_) => panic!(), | |
626 | NodeKind::File(f) => { |
|
626 | NodeKind::File(f) => { | |
627 | // Directory in `was_directory` was emptied, should be removed |
|
627 | // Directory in `was_directory` was emptied, should be removed | |
628 | assert_eq!(f.was_directory, None); |
|
628 | assert_eq!(f.was_directory, None); | |
629 | } |
|
629 | } | |
630 | } |
|
630 | } | |
631 | } |
|
631 | } | |
632 | #[test] |
|
632 | #[test] | |
633 | fn test_extend() { |
|
633 | fn test_extend() { | |
634 | let insertions = [ |
|
634 | let insertions = [ | |
635 | ( |
|
635 | ( | |
636 | HgPathBuf::from_bytes(b"d"), |
|
636 | HgPathBuf::from_bytes(b"d"), | |
637 | DirstateEntry { |
|
637 | DirstateEntry { | |
638 | state: EntryState::Added, |
|
638 | state: EntryState::Added, | |
639 | mode: 0, |
|
639 | mode: 0, | |
640 | mtime: -1, |
|
640 | mtime: -1, | |
641 | size: -1, |
|
641 | size: -1, | |
642 | }, |
|
642 | }, | |
643 | ), |
|
643 | ), | |
644 | ( |
|
644 | ( | |
645 | HgPathBuf::from_bytes(b"b"), |
|
645 | HgPathBuf::from_bytes(b"b"), | |
646 | DirstateEntry { |
|
646 | DirstateEntry { | |
647 | state: EntryState::Normal, |
|
647 | state: EntryState::Normal, | |
648 | mode: 33188, |
|
648 | mode: 33188, | |
649 | mtime: 1599647984, |
|
649 | mtime: 1599647984, | |
650 | size: 2, |
|
650 | size: 2, | |
651 | }, |
|
651 | }, | |
652 | ), |
|
652 | ), | |
653 | ( |
|
653 | ( | |
654 | HgPathBuf::from_bytes(b"a/a"), |
|
654 | HgPathBuf::from_bytes(b"a/a"), | |
655 | DirstateEntry { |
|
655 | DirstateEntry { | |
656 | state: EntryState::Normal, |
|
656 | state: EntryState::Normal, | |
657 | mode: 33188, |
|
657 | mode: 33188, | |
658 | mtime: 1599647984, |
|
658 | mtime: 1599647984, | |
659 | size: 2, |
|
659 | size: 2, | |
660 | }, |
|
660 | }, | |
661 | ), |
|
661 | ), | |
662 | ( |
|
662 | ( | |
663 | HgPathBuf::from_bytes(b"d/d/d"), |
|
663 | HgPathBuf::from_bytes(b"d/d/d"), | |
664 | DirstateEntry { |
|
664 | DirstateEntry { | |
665 | state: EntryState::Removed, |
|
665 | state: EntryState::Removed, | |
666 | mode: 0, |
|
666 | mode: 0, | |
667 | mtime: 0, |
|
667 | mtime: 0, | |
668 | size: 0, |
|
668 | size: 0, | |
669 | }, |
|
669 | }, | |
670 | ), |
|
670 | ), | |
671 | ] |
|
671 | ] | |
672 | .to_vec(); |
|
672 | .to_vec(); | |
673 | let mut tree = Tree::new(); |
|
673 | let mut tree = Tree::new(); | |
674 |
|
674 | |||
675 | tree.extend(insertions.clone().into_iter()); |
|
675 | tree.extend(insertions.clone().into_iter()); | |
676 |
|
676 | |||
677 | for (path, _) in &insertions { |
|
677 | for (path, _) in &insertions { | |
678 | assert!(tree.contains_key(path), true); |
|
678 | assert!(tree.contains_key(path), true); | |
679 | } |
|
679 | } | |
680 | assert_eq!(tree.files_count, 4); |
|
680 | assert_eq!(tree.files_count, 4); | |
681 | } |
|
681 | } | |
682 | } |
|
682 | } |
General Comments 0
You need to be logged in to leave comments.
Login now