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