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