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