##// END OF EJS Templates
dirstate-tree: Borrow paths from the "on disk" bytes...
Simon Sapin -
r47896:ecfe0819 default
parent child Browse files
Show More
@@ -1,620 +1,623 b''
1 1 use bytes_cast::BytesCast;
2 2 use micro_timer::timed;
3 3 use std::borrow::Cow;
4 4 use std::convert::TryInto;
5 5 use std::path::PathBuf;
6 6
7 7 use super::path_with_basename::WithBasename;
8 8 use crate::dirstate::parsers::clear_ambiguous_mtime;
9 9 use crate::dirstate::parsers::pack_entry;
10 10 use crate::dirstate::parsers::packed_entry_size;
11 11 use crate::dirstate::parsers::parse_dirstate_entries;
12 12 use crate::dirstate::parsers::Timestamp;
13 13 use crate::matchers::Matcher;
14 14 use crate::utils::hg_path::{HgPath, HgPathBuf};
15 15 use crate::CopyMapIter;
16 16 use crate::DirstateEntry;
17 17 use crate::DirstateError;
18 18 use crate::DirstateMapError;
19 19 use crate::DirstateParents;
20 20 use crate::DirstateStatus;
21 21 use crate::EntryState;
22 22 use crate::FastHashMap;
23 23 use crate::PatternFileWarning;
24 24 use crate::StateMapIter;
25 25 use crate::StatusError;
26 26 use crate::StatusOptions;
27 27
28 28 pub struct DirstateMap<'on_disk> {
29 29 /// Contents of the `.hg/dirstate` file
30 30 on_disk: &'on_disk [u8],
31 31
32 32 pub(super) root: ChildNodes<'on_disk>,
33 33
34 34 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
35 35 nodes_with_entry_count: usize,
36 36
37 37 /// Number of nodes anywhere in the tree that have
38 38 /// `.copy_source.is_some()`.
39 39 nodes_with_copy_source_count: usize,
40 40 }
41 41
42 42 /// Using a plain `HgPathBuf` of the full path from the repository root as a
43 43 /// map key would also work: all paths in a given map have the same parent
44 44 /// path, so comparing full paths gives the same result as comparing base
45 45 /// names. However `BTreeMap` would waste time always re-comparing the same
46 46 /// string prefix.
47 47 pub(super) type ChildNodes<'on_disk> =
48 FastHashMap<WithBasename<HgPathBuf>, Node<'on_disk>>;
48 FastHashMap<WithBasename<Cow<'on_disk, HgPath>>, Node<'on_disk>>;
49 49
50 50 /// Represents a file or a directory
51 51 #[derive(Default)]
52 52 pub(super) struct Node<'on_disk> {
53 53 /// `None` for directories
54 54 pub(super) entry: Option<DirstateEntry>,
55 55
56 56 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
57 57
58 58 pub(super) children: ChildNodes<'on_disk>,
59 59
60 60 /// How many (non-inclusive) descendants of this node are tracked files
61 61 tracked_descendants_count: usize,
62 62 }
63 63
64 64 impl Node<'_> {
65 65 pub(super) fn state(&self) -> Option<EntryState> {
66 66 self.entry.as_ref().map(|entry| entry.state)
67 67 }
68 68 }
69 69
70 70 /// `(full_path, entry, copy_source)`
71 71 type NodeDataMut<'tree, 'on_disk> = (
72 72 &'tree HgPath,
73 73 &'tree mut Option<DirstateEntry>,
74 74 &'tree mut Option<Cow<'on_disk, HgPath>>,
75 75 );
76 76
77 77 impl<'on_disk> DirstateMap<'on_disk> {
78 78 pub fn new(
79 79 on_disk: &'on_disk [u8],
80 80 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
81 81 let mut map = Self {
82 82 on_disk,
83 83 root: ChildNodes::default(),
84 84 nodes_with_entry_count: 0,
85 85 nodes_with_copy_source_count: 0,
86 86 };
87 87 let parents = map.read()?;
88 88 Ok((map, parents))
89 89 }
90 90
91 91 /// Should only be called in `new`
92 92 #[timed]
93 93 fn read(&mut self) -> Result<Option<DirstateParents>, DirstateError> {
94 94 if self.on_disk.is_empty() {
95 95 return Ok(None);
96 96 }
97 97
98 98 let parents = parse_dirstate_entries(
99 99 self.on_disk,
100 100 |path, entry, copy_source| {
101 101 let tracked = entry.state.is_tracked();
102 let node = Self::get_or_insert_node_tracing_ancestors(
102 let node = Self::get_or_insert_node(
103 103 &mut self.root,
104 104 path,
105 WithBasename::to_cow_borrowed,
105 106 |ancestor| {
106 107 if tracked {
107 108 ancestor.tracked_descendants_count += 1
108 109 }
109 110 },
110 111 );
111 112 assert!(
112 113 node.entry.is_none(),
113 114 "duplicate dirstate entry in read"
114 115 );
115 116 assert!(
116 117 node.copy_source.is_none(),
117 118 "duplicate dirstate entry in read"
118 119 );
119 120 node.entry = Some(*entry);
120 121 node.copy_source = copy_source.map(Cow::Borrowed);
121 122 self.nodes_with_entry_count += 1;
122 123 if copy_source.is_some() {
123 124 self.nodes_with_copy_source_count += 1
124 125 }
125 126 },
126 127 )?;
127 128
128 129 Ok(Some(parents.clone()))
129 130 }
130 131
131 132 fn get_node(&self, path: &HgPath) -> Option<&Node> {
132 133 let mut children = &self.root;
133 134 let mut components = path.components();
134 135 let mut component =
135 136 components.next().expect("expected at least one components");
136 137 loop {
137 138 let child = children.get(component)?;
138 139 if let Some(next_component) = components.next() {
139 140 component = next_component;
140 141 children = &child.children;
141 142 } else {
142 143 return Some(child);
143 144 }
144 145 }
145 146 }
146 147
147 148 /// Returns a mutable reference to the node at `path` if it exists
148 149 ///
149 150 /// This takes `root` instead of `&mut self` so that callers can mutate
150 151 /// other fields while the returned borrow is still valid
151 152 fn get_node_mut<'tree>(
152 153 root: &'tree mut ChildNodes<'on_disk>,
153 154 path: &HgPath,
154 155 ) -> Option<&'tree mut Node<'on_disk>> {
155 156 Self::get_node_mut_tracing_ancestors(root, path, |_| {})
156 157 }
157 158
158 159 /// Same as `get_node_mut`, and calls `each_ancestor` for each ancestor of
159 160 /// the node.
160 161 ///
161 162 /// Note that `each_ancestor` may be called (with what would be ancestors)
162 163 /// even if it turns out there is no node at `path`.
163 164 fn get_node_mut_tracing_ancestors<'tree>(
164 165 root: &'tree mut ChildNodes<'on_disk>,
165 166 path: &HgPath,
166 167 mut each_ancestor: impl FnMut(&mut Node),
167 168 ) -> Option<&'tree mut Node<'on_disk>> {
168 169 let mut children = root;
169 170 let mut components = path.components();
170 171 let mut component =
171 172 components.next().expect("expected at least one components");
172 173 loop {
173 174 let child = children.get_mut(component)?;
174 175 if let Some(next_component) = components.next() {
175 176 each_ancestor(child);
176 177 component = next_component;
177 178 children = &mut child.children;
178 179 } else {
179 180 return Some(child);
180 181 }
181 182 }
182 183 }
183 184
184 fn get_or_insert_node<'tree>(
185 fn get_or_insert_node<'tree, 'path>(
185 186 root: &'tree mut ChildNodes<'on_disk>,
186 path: &HgPath,
187 ) -> &'tree mut Node<'on_disk> {
188 Self::get_or_insert_node_tracing_ancestors(root, path, |_| {})
189 }
190
191 fn get_or_insert_node_tracing_ancestors<'tree>(
192 root: &'tree mut ChildNodes<'on_disk>,
193 path: &HgPath,
187 path: &'path HgPath,
188 to_cow: impl Fn(
189 WithBasename<&'path HgPath>,
190 ) -> WithBasename<Cow<'on_disk, HgPath>>,
194 191 mut each_ancestor: impl FnMut(&mut Node),
195 192 ) -> &'tree mut Node<'on_disk> {
196 193 let mut child_nodes = root;
197 194 let mut inclusive_ancestor_paths =
198 195 WithBasename::inclusive_ancestors_of(path);
199 196 let mut ancestor_path = inclusive_ancestor_paths
200 197 .next()
201 198 .expect("expected at least one inclusive ancestor");
202 199 loop {
203 200 // TODO: can we avoid allocating an owned key in cases where the
204 201 // map already contains that key, without introducing double
205 202 // lookup?
206 203 let child_node =
207 child_nodes.entry(ancestor_path.to_owned()).or_default();
204 child_nodes.entry(to_cow(ancestor_path)).or_default();
208 205 if let Some(next) = inclusive_ancestor_paths.next() {
209 206 each_ancestor(child_node);
210 207 ancestor_path = next;
211 208 child_nodes = &mut child_node.children;
212 209 } else {
213 210 return child_node;
214 211 }
215 212 }
216 213 }
217 214
218 215 fn add_or_remove_file(
219 216 &mut self,
220 217 path: &HgPath,
221 218 old_state: EntryState,
222 219 new_entry: DirstateEntry,
223 220 ) {
224 221 let tracked_count_increment =
225 222 match (old_state.is_tracked(), new_entry.state.is_tracked()) {
226 223 (false, true) => 1,
227 224 (true, false) => -1,
228 225 _ => 0,
229 226 };
230 227
231 let node = Self::get_or_insert_node_tracing_ancestors(
228 let node = Self::get_or_insert_node(
232 229 &mut self.root,
233 230 path,
231 WithBasename::to_cow_owned,
234 232 |ancestor| {
235 233 // We can’t use `+= increment` because the counter is unsigned,
236 234 // and we want debug builds to detect accidental underflow
237 235 // through zero
238 236 match tracked_count_increment {
239 237 1 => ancestor.tracked_descendants_count += 1,
240 238 -1 => ancestor.tracked_descendants_count -= 1,
241 239 _ => {}
242 240 }
243 241 },
244 242 );
245 243 if node.entry.is_none() {
246 244 self.nodes_with_entry_count += 1
247 245 }
248 246 node.entry = Some(new_entry)
249 247 }
250 248
251 249 fn iter_nodes<'a>(
252 250 &'a self,
253 251 ) -> impl Iterator<Item = (&'a HgPath, &'a Node)> + 'a {
254 252 // Depth first tree traversal.
255 253 //
256 254 // If we could afford internal iteration and recursion,
257 255 // this would look like:
258 256 //
259 257 // ```
260 258 // fn traverse_children(
261 259 // children: &ChildNodes,
262 260 // each: &mut impl FnMut(&Node),
263 261 // ) {
264 262 // for child in children.values() {
265 263 // traverse_children(&child.children, each);
266 264 // each(child);
267 265 // }
268 266 // }
269 267 // ```
270 268 //
271 269 // However we want an external iterator and therefore can’t use the
272 270 // call stack. Use an explicit stack instead:
273 271 let mut stack = Vec::new();
274 272 let mut iter = self.root.iter();
275 273 std::iter::from_fn(move || {
276 274 while let Some((key, child_node)) = iter.next() {
277 275 // Pseudo-recursion
278 276 let new_iter = child_node.children.iter();
279 277 let old_iter = std::mem::replace(&mut iter, new_iter);
280 278 let key = &**key.full_path();
281 279 stack.push((key, child_node, old_iter));
282 280 }
283 281 // Found the end of a `children.iter()` iterator.
284 282 if let Some((key, child_node, next_iter)) = stack.pop() {
285 283 // "Return" from pseudo-recursion by restoring state from the
286 284 // explicit stack
287 285 iter = next_iter;
288 286
289 287 Some((key, child_node))
290 288 } else {
291 289 // Reached the bottom of the stack, we’re done
292 290 None
293 291 }
294 292 })
295 293 }
296 294
297 295 /// Mutable iterator for the `(entry, copy source)` of each node.
298 296 ///
299 297 /// It would not be safe to yield mutable references to nodes themeselves
300 298 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
301 299 /// reachable from their ancestor nodes, potentially creating multiple
302 300 /// `&mut` references to a given node.
303 301 fn iter_node_data_mut<'tree>(
304 302 &'tree mut self,
305 303 ) -> impl Iterator<Item = NodeDataMut<'tree, 'on_disk>> + 'tree {
306 304 // Explict stack for pseudo-recursion, see `iter_nodes` above.
307 305 let mut stack = Vec::new();
308 306 let mut iter = self.root.iter_mut();
309 307 std::iter::from_fn(move || {
310 308 while let Some((key, child_node)) = iter.next() {
311 309 // Pseudo-recursion
312 310 let data = (
313 311 &**key.full_path(),
314 312 &mut child_node.entry,
315 313 &mut child_node.copy_source,
316 314 );
317 315 let new_iter = child_node.children.iter_mut();
318 316 let old_iter = std::mem::replace(&mut iter, new_iter);
319 317 stack.push((data, old_iter));
320 318 }
321 319 // Found the end of a `children.values_mut()` iterator.
322 320 if let Some((data, next_iter)) = stack.pop() {
323 321 // "Return" from pseudo-recursion by restoring state from the
324 322 // explicit stack
325 323 iter = next_iter;
326 324
327 325 Some(data)
328 326 } else {
329 327 // Reached the bottom of the stack, we’re done
330 328 None
331 329 }
332 330 })
333 331 }
334 332 }
335 333
336 334 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
337 335 fn clear(&mut self) {
338 336 self.root.clear();
339 337 self.nodes_with_entry_count = 0;
340 338 self.nodes_with_copy_source_count = 0;
341 339 }
342 340
343 341 fn add_file(
344 342 &mut self,
345 343 filename: &HgPath,
346 344 old_state: EntryState,
347 345 entry: DirstateEntry,
348 346 ) -> Result<(), DirstateMapError> {
349 347 self.add_or_remove_file(filename, old_state, entry);
350 348 Ok(())
351 349 }
352 350
353 351 fn remove_file(
354 352 &mut self,
355 353 filename: &HgPath,
356 354 old_state: EntryState,
357 355 size: i32,
358 356 ) -> Result<(), DirstateMapError> {
359 357 let entry = DirstateEntry {
360 358 state: EntryState::Removed,
361 359 mode: 0,
362 360 size,
363 361 mtime: 0,
364 362 };
365 363 self.add_or_remove_file(filename, old_state, entry);
366 364 Ok(())
367 365 }
368 366
369 367 fn drop_file(
370 368 &mut self,
371 369 filename: &HgPath,
372 370 old_state: EntryState,
373 371 ) -> Result<bool, DirstateMapError> {
374 372 let was_tracked = old_state.is_tracked();
375 373 if let Some(node) = Self::get_node_mut_tracing_ancestors(
376 374 &mut self.root,
377 375 filename,
378 376 |ancestor| {
379 377 if was_tracked {
380 378 ancestor.tracked_descendants_count -= 1
381 379 }
382 380 },
383 381 ) {
384 382 let had_entry = node.entry.is_some();
385 383 let had_copy_source = node.copy_source.is_some();
386 384
387 385 // TODO: this leaves in the tree a "non-file" node. Should we
388 386 // remove the node instead, together with ancestor nodes for
389 387 // directories that become empty?
390 388 node.entry = None;
391 389 node.copy_source = None;
392 390
393 391 if had_entry {
394 392 self.nodes_with_entry_count -= 1
395 393 }
396 394 if had_copy_source {
397 395 self.nodes_with_copy_source_count -= 1
398 396 }
399 397 Ok(had_entry)
400 398 } else {
401 399 assert!(!was_tracked);
402 400 Ok(false)
403 401 }
404 402 }
405 403
406 404 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
407 405 for filename in filenames {
408 406 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
409 407 if let Some(entry) = node.entry.as_mut() {
410 408 clear_ambiguous_mtime(entry, now);
411 409 }
412 410 }
413 411 }
414 412 }
415 413
416 414 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
417 415 self.get_node(key)
418 416 .and_then(|node| node.entry.as_ref())
419 417 .map_or(false, DirstateEntry::is_non_normal)
420 418 }
421 419
422 420 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
423 421 // Do nothing, this `DirstateMap` does not have a separate "non normal
424 422 // entries" set that need to be kept up to date
425 423 }
426 424
427 425 fn non_normal_or_other_parent_paths(
428 426 &mut self,
429 427 ) -> Box<dyn Iterator<Item = &HgPath> + '_> {
430 428 Box::new(self.iter_nodes().filter_map(|(path, node)| {
431 429 node.entry
432 430 .as_ref()
433 431 .filter(|entry| {
434 432 entry.is_non_normal() || entry.is_from_other_parent()
435 433 })
436 434 .map(|_| path)
437 435 }))
438 436 }
439 437
440 438 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
441 439 // Do nothing, this `DirstateMap` does not have a separate "non normal
442 440 // entries" and "from other parent" sets that need to be recomputed
443 441 }
444 442
445 443 fn iter_non_normal_paths(
446 444 &mut self,
447 445 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
448 446 self.iter_non_normal_paths_panic()
449 447 }
450 448
451 449 fn iter_non_normal_paths_panic(
452 450 &self,
453 451 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
454 452 Box::new(self.iter_nodes().filter_map(|(path, node)| {
455 453 node.entry
456 454 .as_ref()
457 455 .filter(|entry| entry.is_non_normal())
458 456 .map(|_| path)
459 457 }))
460 458 }
461 459
462 460 fn iter_other_parent_paths(
463 461 &mut self,
464 462 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
465 463 Box::new(self.iter_nodes().filter_map(|(path, node)| {
466 464 node.entry
467 465 .as_ref()
468 466 .filter(|entry| entry.is_from_other_parent())
469 467 .map(|_| path)
470 468 }))
471 469 }
472 470
473 471 fn has_tracked_dir(
474 472 &mut self,
475 473 directory: &HgPath,
476 474 ) -> Result<bool, DirstateMapError> {
477 475 if let Some(node) = self.get_node(directory) {
478 476 // A node without a `DirstateEntry` was created to hold child
479 477 // nodes, and is therefore a directory.
480 478 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
481 479 } else {
482 480 Ok(false)
483 481 }
484 482 }
485 483
486 484 fn has_dir(
487 485 &mut self,
488 486 directory: &HgPath,
489 487 ) -> Result<bool, DirstateMapError> {
490 488 if let Some(node) = self.get_node(directory) {
491 489 // A node without a `DirstateEntry` was created to hold child
492 490 // nodes, and is therefore a directory.
493 491 Ok(node.entry.is_none())
494 492 } else {
495 493 Ok(false)
496 494 }
497 495 }
498 496
499 497 fn pack(
500 498 &mut self,
501 499 parents: DirstateParents,
502 500 now: Timestamp,
503 501 ) -> Result<Vec<u8>, DirstateError> {
504 502 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
505 503 // reallocations
506 504 let mut size = parents.as_bytes().len();
507 505 for (path, node) in self.iter_nodes() {
508 506 if node.entry.is_some() {
509 507 size += packed_entry_size(
510 508 path,
511 509 node.copy_source.as_ref().map(|p| &**p),
512 510 )
513 511 }
514 512 }
515 513
516 514 let mut packed = Vec::with_capacity(size);
517 515 packed.extend(parents.as_bytes());
518 516
519 517 let now: i32 = now.0.try_into().expect("time overflow");
520 518 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
521 519 if let Some(entry) = opt_entry {
522 520 clear_ambiguous_mtime(entry, now);
523 521 pack_entry(
524 522 path,
525 523 entry,
526 524 copy_source.as_ref().map(|p| &**p),
527 525 &mut packed,
528 526 );
529 527 }
530 528 }
531 529 Ok(packed)
532 530 }
533 531
534 532 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
535 533 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
536 534 // needs to be recomputed
537 535 Ok(())
538 536 }
539 537
540 538 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
541 539 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
542 540 // to be recomputed
543 541 Ok(())
544 542 }
545 543
546 544 fn status<'a>(
547 545 &'a mut self,
548 546 matcher: &'a (dyn Matcher + Sync),
549 547 root_dir: PathBuf,
550 548 ignore_files: Vec<PathBuf>,
551 549 options: StatusOptions,
552 550 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
553 551 {
554 552 super::status::status(self, matcher, root_dir, ignore_files, options)
555 553 }
556 554
557 555 fn copy_map_len(&self) -> usize {
558 556 self.nodes_with_copy_source_count
559 557 }
560 558
561 559 fn copy_map_iter(&self) -> CopyMapIter<'_> {
562 560 Box::new(self.iter_nodes().filter_map(|(path, node)| {
563 561 node.copy_source
564 562 .as_ref()
565 563 .map(|copy_source| (path, &**copy_source))
566 564 }))
567 565 }
568 566
569 567 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
570 568 if let Some(node) = self.get_node(key) {
571 569 node.copy_source.is_some()
572 570 } else {
573 571 false
574 572 }
575 573 }
576 574
577 575 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPath> {
578 576 self.get_node(key)?.copy_source.as_ref().map(|p| &**p)
579 577 }
580 578
581 579 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
582 580 let count = &mut self.nodes_with_copy_source_count;
583 581 Self::get_node_mut(&mut self.root, key).and_then(|node| {
584 582 if node.copy_source.is_some() {
585 583 *count -= 1
586 584 }
587 585 node.copy_source.take().map(Cow::into_owned)
588 586 })
589 587 }
590 588
591 589 fn copy_map_insert(
592 590 &mut self,
593 591 key: HgPathBuf,
594 592 value: HgPathBuf,
595 593 ) -> Option<HgPathBuf> {
596 let node = Self::get_or_insert_node(&mut self.root, &key);
594 let node = Self::get_or_insert_node(
595 &mut self.root,
596 &key,
597 WithBasename::to_cow_owned,
598 |_ancestor| {},
599 );
597 600 if node.copy_source.is_none() {
598 601 self.nodes_with_copy_source_count += 1
599 602 }
600 603 node.copy_source.replace(value.into()).map(Cow::into_owned)
601 604 }
602 605
603 606 fn len(&self) -> usize {
604 607 self.nodes_with_entry_count
605 608 }
606 609
607 610 fn contains_key(&self, key: &HgPath) -> bool {
608 611 self.get(key).is_some()
609 612 }
610 613
611 614 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
612 615 self.get_node(key)?.entry.as_ref()
613 616 }
614 617
615 618 fn iter(&self) -> StateMapIter<'_> {
616 619 Box::new(self.iter_nodes().filter_map(|(path, node)| {
617 620 node.entry.as_ref().map(|entry| (path, entry))
618 621 }))
619 622 }
620 623 }
@@ -1,165 +1,172 b''
1 1 use crate::utils::hg_path::HgPath;
2 use std::borrow::Borrow;
2 use std::borrow::{Borrow, Cow};
3 3
4 4 /// Wraps `HgPath` or `HgPathBuf` to make it behave "as" its last path
5 5 /// component, a.k.a. its base name (as in Python’s `os.path.basename`), but
6 6 /// also allow recovering the full path.
7 7 ///
8 8 /// "Behaving as" means that equality and comparison consider only the base
9 9 /// name, and `std::borrow::Borrow` is implemented to return only the base
10 10 /// name. This allows using the base name as a map key while still being able
11 11 /// to recover the full path, in a single memory allocation.
12 12 #[derive(Debug)]
13 13 pub struct WithBasename<T> {
14 14 full_path: T,
15 15
16 16 /// The position after the last slash separator in `full_path`, or `0`
17 17 /// if there is no slash.
18 18 base_name_start: usize,
19 19 }
20 20
21 21 impl<T> WithBasename<T> {
22 22 pub fn full_path(&self) -> &T {
23 23 &self.full_path
24 24 }
25 25 }
26 26
27 27 impl<T: AsRef<HgPath>> WithBasename<T> {
28 28 pub fn new(full_path: T) -> Self {
29 29 let base_name_start = if let Some(last_slash_position) = full_path
30 30 .as_ref()
31 31 .as_bytes()
32 32 .iter()
33 33 .rposition(|&byte| byte == b'/')
34 34 {
35 35 last_slash_position + 1
36 36 } else {
37 37 0
38 38 };
39 39 Self {
40 40 base_name_start,
41 41 full_path,
42 42 }
43 43 }
44 44
45 45 pub fn base_name(&self) -> &HgPath {
46 46 HgPath::new(
47 47 &self.full_path.as_ref().as_bytes()[self.base_name_start..],
48 48 )
49 49 }
50 50 }
51 51
52 52 impl<T: AsRef<HgPath>> Borrow<HgPath> for WithBasename<T> {
53 53 fn borrow(&self) -> &HgPath {
54 54 self.base_name()
55 55 }
56 56 }
57 57
58 58 impl<T: AsRef<HgPath>> std::hash::Hash for WithBasename<T> {
59 59 fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
60 60 self.base_name().hash(hasher)
61 61 }
62 62 }
63 63
64 64 impl<T: AsRef<HgPath> + PartialEq> PartialEq for WithBasename<T> {
65 65 fn eq(&self, other: &Self) -> bool {
66 66 self.base_name() == other.base_name()
67 67 }
68 68 }
69 69
70 70 impl<T: AsRef<HgPath> + Eq> Eq for WithBasename<T> {}
71 71
72 72 impl<T: AsRef<HgPath> + PartialOrd> PartialOrd for WithBasename<T> {
73 73 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
74 74 self.base_name().partial_cmp(other.base_name())
75 75 }
76 76 }
77 77
78 78 impl<T: AsRef<HgPath> + Ord> Ord for WithBasename<T> {
79 79 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
80 80 self.base_name().cmp(other.base_name())
81 81 }
82 82 }
83 83
84 impl<T: ?Sized + ToOwned> WithBasename<&'_ T> {
85 pub fn to_owned(&self) -> WithBasename<T::Owned> {
84 impl<'a> WithBasename<&'a HgPath> {
85 pub fn to_cow_borrowed(self) -> WithBasename<Cow<'a, HgPath>> {
86 86 WithBasename {
87 full_path: self.full_path.to_owned(),
87 full_path: Cow::Borrowed(self.full_path),
88 base_name_start: self.base_name_start,
89 }
90 }
91
92 pub fn to_cow_owned<'b>(self) -> WithBasename<Cow<'b, HgPath>> {
93 WithBasename {
94 full_path: Cow::Owned(self.full_path.to_owned()),
88 95 base_name_start: self.base_name_start,
89 96 }
90 97 }
91 98 }
92 99
93 100 impl<'a> WithBasename<&'a HgPath> {
94 101 /// Returns an iterator of `WithBasename<&HgPath>` for the ancestor
95 102 /// directory paths of the given `path`, as well as `path` itself.
96 103 ///
97 104 /// For example, the full paths of inclusive ancestors of "a/b/c" are "a",
98 105 /// "a/b", and "a/b/c" in that order.
99 106 pub fn inclusive_ancestors_of(
100 107 path: &'a HgPath,
101 108 ) -> impl Iterator<Item = WithBasename<&'a HgPath>> {
102 109 let mut slash_positions =
103 110 path.as_bytes().iter().enumerate().filter_map(|(i, &byte)| {
104 111 if byte == b'/' {
105 112 Some(i)
106 113 } else {
107 114 None
108 115 }
109 116 });
110 117 let mut opt_next_component_start = Some(0);
111 118 std::iter::from_fn(move || {
112 119 opt_next_component_start.take().map(|next_component_start| {
113 120 if let Some(slash_pos) = slash_positions.next() {
114 121 opt_next_component_start = Some(slash_pos + 1);
115 122 Self {
116 123 full_path: HgPath::new(&path.as_bytes()[..slash_pos]),
117 124 base_name_start: next_component_start,
118 125 }
119 126 } else {
120 127 // Not setting `opt_next_component_start` here: there will
121 128 // be no iteration after this one because `.take()` set it
122 129 // to `None`.
123 130 Self {
124 131 full_path: path,
125 132 base_name_start: next_component_start,
126 133 }
127 134 }
128 135 })
129 136 })
130 137 }
131 138 }
132 139
133 140 #[test]
134 141 fn test() {
135 142 let a = WithBasename::new(HgPath::new("a").to_owned());
136 143 assert_eq!(&**a.full_path(), HgPath::new(b"a"));
137 144 assert_eq!(a.base_name(), HgPath::new(b"a"));
138 145
139 146 let cba = WithBasename::new(HgPath::new("c/b/a").to_owned());
140 147 assert_eq!(&**cba.full_path(), HgPath::new(b"c/b/a"));
141 148 assert_eq!(cba.base_name(), HgPath::new(b"a"));
142 149
143 150 assert_eq!(a, cba);
144 151 let borrowed: &HgPath = cba.borrow();
145 152 assert_eq!(borrowed, HgPath::new("a"));
146 153 }
147 154
148 155 #[test]
149 156 fn test_inclusive_ancestors() {
150 157 let mut iter = WithBasename::inclusive_ancestors_of(HgPath::new("a/bb/c"));
151 158
152 159 let next = iter.next().unwrap();
153 160 assert_eq!(*next.full_path(), HgPath::new("a"));
154 161 assert_eq!(next.base_name(), HgPath::new("a"));
155 162
156 163 let next = iter.next().unwrap();
157 164 assert_eq!(*next.full_path(), HgPath::new("a/bb"));
158 165 assert_eq!(next.base_name(), HgPath::new("bb"));
159 166
160 167 let next = iter.next().unwrap();
161 168 assert_eq!(*next.full_path(), HgPath::new("a/bb/c"));
162 169 assert_eq!(next.base_name(), HgPath::new("c"));
163 170
164 171 assert!(iter.next().is_none());
165 172 }
General Comments 0
You need to be logged in to leave comments. Login now