##// END OF EJS Templates
rust-dirs-multiset: add `DirsChildrenMultiset`...
Raphaël Gomès -
r44739:0e9ac396 default
parent child Browse files
Show More
@@ -1,346 +1,411 b''
1 // dirs_multiset.rs
1 // dirs_multiset.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 //! A multiset of directory names.
8 //! A multiset of directory names.
9 //!
9 //!
10 //! Used to counts the references to directories in a manifest or dirstate.
10 //! Used to counts the references to directories in a manifest or dirstate.
11 use crate::utils::hg_path::{HgPath, HgPathBuf};
12 use crate::{
11 use crate::{
13 dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError,
12 dirstate::EntryState,
14 FastHashMap,
13 utils::{
14 files,
15 hg_path::{HgPath, HgPathBuf},
16 },
17 DirstateEntry, DirstateMapError, FastHashMap,
15 };
18 };
16 use std::collections::hash_map::{self, Entry};
19 use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet};
17
20
18 // could be encapsulated if we care API stability more seriously
21 // could be encapsulated if we care API stability more seriously
19 pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>;
22 pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>;
20
23
21 #[derive(PartialEq, Debug)]
24 #[derive(PartialEq, Debug)]
22 pub struct DirsMultiset {
25 pub struct DirsMultiset {
23 inner: FastHashMap<HgPathBuf, u32>,
26 inner: FastHashMap<HgPathBuf, u32>,
24 }
27 }
25
28
26 impl DirsMultiset {
29 impl DirsMultiset {
27 /// Initializes the multiset from a dirstate.
30 /// Initializes the multiset from a dirstate.
28 ///
31 ///
29 /// If `skip_state` is provided, skips dirstate entries with equal state.
32 /// If `skip_state` is provided, skips dirstate entries with equal state.
30 pub fn from_dirstate(
33 pub fn from_dirstate(
31 dirstate: &FastHashMap<HgPathBuf, DirstateEntry>,
34 dirstate: &FastHashMap<HgPathBuf, DirstateEntry>,
32 skip_state: Option<EntryState>,
35 skip_state: Option<EntryState>,
33 ) -> Result<Self, DirstateMapError> {
36 ) -> Result<Self, DirstateMapError> {
34 let mut multiset = DirsMultiset {
37 let mut multiset = DirsMultiset {
35 inner: FastHashMap::default(),
38 inner: FastHashMap::default(),
36 };
39 };
37
40
38 for (filename, DirstateEntry { state, .. }) in dirstate {
41 for (filename, DirstateEntry { state, .. }) in dirstate {
39 // This `if` is optimized out of the loop
42 // This `if` is optimized out of the loop
40 if let Some(skip) = skip_state {
43 if let Some(skip) = skip_state {
41 if skip != *state {
44 if skip != *state {
42 multiset.add_path(filename)?;
45 multiset.add_path(filename)?;
43 }
46 }
44 } else {
47 } else {
45 multiset.add_path(filename)?;
48 multiset.add_path(filename)?;
46 }
49 }
47 }
50 }
48
51
49 Ok(multiset)
52 Ok(multiset)
50 }
53 }
51
54
52 /// Initializes the multiset from a manifest.
55 /// Initializes the multiset from a manifest.
53 pub fn from_manifest(
56 pub fn from_manifest(
54 manifest: &[impl AsRef<HgPath>],
57 manifest: &[impl AsRef<HgPath>],
55 ) -> Result<Self, DirstateMapError> {
58 ) -> Result<Self, DirstateMapError> {
56 let mut multiset = DirsMultiset {
59 let mut multiset = DirsMultiset {
57 inner: FastHashMap::default(),
60 inner: FastHashMap::default(),
58 };
61 };
59
62
60 for filename in manifest {
63 for filename in manifest {
61 multiset.add_path(filename.as_ref())?;
64 multiset.add_path(filename.as_ref())?;
62 }
65 }
63
66
64 Ok(multiset)
67 Ok(multiset)
65 }
68 }
66
69
67 /// Increases the count of deepest directory contained in the path.
70 /// Increases the count of deepest directory contained in the path.
68 ///
71 ///
69 /// If the directory is not yet in the map, adds its parents.
72 /// If the directory is not yet in the map, adds its parents.
70 pub fn add_path(
73 pub fn add_path(
71 &mut self,
74 &mut self,
72 path: impl AsRef<HgPath>,
75 path: impl AsRef<HgPath>,
73 ) -> Result<(), DirstateMapError> {
76 ) -> Result<(), DirstateMapError> {
74 for subpath in files::find_dirs(path.as_ref()) {
77 for subpath in files::find_dirs(path.as_ref()) {
75 if subpath.as_bytes().last() == Some(&b'/') {
78 if subpath.as_bytes().last() == Some(&b'/') {
76 // TODO Remove this once PathAuditor is certified
79 // TODO Remove this once PathAuditor is certified
77 // as the only entrypoint for path data
80 // as the only entrypoint for path data
78 return Err(DirstateMapError::ConsecutiveSlashes);
81 return Err(DirstateMapError::ConsecutiveSlashes);
79 }
82 }
80 if let Some(val) = self.inner.get_mut(subpath) {
83 if let Some(val) = self.inner.get_mut(subpath) {
81 *val += 1;
84 *val += 1;
82 break;
85 break;
83 }
86 }
84 self.inner.insert(subpath.to_owned(), 1);
87 self.inner.insert(subpath.to_owned(), 1);
85 }
88 }
86 Ok(())
89 Ok(())
87 }
90 }
88
91
89 /// Decreases the count of deepest directory contained in the path.
92 /// Decreases the count of deepest directory contained in the path.
90 ///
93 ///
91 /// If it is the only reference, decreases all parents until one is
94 /// If it is the only reference, decreases all parents until one is
92 /// removed.
95 /// removed.
93 /// If the directory is not in the map, something horrible has happened.
96 /// If the directory is not in the map, something horrible has happened.
94 pub fn delete_path(
97 pub fn delete_path(
95 &mut self,
98 &mut self,
96 path: impl AsRef<HgPath>,
99 path: impl AsRef<HgPath>,
97 ) -> Result<(), DirstateMapError> {
100 ) -> Result<(), DirstateMapError> {
98 for subpath in files::find_dirs(path.as_ref()) {
101 for subpath in files::find_dirs(path.as_ref()) {
99 match self.inner.entry(subpath.to_owned()) {
102 match self.inner.entry(subpath.to_owned()) {
100 Entry::Occupied(mut entry) => {
103 Entry::Occupied(mut entry) => {
101 let val = entry.get().clone();
104 let val = entry.get().clone();
102 if val > 1 {
105 if val > 1 {
103 entry.insert(val - 1);
106 entry.insert(val - 1);
104 break;
107 break;
105 }
108 }
106 entry.remove();
109 entry.remove();
107 }
110 }
108 Entry::Vacant(_) => {
111 Entry::Vacant(_) => {
109 return Err(DirstateMapError::PathNotFound(
112 return Err(DirstateMapError::PathNotFound(
110 path.as_ref().to_owned(),
113 path.as_ref().to_owned(),
111 ))
114 ))
112 }
115 }
113 };
116 };
114 }
117 }
115
118
116 Ok(())
119 Ok(())
117 }
120 }
118
121
119 pub fn contains(&self, key: impl AsRef<HgPath>) -> bool {
122 pub fn contains(&self, key: impl AsRef<HgPath>) -> bool {
120 self.inner.contains_key(key.as_ref())
123 self.inner.contains_key(key.as_ref())
121 }
124 }
122
125
123 pub fn iter(&self) -> DirsMultisetIter {
126 pub fn iter(&self) -> DirsMultisetIter {
124 self.inner.keys()
127 self.inner.keys()
125 }
128 }
126
129
127 pub fn len(&self) -> usize {
130 pub fn len(&self) -> usize {
128 self.inner.len()
131 self.inner.len()
129 }
132 }
130 }
133 }
131
134
135 /// This is basically a reimplementation of `DirsMultiset` that stores the
136 /// children instead of just a count of them, plus a small optional
137 /// optimization to avoid some directories we don't need.
138 #[derive(PartialEq, Debug)]
139 pub struct DirsChildrenMultiset<'a> {
140 inner: FastHashMap<&'a HgPath, HashSet<&'a HgPath>>,
141 only_include: Option<HashSet<&'a HgPath>>,
142 }
143
144 impl<'a> DirsChildrenMultiset<'a> {
145 pub fn new(
146 paths: impl Iterator<Item = &'a HgPathBuf>,
147 only_include: Option<&'a HashSet<impl AsRef<HgPath> + 'a>>,
148 ) -> Self {
149 let mut new = Self {
150 inner: HashMap::default(),
151 only_include: only_include
152 .map(|s| s.iter().map(|p| p.as_ref()).collect()),
153 };
154
155 for path in paths {
156 new.add_path(path)
157 }
158
159 new
160 }
161 fn add_path(&mut self, path: &'a (impl AsRef<HgPath> + 'a)) {
162 if path.as_ref().is_empty() {
163 return;
164 }
165 for (directory, basename) in files::find_dirs_with_base(path.as_ref())
166 {
167 if !self.is_dir_included(directory) {
168 continue;
169 }
170 self.inner
171 .entry(directory)
172 .and_modify(|e| {
173 e.insert(basename);
174 })
175 .or_insert_with(|| {
176 let mut set = HashSet::new();
177 set.insert(basename);
178 set
179 });
180 }
181 }
182 fn is_dir_included(&self, dir: impl AsRef<HgPath>) -> bool {
183 match &self.only_include {
184 None => false,
185 Some(i) => i.contains(dir.as_ref()),
186 }
187 }
188
189 pub fn get(
190 &self,
191 path: impl AsRef<HgPath>,
192 ) -> Option<&HashSet<&'a HgPath>> {
193 self.inner.get(path.as_ref())
194 }
195 }
196
132 #[cfg(test)]
197 #[cfg(test)]
133 mod tests {
198 mod tests {
134 use super::*;
199 use super::*;
135
200
136 #[test]
201 #[test]
137 fn test_delete_path_path_not_found() {
202 fn test_delete_path_path_not_found() {
138 let manifest: Vec<HgPathBuf> = vec![];
203 let manifest: Vec<HgPathBuf> = vec![];
139 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
204 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
140 let path = HgPathBuf::from_bytes(b"doesnotexist/");
205 let path = HgPathBuf::from_bytes(b"doesnotexist/");
141 assert_eq!(
206 assert_eq!(
142 Err(DirstateMapError::PathNotFound(path.to_owned())),
207 Err(DirstateMapError::PathNotFound(path.to_owned())),
143 map.delete_path(&path)
208 map.delete_path(&path)
144 );
209 );
145 }
210 }
146
211
147 #[test]
212 #[test]
148 fn test_delete_path_empty_path() {
213 fn test_delete_path_empty_path() {
149 let mut map =
214 let mut map =
150 DirsMultiset::from_manifest(&vec![HgPathBuf::new()]).unwrap();
215 DirsMultiset::from_manifest(&vec![HgPathBuf::new()]).unwrap();
151 let path = HgPath::new(b"");
216 let path = HgPath::new(b"");
152 assert_eq!(Ok(()), map.delete_path(path));
217 assert_eq!(Ok(()), map.delete_path(path));
153 assert_eq!(
218 assert_eq!(
154 Err(DirstateMapError::PathNotFound(path.to_owned())),
219 Err(DirstateMapError::PathNotFound(path.to_owned())),
155 map.delete_path(path)
220 map.delete_path(path)
156 );
221 );
157 }
222 }
158
223
159 #[test]
224 #[test]
160 fn test_delete_path_successful() {
225 fn test_delete_path_successful() {
161 let mut map = DirsMultiset {
226 let mut map = DirsMultiset {
162 inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)]
227 inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)]
163 .iter()
228 .iter()
164 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
229 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
165 .collect(),
230 .collect(),
166 };
231 };
167
232
168 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
233 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
169 eprintln!("{:?}", map);
234 eprintln!("{:?}", map);
170 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
235 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
171 eprintln!("{:?}", map);
236 eprintln!("{:?}", map);
172 assert_eq!(
237 assert_eq!(
173 Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
238 Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
174 b"a/b/"
239 b"a/b/"
175 ))),
240 ))),
176 map.delete_path(HgPath::new(b"a/b/"))
241 map.delete_path(HgPath::new(b"a/b/"))
177 );
242 );
178
243
179 assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
244 assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
180 assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
245 assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
181 eprintln!("{:?}", map);
246 eprintln!("{:?}", map);
182 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/")));
247 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/")));
183 eprintln!("{:?}", map);
248 eprintln!("{:?}", map);
184
249
185 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/c/")));
250 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/c/")));
186 assert_eq!(
251 assert_eq!(
187 Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
252 Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
188 b"a/c/"
253 b"a/c/"
189 ))),
254 ))),
190 map.delete_path(HgPath::new(b"a/c/"))
255 map.delete_path(HgPath::new(b"a/c/"))
191 );
256 );
192 }
257 }
193
258
194 #[test]
259 #[test]
195 fn test_add_path_empty_path() {
260 fn test_add_path_empty_path() {
196 let manifest: Vec<HgPathBuf> = vec![];
261 let manifest: Vec<HgPathBuf> = vec![];
197 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
262 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
198 let path = HgPath::new(b"");
263 let path = HgPath::new(b"");
199 map.add_path(path).unwrap();
264 map.add_path(path).unwrap();
200
265
201 assert_eq!(1, map.len());
266 assert_eq!(1, map.len());
202 }
267 }
203
268
204 #[test]
269 #[test]
205 fn test_add_path_successful() {
270 fn test_add_path_successful() {
206 let manifest: Vec<HgPathBuf> = vec![];
271 let manifest: Vec<HgPathBuf> = vec![];
207 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
272 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
208
273
209 map.add_path(HgPath::new(b"a/")).unwrap();
274 map.add_path(HgPath::new(b"a/")).unwrap();
210 assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
275 assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
211 assert_eq!(1, *map.inner.get(HgPath::new(b"")).unwrap());
276 assert_eq!(1, *map.inner.get(HgPath::new(b"")).unwrap());
212 assert_eq!(2, map.len());
277 assert_eq!(2, map.len());
213
278
214 // Non directory should be ignored
279 // Non directory should be ignored
215 map.add_path(HgPath::new(b"a")).unwrap();
280 map.add_path(HgPath::new(b"a")).unwrap();
216 assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
281 assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
217 assert_eq!(2, map.len());
282 assert_eq!(2, map.len());
218
283
219 // Non directory will still add its base
284 // Non directory will still add its base
220 map.add_path(HgPath::new(b"a/b")).unwrap();
285 map.add_path(HgPath::new(b"a/b")).unwrap();
221 assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
286 assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
222 assert_eq!(2, map.len());
287 assert_eq!(2, map.len());
223
288
224 // Duplicate path works
289 // Duplicate path works
225 map.add_path(HgPath::new(b"a/")).unwrap();
290 map.add_path(HgPath::new(b"a/")).unwrap();
226 assert_eq!(3, *map.inner.get(HgPath::new(b"a")).unwrap());
291 assert_eq!(3, *map.inner.get(HgPath::new(b"a")).unwrap());
227
292
228 // Nested dir adds to its base
293 // Nested dir adds to its base
229 map.add_path(HgPath::new(b"a/b/")).unwrap();
294 map.add_path(HgPath::new(b"a/b/")).unwrap();
230 assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
295 assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
231 assert_eq!(1, *map.inner.get(HgPath::new(b"a/b")).unwrap());
296 assert_eq!(1, *map.inner.get(HgPath::new(b"a/b")).unwrap());
232
297
233 // but not its base's base, because it already existed
298 // but not its base's base, because it already existed
234 map.add_path(HgPath::new(b"a/b/c/")).unwrap();
299 map.add_path(HgPath::new(b"a/b/c/")).unwrap();
235 assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
300 assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
236 assert_eq!(2, *map.inner.get(HgPath::new(b"a/b")).unwrap());
301 assert_eq!(2, *map.inner.get(HgPath::new(b"a/b")).unwrap());
237
302
238 map.add_path(HgPath::new(b"a/c/")).unwrap();
303 map.add_path(HgPath::new(b"a/c/")).unwrap();
239 assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
304 assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
240
305
241 let expected = DirsMultiset {
306 let expected = DirsMultiset {
242 inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)]
307 inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)]
243 .iter()
308 .iter()
244 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
309 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
245 .collect(),
310 .collect(),
246 };
311 };
247 assert_eq!(map, expected);
312 assert_eq!(map, expected);
248 }
313 }
249
314
250 #[test]
315 #[test]
251 fn test_dirsmultiset_new_empty() {
316 fn test_dirsmultiset_new_empty() {
252 let manifest: Vec<HgPathBuf> = vec![];
317 let manifest: Vec<HgPathBuf> = vec![];
253 let new = DirsMultiset::from_manifest(&manifest).unwrap();
318 let new = DirsMultiset::from_manifest(&manifest).unwrap();
254 let expected = DirsMultiset {
319 let expected = DirsMultiset {
255 inner: FastHashMap::default(),
320 inner: FastHashMap::default(),
256 };
321 };
257 assert_eq!(expected, new);
322 assert_eq!(expected, new);
258
323
259 let new = DirsMultiset::from_dirstate(&FastHashMap::default(), None)
324 let new = DirsMultiset::from_dirstate(&FastHashMap::default(), None)
260 .unwrap();
325 .unwrap();
261 let expected = DirsMultiset {
326 let expected = DirsMultiset {
262 inner: FastHashMap::default(),
327 inner: FastHashMap::default(),
263 };
328 };
264 assert_eq!(expected, new);
329 assert_eq!(expected, new);
265 }
330 }
266
331
267 #[test]
332 #[test]
268 fn test_dirsmultiset_new_no_skip() {
333 fn test_dirsmultiset_new_no_skip() {
269 let input_vec: Vec<HgPathBuf> = ["a/", "b/", "a/c", "a/d/"]
334 let input_vec: Vec<HgPathBuf> = ["a/", "b/", "a/c", "a/d/"]
270 .iter()
335 .iter()
271 .map(|e| HgPathBuf::from_bytes(e.as_bytes()))
336 .map(|e| HgPathBuf::from_bytes(e.as_bytes()))
272 .collect();
337 .collect();
273 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
338 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
274 .iter()
339 .iter()
275 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
340 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
276 .collect();
341 .collect();
277
342
278 let new = DirsMultiset::from_manifest(&input_vec).unwrap();
343 let new = DirsMultiset::from_manifest(&input_vec).unwrap();
279 let expected = DirsMultiset {
344 let expected = DirsMultiset {
280 inner: expected_inner,
345 inner: expected_inner,
281 };
346 };
282 assert_eq!(expected, new);
347 assert_eq!(expected, new);
283
348
284 let input_map = ["a/", "b/", "a/c", "a/d/"]
349 let input_map = ["a/", "b/", "a/c", "a/d/"]
285 .iter()
350 .iter()
286 .map(|f| {
351 .map(|f| {
287 (
352 (
288 HgPathBuf::from_bytes(f.as_bytes()),
353 HgPathBuf::from_bytes(f.as_bytes()),
289 DirstateEntry {
354 DirstateEntry {
290 state: EntryState::Normal,
355 state: EntryState::Normal,
291 mode: 0,
356 mode: 0,
292 mtime: 0,
357 mtime: 0,
293 size: 0,
358 size: 0,
294 },
359 },
295 )
360 )
296 })
361 })
297 .collect();
362 .collect();
298 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
363 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
299 .iter()
364 .iter()
300 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
365 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
301 .collect();
366 .collect();
302
367
303 let new = DirsMultiset::from_dirstate(&input_map, None).unwrap();
368 let new = DirsMultiset::from_dirstate(&input_map, None).unwrap();
304 let expected = DirsMultiset {
369 let expected = DirsMultiset {
305 inner: expected_inner,
370 inner: expected_inner,
306 };
371 };
307 assert_eq!(expected, new);
372 assert_eq!(expected, new);
308 }
373 }
309
374
310 #[test]
375 #[test]
311 fn test_dirsmultiset_new_skip() {
376 fn test_dirsmultiset_new_skip() {
312 let input_map = [
377 let input_map = [
313 ("a/", EntryState::Normal),
378 ("a/", EntryState::Normal),
314 ("a/b/", EntryState::Normal),
379 ("a/b/", EntryState::Normal),
315 ("a/c", EntryState::Removed),
380 ("a/c", EntryState::Removed),
316 ("a/d/", EntryState::Merged),
381 ("a/d/", EntryState::Merged),
317 ]
382 ]
318 .iter()
383 .iter()
319 .map(|(f, state)| {
384 .map(|(f, state)| {
320 (
385 (
321 HgPathBuf::from_bytes(f.as_bytes()),
386 HgPathBuf::from_bytes(f.as_bytes()),
322 DirstateEntry {
387 DirstateEntry {
323 state: *state,
388 state: *state,
324 mode: 0,
389 mode: 0,
325 mtime: 0,
390 mtime: 0,
326 size: 0,
391 size: 0,
327 },
392 },
328 )
393 )
329 })
394 })
330 .collect();
395 .collect();
331
396
332 // "a" incremented with "a/c" and "a/d/"
397 // "a" incremented with "a/c" and "a/d/"
333 let expected_inner = [("", 1), ("a", 2), ("a/d", 1)]
398 let expected_inner = [("", 1), ("a", 2), ("a/d", 1)]
334 .iter()
399 .iter()
335 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
400 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
336 .collect();
401 .collect();
337
402
338 let new =
403 let new =
339 DirsMultiset::from_dirstate(&input_map, Some(EntryState::Normal))
404 DirsMultiset::from_dirstate(&input_map, Some(EntryState::Normal))
340 .unwrap();
405 .unwrap();
341 let expected = DirsMultiset {
406 let expected = DirsMultiset {
342 inner: expected_inner,
407 inner: expected_inner,
343 };
408 };
344 assert_eq!(expected, new);
409 assert_eq!(expected, new);
345 }
410 }
346 }
411 }
@@ -1,173 +1,238 b''
1 // files.rs
1 // files.rs
2 //
2 //
3 // Copyright 2019
3 // Copyright 2019
4 // Raphaël Gomès <rgomes@octobus.net>,
4 // Raphaël Gomès <rgomes@octobus.net>,
5 // Yuya Nishihara <yuya@tcha.org>
5 // Yuya Nishihara <yuya@tcha.org>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 //! Functions for fiddling with files.
10 //! Functions for fiddling with files.
11
11
12 use crate::utils::hg_path::{HgPath, HgPathBuf};
12 use crate::utils::hg_path::{HgPath, HgPathBuf};
13 use std::iter::FusedIterator;
14
13
15 use crate::utils::replace_slice;
14 use crate::utils::replace_slice;
16 use lazy_static::lazy_static;
15 use lazy_static::lazy_static;
17 use std::fs::Metadata;
16 use std::fs::Metadata;
17 use std::iter::FusedIterator;
18 use std::path::Path;
18 use std::path::Path;
19
19
20 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
20 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
21 let os_str;
21 let os_str;
22 #[cfg(unix)]
22 #[cfg(unix)]
23 {
23 {
24 use std::os::unix::ffi::OsStrExt;
24 use std::os::unix::ffi::OsStrExt;
25 os_str = std::ffi::OsStr::from_bytes(bytes);
25 os_str = std::ffi::OsStr::from_bytes(bytes);
26 }
26 }
27 // TODO Handle other platforms
27 // TODO Handle other platforms
28 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
28 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
29 // Perhaps, the return type would have to be Result<PathBuf>.
29 // Perhaps, the return type would have to be Result<PathBuf>.
30
30
31 Path::new(os_str)
31 Path::new(os_str)
32 }
32 }
33
33
34 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
34 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
35 // that's why Vec<u8> is returned.
35 // that's why Vec<u8> is returned.
36 #[cfg(unix)]
36 #[cfg(unix)]
37 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
37 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
38 use std::os::unix::ffi::OsStrExt;
38 use std::os::unix::ffi::OsStrExt;
39 path.as_ref().as_os_str().as_bytes().to_vec()
39 path.as_ref().as_os_str().as_bytes().to_vec()
40 }
40 }
41
41
42 /// An iterator over repository path yielding itself and its ancestors.
42 /// An iterator over repository path yielding itself and its ancestors.
43 #[derive(Copy, Clone, Debug)]
43 #[derive(Copy, Clone, Debug)]
44 pub struct Ancestors<'a> {
44 pub struct Ancestors<'a> {
45 next: Option<&'a HgPath>,
45 next: Option<&'a HgPath>,
46 }
46 }
47
47
48 impl<'a> Iterator for Ancestors<'a> {
48 impl<'a> Iterator for Ancestors<'a> {
49 type Item = &'a HgPath;
49 type Item = &'a HgPath;
50
50
51 fn next(&mut self) -> Option<Self::Item> {
51 fn next(&mut self) -> Option<Self::Item> {
52 let next = self.next;
52 let next = self.next;
53 self.next = match self.next {
53 self.next = match self.next {
54 Some(s) if s.is_empty() => None,
54 Some(s) if s.is_empty() => None,
55 Some(s) => {
55 Some(s) => {
56 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
56 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
57 Some(HgPath::new(&s.as_bytes()[..p]))
57 Some(HgPath::new(&s.as_bytes()[..p]))
58 }
58 }
59 None => None,
59 None => None,
60 };
60 };
61 next
61 next
62 }
62 }
63 }
63 }
64
64
65 impl<'a> FusedIterator for Ancestors<'a> {}
65 impl<'a> FusedIterator for Ancestors<'a> {}
66
66
67 /// An iterator over repository path yielding itself and its ancestors.
68 #[derive(Copy, Clone, Debug)]
69 pub(crate) struct AncestorsWithBase<'a> {
70 next: Option<(&'a HgPath, &'a HgPath)>,
71 }
72
73 impl<'a> Iterator for AncestorsWithBase<'a> {
74 type Item = (&'a HgPath, &'a HgPath);
75
76 fn next(&mut self) -> Option<Self::Item> {
77 let next = self.next;
78 self.next = match self.next {
79 Some((s, _)) if s.is_empty() => None,
80 Some((s, _)) => Some(s.split_filename()),
81 None => None,
82 };
83 next
84 }
85 }
86
87 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
88
67 /// Returns an iterator yielding ancestor directories of the given repository
89 /// Returns an iterator yielding ancestor directories of the given repository
68 /// path.
90 /// path.
69 ///
91 ///
70 /// The path is separated by '/', and must not start with '/'.
92 /// The path is separated by '/', and must not start with '/'.
71 ///
93 ///
72 /// The path itself isn't included unless it is b"" (meaning the root
94 /// The path itself isn't included unless it is b"" (meaning the root
73 /// directory.)
95 /// directory.)
74 pub fn find_dirs<'a>(path: &'a HgPath) -> Ancestors<'a> {
96 pub fn find_dirs<'a>(path: &'a HgPath) -> Ancestors<'a> {
75 let mut dirs = Ancestors { next: Some(path) };
97 let mut dirs = Ancestors { next: Some(path) };
76 if !path.is_empty() {
98 if !path.is_empty() {
77 dirs.next(); // skip itself
99 dirs.next(); // skip itself
78 }
100 }
79 dirs
101 dirs
80 }
102 }
81
103
104 /// Returns an iterator yielding ancestor directories of the given repository
105 /// path.
106 ///
107 /// The path is separated by '/', and must not start with '/'.
108 ///
109 /// The path itself isn't included unless it is b"" (meaning the root
110 /// directory.)
111 pub(crate) fn find_dirs_with_base<'a>(
112 path: &'a HgPath,
113 ) -> AncestorsWithBase<'a> {
114 let mut dirs = AncestorsWithBase {
115 next: Some((path, HgPath::new(b""))),
116 };
117 if !path.is_empty() {
118 dirs.next(); // skip itself
119 }
120 dirs
121 }
122
82 /// TODO more than ASCII?
123 /// TODO more than ASCII?
83 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
124 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
84 #[cfg(windows)] // NTFS compares via upper()
125 #[cfg(windows)] // NTFS compares via upper()
85 return path.to_ascii_uppercase();
126 return path.to_ascii_uppercase();
86 #[cfg(unix)]
127 #[cfg(unix)]
87 path.to_ascii_lowercase()
128 path.to_ascii_lowercase()
88 }
129 }
89
130
90 lazy_static! {
131 lazy_static! {
91 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
132 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
92 [
133 [
93 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
134 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
94 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
135 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
95 ]
136 ]
96 .iter()
137 .iter()
97 .map(|code| {
138 .map(|code| {
98 std::char::from_u32(*code)
139 std::char::from_u32(*code)
99 .unwrap()
140 .unwrap()
100 .encode_utf8(&mut [0; 3])
141 .encode_utf8(&mut [0; 3])
101 .bytes()
142 .bytes()
102 .collect()
143 .collect()
103 })
144 })
104 .collect()
145 .collect()
105 };
146 };
106 }
147 }
107
148
108 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
149 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
109 let mut buf = bytes.to_owned();
150 let mut buf = bytes.to_owned();
110 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
151 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
111 if needs_escaping {
152 if needs_escaping {
112 for forbidden in IGNORED_CHARS.iter() {
153 for forbidden in IGNORED_CHARS.iter() {
113 replace_slice(&mut buf, forbidden, &[])
154 replace_slice(&mut buf, forbidden, &[])
114 }
155 }
115 buf
156 buf
116 } else {
157 } else {
117 buf
158 buf
118 }
159 }
119 }
160 }
120
161
121 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
162 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
122 hfs_ignore_clean(&bytes.to_ascii_lowercase())
163 hfs_ignore_clean(&bytes.to_ascii_lowercase())
123 }
164 }
124
165
125 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
166 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
126 pub struct HgMetadata {
167 pub struct HgMetadata {
127 pub st_dev: u64,
168 pub st_dev: u64,
128 pub st_mode: u32,
169 pub st_mode: u32,
129 pub st_nlink: u64,
170 pub st_nlink: u64,
130 pub st_size: u64,
171 pub st_size: u64,
131 pub st_mtime: i64,
172 pub st_mtime: i64,
132 pub st_ctime: i64,
173 pub st_ctime: i64,
133 }
174 }
134
175
135 // TODO support other plaforms
176 // TODO support other plaforms
136 #[cfg(unix)]
177 #[cfg(unix)]
137 impl HgMetadata {
178 impl HgMetadata {
138 pub fn from_metadata(metadata: Metadata) -> Self {
179 pub fn from_metadata(metadata: Metadata) -> Self {
139 use std::os::unix::fs::MetadataExt;
180 use std::os::unix::fs::MetadataExt;
140 Self {
181 Self {
141 st_dev: metadata.dev(),
182 st_dev: metadata.dev(),
142 st_mode: metadata.mode(),
183 st_mode: metadata.mode(),
143 st_nlink: metadata.nlink(),
184 st_nlink: metadata.nlink(),
144 st_size: metadata.size(),
185 st_size: metadata.size(),
145 st_mtime: metadata.mtime(),
186 st_mtime: metadata.mtime(),
146 st_ctime: metadata.ctime(),
187 st_ctime: metadata.ctime(),
147 }
188 }
148 }
189 }
149 }
190 }
150
191
151 #[cfg(test)]
192 #[cfg(test)]
152 mod tests {
193 mod tests {
153 use super::*;
194 use super::*;
154
195
155 #[test]
196 #[test]
156 fn find_dirs_some() {
197 fn find_dirs_some() {
157 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
198 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
158 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
199 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
159 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
200 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
160 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
201 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
161 assert_eq!(dirs.next(), None);
202 assert_eq!(dirs.next(), None);
162 assert_eq!(dirs.next(), None);
203 assert_eq!(dirs.next(), None);
163 }
204 }
164
205
165 #[test]
206 #[test]
166 fn find_dirs_empty() {
207 fn find_dirs_empty() {
167 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
208 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
168 let mut dirs = super::find_dirs(HgPath::new(b""));
209 let mut dirs = super::find_dirs(HgPath::new(b""));
169 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
210 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
170 assert_eq!(dirs.next(), None);
211 assert_eq!(dirs.next(), None);
171 assert_eq!(dirs.next(), None);
212 assert_eq!(dirs.next(), None);
172 }
213 }
214
215 #[test]
216 fn test_find_dirs_with_base_some() {
217 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
218 assert_eq!(
219 dirs.next(),
220 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
221 );
222 assert_eq!(
223 dirs.next(),
224 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
225 );
226 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
227 assert_eq!(dirs.next(), None);
228 assert_eq!(dirs.next(), None);
229 }
230
231 #[test]
232 fn test_find_dirs_with_base_empty() {
233 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
234 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
235 assert_eq!(dirs.next(), None);
236 assert_eq!(dirs.next(), None);
237 }
173 }
238 }
@@ -1,745 +1,768 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 std::borrow::Borrow;
8 use std::borrow::Borrow;
9 use std::ffi::{OsStr, OsString};
9 use std::ffi::{OsStr, OsString};
10 use std::fmt;
10 use std::fmt;
11 use std::ops::Deref;
11 use std::ops::Deref;
12 use std::path::{Path, PathBuf};
12 use std::path::{Path, PathBuf};
13
13
14 #[derive(Debug, Eq, PartialEq)]
14 #[derive(Debug, Eq, PartialEq)]
15 pub enum HgPathError {
15 pub enum HgPathError {
16 /// Bytes from the invalid `HgPath`
16 /// Bytes from the invalid `HgPath`
17 LeadingSlash(Vec<u8>),
17 LeadingSlash(Vec<u8>),
18 ConsecutiveSlashes {
18 ConsecutiveSlashes {
19 bytes: Vec<u8>,
19 bytes: Vec<u8>,
20 second_slash_index: usize,
20 second_slash_index: usize,
21 },
21 },
22 ContainsNullByte {
22 ContainsNullByte {
23 bytes: Vec<u8>,
23 bytes: Vec<u8>,
24 null_byte_index: usize,
24 null_byte_index: usize,
25 },
25 },
26 /// Bytes
26 /// Bytes
27 DecodeError(Vec<u8>),
27 DecodeError(Vec<u8>),
28 /// The rest come from audit errors
28 /// The rest come from audit errors
29 EndsWithSlash(HgPathBuf),
29 EndsWithSlash(HgPathBuf),
30 ContainsIllegalComponent(HgPathBuf),
30 ContainsIllegalComponent(HgPathBuf),
31 /// Path is inside the `.hg` folder
31 /// Path is inside the `.hg` folder
32 InsideDotHg(HgPathBuf),
32 InsideDotHg(HgPathBuf),
33 IsInsideNestedRepo {
33 IsInsideNestedRepo {
34 path: HgPathBuf,
34 path: HgPathBuf,
35 nested_repo: HgPathBuf,
35 nested_repo: HgPathBuf,
36 },
36 },
37 TraversesSymbolicLink {
37 TraversesSymbolicLink {
38 path: HgPathBuf,
38 path: HgPathBuf,
39 symlink: HgPathBuf,
39 symlink: HgPathBuf,
40 },
40 },
41 NotFsCompliant(HgPathBuf),
41 NotFsCompliant(HgPathBuf),
42 /// `path` is the smallest invalid path
42 /// `path` is the smallest invalid path
43 NotUnderRoot {
43 NotUnderRoot {
44 path: PathBuf,
44 path: PathBuf,
45 root: PathBuf,
45 root: PathBuf,
46 },
46 },
47 }
47 }
48
48
49 impl ToString for HgPathError {
49 impl ToString for HgPathError {
50 fn to_string(&self) -> String {
50 fn to_string(&self) -> String {
51 match self {
51 match self {
52 HgPathError::LeadingSlash(bytes) => {
52 HgPathError::LeadingSlash(bytes) => {
53 format!("Invalid HgPath '{:?}': has a leading slash.", bytes)
53 format!("Invalid HgPath '{:?}': has a leading slash.", bytes)
54 }
54 }
55 HgPathError::ConsecutiveSlashes {
55 HgPathError::ConsecutiveSlashes {
56 bytes,
56 bytes,
57 second_slash_index: pos,
57 second_slash_index: pos,
58 } => format!(
58 } => format!(
59 "Invalid HgPath '{:?}': consecutive slashes at pos {}.",
59 "Invalid HgPath '{:?}': consecutive slashes at pos {}.",
60 bytes, pos
60 bytes, pos
61 ),
61 ),
62 HgPathError::ContainsNullByte {
62 HgPathError::ContainsNullByte {
63 bytes,
63 bytes,
64 null_byte_index: pos,
64 null_byte_index: pos,
65 } => format!(
65 } => format!(
66 "Invalid HgPath '{:?}': contains null byte at pos {}.",
66 "Invalid HgPath '{:?}': contains null byte at pos {}.",
67 bytes, pos
67 bytes, pos
68 ),
68 ),
69 HgPathError::DecodeError(bytes) => {
69 HgPathError::DecodeError(bytes) => {
70 format!("Invalid HgPath '{:?}': could not be decoded.", bytes)
70 format!("Invalid HgPath '{:?}': could not be decoded.", bytes)
71 }
71 }
72 HgPathError::EndsWithSlash(path) => {
72 HgPathError::EndsWithSlash(path) => {
73 format!("Audit failed for '{}': ends with a slash.", path)
73 format!("Audit failed for '{}': ends with a slash.", path)
74 }
74 }
75 HgPathError::ContainsIllegalComponent(path) => format!(
75 HgPathError::ContainsIllegalComponent(path) => format!(
76 "Audit failed for '{}': contains an illegal component.",
76 "Audit failed for '{}': contains an illegal component.",
77 path
77 path
78 ),
78 ),
79 HgPathError::InsideDotHg(path) => format!(
79 HgPathError::InsideDotHg(path) => format!(
80 "Audit failed for '{}': is inside the '.hg' folder.",
80 "Audit failed for '{}': is inside the '.hg' folder.",
81 path
81 path
82 ),
82 ),
83 HgPathError::IsInsideNestedRepo {
83 HgPathError::IsInsideNestedRepo {
84 path,
84 path,
85 nested_repo: nested,
85 nested_repo: nested,
86 } => format!(
86 } => format!(
87 "Audit failed for '{}': is inside a nested repository '{}'.",
87 "Audit failed for '{}': is inside a nested repository '{}'.",
88 path, nested
88 path, nested
89 ),
89 ),
90 HgPathError::TraversesSymbolicLink { path, symlink } => format!(
90 HgPathError::TraversesSymbolicLink { path, symlink } => format!(
91 "Audit failed for '{}': traverses symbolic link '{}'.",
91 "Audit failed for '{}': traverses symbolic link '{}'.",
92 path, symlink
92 path, symlink
93 ),
93 ),
94 HgPathError::NotFsCompliant(path) => format!(
94 HgPathError::NotFsCompliant(path) => format!(
95 "Audit failed for '{}': cannot be turned into a \
95 "Audit failed for '{}': cannot be turned into a \
96 filesystem path.",
96 filesystem path.",
97 path
97 path
98 ),
98 ),
99 HgPathError::NotUnderRoot { path, root } => format!(
99 HgPathError::NotUnderRoot { path, root } => format!(
100 "Audit failed for '{}': not under root {}.",
100 "Audit failed for '{}': not under root {}.",
101 path.display(),
101 path.display(),
102 root.display()
102 root.display()
103 ),
103 ),
104 }
104 }
105 }
105 }
106 }
106 }
107
107
108 impl From<HgPathError> for std::io::Error {
108 impl From<HgPathError> for std::io::Error {
109 fn from(e: HgPathError) -> Self {
109 fn from(e: HgPathError) -> Self {
110 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
110 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
111 }
111 }
112 }
112 }
113
113
114 /// This is a repository-relative path (or canonical path):
114 /// This is a repository-relative path (or canonical path):
115 /// - no null characters
115 /// - no null characters
116 /// - `/` separates directories
116 /// - `/` separates directories
117 /// - no consecutive slashes
117 /// - no consecutive slashes
118 /// - no leading slash,
118 /// - no leading slash,
119 /// - no `.` nor `..` of special meaning
119 /// - no `.` nor `..` of special meaning
120 /// - stored in repository and shared across platforms
120 /// - stored in repository and shared across platforms
121 ///
121 ///
122 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
122 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
123 /// in its lifetime for performance reasons and to ease ergonomics. It is
123 /// in its lifetime for performance reasons and to ease ergonomics. It is
124 /// however checked using the `check_state` method before any file-system
124 /// however checked using the `check_state` method before any file-system
125 /// operation.
125 /// operation.
126 ///
126 ///
127 /// This allows us to be encoding-transparent as much as possible, until really
127 /// This allows us to be encoding-transparent as much as possible, until really
128 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
128 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
129 /// or `Path`) whenever more complex operations are needed:
129 /// or `Path`) whenever more complex operations are needed:
130 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
130 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
131 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
131 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
132 /// character encoding will be determined on a per-repository basis.
132 /// character encoding will be determined on a per-repository basis.
133 //
133 //
134 // FIXME: (adapted from a comment in the stdlib)
134 // FIXME: (adapted from a comment in the stdlib)
135 // `HgPath::new()` current implementation relies on `Slice` being
135 // `HgPath::new()` current implementation relies on `Slice` being
136 // layout-compatible with `[u8]`.
136 // layout-compatible with `[u8]`.
137 // When attribute privacy is implemented, `Slice` should be annotated as
137 // When attribute privacy is implemented, `Slice` should be annotated as
138 // `#[repr(transparent)]`.
138 // `#[repr(transparent)]`.
139 // Anyway, `Slice` representation and layout are considered implementation
139 // Anyway, `Slice` representation and layout are considered implementation
140 // detail, are not documented and must not be relied upon.
140 // detail, are not documented and must not be relied upon.
141 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
141 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
142 pub struct HgPath {
142 pub struct HgPath {
143 inner: [u8],
143 inner: [u8],
144 }
144 }
145
145
146 impl HgPath {
146 impl HgPath {
147 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
147 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
148 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
148 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
149 }
149 }
150 pub fn is_empty(&self) -> bool {
150 pub fn is_empty(&self) -> bool {
151 self.inner.is_empty()
151 self.inner.is_empty()
152 }
152 }
153 pub fn len(&self) -> usize {
153 pub fn len(&self) -> usize {
154 self.inner.len()
154 self.inner.len()
155 }
155 }
156 fn to_hg_path_buf(&self) -> HgPathBuf {
156 fn to_hg_path_buf(&self) -> HgPathBuf {
157 HgPathBuf {
157 HgPathBuf {
158 inner: self.inner.to_owned(),
158 inner: self.inner.to_owned(),
159 }
159 }
160 }
160 }
161 pub fn bytes(&self) -> std::slice::Iter<u8> {
161 pub fn bytes(&self) -> std::slice::Iter<u8> {
162 self.inner.iter()
162 self.inner.iter()
163 }
163 }
164 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
164 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
165 HgPathBuf::from(self.inner.to_ascii_uppercase())
165 HgPathBuf::from(self.inner.to_ascii_uppercase())
166 }
166 }
167 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
167 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
168 HgPathBuf::from(self.inner.to_ascii_lowercase())
168 HgPathBuf::from(self.inner.to_ascii_lowercase())
169 }
169 }
170 pub fn as_bytes(&self) -> &[u8] {
170 pub fn as_bytes(&self) -> &[u8] {
171 &self.inner
171 &self.inner
172 }
172 }
173 pub fn contains(&self, other: u8) -> bool {
173 pub fn contains(&self, other: u8) -> bool {
174 self.inner.contains(&other)
174 self.inner.contains(&other)
175 }
175 }
176 pub fn starts_with(&self, needle: impl AsRef<Self>) -> bool {
176 pub fn starts_with(&self, needle: impl AsRef<Self>) -> bool {
177 self.inner.starts_with(needle.as_ref().as_bytes())
177 self.inner.starts_with(needle.as_ref().as_bytes())
178 }
178 }
179 pub fn trim_trailing_slash(&self) -> &Self {
179 pub fn trim_trailing_slash(&self) -> &Self {
180 Self::new(if self.inner.last() == Some(&b'/') {
180 Self::new(if self.inner.last() == Some(&b'/') {
181 &self.inner[..self.inner.len() - 1]
181 &self.inner[..self.inner.len() - 1]
182 } else {
182 } else {
183 &self.inner[..]
183 &self.inner[..]
184 })
184 })
185 }
185 }
186 /// Returns a tuple of slices `(base, filename)` resulting from the split
187 /// at the rightmost `/`, if any.
188 ///
189 /// # Examples:
190 ///
191 /// ```
192 /// use hg::utils::hg_path::HgPath;
193 ///
194 /// let path = HgPath::new(b"cool/hg/path").split_filename();
195 /// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path")));
196 ///
197 /// let path = HgPath::new(b"pathwithoutsep").split_filename();
198 /// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep")));
199 /// ```
200 pub fn split_filename(&self) -> (&Self, &Self) {
201 match &self.inner.iter().rposition(|c| *c == b'/') {
202 None => (HgPath::new(""), &self),
203 Some(size) => (
204 HgPath::new(&self.inner[..*size]),
205 HgPath::new(&self.inner[*size + 1..]),
206 ),
207 }
208 }
186 pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf {
209 pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf {
187 let mut inner = self.inner.to_owned();
210 let mut inner = self.inner.to_owned();
188 if inner.len() != 0 && inner.last() != Some(&b'/') {
211 if inner.len() != 0 && inner.last() != Some(&b'/') {
189 inner.push(b'/');
212 inner.push(b'/');
190 }
213 }
191 inner.extend(other.as_ref().bytes());
214 inner.extend(other.as_ref().bytes());
192 HgPathBuf::from_bytes(&inner)
215 HgPathBuf::from_bytes(&inner)
193 }
216 }
194 pub fn parent(&self) -> &Self {
217 pub fn parent(&self) -> &Self {
195 let inner = self.as_bytes();
218 let inner = self.as_bytes();
196 HgPath::new(match inner.iter().rposition(|b| *b == b'/') {
219 HgPath::new(match inner.iter().rposition(|b| *b == b'/') {
197 Some(pos) => &inner[..pos],
220 Some(pos) => &inner[..pos],
198 None => &[],
221 None => &[],
199 })
222 })
200 }
223 }
201 /// Given a base directory, returns the slice of `self` relative to the
224 /// Given a base directory, returns the slice of `self` relative to the
202 /// base directory. If `base` is not a directory (does not end with a
225 /// base directory. If `base` is not a directory (does not end with a
203 /// `b'/'`), returns `None`.
226 /// `b'/'`), returns `None`.
204 pub fn relative_to(&self, base: impl AsRef<Self>) -> Option<&Self> {
227 pub fn relative_to(&self, base: impl AsRef<Self>) -> Option<&Self> {
205 let base = base.as_ref();
228 let base = base.as_ref();
206 if base.is_empty() {
229 if base.is_empty() {
207 return Some(self);
230 return Some(self);
208 }
231 }
209 let is_dir = base.as_bytes().ends_with(b"/");
232 let is_dir = base.as_bytes().ends_with(b"/");
210 if is_dir && self.starts_with(base) {
233 if is_dir && self.starts_with(base) {
211 Some(Self::new(&self.inner[base.len()..]))
234 Some(Self::new(&self.inner[base.len()..]))
212 } else {
235 } else {
213 None
236 None
214 }
237 }
215 }
238 }
216
239
217 #[cfg(windows)]
240 #[cfg(windows)]
218 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
241 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
219 ///
242 ///
220 /// Split a pathname into drive/UNC sharepoint and relative path
243 /// Split a pathname into drive/UNC sharepoint and relative path
221 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
244 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
222 /// be empty.
245 /// be empty.
223 ///
246 ///
224 /// If you assign
247 /// If you assign
225 /// result = split_drive(p)
248 /// result = split_drive(p)
226 /// It is always true that:
249 /// It is always true that:
227 /// result[0] + result[1] == p
250 /// result[0] + result[1] == p
228 ///
251 ///
229 /// If the path contained a drive letter, drive_or_unc will contain
252 /// If the path contained a drive letter, drive_or_unc will contain
230 /// everything up to and including the colon.
253 /// everything up to and including the colon.
231 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
254 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
232 ///
255 ///
233 /// If the path contained a UNC path, the drive_or_unc will contain the
256 /// If the path contained a UNC path, the drive_or_unc will contain the
234 /// host name and share up to but not including the fourth directory
257 /// host name and share up to but not including the fourth directory
235 /// separator character.
258 /// separator character.
236 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
259 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
237 /// "/dir")
260 /// "/dir")
238 ///
261 ///
239 /// Paths cannot contain both a drive letter and a UNC path.
262 /// Paths cannot contain both a drive letter and a UNC path.
240 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
263 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
241 let bytes = self.as_bytes();
264 let bytes = self.as_bytes();
242 let is_sep = |b| std::path::is_separator(b as char);
265 let is_sep = |b| std::path::is_separator(b as char);
243
266
244 if self.len() < 2 {
267 if self.len() < 2 {
245 (HgPath::new(b""), &self)
268 (HgPath::new(b""), &self)
246 } else if is_sep(bytes[0])
269 } else if is_sep(bytes[0])
247 && is_sep(bytes[1])
270 && is_sep(bytes[1])
248 && (self.len() == 2 || !is_sep(bytes[2]))
271 && (self.len() == 2 || !is_sep(bytes[2]))
249 {
272 {
250 // Is a UNC path:
273 // Is a UNC path:
251 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
274 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
252 // \\machine\mountpoint\directory\etc\...
275 // \\machine\mountpoint\directory\etc\...
253 // directory ^^^^^^^^^^^^^^^
276 // directory ^^^^^^^^^^^^^^^
254
277
255 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
278 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
256 let mountpoint_start_index = if let Some(i) = machine_end_index {
279 let mountpoint_start_index = if let Some(i) = machine_end_index {
257 i + 2
280 i + 2
258 } else {
281 } else {
259 return (HgPath::new(b""), &self);
282 return (HgPath::new(b""), &self);
260 };
283 };
261
284
262 match bytes[mountpoint_start_index + 1..]
285 match bytes[mountpoint_start_index + 1..]
263 .iter()
286 .iter()
264 .position(|b| is_sep(*b))
287 .position(|b| is_sep(*b))
265 {
288 {
266 // A UNC path can't have two slashes in a row
289 // A UNC path can't have two slashes in a row
267 // (after the initial two)
290 // (after the initial two)
268 Some(0) => (HgPath::new(b""), &self),
291 Some(0) => (HgPath::new(b""), &self),
269 Some(i) => {
292 Some(i) => {
270 let (a, b) =
293 let (a, b) =
271 bytes.split_at(mountpoint_start_index + 1 + i);
294 bytes.split_at(mountpoint_start_index + 1 + i);
272 (HgPath::new(a), HgPath::new(b))
295 (HgPath::new(a), HgPath::new(b))
273 }
296 }
274 None => (&self, HgPath::new(b"")),
297 None => (&self, HgPath::new(b"")),
275 }
298 }
276 } else if bytes[1] == b':' {
299 } else if bytes[1] == b':' {
277 // Drive path c:\directory
300 // Drive path c:\directory
278 let (a, b) = bytes.split_at(2);
301 let (a, b) = bytes.split_at(2);
279 (HgPath::new(a), HgPath::new(b))
302 (HgPath::new(a), HgPath::new(b))
280 } else {
303 } else {
281 (HgPath::new(b""), &self)
304 (HgPath::new(b""), &self)
282 }
305 }
283 }
306 }
284
307
285 #[cfg(unix)]
308 #[cfg(unix)]
286 /// Split a pathname into drive and path. On Posix, drive is always empty.
309 /// Split a pathname into drive and path. On Posix, drive is always empty.
287 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
310 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
288 (HgPath::new(b""), &self)
311 (HgPath::new(b""), &self)
289 }
312 }
290
313
291 /// Checks for errors in the path, short-circuiting at the first one.
314 /// Checks for errors in the path, short-circuiting at the first one.
292 /// This generates fine-grained errors useful for debugging.
315 /// This generates fine-grained errors useful for debugging.
293 /// To simply check if the path is valid during tests, use `is_valid`.
316 /// To simply check if the path is valid during tests, use `is_valid`.
294 pub fn check_state(&self) -> Result<(), HgPathError> {
317 pub fn check_state(&self) -> Result<(), HgPathError> {
295 if self.len() == 0 {
318 if self.len() == 0 {
296 return Ok(());
319 return Ok(());
297 }
320 }
298 let bytes = self.as_bytes();
321 let bytes = self.as_bytes();
299 let mut previous_byte = None;
322 let mut previous_byte = None;
300
323
301 if bytes[0] == b'/' {
324 if bytes[0] == b'/' {
302 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
325 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
303 }
326 }
304 for (index, byte) in bytes.iter().enumerate() {
327 for (index, byte) in bytes.iter().enumerate() {
305 match byte {
328 match byte {
306 0 => {
329 0 => {
307 return Err(HgPathError::ContainsNullByte {
330 return Err(HgPathError::ContainsNullByte {
308 bytes: bytes.to_vec(),
331 bytes: bytes.to_vec(),
309 null_byte_index: index,
332 null_byte_index: index,
310 })
333 })
311 }
334 }
312 b'/' => {
335 b'/' => {
313 if previous_byte.is_some() && previous_byte == Some(b'/') {
336 if previous_byte.is_some() && previous_byte == Some(b'/') {
314 return Err(HgPathError::ConsecutiveSlashes {
337 return Err(HgPathError::ConsecutiveSlashes {
315 bytes: bytes.to_vec(),
338 bytes: bytes.to_vec(),
316 second_slash_index: index,
339 second_slash_index: index,
317 });
340 });
318 }
341 }
319 }
342 }
320 _ => (),
343 _ => (),
321 };
344 };
322 previous_byte = Some(*byte);
345 previous_byte = Some(*byte);
323 }
346 }
324 Ok(())
347 Ok(())
325 }
348 }
326
349
327 #[cfg(test)]
350 #[cfg(test)]
328 /// Only usable during tests to force developers to handle invalid states
351 /// Only usable during tests to force developers to handle invalid states
329 fn is_valid(&self) -> bool {
352 fn is_valid(&self) -> bool {
330 self.check_state().is_ok()
353 self.check_state().is_ok()
331 }
354 }
332 }
355 }
333
356
334 impl fmt::Debug for HgPath {
357 impl fmt::Debug for HgPath {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
359 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
337 }
360 }
338 }
361 }
339
362
340 impl fmt::Display for HgPath {
363 impl fmt::Display for HgPath {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 write!(f, "{}", String::from_utf8_lossy(&self.inner))
365 write!(f, "{}", String::from_utf8_lossy(&self.inner))
343 }
366 }
344 }
367 }
345
368
346 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
369 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
347 pub struct HgPathBuf {
370 pub struct HgPathBuf {
348 inner: Vec<u8>,
371 inner: Vec<u8>,
349 }
372 }
350
373
351 impl HgPathBuf {
374 impl HgPathBuf {
352 pub fn new() -> Self {
375 pub fn new() -> Self {
353 Self { inner: Vec::new() }
376 Self { inner: Vec::new() }
354 }
377 }
355 pub fn push(&mut self, byte: u8) {
378 pub fn push(&mut self, byte: u8) {
356 self.inner.push(byte);
379 self.inner.push(byte);
357 }
380 }
358 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
381 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
359 HgPath::new(s).to_owned()
382 HgPath::new(s).to_owned()
360 }
383 }
361 pub fn into_vec(self) -> Vec<u8> {
384 pub fn into_vec(self) -> Vec<u8> {
362 self.inner
385 self.inner
363 }
386 }
364 pub fn as_ref(&self) -> &[u8] {
387 pub fn as_ref(&self) -> &[u8] {
365 self.inner.as_ref()
388 self.inner.as_ref()
366 }
389 }
367 }
390 }
368
391
369 impl fmt::Debug for HgPathBuf {
392 impl fmt::Debug for HgPathBuf {
370 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
394 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
372 }
395 }
373 }
396 }
374
397
375 impl fmt::Display for HgPathBuf {
398 impl fmt::Display for HgPathBuf {
376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 write!(f, "{}", String::from_utf8_lossy(&self.inner))
400 write!(f, "{}", String::from_utf8_lossy(&self.inner))
378 }
401 }
379 }
402 }
380
403
381 impl Deref for HgPathBuf {
404 impl Deref for HgPathBuf {
382 type Target = HgPath;
405 type Target = HgPath;
383
406
384 #[inline]
407 #[inline]
385 fn deref(&self) -> &HgPath {
408 fn deref(&self) -> &HgPath {
386 &HgPath::new(&self.inner)
409 &HgPath::new(&self.inner)
387 }
410 }
388 }
411 }
389
412
390 impl From<Vec<u8>> for HgPathBuf {
413 impl From<Vec<u8>> for HgPathBuf {
391 fn from(vec: Vec<u8>) -> Self {
414 fn from(vec: Vec<u8>) -> Self {
392 Self { inner: vec }
415 Self { inner: vec }
393 }
416 }
394 }
417 }
395
418
396 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
419 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
397 fn from(s: &T) -> HgPathBuf {
420 fn from(s: &T) -> HgPathBuf {
398 s.as_ref().to_owned()
421 s.as_ref().to_owned()
399 }
422 }
400 }
423 }
401
424
402 impl Into<Vec<u8>> for HgPathBuf {
425 impl Into<Vec<u8>> for HgPathBuf {
403 fn into(self) -> Vec<u8> {
426 fn into(self) -> Vec<u8> {
404 self.inner
427 self.inner
405 }
428 }
406 }
429 }
407
430
408 impl Borrow<HgPath> for HgPathBuf {
431 impl Borrow<HgPath> for HgPathBuf {
409 fn borrow(&self) -> &HgPath {
432 fn borrow(&self) -> &HgPath {
410 &HgPath::new(self.as_bytes())
433 &HgPath::new(self.as_bytes())
411 }
434 }
412 }
435 }
413
436
414 impl ToOwned for HgPath {
437 impl ToOwned for HgPath {
415 type Owned = HgPathBuf;
438 type Owned = HgPathBuf;
416
439
417 fn to_owned(&self) -> HgPathBuf {
440 fn to_owned(&self) -> HgPathBuf {
418 self.to_hg_path_buf()
441 self.to_hg_path_buf()
419 }
442 }
420 }
443 }
421
444
422 impl AsRef<HgPath> for HgPath {
445 impl AsRef<HgPath> for HgPath {
423 fn as_ref(&self) -> &HgPath {
446 fn as_ref(&self) -> &HgPath {
424 self
447 self
425 }
448 }
426 }
449 }
427
450
428 impl AsRef<HgPath> for HgPathBuf {
451 impl AsRef<HgPath> for HgPathBuf {
429 fn as_ref(&self) -> &HgPath {
452 fn as_ref(&self) -> &HgPath {
430 self
453 self
431 }
454 }
432 }
455 }
433
456
434 impl Extend<u8> for HgPathBuf {
457 impl Extend<u8> for HgPathBuf {
435 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
458 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
436 self.inner.extend(iter);
459 self.inner.extend(iter);
437 }
460 }
438 }
461 }
439
462
440 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
463 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
441 /// implemented, these conversion utils will have to work differently depending
464 /// implemented, these conversion utils will have to work differently depending
442 /// on the repository encoding: either `UTF-8` or `MBCS`.
465 /// on the repository encoding: either `UTF-8` or `MBCS`.
443
466
444 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
467 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
445 hg_path: P,
468 hg_path: P,
446 ) -> Result<OsString, HgPathError> {
469 ) -> Result<OsString, HgPathError> {
447 hg_path.as_ref().check_state()?;
470 hg_path.as_ref().check_state()?;
448 let os_str;
471 let os_str;
449 #[cfg(unix)]
472 #[cfg(unix)]
450 {
473 {
451 use std::os::unix::ffi::OsStrExt;
474 use std::os::unix::ffi::OsStrExt;
452 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
475 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
453 }
476 }
454 // TODO Handle other platforms
477 // TODO Handle other platforms
455 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
478 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
456 Ok(os_str.to_os_string())
479 Ok(os_str.to_os_string())
457 }
480 }
458
481
459 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
482 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
460 hg_path: P,
483 hg_path: P,
461 ) -> Result<PathBuf, HgPathError> {
484 ) -> Result<PathBuf, HgPathError> {
462 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
485 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
463 }
486 }
464
487
465 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
488 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
466 os_string: S,
489 os_string: S,
467 ) -> Result<HgPathBuf, HgPathError> {
490 ) -> Result<HgPathBuf, HgPathError> {
468 let buf;
491 let buf;
469 #[cfg(unix)]
492 #[cfg(unix)]
470 {
493 {
471 use std::os::unix::ffi::OsStrExt;
494 use std::os::unix::ffi::OsStrExt;
472 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
495 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
473 }
496 }
474 // TODO Handle other platforms
497 // TODO Handle other platforms
475 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
498 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
476
499
477 buf.check_state()?;
500 buf.check_state()?;
478 Ok(buf)
501 Ok(buf)
479 }
502 }
480
503
481 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
504 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
482 path: P,
505 path: P,
483 ) -> Result<HgPathBuf, HgPathError> {
506 ) -> Result<HgPathBuf, HgPathError> {
484 let buf;
507 let buf;
485 let os_str = path.as_ref().as_os_str();
508 let os_str = path.as_ref().as_os_str();
486 #[cfg(unix)]
509 #[cfg(unix)]
487 {
510 {
488 use std::os::unix::ffi::OsStrExt;
511 use std::os::unix::ffi::OsStrExt;
489 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
512 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
490 }
513 }
491 // TODO Handle other platforms
514 // TODO Handle other platforms
492 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
515 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
493
516
494 buf.check_state()?;
517 buf.check_state()?;
495 Ok(buf)
518 Ok(buf)
496 }
519 }
497
520
498 #[cfg(test)]
521 #[cfg(test)]
499 mod tests {
522 mod tests {
500 use super::*;
523 use super::*;
501 use pretty_assertions::assert_eq;
524 use pretty_assertions::assert_eq;
502
525
503 #[test]
526 #[test]
504 fn test_path_states() {
527 fn test_path_states() {
505 assert_eq!(
528 assert_eq!(
506 Err(HgPathError::LeadingSlash(b"/".to_vec())),
529 Err(HgPathError::LeadingSlash(b"/".to_vec())),
507 HgPath::new(b"/").check_state()
530 HgPath::new(b"/").check_state()
508 );
531 );
509 assert_eq!(
532 assert_eq!(
510 Err(HgPathError::ConsecutiveSlashes {
533 Err(HgPathError::ConsecutiveSlashes {
511 bytes: b"a/b//c".to_vec(),
534 bytes: b"a/b//c".to_vec(),
512 second_slash_index: 4
535 second_slash_index: 4
513 }),
536 }),
514 HgPath::new(b"a/b//c").check_state()
537 HgPath::new(b"a/b//c").check_state()
515 );
538 );
516 assert_eq!(
539 assert_eq!(
517 Err(HgPathError::ContainsNullByte {
540 Err(HgPathError::ContainsNullByte {
518 bytes: b"a/b/\0c".to_vec(),
541 bytes: b"a/b/\0c".to_vec(),
519 null_byte_index: 4
542 null_byte_index: 4
520 }),
543 }),
521 HgPath::new(b"a/b/\0c").check_state()
544 HgPath::new(b"a/b/\0c").check_state()
522 );
545 );
523 // TODO test HgPathError::DecodeError for the Windows implementation.
546 // TODO test HgPathError::DecodeError for the Windows implementation.
524 assert_eq!(true, HgPath::new(b"").is_valid());
547 assert_eq!(true, HgPath::new(b"").is_valid());
525 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
548 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
526 // Backslashes in paths are not significant, but allowed
549 // Backslashes in paths are not significant, but allowed
527 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
550 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
528 // Dots in paths are not significant, but allowed
551 // Dots in paths are not significant, but allowed
529 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
552 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
530 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
553 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
531 }
554 }
532
555
533 #[test]
556 #[test]
534 fn test_iter() {
557 fn test_iter() {
535 let path = HgPath::new(b"a");
558 let path = HgPath::new(b"a");
536 let mut iter = path.bytes();
559 let mut iter = path.bytes();
537 assert_eq!(Some(&b'a'), iter.next());
560 assert_eq!(Some(&b'a'), iter.next());
538 assert_eq!(None, iter.next_back());
561 assert_eq!(None, iter.next_back());
539 assert_eq!(None, iter.next());
562 assert_eq!(None, iter.next());
540
563
541 let path = HgPath::new(b"a");
564 let path = HgPath::new(b"a");
542 let mut iter = path.bytes();
565 let mut iter = path.bytes();
543 assert_eq!(Some(&b'a'), iter.next_back());
566 assert_eq!(Some(&b'a'), iter.next_back());
544 assert_eq!(None, iter.next_back());
567 assert_eq!(None, iter.next_back());
545 assert_eq!(None, iter.next());
568 assert_eq!(None, iter.next());
546
569
547 let path = HgPath::new(b"abc");
570 let path = HgPath::new(b"abc");
548 let mut iter = path.bytes();
571 let mut iter = path.bytes();
549 assert_eq!(Some(&b'a'), iter.next());
572 assert_eq!(Some(&b'a'), iter.next());
550 assert_eq!(Some(&b'c'), iter.next_back());
573 assert_eq!(Some(&b'c'), iter.next_back());
551 assert_eq!(Some(&b'b'), iter.next_back());
574 assert_eq!(Some(&b'b'), iter.next_back());
552 assert_eq!(None, iter.next_back());
575 assert_eq!(None, iter.next_back());
553 assert_eq!(None, iter.next());
576 assert_eq!(None, iter.next());
554
577
555 let path = HgPath::new(b"abc");
578 let path = HgPath::new(b"abc");
556 let mut iter = path.bytes();
579 let mut iter = path.bytes();
557 assert_eq!(Some(&b'a'), iter.next());
580 assert_eq!(Some(&b'a'), iter.next());
558 assert_eq!(Some(&b'b'), iter.next());
581 assert_eq!(Some(&b'b'), iter.next());
559 assert_eq!(Some(&b'c'), iter.next());
582 assert_eq!(Some(&b'c'), iter.next());
560 assert_eq!(None, iter.next_back());
583 assert_eq!(None, iter.next_back());
561 assert_eq!(None, iter.next());
584 assert_eq!(None, iter.next());
562
585
563 let path = HgPath::new(b"abc");
586 let path = HgPath::new(b"abc");
564 let iter = path.bytes();
587 let iter = path.bytes();
565 let mut vec = Vec::new();
588 let mut vec = Vec::new();
566 vec.extend(iter);
589 vec.extend(iter);
567 assert_eq!(vec![b'a', b'b', b'c'], vec);
590 assert_eq!(vec![b'a', b'b', b'c'], vec);
568
591
569 let path = HgPath::new(b"abc");
592 let path = HgPath::new(b"abc");
570 let mut iter = path.bytes();
593 let mut iter = path.bytes();
571 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
594 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
572
595
573 let path = HgPath::new(b"abc");
596 let path = HgPath::new(b"abc");
574 let mut iter = path.bytes();
597 let mut iter = path.bytes();
575 assert_eq!(None, iter.rposition(|c| *c == b'd'));
598 assert_eq!(None, iter.rposition(|c| *c == b'd'));
576 }
599 }
577
600
578 #[test]
601 #[test]
579 fn test_join() {
602 fn test_join() {
580 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
603 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
581 assert_eq!(b"a/b", path.as_bytes());
604 assert_eq!(b"a/b", path.as_bytes());
582
605
583 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
606 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
584 assert_eq!(b"a/b/c", path.as_bytes());
607 assert_eq!(b"a/b/c", path.as_bytes());
585
608
586 // No leading slash if empty before join
609 // No leading slash if empty before join
587 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
610 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
588 assert_eq!(b"b/c", path.as_bytes());
611 assert_eq!(b"b/c", path.as_bytes());
589
612
590 // The leading slash is an invalid representation of an `HgPath`, but
613 // The leading slash is an invalid representation of an `HgPath`, but
591 // it can happen. This creates another invalid representation of
614 // it can happen. This creates another invalid representation of
592 // consecutive bytes.
615 // consecutive bytes.
593 // TODO What should be done in this case? Should we silently remove
616 // TODO What should be done in this case? Should we silently remove
594 // the extra slash? Should we change the signature to a problematic
617 // the extra slash? Should we change the signature to a problematic
595 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
618 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
596 // let the error happen upon filesystem interaction?
619 // let the error happen upon filesystem interaction?
597 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
620 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
598 assert_eq!(b"a//b", path.as_bytes());
621 assert_eq!(b"a//b", path.as_bytes());
599 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
622 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
600 assert_eq!(b"a//b", path.as_bytes());
623 assert_eq!(b"a//b", path.as_bytes());
601 }
624 }
602
625
603 #[test]
626 #[test]
604 fn test_relative_to() {
627 fn test_relative_to() {
605 let path = HgPath::new(b"");
628 let path = HgPath::new(b"");
606 let base = HgPath::new(b"");
629 let base = HgPath::new(b"");
607 assert_eq!(Some(path), path.relative_to(base));
630 assert_eq!(Some(path), path.relative_to(base));
608
631
609 let path = HgPath::new(b"path");
632 let path = HgPath::new(b"path");
610 let base = HgPath::new(b"");
633 let base = HgPath::new(b"");
611 assert_eq!(Some(path), path.relative_to(base));
634 assert_eq!(Some(path), path.relative_to(base));
612
635
613 let path = HgPath::new(b"a");
636 let path = HgPath::new(b"a");
614 let base = HgPath::new(b"b");
637 let base = HgPath::new(b"b");
615 assert_eq!(None, path.relative_to(base));
638 assert_eq!(None, path.relative_to(base));
616
639
617 let path = HgPath::new(b"a/b");
640 let path = HgPath::new(b"a/b");
618 let base = HgPath::new(b"a");
641 let base = HgPath::new(b"a");
619 assert_eq!(None, path.relative_to(base));
642 assert_eq!(None, path.relative_to(base));
620
643
621 let path = HgPath::new(b"a/b");
644 let path = HgPath::new(b"a/b");
622 let base = HgPath::new(b"a/");
645 let base = HgPath::new(b"a/");
623 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
646 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
624
647
625 let path = HgPath::new(b"nested/path/to/b");
648 let path = HgPath::new(b"nested/path/to/b");
626 let base = HgPath::new(b"nested/path/");
649 let base = HgPath::new(b"nested/path/");
627 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
650 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
628
651
629 let path = HgPath::new(b"ends/with/dir/");
652 let path = HgPath::new(b"ends/with/dir/");
630 let base = HgPath::new(b"ends/");
653 let base = HgPath::new(b"ends/");
631 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
654 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
632 }
655 }
633
656
634 #[test]
657 #[test]
635 #[cfg(unix)]
658 #[cfg(unix)]
636 fn test_split_drive() {
659 fn test_split_drive() {
637 // Taken from the Python stdlib's tests
660 // Taken from the Python stdlib's tests
638 assert_eq!(
661 assert_eq!(
639 HgPath::new(br"/foo/bar").split_drive(),
662 HgPath::new(br"/foo/bar").split_drive(),
640 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
663 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
641 );
664 );
642 assert_eq!(
665 assert_eq!(
643 HgPath::new(br"foo:bar").split_drive(),
666 HgPath::new(br"foo:bar").split_drive(),
644 (HgPath::new(b""), HgPath::new(br"foo:bar"))
667 (HgPath::new(b""), HgPath::new(br"foo:bar"))
645 );
668 );
646 assert_eq!(
669 assert_eq!(
647 HgPath::new(br":foo:bar").split_drive(),
670 HgPath::new(br":foo:bar").split_drive(),
648 (HgPath::new(b""), HgPath::new(br":foo:bar"))
671 (HgPath::new(b""), HgPath::new(br":foo:bar"))
649 );
672 );
650 // Also try NT paths; should not split them
673 // Also try NT paths; should not split them
651 assert_eq!(
674 assert_eq!(
652 HgPath::new(br"c:\foo\bar").split_drive(),
675 HgPath::new(br"c:\foo\bar").split_drive(),
653 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
676 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
654 );
677 );
655 assert_eq!(
678 assert_eq!(
656 HgPath::new(b"c:/foo/bar").split_drive(),
679 HgPath::new(b"c:/foo/bar").split_drive(),
657 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
680 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
658 );
681 );
659 assert_eq!(
682 assert_eq!(
660 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
683 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
661 (
684 (
662 HgPath::new(b""),
685 HgPath::new(b""),
663 HgPath::new(br"\\conky\mountpoint\foo\bar")
686 HgPath::new(br"\\conky\mountpoint\foo\bar")
664 )
687 )
665 );
688 );
666 }
689 }
667
690
668 #[test]
691 #[test]
669 #[cfg(windows)]
692 #[cfg(windows)]
670 fn test_split_drive() {
693 fn test_split_drive() {
671 assert_eq!(
694 assert_eq!(
672 HgPath::new(br"c:\foo\bar").split_drive(),
695 HgPath::new(br"c:\foo\bar").split_drive(),
673 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
696 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
674 );
697 );
675 assert_eq!(
698 assert_eq!(
676 HgPath::new(b"c:/foo/bar").split_drive(),
699 HgPath::new(b"c:/foo/bar").split_drive(),
677 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
700 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
678 );
701 );
679 assert_eq!(
702 assert_eq!(
680 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
703 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
681 (
704 (
682 HgPath::new(br"\\conky\mountpoint"),
705 HgPath::new(br"\\conky\mountpoint"),
683 HgPath::new(br"\foo\bar")
706 HgPath::new(br"\foo\bar")
684 )
707 )
685 );
708 );
686 assert_eq!(
709 assert_eq!(
687 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
710 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
688 (
711 (
689 HgPath::new(br"//conky/mountpoint"),
712 HgPath::new(br"//conky/mountpoint"),
690 HgPath::new(br"/foo/bar")
713 HgPath::new(br"/foo/bar")
691 )
714 )
692 );
715 );
693 assert_eq!(
716 assert_eq!(
694 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
717 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
695 (
718 (
696 HgPath::new(br""),
719 HgPath::new(br""),
697 HgPath::new(br"\\\conky\mountpoint\foo\bar")
720 HgPath::new(br"\\\conky\mountpoint\foo\bar")
698 )
721 )
699 );
722 );
700 assert_eq!(
723 assert_eq!(
701 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
724 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
702 (
725 (
703 HgPath::new(br""),
726 HgPath::new(br""),
704 HgPath::new(br"///conky/mountpoint/foo/bar")
727 HgPath::new(br"///conky/mountpoint/foo/bar")
705 )
728 )
706 );
729 );
707 assert_eq!(
730 assert_eq!(
708 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
731 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
709 (
732 (
710 HgPath::new(br""),
733 HgPath::new(br""),
711 HgPath::new(br"\\conky\\mountpoint\foo\bar")
734 HgPath::new(br"\\conky\\mountpoint\foo\bar")
712 )
735 )
713 );
736 );
714 assert_eq!(
737 assert_eq!(
715 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
738 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
716 (
739 (
717 HgPath::new(br""),
740 HgPath::new(br""),
718 HgPath::new(br"//conky//mountpoint/foo/bar")
741 HgPath::new(br"//conky//mountpoint/foo/bar")
719 )
742 )
720 );
743 );
721 // UNC part containing U+0130
744 // UNC part containing U+0130
722 assert_eq!(
745 assert_eq!(
723 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
746 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
724 (
747 (
725 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
748 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
726 HgPath::new(br"/foo/bar")
749 HgPath::new(br"/foo/bar")
727 )
750 )
728 );
751 );
729 }
752 }
730
753
731 #[test]
754 #[test]
732 fn test_parent() {
755 fn test_parent() {
733 let path = HgPath::new(b"");
756 let path = HgPath::new(b"");
734 assert_eq!(path.parent(), path);
757 assert_eq!(path.parent(), path);
735
758
736 let path = HgPath::new(b"a");
759 let path = HgPath::new(b"a");
737 assert_eq!(path.parent(), HgPath::new(b""));
760 assert_eq!(path.parent(), HgPath::new(b""));
738
761
739 let path = HgPath::new(b"a/b");
762 let path = HgPath::new(b"a/b");
740 assert_eq!(path.parent(), HgPath::new(b"a"));
763 assert_eq!(path.parent(), HgPath::new(b"a"));
741
764
742 let path = HgPath::new(b"a/other/b");
765 let path = HgPath::new(b"a/other/b");
743 assert_eq!(path.parent(), HgPath::new(b"a/other"));
766 assert_eq!(path.parent(), HgPath::new(b"a/other"));
744 }
767 }
745 }
768 }
General Comments 0
You need to be logged in to leave comments. Login now