##// END OF EJS Templates
dirstate-tree: simplify the control flow in the Node.insert method...
marmoute -
r46365:ae2873e9 default
parent child Browse files
Show More
@@ -1,395 +1,398 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 // Are we're modifying the current file ? Is the the end of the path ?
60 // Are we're modifying the current file ? Is the the end of the path ?
61 let is_current_file = tail.is_empty() && head.is_empty();
61 let is_current_file = tail.is_empty() && head.is_empty();
62
62
63 if let NodeKind::File(file) = &mut self.kind {
63 // Potentially Replace the current file with a directory if it's marked
64 if is_current_file {
64 // as `Removed`
65 let new = Self {
65 if !is_current_file {
66 kind: NodeKind::File(File {
66 if let NodeKind::File(file) = &mut self.kind {
67 entry: new_entry,
67 if file.entry.state == EntryState::Removed {
68 ..file.clone()
68 self.kind = NodeKind::Directory(Directory {
69 }),
69 was_file: Some(Box::from(file.clone())),
70 };
70 children: Default::default(),
71 return InsertResult {
71 })
72 did_insert: false,
73 old_entry: Some(std::mem::replace(self, new)),
74 };
75 } else {
76 match file.entry.state {
77 // Only replace the current file with a directory if it's
78 // marked as `Removed`
79 EntryState::Removed => {
80 self.kind = NodeKind::Directory(Directory {
81 was_file: Some(Box::from(file.clone())),
82 children: Default::default(),
83 })
84 }
85 _ => {
86 return Node::insert_in_file(
87 file, new_entry, head, tail,
88 )
89 }
90 }
72 }
91 }
73 }
92 }
74 }
93
94 match &mut self.kind {
75 match &mut self.kind {
95 NodeKind::Directory(directory) => {
76 NodeKind::Directory(directory) => {
96 Node::insert_in_directory(directory, new_entry, head, tail)
77 Node::insert_in_directory(directory, new_entry, head, tail)
97 }
78 }
98 NodeKind::File(_) => {
79 NodeKind::File(file) => {
99 unreachable!("The file case has already been handled")
80 if is_current_file {
81 let new = Self {
82 kind: NodeKind::File(File {
83 entry: new_entry,
84 ..file.clone()
85 }),
86 };
87 InsertResult {
88 did_insert: false,
89 old_entry: Some(std::mem::replace(self, new)),
90 }
91 } else {
92 match file.entry.state {
93 EntryState::Removed => {
94 unreachable!("Removed file turning into a directory was dealt with earlier")
95 }
96 _ => {
97 Node::insert_in_file(
98 file, new_entry, head, tail,
99 )
100 }
101 }
102 }
100 }
103 }
101 }
104 }
102 }
105 }
103
106
104 /// The current file still exists and is not marked as `Removed`.
107 /// The current file still exists and is not marked as `Removed`.
105 /// Insert the entry in its `was_directory`.
108 /// Insert the entry in its `was_directory`.
106 fn insert_in_file(
109 fn insert_in_file(
107 file: &mut File,
110 file: &mut File,
108 new_entry: DirstateEntry,
111 new_entry: DirstateEntry,
109 head: &[u8],
112 head: &[u8],
110 tail: &[u8],
113 tail: &[u8],
111 ) -> InsertResult {
114 ) -> InsertResult {
112 if let Some(d) = &mut file.was_directory {
115 if let Some(d) = &mut file.was_directory {
113 Node::insert_in_directory(d, new_entry, head, tail)
116 Node::insert_in_directory(d, new_entry, head, tail)
114 } else {
117 } else {
115 let mut dir = Directory {
118 let mut dir = Directory {
116 was_file: None,
119 was_file: None,
117 children: FastHashMap::default(),
120 children: FastHashMap::default(),
118 };
121 };
119 let res =
122 let res =
120 Node::insert_in_directory(&mut dir, new_entry, head, tail);
123 Node::insert_in_directory(&mut dir, new_entry, head, tail);
121 file.was_directory = Some(Box::new(dir));
124 file.was_directory = Some(Box::new(dir));
122 res
125 res
123 }
126 }
124 }
127 }
125
128
126 /// Insert an entry in the subtree of `directory`
129 /// Insert an entry in the subtree of `directory`
127 fn insert_in_directory(
130 fn insert_in_directory(
128 directory: &mut Directory,
131 directory: &mut Directory,
129 new_entry: DirstateEntry,
132 new_entry: DirstateEntry,
130 head: &[u8],
133 head: &[u8],
131 tail: &[u8],
134 tail: &[u8],
132 ) -> InsertResult {
135 ) -> InsertResult {
133 let mut res = InsertResult::default();
136 let mut res = InsertResult::default();
134
137
135 if let Some(node) = directory.children.get_mut(head) {
138 if let Some(node) = directory.children.get_mut(head) {
136 // Node exists
139 // Node exists
137 match &mut node.kind {
140 match &mut node.kind {
138 NodeKind::Directory(subdir) => {
141 NodeKind::Directory(subdir) => {
139 if tail.is_empty() {
142 if tail.is_empty() {
140 let becomes_file = Self {
143 let becomes_file = Self {
141 kind: NodeKind::File(File {
144 kind: NodeKind::File(File {
142 was_directory: Some(Box::from(subdir.clone())),
145 was_directory: Some(Box::from(subdir.clone())),
143 entry: new_entry,
146 entry: new_entry,
144 }),
147 }),
145 };
148 };
146 let old_entry = directory
149 let old_entry = directory
147 .children
150 .children
148 .insert(head.to_owned(), becomes_file);
151 .insert(head.to_owned(), becomes_file);
149 return InsertResult {
152 return InsertResult {
150 did_insert: true,
153 did_insert: true,
151 old_entry,
154 old_entry,
152 };
155 };
153 } else {
156 } else {
154 res = node.insert(tail, new_entry);
157 res = node.insert(tail, new_entry);
155 }
158 }
156 }
159 }
157 NodeKind::File(_) => {
160 NodeKind::File(_) => {
158 res = node.insert(tail, new_entry);
161 res = node.insert(tail, new_entry);
159 }
162 }
160 }
163 }
161 } else if tail.is_empty() {
164 } else if tail.is_empty() {
162 // File does not already exist
165 // File does not already exist
163 directory.children.insert(
166 directory.children.insert(
164 head.to_owned(),
167 head.to_owned(),
165 Self {
168 Self {
166 kind: NodeKind::File(File {
169 kind: NodeKind::File(File {
167 was_directory: None,
170 was_directory: None,
168 entry: new_entry,
171 entry: new_entry,
169 }),
172 }),
170 },
173 },
171 );
174 );
172 res.did_insert = true;
175 res.did_insert = true;
173 } else {
176 } else {
174 // Directory does not already exist
177 // Directory does not already exist
175 let mut nested = Self {
178 let mut nested = Self {
176 kind: NodeKind::Directory(Directory {
179 kind: NodeKind::Directory(Directory {
177 was_file: None,
180 was_file: None,
178 children: Default::default(),
181 children: Default::default(),
179 }),
182 }),
180 };
183 };
181 res = nested.insert(tail, new_entry);
184 res = nested.insert(tail, new_entry);
182 directory.children.insert(head.to_owned(), nested);
185 directory.children.insert(head.to_owned(), nested);
183 }
186 }
184 res
187 res
185 }
188 }
186
189
187 /// Removes an entry from the tree, returns a `RemoveResult`.
190 /// Removes an entry from the tree, returns a `RemoveResult`.
188 pub fn remove(&mut self, path: &[u8]) -> RemoveResult {
191 pub fn remove(&mut self, path: &[u8]) -> RemoveResult {
189 let empty_result = RemoveResult::default();
192 let empty_result = RemoveResult::default();
190 if path.is_empty() {
193 if path.is_empty() {
191 return empty_result;
194 return empty_result;
192 }
195 }
193 let mut split = path.splitn(2, |&c| c == b'/');
196 let mut split = path.splitn(2, |&c| c == b'/');
194 let head = split.next();
197 let head = split.next();
195 let tail = split.next().unwrap_or(b"");
198 let tail = split.next().unwrap_or(b"");
196
199
197 let head = match head {
200 let head = match head {
198 None => {
201 None => {
199 return empty_result;
202 return empty_result;
200 }
203 }
201 Some(h) => h,
204 Some(h) => h,
202 };
205 };
203 if head == path {
206 if head == path {
204 match &mut self.kind {
207 match &mut self.kind {
205 NodeKind::Directory(d) => {
208 NodeKind::Directory(d) => {
206 return Node::remove_from_directory(head, d);
209 return Node::remove_from_directory(head, d);
207 }
210 }
208 NodeKind::File(f) => {
211 NodeKind::File(f) => {
209 if let Some(d) = &mut f.was_directory {
212 if let Some(d) = &mut f.was_directory {
210 let RemoveResult { old_entry, .. } =
213 let RemoveResult { old_entry, .. } =
211 Node::remove_from_directory(head, d);
214 Node::remove_from_directory(head, d);
212 return RemoveResult {
215 return RemoveResult {
213 cleanup: false,
216 cleanup: false,
214 old_entry,
217 old_entry,
215 };
218 };
216 }
219 }
217 }
220 }
218 }
221 }
219 empty_result
222 empty_result
220 } else {
223 } else {
221 // Look into the dirs
224 // Look into the dirs
222 match &mut self.kind {
225 match &mut self.kind {
223 NodeKind::Directory(d) => {
226 NodeKind::Directory(d) => {
224 if let Some(child) = d.children.get_mut(head) {
227 if let Some(child) = d.children.get_mut(head) {
225 let mut res = child.remove(tail);
228 let mut res = child.remove(tail);
226 if res.cleanup {
229 if res.cleanup {
227 d.children.remove(head);
230 d.children.remove(head);
228 }
231 }
229 res.cleanup =
232 res.cleanup =
230 d.children.is_empty() && d.was_file.is_none();
233 d.children.is_empty() && d.was_file.is_none();
231 res
234 res
232 } else {
235 } else {
233 empty_result
236 empty_result
234 }
237 }
235 }
238 }
236 NodeKind::File(f) => {
239 NodeKind::File(f) => {
237 if let Some(d) = &mut f.was_directory {
240 if let Some(d) = &mut f.was_directory {
238 if let Some(child) = d.children.get_mut(head) {
241 if let Some(child) = d.children.get_mut(head) {
239 let RemoveResult { cleanup, old_entry } =
242 let RemoveResult { cleanup, old_entry } =
240 child.remove(tail);
243 child.remove(tail);
241 if cleanup {
244 if cleanup {
242 d.children.remove(head);
245 d.children.remove(head);
243 }
246 }
244 if d.children.is_empty() && d.was_file.is_none() {
247 if d.children.is_empty() && d.was_file.is_none() {
245 f.was_directory = None;
248 f.was_directory = None;
246 }
249 }
247
250
248 return RemoveResult {
251 return RemoveResult {
249 cleanup: false,
252 cleanup: false,
250 old_entry,
253 old_entry,
251 };
254 };
252 }
255 }
253 }
256 }
254 empty_result
257 empty_result
255 }
258 }
256 }
259 }
257 }
260 }
258 }
261 }
259
262
260 fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult {
263 fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult {
261 if let Some(node) = d.children.get_mut(head) {
264 if let Some(node) = d.children.get_mut(head) {
262 return match &mut node.kind {
265 return match &mut node.kind {
263 NodeKind::Directory(d) => {
266 NodeKind::Directory(d) => {
264 if let Some(f) = &mut d.was_file {
267 if let Some(f) = &mut d.was_file {
265 let entry = f.entry;
268 let entry = f.entry;
266 d.was_file = None;
269 d.was_file = None;
267 RemoveResult {
270 RemoveResult {
268 cleanup: false,
271 cleanup: false,
269 old_entry: Some(entry),
272 old_entry: Some(entry),
270 }
273 }
271 } else {
274 } else {
272 RemoveResult::default()
275 RemoveResult::default()
273 }
276 }
274 }
277 }
275 NodeKind::File(f) => {
278 NodeKind::File(f) => {
276 let entry = f.entry;
279 let entry = f.entry;
277 let mut cleanup = false;
280 let mut cleanup = false;
278 match &f.was_directory {
281 match &f.was_directory {
279 None => {
282 None => {
280 if d.children.len() == 1 {
283 if d.children.len() == 1 {
281 cleanup = true;
284 cleanup = true;
282 }
285 }
283 d.children.remove(head);
286 d.children.remove(head);
284 }
287 }
285 Some(dir) => {
288 Some(dir) => {
286 node.kind = NodeKind::Directory(*dir.clone());
289 node.kind = NodeKind::Directory(*dir.clone());
287 }
290 }
288 }
291 }
289
292
290 RemoveResult {
293 RemoveResult {
291 cleanup,
294 cleanup,
292 old_entry: Some(entry),
295 old_entry: Some(entry),
293 }
296 }
294 }
297 }
295 };
298 };
296 }
299 }
297 RemoveResult::default()
300 RemoveResult::default()
298 }
301 }
299
302
300 pub fn get(&self, path: &[u8]) -> Option<&Node> {
303 pub fn get(&self, path: &[u8]) -> Option<&Node> {
301 if path.is_empty() {
304 if path.is_empty() {
302 return Some(&self);
305 return Some(&self);
303 }
306 }
304 let mut split = path.splitn(2, |&c| c == b'/');
307 let mut split = path.splitn(2, |&c| c == b'/');
305 let head = split.next();
308 let head = split.next();
306 let tail = split.next().unwrap_or(b"");
309 let tail = split.next().unwrap_or(b"");
307
310
308 let head = match head {
311 let head = match head {
309 None => {
312 None => {
310 return Some(&self);
313 return Some(&self);
311 }
314 }
312 Some(h) => h,
315 Some(h) => h,
313 };
316 };
314 match &self.kind {
317 match &self.kind {
315 NodeKind::Directory(d) => {
318 NodeKind::Directory(d) => {
316 if let Some(child) = d.children.get(head) {
319 if let Some(child) = d.children.get(head) {
317 return child.get(tail);
320 return child.get(tail);
318 }
321 }
319 }
322 }
320 NodeKind::File(f) => {
323 NodeKind::File(f) => {
321 if let Some(d) = &f.was_directory {
324 if let Some(d) = &f.was_directory {
322 if let Some(child) = d.children.get(head) {
325 if let Some(child) = d.children.get(head) {
323 return child.get(tail);
326 return child.get(tail);
324 }
327 }
325 }
328 }
326 }
329 }
327 }
330 }
328
331
329 None
332 None
330 }
333 }
331
334
332 pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> {
335 pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> {
333 if path.is_empty() {
336 if path.is_empty() {
334 return Some(&mut self.kind);
337 return Some(&mut self.kind);
335 }
338 }
336 let mut split = path.splitn(2, |&c| c == b'/');
339 let mut split = path.splitn(2, |&c| c == b'/');
337 let head = split.next();
340 let head = split.next();
338 let tail = split.next().unwrap_or(b"");
341 let tail = split.next().unwrap_or(b"");
339
342
340 let head = match head {
343 let head = match head {
341 None => {
344 None => {
342 return Some(&mut self.kind);
345 return Some(&mut self.kind);
343 }
346 }
344 Some(h) => h,
347 Some(h) => h,
345 };
348 };
346 match &mut self.kind {
349 match &mut self.kind {
347 NodeKind::Directory(d) => {
350 NodeKind::Directory(d) => {
348 if let Some(child) = d.children.get_mut(head) {
351 if let Some(child) = d.children.get_mut(head) {
349 return child.get_mut(tail);
352 return child.get_mut(tail);
350 }
353 }
351 }
354 }
352 NodeKind::File(f) => {
355 NodeKind::File(f) => {
353 if let Some(d) = &mut f.was_directory {
356 if let Some(d) = &mut f.was_directory {
354 if let Some(child) = d.children.get_mut(head) {
357 if let Some(child) = d.children.get_mut(head) {
355 return child.get_mut(tail);
358 return child.get_mut(tail);
356 }
359 }
357 }
360 }
358 }
361 }
359 }
362 }
360
363
361 None
364 None
362 }
365 }
363
366
364 pub fn iter(&self) -> Iter {
367 pub fn iter(&self) -> Iter {
365 Iter::new(self)
368 Iter::new(self)
366 }
369 }
367 }
370 }
368
371
369 /// Information returned to the caller of an `insert` operation for integrity.
372 /// Information returned to the caller of an `insert` operation for integrity.
370 #[derive(Debug, Default)]
373 #[derive(Debug, Default)]
371 pub struct InsertResult {
374 pub struct InsertResult {
372 /// Whether the insertion resulted in an actual insertion and not an
375 /// Whether the insertion resulted in an actual insertion and not an
373 /// update
376 /// update
374 pub(super) did_insert: bool,
377 pub(super) did_insert: bool,
375 /// The entry that was replaced, if it exists
378 /// The entry that was replaced, if it exists
376 pub(super) old_entry: Option<Node>,
379 pub(super) old_entry: Option<Node>,
377 }
380 }
378
381
379 /// Information returned to the caller of a `remove` operation integrity.
382 /// Information returned to the caller of a `remove` operation integrity.
380 #[derive(Debug, Default)]
383 #[derive(Debug, Default)]
381 pub struct RemoveResult {
384 pub struct RemoveResult {
382 /// If the caller needs to remove the current node
385 /// If the caller needs to remove the current node
383 pub(super) cleanup: bool,
386 pub(super) cleanup: bool,
384 /// The entry that was replaced, if it exists
387 /// The entry that was replaced, if it exists
385 pub(super) old_entry: Option<DirstateEntry>,
388 pub(super) old_entry: Option<DirstateEntry>,
386 }
389 }
387
390
388 impl<'a> IntoIterator for &'a Node {
391 impl<'a> IntoIterator for &'a Node {
389 type Item = (HgPathBuf, DirstateEntry);
392 type Item = (HgPathBuf, DirstateEntry);
390 type IntoIter = Iter<'a>;
393 type IntoIter = Iter<'a>;
391
394
392 fn into_iter(self) -> Self::IntoIter {
395 fn into_iter(self) -> Self::IntoIter {
393 self.iter()
396 self.iter()
394 }
397 }
395 }
398 }
General Comments 0
You need to be logged in to leave comments. Login now