##// END OF EJS Templates
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
Simon Sapin -
r47963:1249eb9c default
parent child Browse files
Show More
@@ -1,623 +1,622 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 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 102 let node = Self::get_or_insert_node(
103 103 &mut self.root,
104 104 path,
105 105 WithBasename::to_cow_borrowed,
106 106 |ancestor| {
107 107 if tracked {
108 108 ancestor.tracked_descendants_count += 1
109 109 }
110 110 },
111 111 );
112 112 assert!(
113 113 node.entry.is_none(),
114 114 "duplicate dirstate entry in read"
115 115 );
116 116 assert!(
117 117 node.copy_source.is_none(),
118 118 "duplicate dirstate entry in read"
119 119 );
120 120 node.entry = Some(*entry);
121 121 node.copy_source = copy_source.map(Cow::Borrowed);
122 122 self.nodes_with_entry_count += 1;
123 123 if copy_source.is_some() {
124 124 self.nodes_with_copy_source_count += 1
125 125 }
126 126 },
127 127 )?;
128 128
129 129 Ok(Some(parents.clone()))
130 130 }
131 131
132 132 fn get_node(&self, path: &HgPath) -> Option<&Node> {
133 133 let mut children = &self.root;
134 134 let mut components = path.components();
135 135 let mut component =
136 136 components.next().expect("expected at least one components");
137 137 loop {
138 138 let child = children.get(component)?;
139 139 if let Some(next_component) = components.next() {
140 140 component = next_component;
141 141 children = &child.children;
142 142 } else {
143 143 return Some(child);
144 144 }
145 145 }
146 146 }
147 147
148 148 /// Returns a mutable reference to the node at `path` if it exists
149 149 ///
150 150 /// This takes `root` instead of `&mut self` so that callers can mutate
151 151 /// other fields while the returned borrow is still valid
152 152 fn get_node_mut<'tree>(
153 153 root: &'tree mut ChildNodes<'on_disk>,
154 154 path: &HgPath,
155 155 ) -> Option<&'tree mut Node<'on_disk>> {
156 Self::get_node_mut_tracing_ancestors(root, path, |_| {})
157 }
158
159 /// Same as `get_node_mut`, and calls `each_ancestor` for each ancestor of
160 /// the node.
161 ///
162 /// Note that `each_ancestor` may be called (with what would be ancestors)
163 /// even if it turns out there is no node at `path`.
164 fn get_node_mut_tracing_ancestors<'tree>(
165 root: &'tree mut ChildNodes<'on_disk>,
166 path: &HgPath,
167 mut each_ancestor: impl FnMut(&mut Node),
168 ) -> Option<&'tree mut Node<'on_disk>> {
169 156 let mut children = root;
170 157 let mut components = path.components();
171 158 let mut component =
172 159 components.next().expect("expected at least one components");
173 160 loop {
174 161 let child = children.get_mut(component)?;
175 162 if let Some(next_component) = components.next() {
176 each_ancestor(child);
177 163 component = next_component;
178 164 children = &mut child.children;
179 165 } else {
180 166 return Some(child);
181 167 }
182 168 }
183 169 }
184 170
185 171 fn get_or_insert_node<'tree, 'path>(
186 172 root: &'tree mut ChildNodes<'on_disk>,
187 173 path: &'path HgPath,
188 174 to_cow: impl Fn(
189 175 WithBasename<&'path HgPath>,
190 176 ) -> WithBasename<Cow<'on_disk, HgPath>>,
191 177 mut each_ancestor: impl FnMut(&mut Node),
192 178 ) -> &'tree mut Node<'on_disk> {
193 179 let mut child_nodes = root;
194 180 let mut inclusive_ancestor_paths =
195 181 WithBasename::inclusive_ancestors_of(path);
196 182 let mut ancestor_path = inclusive_ancestor_paths
197 183 .next()
198 184 .expect("expected at least one inclusive ancestor");
199 185 loop {
200 186 // TODO: can we avoid allocating an owned key in cases where the
201 187 // map already contains that key, without introducing double
202 188 // lookup?
203 189 let child_node =
204 190 child_nodes.entry(to_cow(ancestor_path)).or_default();
205 191 if let Some(next) = inclusive_ancestor_paths.next() {
206 192 each_ancestor(child_node);
207 193 ancestor_path = next;
208 194 child_nodes = &mut child_node.children;
209 195 } else {
210 196 return child_node;
211 197 }
212 198 }
213 199 }
214 200
215 201 fn add_or_remove_file(
216 202 &mut self,
217 203 path: &HgPath,
218 204 old_state: EntryState,
219 205 new_entry: DirstateEntry,
220 206 ) {
221 207 let tracked_count_increment =
222 208 match (old_state.is_tracked(), new_entry.state.is_tracked()) {
223 209 (false, true) => 1,
224 210 (true, false) => -1,
225 211 _ => 0,
226 212 };
227 213
228 214 let node = Self::get_or_insert_node(
229 215 &mut self.root,
230 216 path,
231 217 WithBasename::to_cow_owned,
232 218 |ancestor| {
233 219 // We can’t use `+= increment` because the counter is unsigned,
234 220 // and we want debug builds to detect accidental underflow
235 221 // through zero
236 222 match tracked_count_increment {
237 223 1 => ancestor.tracked_descendants_count += 1,
238 224 -1 => ancestor.tracked_descendants_count -= 1,
239 225 _ => {}
240 226 }
241 227 },
242 228 );
243 229 if node.entry.is_none() {
244 230 self.nodes_with_entry_count += 1
245 231 }
246 232 node.entry = Some(new_entry)
247 233 }
248 234
249 235 fn iter_nodes<'a>(
250 236 &'a self,
251 237 ) -> impl Iterator<Item = (&'a HgPath, &'a Node)> + 'a {
252 238 // Depth first tree traversal.
253 239 //
254 240 // If we could afford internal iteration and recursion,
255 241 // this would look like:
256 242 //
257 243 // ```
258 244 // fn traverse_children(
259 245 // children: &ChildNodes,
260 246 // each: &mut impl FnMut(&Node),
261 247 // ) {
262 248 // for child in children.values() {
263 249 // traverse_children(&child.children, each);
264 250 // each(child);
265 251 // }
266 252 // }
267 253 // ```
268 254 //
269 255 // However we want an external iterator and therefore can’t use the
270 256 // call stack. Use an explicit stack instead:
271 257 let mut stack = Vec::new();
272 258 let mut iter = self.root.iter();
273 259 std::iter::from_fn(move || {
274 260 while let Some((key, child_node)) = iter.next() {
275 261 // Pseudo-recursion
276 262 let new_iter = child_node.children.iter();
277 263 let old_iter = std::mem::replace(&mut iter, new_iter);
278 264 let key = &**key.full_path();
279 265 stack.push((key, child_node, old_iter));
280 266 }
281 267 // Found the end of a `children.iter()` iterator.
282 268 if let Some((key, child_node, next_iter)) = stack.pop() {
283 269 // "Return" from pseudo-recursion by restoring state from the
284 270 // explicit stack
285 271 iter = next_iter;
286 272
287 273 Some((key, child_node))
288 274 } else {
289 275 // Reached the bottom of the stack, we’re done
290 276 None
291 277 }
292 278 })
293 279 }
294 280
295 281 /// Mutable iterator for the `(entry, copy source)` of each node.
296 282 ///
297 283 /// It would not be safe to yield mutable references to nodes themeselves
298 284 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
299 285 /// reachable from their ancestor nodes, potentially creating multiple
300 286 /// `&mut` references to a given node.
301 287 fn iter_node_data_mut<'tree>(
302 288 &'tree mut self,
303 289 ) -> impl Iterator<Item = NodeDataMut<'tree, 'on_disk>> + 'tree {
304 290 // Explict stack for pseudo-recursion, see `iter_nodes` above.
305 291 let mut stack = Vec::new();
306 292 let mut iter = self.root.iter_mut();
307 293 std::iter::from_fn(move || {
308 294 while let Some((key, child_node)) = iter.next() {
309 295 // Pseudo-recursion
310 296 let data = (
311 297 &**key.full_path(),
312 298 &mut child_node.entry,
313 299 &mut child_node.copy_source,
314 300 );
315 301 let new_iter = child_node.children.iter_mut();
316 302 let old_iter = std::mem::replace(&mut iter, new_iter);
317 303 stack.push((data, old_iter));
318 304 }
319 305 // Found the end of a `children.values_mut()` iterator.
320 306 if let Some((data, next_iter)) = stack.pop() {
321 307 // "Return" from pseudo-recursion by restoring state from the
322 308 // explicit stack
323 309 iter = next_iter;
324 310
325 311 Some(data)
326 312 } else {
327 313 // Reached the bottom of the stack, we’re done
328 314 None
329 315 }
330 316 })
331 317 }
332 318 }
333 319
334 320 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
335 321 fn clear(&mut self) {
336 322 self.root.clear();
337 323 self.nodes_with_entry_count = 0;
338 324 self.nodes_with_copy_source_count = 0;
339 325 }
340 326
341 327 fn add_file(
342 328 &mut self,
343 329 filename: &HgPath,
344 330 old_state: EntryState,
345 331 entry: DirstateEntry,
346 332 ) -> Result<(), DirstateMapError> {
347 333 self.add_or_remove_file(filename, old_state, entry);
348 334 Ok(())
349 335 }
350 336
351 337 fn remove_file(
352 338 &mut self,
353 339 filename: &HgPath,
354 340 old_state: EntryState,
355 341 size: i32,
356 342 ) -> Result<(), DirstateMapError> {
357 343 let entry = DirstateEntry {
358 344 state: EntryState::Removed,
359 345 mode: 0,
360 346 size,
361 347 mtime: 0,
362 348 };
363 349 self.add_or_remove_file(filename, old_state, entry);
364 350 Ok(())
365 351 }
366 352
367 353 fn drop_file(
368 354 &mut self,
369 355 filename: &HgPath,
370 356 old_state: EntryState,
371 357 ) -> Result<bool, DirstateMapError> {
372 let was_tracked = old_state.is_tracked();
373 if let Some(node) = Self::get_node_mut_tracing_ancestors(
374 &mut self.root,
375 filename,
376 |ancestor| {
377 if was_tracked {
378 ancestor.tracked_descendants_count -= 1
358 struct Dropped {
359 was_tracked: bool,
360 had_entry: bool,
361 had_copy_source: bool,
362 }
363 fn recur(nodes: &mut ChildNodes, path: &HgPath) -> Option<Dropped> {
364 let (first_path_component, rest_of_path) =
365 path.split_first_component();
366 let node = nodes.get_mut(first_path_component)?;
367 let dropped;
368 if let Some(rest) = rest_of_path {
369 dropped = recur(&mut node.children, rest)?;
370 if dropped.was_tracked {
371 node.tracked_descendants_count -= 1;
379 372 }
380 },
381 ) {
382 let had_entry = node.entry.is_some();
383 let had_copy_source = node.copy_source.is_some();
373 } else {
374 dropped = Dropped {
375 was_tracked: node
376 .entry
377 .as_ref()
378 .map_or(false, |entry| entry.state.is_tracked()),
379 had_entry: node.entry.take().is_some(),
380 had_copy_source: node.copy_source.take().is_some(),
381 };
382 // TODO: this leaves in the tree a "non-file" node. Should we
383 // remove the node instead, together with ancestor nodes for
384 // directories that become empty?
385 }
386 Some(dropped)
387 }
384 388
385 // TODO: this leaves in the tree a "non-file" node. Should we
386 // remove the node instead, together with ancestor nodes for
387 // directories that become empty?
388 node.entry = None;
389 node.copy_source = None;
390
391 if had_entry {
389 if let Some(dropped) = recur(&mut self.root, filename) {
390 if dropped.had_entry {
392 391 self.nodes_with_entry_count -= 1
393 392 }
394 if had_copy_source {
393 if dropped.had_copy_source {
395 394 self.nodes_with_copy_source_count -= 1
396 395 }
397 Ok(had_entry)
396 Ok(dropped.had_entry)
398 397 } else {
399 assert!(!was_tracked);
398 debug_assert!(!old_state.is_tracked());
400 399 Ok(false)
401 400 }
402 401 }
403 402
404 403 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
405 404 for filename in filenames {
406 405 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
407 406 if let Some(entry) = node.entry.as_mut() {
408 407 clear_ambiguous_mtime(entry, now);
409 408 }
410 409 }
411 410 }
412 411 }
413 412
414 413 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
415 414 self.get_node(key)
416 415 .and_then(|node| node.entry.as_ref())
417 416 .map_or(false, DirstateEntry::is_non_normal)
418 417 }
419 418
420 419 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
421 420 // Do nothing, this `DirstateMap` does not have a separate "non normal
422 421 // entries" set that need to be kept up to date
423 422 }
424 423
425 424 fn non_normal_or_other_parent_paths(
426 425 &mut self,
427 426 ) -> Box<dyn Iterator<Item = &HgPath> + '_> {
428 427 Box::new(self.iter_nodes().filter_map(|(path, node)| {
429 428 node.entry
430 429 .as_ref()
431 430 .filter(|entry| {
432 431 entry.is_non_normal() || entry.is_from_other_parent()
433 432 })
434 433 .map(|_| path)
435 434 }))
436 435 }
437 436
438 437 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
439 438 // Do nothing, this `DirstateMap` does not have a separate "non normal
440 439 // entries" and "from other parent" sets that need to be recomputed
441 440 }
442 441
443 442 fn iter_non_normal_paths(
444 443 &mut self,
445 444 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
446 445 self.iter_non_normal_paths_panic()
447 446 }
448 447
449 448 fn iter_non_normal_paths_panic(
450 449 &self,
451 450 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
452 451 Box::new(self.iter_nodes().filter_map(|(path, node)| {
453 452 node.entry
454 453 .as_ref()
455 454 .filter(|entry| entry.is_non_normal())
456 455 .map(|_| path)
457 456 }))
458 457 }
459 458
460 459 fn iter_other_parent_paths(
461 460 &mut self,
462 461 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
463 462 Box::new(self.iter_nodes().filter_map(|(path, node)| {
464 463 node.entry
465 464 .as_ref()
466 465 .filter(|entry| entry.is_from_other_parent())
467 466 .map(|_| path)
468 467 }))
469 468 }
470 469
471 470 fn has_tracked_dir(
472 471 &mut self,
473 472 directory: &HgPath,
474 473 ) -> Result<bool, DirstateMapError> {
475 474 if let Some(node) = self.get_node(directory) {
476 475 // A node without a `DirstateEntry` was created to hold child
477 476 // nodes, and is therefore a directory.
478 477 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
479 478 } else {
480 479 Ok(false)
481 480 }
482 481 }
483 482
484 483 fn has_dir(
485 484 &mut self,
486 485 directory: &HgPath,
487 486 ) -> Result<bool, DirstateMapError> {
488 487 if let Some(node) = self.get_node(directory) {
489 488 // A node without a `DirstateEntry` was created to hold child
490 489 // nodes, and is therefore a directory.
491 490 Ok(node.entry.is_none())
492 491 } else {
493 492 Ok(false)
494 493 }
495 494 }
496 495
497 496 fn pack(
498 497 &mut self,
499 498 parents: DirstateParents,
500 499 now: Timestamp,
501 500 ) -> Result<Vec<u8>, DirstateError> {
502 501 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
503 502 // reallocations
504 503 let mut size = parents.as_bytes().len();
505 504 for (path, node) in self.iter_nodes() {
506 505 if node.entry.is_some() {
507 506 size += packed_entry_size(
508 507 path,
509 508 node.copy_source.as_ref().map(|p| &**p),
510 509 )
511 510 }
512 511 }
513 512
514 513 let mut packed = Vec::with_capacity(size);
515 514 packed.extend(parents.as_bytes());
516 515
517 516 let now: i32 = now.0.try_into().expect("time overflow");
518 517 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
519 518 if let Some(entry) = opt_entry {
520 519 clear_ambiguous_mtime(entry, now);
521 520 pack_entry(
522 521 path,
523 522 entry,
524 523 copy_source.as_ref().map(|p| &**p),
525 524 &mut packed,
526 525 );
527 526 }
528 527 }
529 528 Ok(packed)
530 529 }
531 530
532 531 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
533 532 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
534 533 // needs to be recomputed
535 534 Ok(())
536 535 }
537 536
538 537 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
539 538 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
540 539 // to be recomputed
541 540 Ok(())
542 541 }
543 542
544 543 fn status<'a>(
545 544 &'a mut self,
546 545 matcher: &'a (dyn Matcher + Sync),
547 546 root_dir: PathBuf,
548 547 ignore_files: Vec<PathBuf>,
549 548 options: StatusOptions,
550 549 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
551 550 {
552 551 super::status::status(self, matcher, root_dir, ignore_files, options)
553 552 }
554 553
555 554 fn copy_map_len(&self) -> usize {
556 555 self.nodes_with_copy_source_count
557 556 }
558 557
559 558 fn copy_map_iter(&self) -> CopyMapIter<'_> {
560 559 Box::new(self.iter_nodes().filter_map(|(path, node)| {
561 560 node.copy_source
562 561 .as_ref()
563 562 .map(|copy_source| (path, &**copy_source))
564 563 }))
565 564 }
566 565
567 566 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
568 567 if let Some(node) = self.get_node(key) {
569 568 node.copy_source.is_some()
570 569 } else {
571 570 false
572 571 }
573 572 }
574 573
575 574 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPath> {
576 575 self.get_node(key)?.copy_source.as_ref().map(|p| &**p)
577 576 }
578 577
579 578 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
580 579 let count = &mut self.nodes_with_copy_source_count;
581 580 Self::get_node_mut(&mut self.root, key).and_then(|node| {
582 581 if node.copy_source.is_some() {
583 582 *count -= 1
584 583 }
585 584 node.copy_source.take().map(Cow::into_owned)
586 585 })
587 586 }
588 587
589 588 fn copy_map_insert(
590 589 &mut self,
591 590 key: HgPathBuf,
592 591 value: HgPathBuf,
593 592 ) -> Option<HgPathBuf> {
594 593 let node = Self::get_or_insert_node(
595 594 &mut self.root,
596 595 &key,
597 596 WithBasename::to_cow_owned,
598 597 |_ancestor| {},
599 598 );
600 599 if node.copy_source.is_none() {
601 600 self.nodes_with_copy_source_count += 1
602 601 }
603 602 node.copy_source.replace(value.into()).map(Cow::into_owned)
604 603 }
605 604
606 605 fn len(&self) -> usize {
607 606 self.nodes_with_entry_count
608 607 }
609 608
610 609 fn contains_key(&self, key: &HgPath) -> bool {
611 610 self.get(key).is_some()
612 611 }
613 612
614 613 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
615 614 self.get_node(key)?.entry.as_ref()
616 615 }
617 616
618 617 fn iter(&self) -> StateMapIter<'_> {
619 618 Box::new(self.iter_nodes().filter_map(|(path, node)| {
620 619 node.entry.as_ref().map(|entry| (path, entry))
621 620 }))
622 621 }
623 622 }
@@ -1,804 +1,814 b''
1 1 // hg_path.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 use crate::utils::SliceExt;
8 9 use std::borrow::Borrow;
9 10 use std::borrow::Cow;
10 11 use std::convert::TryFrom;
11 12 use std::ffi::{OsStr, OsString};
12 13 use std::fmt;
13 14 use std::ops::Deref;
14 15 use std::path::{Path, PathBuf};
15 16
16 17 #[derive(Debug, Eq, PartialEq)]
17 18 pub enum HgPathError {
18 19 /// Bytes from the invalid `HgPath`
19 20 LeadingSlash(Vec<u8>),
20 21 ConsecutiveSlashes {
21 22 bytes: Vec<u8>,
22 23 second_slash_index: usize,
23 24 },
24 25 ContainsNullByte {
25 26 bytes: Vec<u8>,
26 27 null_byte_index: usize,
27 28 },
28 29 /// Bytes
29 30 DecodeError(Vec<u8>),
30 31 /// The rest come from audit errors
31 32 EndsWithSlash(HgPathBuf),
32 33 ContainsIllegalComponent(HgPathBuf),
33 34 /// Path is inside the `.hg` folder
34 35 InsideDotHg(HgPathBuf),
35 36 IsInsideNestedRepo {
36 37 path: HgPathBuf,
37 38 nested_repo: HgPathBuf,
38 39 },
39 40 TraversesSymbolicLink {
40 41 path: HgPathBuf,
41 42 symlink: HgPathBuf,
42 43 },
43 44 NotFsCompliant(HgPathBuf),
44 45 /// `path` is the smallest invalid path
45 46 NotUnderRoot {
46 47 path: PathBuf,
47 48 root: PathBuf,
48 49 },
49 50 }
50 51
51 52 impl fmt::Display for HgPathError {
52 53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53 54 match self {
54 55 HgPathError::LeadingSlash(bytes) => {
55 56 write!(f, "Invalid HgPath '{:?}': has a leading slash.", bytes)
56 57 }
57 58 HgPathError::ConsecutiveSlashes {
58 59 bytes,
59 60 second_slash_index: pos,
60 61 } => write!(
61 62 f,
62 63 "Invalid HgPath '{:?}': consecutive slashes at pos {}.",
63 64 bytes, pos
64 65 ),
65 66 HgPathError::ContainsNullByte {
66 67 bytes,
67 68 null_byte_index: pos,
68 69 } => write!(
69 70 f,
70 71 "Invalid HgPath '{:?}': contains null byte at pos {}.",
71 72 bytes, pos
72 73 ),
73 74 HgPathError::DecodeError(bytes) => write!(
74 75 f,
75 76 "Invalid HgPath '{:?}': could not be decoded.",
76 77 bytes
77 78 ),
78 79 HgPathError::EndsWithSlash(path) => {
79 80 write!(f, "Audit failed for '{}': ends with a slash.", path)
80 81 }
81 82 HgPathError::ContainsIllegalComponent(path) => write!(
82 83 f,
83 84 "Audit failed for '{}': contains an illegal component.",
84 85 path
85 86 ),
86 87 HgPathError::InsideDotHg(path) => write!(
87 88 f,
88 89 "Audit failed for '{}': is inside the '.hg' folder.",
89 90 path
90 91 ),
91 92 HgPathError::IsInsideNestedRepo {
92 93 path,
93 94 nested_repo: nested,
94 95 } => {
95 96 write!(f,
96 97 "Audit failed for '{}': is inside a nested repository '{}'.",
97 98 path, nested
98 99 )
99 100 }
100 101 HgPathError::TraversesSymbolicLink { path, symlink } => write!(
101 102 f,
102 103 "Audit failed for '{}': traverses symbolic link '{}'.",
103 104 path, symlink
104 105 ),
105 106 HgPathError::NotFsCompliant(path) => write!(
106 107 f,
107 108 "Audit failed for '{}': cannot be turned into a \
108 109 filesystem path.",
109 110 path
110 111 ),
111 112 HgPathError::NotUnderRoot { path, root } => write!(
112 113 f,
113 114 "Audit failed for '{}': not under root {}.",
114 115 path.display(),
115 116 root.display()
116 117 ),
117 118 }
118 119 }
119 120 }
120 121
121 122 impl From<HgPathError> for std::io::Error {
122 123 fn from(e: HgPathError) -> Self {
123 124 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
124 125 }
125 126 }
126 127
127 128 /// This is a repository-relative path (or canonical path):
128 129 /// - no null characters
129 130 /// - `/` separates directories
130 131 /// - no consecutive slashes
131 132 /// - no leading slash,
132 133 /// - no `.` nor `..` of special meaning
133 134 /// - stored in repository and shared across platforms
134 135 ///
135 136 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
136 137 /// in its lifetime for performance reasons and to ease ergonomics. It is
137 138 /// however checked using the `check_state` method before any file-system
138 139 /// operation.
139 140 ///
140 141 /// This allows us to be encoding-transparent as much as possible, until really
141 142 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
142 143 /// or `Path`) whenever more complex operations are needed:
143 144 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
144 145 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
145 146 /// character encoding will be determined on a per-repository basis.
146 147 //
147 148 // FIXME: (adapted from a comment in the stdlib)
148 149 // `HgPath::new()` current implementation relies on `Slice` being
149 150 // layout-compatible with `[u8]`.
150 151 // When attribute privacy is implemented, `Slice` should be annotated as
151 152 // `#[repr(transparent)]`.
152 153 // Anyway, `Slice` representation and layout are considered implementation
153 154 // detail, are not documented and must not be relied upon.
154 155 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
155 156 pub struct HgPath {
156 157 inner: [u8],
157 158 }
158 159
159 160 impl HgPath {
160 161 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
161 162 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
162 163 }
163 164 pub fn is_empty(&self) -> bool {
164 165 self.inner.is_empty()
165 166 }
166 167 pub fn len(&self) -> usize {
167 168 self.inner.len()
168 169 }
169 170 fn to_hg_path_buf(&self) -> HgPathBuf {
170 171 HgPathBuf {
171 172 inner: self.inner.to_owned(),
172 173 }
173 174 }
174 175 pub fn bytes(&self) -> std::slice::Iter<u8> {
175 176 self.inner.iter()
176 177 }
177 178 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
178 179 HgPathBuf::from(self.inner.to_ascii_uppercase())
179 180 }
180 181 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
181 182 HgPathBuf::from(self.inner.to_ascii_lowercase())
182 183 }
183 184 pub fn as_bytes(&self) -> &[u8] {
184 185 &self.inner
185 186 }
186 187 pub fn contains(&self, other: u8) -> bool {
187 188 self.inner.contains(&other)
188 189 }
189 190 pub fn starts_with(&self, needle: impl AsRef<Self>) -> bool {
190 191 self.inner.starts_with(needle.as_ref().as_bytes())
191 192 }
192 193 pub fn trim_trailing_slash(&self) -> &Self {
193 194 Self::new(if self.inner.last() == Some(&b'/') {
194 195 &self.inner[..self.inner.len() - 1]
195 196 } else {
196 197 &self.inner[..]
197 198 })
198 199 }
199 200 /// Returns a tuple of slices `(base, filename)` resulting from the split
200 201 /// at the rightmost `/`, if any.
201 202 ///
202 203 /// # Examples:
203 204 ///
204 205 /// ```
205 206 /// use hg::utils::hg_path::HgPath;
206 207 ///
207 208 /// let path = HgPath::new(b"cool/hg/path").split_filename();
208 209 /// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path")));
209 210 ///
210 211 /// let path = HgPath::new(b"pathwithoutsep").split_filename();
211 212 /// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep")));
212 213 /// ```
213 214 pub fn split_filename(&self) -> (&Self, &Self) {
214 215 match &self.inner.iter().rposition(|c| *c == b'/') {
215 216 None => (HgPath::new(""), &self),
216 217 Some(size) => (
217 218 HgPath::new(&self.inner[..*size]),
218 219 HgPath::new(&self.inner[*size + 1..]),
219 220 ),
220 221 }
221 222 }
222 223 pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf {
223 224 let mut inner = self.inner.to_owned();
224 225 if !inner.is_empty() && inner.last() != Some(&b'/') {
225 226 inner.push(b'/');
226 227 }
227 228 inner.extend(other.as_ref().bytes());
228 229 HgPathBuf::from_bytes(&inner)
229 230 }
230 231
231 232 pub fn components(&self) -> impl Iterator<Item = &HgPath> {
232 233 self.inner.split(|&byte| byte == b'/').map(HgPath::new)
233 234 }
234 235
236 /// Returns the first (that is "root-most") slash-separated component of
237 /// the path, and the rest after the first slash if there is one.
238 pub fn split_first_component(&self) -> (&HgPath, Option<&HgPath>) {
239 match self.inner.split_2(b'/') {
240 Some((a, b)) => (HgPath::new(a), Some(HgPath::new(b))),
241 None => (self, None),
242 }
243 }
244
235 245 pub fn parent(&self) -> &Self {
236 246 let inner = self.as_bytes();
237 247 HgPath::new(match inner.iter().rposition(|b| *b == b'/') {
238 248 Some(pos) => &inner[..pos],
239 249 None => &[],
240 250 })
241 251 }
242 252 /// Given a base directory, returns the slice of `self` relative to the
243 253 /// base directory. If `base` is not a directory (does not end with a
244 254 /// `b'/'`), returns `None`.
245 255 pub fn relative_to(&self, base: impl AsRef<Self>) -> Option<&Self> {
246 256 let base = base.as_ref();
247 257 if base.is_empty() {
248 258 return Some(self);
249 259 }
250 260 let is_dir = base.as_bytes().ends_with(b"/");
251 261 if is_dir && self.starts_with(base) {
252 262 Some(Self::new(&self.inner[base.len()..]))
253 263 } else {
254 264 None
255 265 }
256 266 }
257 267
258 268 #[cfg(windows)]
259 269 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
260 270 ///
261 271 /// Split a pathname into drive/UNC sharepoint and relative path
262 272 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
263 273 /// be empty.
264 274 ///
265 275 /// If you assign
266 276 /// result = split_drive(p)
267 277 /// It is always true that:
268 278 /// result[0] + result[1] == p
269 279 ///
270 280 /// If the path contained a drive letter, drive_or_unc will contain
271 281 /// everything up to and including the colon.
272 282 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
273 283 ///
274 284 /// If the path contained a UNC path, the drive_or_unc will contain the
275 285 /// host name and share up to but not including the fourth directory
276 286 /// separator character.
277 287 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
278 288 /// "/dir")
279 289 ///
280 290 /// Paths cannot contain both a drive letter and a UNC path.
281 291 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
282 292 let bytes = self.as_bytes();
283 293 let is_sep = |b| std::path::is_separator(b as char);
284 294
285 295 if self.len() < 2 {
286 296 (HgPath::new(b""), &self)
287 297 } else if is_sep(bytes[0])
288 298 && is_sep(bytes[1])
289 299 && (self.len() == 2 || !is_sep(bytes[2]))
290 300 {
291 301 // Is a UNC path:
292 302 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
293 303 // \\machine\mountpoint\directory\etc\...
294 304 // directory ^^^^^^^^^^^^^^^
295 305
296 306 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
297 307 let mountpoint_start_index = if let Some(i) = machine_end_index {
298 308 i + 2
299 309 } else {
300 310 return (HgPath::new(b""), &self);
301 311 };
302 312
303 313 match bytes[mountpoint_start_index + 1..]
304 314 .iter()
305 315 .position(|b| is_sep(*b))
306 316 {
307 317 // A UNC path can't have two slashes in a row
308 318 // (after the initial two)
309 319 Some(0) => (HgPath::new(b""), &self),
310 320 Some(i) => {
311 321 let (a, b) =
312 322 bytes.split_at(mountpoint_start_index + 1 + i);
313 323 (HgPath::new(a), HgPath::new(b))
314 324 }
315 325 None => (&self, HgPath::new(b"")),
316 326 }
317 327 } else if bytes[1] == b':' {
318 328 // Drive path c:\directory
319 329 let (a, b) = bytes.split_at(2);
320 330 (HgPath::new(a), HgPath::new(b))
321 331 } else {
322 332 (HgPath::new(b""), &self)
323 333 }
324 334 }
325 335
326 336 #[cfg(unix)]
327 337 /// Split a pathname into drive and path. On Posix, drive is always empty.
328 338 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
329 339 (HgPath::new(b""), &self)
330 340 }
331 341
332 342 /// Checks for errors in the path, short-circuiting at the first one.
333 343 /// This generates fine-grained errors useful for debugging.
334 344 /// To simply check if the path is valid during tests, use `is_valid`.
335 345 pub fn check_state(&self) -> Result<(), HgPathError> {
336 346 if self.is_empty() {
337 347 return Ok(());
338 348 }
339 349 let bytes = self.as_bytes();
340 350 let mut previous_byte = None;
341 351
342 352 if bytes[0] == b'/' {
343 353 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
344 354 }
345 355 for (index, byte) in bytes.iter().enumerate() {
346 356 match byte {
347 357 0 => {
348 358 return Err(HgPathError::ContainsNullByte {
349 359 bytes: bytes.to_vec(),
350 360 null_byte_index: index,
351 361 })
352 362 }
353 363 b'/' => {
354 364 if previous_byte.is_some() && previous_byte == Some(b'/') {
355 365 return Err(HgPathError::ConsecutiveSlashes {
356 366 bytes: bytes.to_vec(),
357 367 second_slash_index: index,
358 368 });
359 369 }
360 370 }
361 371 _ => (),
362 372 };
363 373 previous_byte = Some(*byte);
364 374 }
365 375 Ok(())
366 376 }
367 377
368 378 #[cfg(test)]
369 379 /// Only usable during tests to force developers to handle invalid states
370 380 fn is_valid(&self) -> bool {
371 381 self.check_state().is_ok()
372 382 }
373 383 }
374 384
375 385 impl fmt::Debug for HgPath {
376 386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 387 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
378 388 }
379 389 }
380 390
381 391 impl fmt::Display for HgPath {
382 392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 393 write!(f, "{}", String::from_utf8_lossy(&self.inner))
384 394 }
385 395 }
386 396
387 397 #[derive(
388 398 Default, Eq, Ord, Clone, PartialEq, PartialOrd, Hash, derive_more::From,
389 399 )]
390 400 pub struct HgPathBuf {
391 401 inner: Vec<u8>,
392 402 }
393 403
394 404 impl HgPathBuf {
395 405 pub fn new() -> Self {
396 406 Default::default()
397 407 }
398 408 pub fn push(&mut self, byte: u8) {
399 409 self.inner.push(byte);
400 410 }
401 411 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
402 412 HgPath::new(s).to_owned()
403 413 }
404 414 pub fn into_vec(self) -> Vec<u8> {
405 415 self.inner
406 416 }
407 417 }
408 418
409 419 impl fmt::Debug for HgPathBuf {
410 420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 421 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
412 422 }
413 423 }
414 424
415 425 impl fmt::Display for HgPathBuf {
416 426 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 427 write!(f, "{}", String::from_utf8_lossy(&self.inner))
418 428 }
419 429 }
420 430
421 431 impl Deref for HgPathBuf {
422 432 type Target = HgPath;
423 433
424 434 #[inline]
425 435 fn deref(&self) -> &HgPath {
426 436 &HgPath::new(&self.inner)
427 437 }
428 438 }
429 439
430 440 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
431 441 fn from(s: &T) -> HgPathBuf {
432 442 s.as_ref().to_owned()
433 443 }
434 444 }
435 445
436 446 impl Into<Vec<u8>> for HgPathBuf {
437 447 fn into(self) -> Vec<u8> {
438 448 self.inner
439 449 }
440 450 }
441 451
442 452 impl Borrow<HgPath> for HgPathBuf {
443 453 fn borrow(&self) -> &HgPath {
444 454 &HgPath::new(self.as_bytes())
445 455 }
446 456 }
447 457
448 458 impl ToOwned for HgPath {
449 459 type Owned = HgPathBuf;
450 460
451 461 fn to_owned(&self) -> HgPathBuf {
452 462 self.to_hg_path_buf()
453 463 }
454 464 }
455 465
456 466 impl AsRef<HgPath> for HgPath {
457 467 fn as_ref(&self) -> &HgPath {
458 468 self
459 469 }
460 470 }
461 471
462 472 impl AsRef<HgPath> for HgPathBuf {
463 473 fn as_ref(&self) -> &HgPath {
464 474 self
465 475 }
466 476 }
467 477
468 478 impl Extend<u8> for HgPathBuf {
469 479 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
470 480 self.inner.extend(iter);
471 481 }
472 482 }
473 483
474 484 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
475 485 /// implemented, these conversion utils will have to work differently depending
476 486 /// on the repository encoding: either `UTF-8` or `MBCS`.
477 487
478 488 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
479 489 hg_path: P,
480 490 ) -> Result<OsString, HgPathError> {
481 491 hg_path.as_ref().check_state()?;
482 492 let os_str;
483 493 #[cfg(unix)]
484 494 {
485 495 use std::os::unix::ffi::OsStrExt;
486 496 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
487 497 }
488 498 // TODO Handle other platforms
489 499 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
490 500 Ok(os_str.to_os_string())
491 501 }
492 502
493 503 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
494 504 hg_path: P,
495 505 ) -> Result<PathBuf, HgPathError> {
496 506 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
497 507 }
498 508
499 509 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
500 510 os_string: S,
501 511 ) -> Result<HgPathBuf, HgPathError> {
502 512 let buf;
503 513 #[cfg(unix)]
504 514 {
505 515 use std::os::unix::ffi::OsStrExt;
506 516 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
507 517 }
508 518 // TODO Handle other platforms
509 519 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
510 520
511 521 buf.check_state()?;
512 522 Ok(buf)
513 523 }
514 524
515 525 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
516 526 path: P,
517 527 ) -> Result<HgPathBuf, HgPathError> {
518 528 let buf;
519 529 let os_str = path.as_ref().as_os_str();
520 530 #[cfg(unix)]
521 531 {
522 532 use std::os::unix::ffi::OsStrExt;
523 533 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
524 534 }
525 535 // TODO Handle other platforms
526 536 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
527 537
528 538 buf.check_state()?;
529 539 Ok(buf)
530 540 }
531 541
532 542 impl TryFrom<PathBuf> for HgPathBuf {
533 543 type Error = HgPathError;
534 544 fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
535 545 path_to_hg_path_buf(path)
536 546 }
537 547 }
538 548
539 549 impl From<HgPathBuf> for Cow<'_, HgPath> {
540 550 fn from(path: HgPathBuf) -> Self {
541 551 Cow::Owned(path)
542 552 }
543 553 }
544 554
545 555 impl<'a> From<&'a HgPath> for Cow<'a, HgPath> {
546 556 fn from(path: &'a HgPath) -> Self {
547 557 Cow::Borrowed(path)
548 558 }
549 559 }
550 560
551 561 impl<'a> From<&'a HgPathBuf> for Cow<'a, HgPath> {
552 562 fn from(path: &'a HgPathBuf) -> Self {
553 563 Cow::Borrowed(&**path)
554 564 }
555 565 }
556 566
557 567 #[cfg(test)]
558 568 mod tests {
559 569 use super::*;
560 570 use pretty_assertions::assert_eq;
561 571
562 572 #[test]
563 573 fn test_path_states() {
564 574 assert_eq!(
565 575 Err(HgPathError::LeadingSlash(b"/".to_vec())),
566 576 HgPath::new(b"/").check_state()
567 577 );
568 578 assert_eq!(
569 579 Err(HgPathError::ConsecutiveSlashes {
570 580 bytes: b"a/b//c".to_vec(),
571 581 second_slash_index: 4
572 582 }),
573 583 HgPath::new(b"a/b//c").check_state()
574 584 );
575 585 assert_eq!(
576 586 Err(HgPathError::ContainsNullByte {
577 587 bytes: b"a/b/\0c".to_vec(),
578 588 null_byte_index: 4
579 589 }),
580 590 HgPath::new(b"a/b/\0c").check_state()
581 591 );
582 592 // TODO test HgPathError::DecodeError for the Windows implementation.
583 593 assert_eq!(true, HgPath::new(b"").is_valid());
584 594 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
585 595 // Backslashes in paths are not significant, but allowed
586 596 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
587 597 // Dots in paths are not significant, but allowed
588 598 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
589 599 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
590 600 }
591 601
592 602 #[test]
593 603 fn test_iter() {
594 604 let path = HgPath::new(b"a");
595 605 let mut iter = path.bytes();
596 606 assert_eq!(Some(&b'a'), iter.next());
597 607 assert_eq!(None, iter.next_back());
598 608 assert_eq!(None, iter.next());
599 609
600 610 let path = HgPath::new(b"a");
601 611 let mut iter = path.bytes();
602 612 assert_eq!(Some(&b'a'), iter.next_back());
603 613 assert_eq!(None, iter.next_back());
604 614 assert_eq!(None, iter.next());
605 615
606 616 let path = HgPath::new(b"abc");
607 617 let mut iter = path.bytes();
608 618 assert_eq!(Some(&b'a'), iter.next());
609 619 assert_eq!(Some(&b'c'), iter.next_back());
610 620 assert_eq!(Some(&b'b'), iter.next_back());
611 621 assert_eq!(None, iter.next_back());
612 622 assert_eq!(None, iter.next());
613 623
614 624 let path = HgPath::new(b"abc");
615 625 let mut iter = path.bytes();
616 626 assert_eq!(Some(&b'a'), iter.next());
617 627 assert_eq!(Some(&b'b'), iter.next());
618 628 assert_eq!(Some(&b'c'), iter.next());
619 629 assert_eq!(None, iter.next_back());
620 630 assert_eq!(None, iter.next());
621 631
622 632 let path = HgPath::new(b"abc");
623 633 let iter = path.bytes();
624 634 let mut vec = Vec::new();
625 635 vec.extend(iter);
626 636 assert_eq!(vec![b'a', b'b', b'c'], vec);
627 637
628 638 let path = HgPath::new(b"abc");
629 639 let mut iter = path.bytes();
630 640 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
631 641
632 642 let path = HgPath::new(b"abc");
633 643 let mut iter = path.bytes();
634 644 assert_eq!(None, iter.rposition(|c| *c == b'd'));
635 645 }
636 646
637 647 #[test]
638 648 fn test_join() {
639 649 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
640 650 assert_eq!(b"a/b", path.as_bytes());
641 651
642 652 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
643 653 assert_eq!(b"a/b/c", path.as_bytes());
644 654
645 655 // No leading slash if empty before join
646 656 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
647 657 assert_eq!(b"b/c", path.as_bytes());
648 658
649 659 // The leading slash is an invalid representation of an `HgPath`, but
650 660 // it can happen. This creates another invalid representation of
651 661 // consecutive bytes.
652 662 // TODO What should be done in this case? Should we silently remove
653 663 // the extra slash? Should we change the signature to a problematic
654 664 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
655 665 // let the error happen upon filesystem interaction?
656 666 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
657 667 assert_eq!(b"a//b", path.as_bytes());
658 668 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
659 669 assert_eq!(b"a//b", path.as_bytes());
660 670 }
661 671
662 672 #[test]
663 673 fn test_relative_to() {
664 674 let path = HgPath::new(b"");
665 675 let base = HgPath::new(b"");
666 676 assert_eq!(Some(path), path.relative_to(base));
667 677
668 678 let path = HgPath::new(b"path");
669 679 let base = HgPath::new(b"");
670 680 assert_eq!(Some(path), path.relative_to(base));
671 681
672 682 let path = HgPath::new(b"a");
673 683 let base = HgPath::new(b"b");
674 684 assert_eq!(None, path.relative_to(base));
675 685
676 686 let path = HgPath::new(b"a/b");
677 687 let base = HgPath::new(b"a");
678 688 assert_eq!(None, path.relative_to(base));
679 689
680 690 let path = HgPath::new(b"a/b");
681 691 let base = HgPath::new(b"a/");
682 692 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
683 693
684 694 let path = HgPath::new(b"nested/path/to/b");
685 695 let base = HgPath::new(b"nested/path/");
686 696 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
687 697
688 698 let path = HgPath::new(b"ends/with/dir/");
689 699 let base = HgPath::new(b"ends/");
690 700 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
691 701 }
692 702
693 703 #[test]
694 704 #[cfg(unix)]
695 705 fn test_split_drive() {
696 706 // Taken from the Python stdlib's tests
697 707 assert_eq!(
698 708 HgPath::new(br"/foo/bar").split_drive(),
699 709 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
700 710 );
701 711 assert_eq!(
702 712 HgPath::new(br"foo:bar").split_drive(),
703 713 (HgPath::new(b""), HgPath::new(br"foo:bar"))
704 714 );
705 715 assert_eq!(
706 716 HgPath::new(br":foo:bar").split_drive(),
707 717 (HgPath::new(b""), HgPath::new(br":foo:bar"))
708 718 );
709 719 // Also try NT paths; should not split them
710 720 assert_eq!(
711 721 HgPath::new(br"c:\foo\bar").split_drive(),
712 722 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
713 723 );
714 724 assert_eq!(
715 725 HgPath::new(b"c:/foo/bar").split_drive(),
716 726 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
717 727 );
718 728 assert_eq!(
719 729 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
720 730 (
721 731 HgPath::new(b""),
722 732 HgPath::new(br"\\conky\mountpoint\foo\bar")
723 733 )
724 734 );
725 735 }
726 736
727 737 #[test]
728 738 #[cfg(windows)]
729 739 fn test_split_drive() {
730 740 assert_eq!(
731 741 HgPath::new(br"c:\foo\bar").split_drive(),
732 742 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
733 743 );
734 744 assert_eq!(
735 745 HgPath::new(b"c:/foo/bar").split_drive(),
736 746 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
737 747 );
738 748 assert_eq!(
739 749 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
740 750 (
741 751 HgPath::new(br"\\conky\mountpoint"),
742 752 HgPath::new(br"\foo\bar")
743 753 )
744 754 );
745 755 assert_eq!(
746 756 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
747 757 (
748 758 HgPath::new(br"//conky/mountpoint"),
749 759 HgPath::new(br"/foo/bar")
750 760 )
751 761 );
752 762 assert_eq!(
753 763 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
754 764 (
755 765 HgPath::new(br""),
756 766 HgPath::new(br"\\\conky\mountpoint\foo\bar")
757 767 )
758 768 );
759 769 assert_eq!(
760 770 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
761 771 (
762 772 HgPath::new(br""),
763 773 HgPath::new(br"///conky/mountpoint/foo/bar")
764 774 )
765 775 );
766 776 assert_eq!(
767 777 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
768 778 (
769 779 HgPath::new(br""),
770 780 HgPath::new(br"\\conky\\mountpoint\foo\bar")
771 781 )
772 782 );
773 783 assert_eq!(
774 784 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
775 785 (
776 786 HgPath::new(br""),
777 787 HgPath::new(br"//conky//mountpoint/foo/bar")
778 788 )
779 789 );
780 790 // UNC part containing U+0130
781 791 assert_eq!(
782 792 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
783 793 (
784 794 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
785 795 HgPath::new(br"/foo/bar")
786 796 )
787 797 );
788 798 }
789 799
790 800 #[test]
791 801 fn test_parent() {
792 802 let path = HgPath::new(b"");
793 803 assert_eq!(path.parent(), path);
794 804
795 805 let path = HgPath::new(b"a");
796 806 assert_eq!(path.parent(), HgPath::new(b""));
797 807
798 808 let path = HgPath::new(b"a/b");
799 809 assert_eq!(path.parent(), HgPath::new(b"a"));
800 810
801 811 let path = HgPath::new(b"a/other/b");
802 812 assert_eq!(path.parent(), HgPath::new(b"a/other"));
803 813 }
804 814 }
General Comments 0
You need to be logged in to leave comments. Login now