##// END OF EJS Templates
rust: Preallocate the returned `Vec` in `utils::files::relativize_path`...
Simon Sapin -
r47533:c94fa884 default
parent child Browse files
Show More
@@ -1,457 +1,463 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::{
12 use crate::utils::{
13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError},
13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError},
14 path_auditor::PathAuditor,
14 path_auditor::PathAuditor,
15 replace_slice,
15 replace_slice,
16 };
16 };
17 use lazy_static::lazy_static;
17 use lazy_static::lazy_static;
18 use same_file::is_same_file;
18 use same_file::is_same_file;
19 use std::borrow::{Cow, ToOwned};
19 use std::borrow::{Cow, ToOwned};
20 use std::ffi::OsStr;
20 use std::ffi::OsStr;
21 use std::fs::Metadata;
21 use std::fs::Metadata;
22 use std::iter::FusedIterator;
22 use std::iter::FusedIterator;
23 use std::ops::Deref;
23 use std::ops::Deref;
24 use std::path::{Path, PathBuf};
24 use std::path::{Path, PathBuf};
25
25
26 pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr {
26 pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr {
27 let os_str;
27 let os_str;
28 #[cfg(unix)]
28 #[cfg(unix)]
29 {
29 {
30 use std::os::unix::ffi::OsStrExt;
30 use std::os::unix::ffi::OsStrExt;
31 os_str = std::ffi::OsStr::from_bytes(bytes);
31 os_str = std::ffi::OsStr::from_bytes(bytes);
32 }
32 }
33 // TODO Handle other platforms
33 // TODO Handle other platforms
34 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
34 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
35 // Perhaps, the return type would have to be Result<PathBuf>.
35 // Perhaps, the return type would have to be Result<PathBuf>.
36 os_str
36 os_str
37 }
37 }
38
38
39 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
39 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
40 Path::new(get_os_str_from_bytes(bytes))
40 Path::new(get_os_str_from_bytes(bytes))
41 }
41 }
42
42
43 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
43 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
44 // that's why Vec<u8> is returned.
44 // that's why Vec<u8> is returned.
45 #[cfg(unix)]
45 #[cfg(unix)]
46 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
46 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
47 get_bytes_from_os_str(path.as_ref())
47 get_bytes_from_os_str(path.as_ref())
48 }
48 }
49
49
50 #[cfg(unix)]
50 #[cfg(unix)]
51 pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> {
51 pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> {
52 use std::os::unix::ffi::OsStrExt;
52 use std::os::unix::ffi::OsStrExt;
53 str.as_ref().as_bytes().to_vec()
53 str.as_ref().as_bytes().to_vec()
54 }
54 }
55
55
56 /// An iterator over repository path yielding itself and its ancestors.
56 /// An iterator over repository path yielding itself and its ancestors.
57 #[derive(Copy, Clone, Debug)]
57 #[derive(Copy, Clone, Debug)]
58 pub struct Ancestors<'a> {
58 pub struct Ancestors<'a> {
59 next: Option<&'a HgPath>,
59 next: Option<&'a HgPath>,
60 }
60 }
61
61
62 impl<'a> Iterator for Ancestors<'a> {
62 impl<'a> Iterator for Ancestors<'a> {
63 type Item = &'a HgPath;
63 type Item = &'a HgPath;
64
64
65 fn next(&mut self) -> Option<Self::Item> {
65 fn next(&mut self) -> Option<Self::Item> {
66 let next = self.next;
66 let next = self.next;
67 self.next = match self.next {
67 self.next = match self.next {
68 Some(s) if s.is_empty() => None,
68 Some(s) if s.is_empty() => None,
69 Some(s) => {
69 Some(s) => {
70 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
70 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
71 Some(HgPath::new(&s.as_bytes()[..p]))
71 Some(HgPath::new(&s.as_bytes()[..p]))
72 }
72 }
73 None => None,
73 None => None,
74 };
74 };
75 next
75 next
76 }
76 }
77 }
77 }
78
78
79 impl<'a> FusedIterator for Ancestors<'a> {}
79 impl<'a> FusedIterator for Ancestors<'a> {}
80
80
81 /// An iterator over repository path yielding itself and its ancestors.
81 /// An iterator over repository path yielding itself and its ancestors.
82 #[derive(Copy, Clone, Debug)]
82 #[derive(Copy, Clone, Debug)]
83 pub(crate) struct AncestorsWithBase<'a> {
83 pub(crate) struct AncestorsWithBase<'a> {
84 next: Option<(&'a HgPath, &'a HgPath)>,
84 next: Option<(&'a HgPath, &'a HgPath)>,
85 }
85 }
86
86
87 impl<'a> Iterator for AncestorsWithBase<'a> {
87 impl<'a> Iterator for AncestorsWithBase<'a> {
88 type Item = (&'a HgPath, &'a HgPath);
88 type Item = (&'a HgPath, &'a HgPath);
89
89
90 fn next(&mut self) -> Option<Self::Item> {
90 fn next(&mut self) -> Option<Self::Item> {
91 let next = self.next;
91 let next = self.next;
92 self.next = match self.next {
92 self.next = match self.next {
93 Some((s, _)) if s.is_empty() => None,
93 Some((s, _)) if s.is_empty() => None,
94 Some((s, _)) => Some(s.split_filename()),
94 Some((s, _)) => Some(s.split_filename()),
95 None => None,
95 None => None,
96 };
96 };
97 next
97 next
98 }
98 }
99 }
99 }
100
100
101 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
101 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
102
102
103 /// Returns an iterator yielding ancestor directories of the given repository
103 /// Returns an iterator yielding ancestor directories of the given repository
104 /// path.
104 /// path.
105 ///
105 ///
106 /// The path is separated by '/', and must not start with '/'.
106 /// The path is separated by '/', and must not start with '/'.
107 ///
107 ///
108 /// The path itself isn't included unless it is b"" (meaning the root
108 /// The path itself isn't included unless it is b"" (meaning the root
109 /// directory.)
109 /// directory.)
110 pub fn find_dirs(path: &HgPath) -> Ancestors {
110 pub fn find_dirs(path: &HgPath) -> Ancestors {
111 let mut dirs = Ancestors { next: Some(path) };
111 let mut dirs = Ancestors { next: Some(path) };
112 if !path.is_empty() {
112 if !path.is_empty() {
113 dirs.next(); // skip itself
113 dirs.next(); // skip itself
114 }
114 }
115 dirs
115 dirs
116 }
116 }
117
117
118 /// Returns an iterator yielding ancestor directories of the given repository
118 /// Returns an iterator yielding ancestor directories of the given repository
119 /// path.
119 /// path.
120 ///
120 ///
121 /// The path is separated by '/', and must not start with '/'.
121 /// The path is separated by '/', and must not start with '/'.
122 ///
122 ///
123 /// The path itself isn't included unless it is b"" (meaning the root
123 /// The path itself isn't included unless it is b"" (meaning the root
124 /// directory.)
124 /// directory.)
125 pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase {
125 pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase {
126 let mut dirs = AncestorsWithBase {
126 let mut dirs = AncestorsWithBase {
127 next: Some((path, HgPath::new(b""))),
127 next: Some((path, HgPath::new(b""))),
128 };
128 };
129 if !path.is_empty() {
129 if !path.is_empty() {
130 dirs.next(); // skip itself
130 dirs.next(); // skip itself
131 }
131 }
132 dirs
132 dirs
133 }
133 }
134
134
135 /// TODO more than ASCII?
135 /// TODO more than ASCII?
136 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
136 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
137 #[cfg(windows)] // NTFS compares via upper()
137 #[cfg(windows)] // NTFS compares via upper()
138 return path.to_ascii_uppercase();
138 return path.to_ascii_uppercase();
139 #[cfg(unix)]
139 #[cfg(unix)]
140 path.to_ascii_lowercase()
140 path.to_ascii_lowercase()
141 }
141 }
142
142
143 lazy_static! {
143 lazy_static! {
144 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
144 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
145 [
145 [
146 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
146 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
147 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
147 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
148 ]
148 ]
149 .iter()
149 .iter()
150 .map(|code| {
150 .map(|code| {
151 std::char::from_u32(*code)
151 std::char::from_u32(*code)
152 .unwrap()
152 .unwrap()
153 .encode_utf8(&mut [0; 3])
153 .encode_utf8(&mut [0; 3])
154 .bytes()
154 .bytes()
155 .collect()
155 .collect()
156 })
156 })
157 .collect()
157 .collect()
158 };
158 };
159 }
159 }
160
160
161 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
161 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
162 let mut buf = bytes.to_owned();
162 let mut buf = bytes.to_owned();
163 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
163 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
164 if needs_escaping {
164 if needs_escaping {
165 for forbidden in IGNORED_CHARS.iter() {
165 for forbidden in IGNORED_CHARS.iter() {
166 replace_slice(&mut buf, forbidden, &[])
166 replace_slice(&mut buf, forbidden, &[])
167 }
167 }
168 buf
168 buf
169 } else {
169 } else {
170 buf
170 buf
171 }
171 }
172 }
172 }
173
173
174 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
174 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
175 hfs_ignore_clean(&bytes.to_ascii_lowercase())
175 hfs_ignore_clean(&bytes.to_ascii_lowercase())
176 }
176 }
177
177
178 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
178 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
179 pub struct HgMetadata {
179 pub struct HgMetadata {
180 pub st_dev: u64,
180 pub st_dev: u64,
181 pub st_mode: u32,
181 pub st_mode: u32,
182 pub st_nlink: u64,
182 pub st_nlink: u64,
183 pub st_size: u64,
183 pub st_size: u64,
184 pub st_mtime: i64,
184 pub st_mtime: i64,
185 pub st_ctime: i64,
185 pub st_ctime: i64,
186 }
186 }
187
187
188 // TODO support other plaforms
188 // TODO support other plaforms
189 #[cfg(unix)]
189 #[cfg(unix)]
190 impl HgMetadata {
190 impl HgMetadata {
191 pub fn from_metadata(metadata: Metadata) -> Self {
191 pub fn from_metadata(metadata: Metadata) -> Self {
192 use std::os::unix::fs::MetadataExt;
192 use std::os::unix::fs::MetadataExt;
193 Self {
193 Self {
194 st_dev: metadata.dev(),
194 st_dev: metadata.dev(),
195 st_mode: metadata.mode(),
195 st_mode: metadata.mode(),
196 st_nlink: metadata.nlink(),
196 st_nlink: metadata.nlink(),
197 st_size: metadata.size(),
197 st_size: metadata.size(),
198 st_mtime: metadata.mtime(),
198 st_mtime: metadata.mtime(),
199 st_ctime: metadata.ctime(),
199 st_ctime: metadata.ctime(),
200 }
200 }
201 }
201 }
202
202
203 pub fn is_symlink(&self) -> bool {
203 pub fn is_symlink(&self) -> bool {
204 // This is way too manual, but `HgMetadata` will go away in the
204 // This is way too manual, but `HgMetadata` will go away in the
205 // near-future dirstate rewrite anyway.
205 // near-future dirstate rewrite anyway.
206 self.st_mode & 0170000 == 0120000
206 self.st_mode & 0170000 == 0120000
207 }
207 }
208 }
208 }
209
209
210 /// Returns the canonical path of `name`, given `cwd` and `root`
210 /// Returns the canonical path of `name`, given `cwd` and `root`
211 pub fn canonical_path(
211 pub fn canonical_path(
212 root: impl AsRef<Path>,
212 root: impl AsRef<Path>,
213 cwd: impl AsRef<Path>,
213 cwd: impl AsRef<Path>,
214 name: impl AsRef<Path>,
214 name: impl AsRef<Path>,
215 ) -> Result<PathBuf, HgPathError> {
215 ) -> Result<PathBuf, HgPathError> {
216 // TODO add missing normalization for other platforms
216 // TODO add missing normalization for other platforms
217 let root = root.as_ref();
217 let root = root.as_ref();
218 let cwd = cwd.as_ref();
218 let cwd = cwd.as_ref();
219 let name = name.as_ref();
219 let name = name.as_ref();
220
220
221 let name = if !name.is_absolute() {
221 let name = if !name.is_absolute() {
222 root.join(&cwd).join(&name)
222 root.join(&cwd).join(&name)
223 } else {
223 } else {
224 name.to_owned()
224 name.to_owned()
225 };
225 };
226 let auditor = PathAuditor::new(&root);
226 let auditor = PathAuditor::new(&root);
227 if name != root && name.starts_with(&root) {
227 if name != root && name.starts_with(&root) {
228 let name = name.strip_prefix(&root).unwrap();
228 let name = name.strip_prefix(&root).unwrap();
229 auditor.audit_path(path_to_hg_path_buf(name)?)?;
229 auditor.audit_path(path_to_hg_path_buf(name)?)?;
230 Ok(name.to_owned())
230 Ok(name.to_owned())
231 } else if name == root {
231 } else if name == root {
232 Ok("".into())
232 Ok("".into())
233 } else {
233 } else {
234 // Determine whether `name' is in the hierarchy at or beneath `root',
234 // Determine whether `name' is in the hierarchy at or beneath `root',
235 // by iterating name=name.parent() until it returns `None` (can't
235 // by iterating name=name.parent() until it returns `None` (can't
236 // check name == '/', because that doesn't work on windows).
236 // check name == '/', because that doesn't work on windows).
237 let mut name = name.deref();
237 let mut name = name.deref();
238 let original_name = name.to_owned();
238 let original_name = name.to_owned();
239 loop {
239 loop {
240 let same = is_same_file(&name, &root).unwrap_or(false);
240 let same = is_same_file(&name, &root).unwrap_or(false);
241 if same {
241 if same {
242 if name == original_name {
242 if name == original_name {
243 // `name` was actually the same as root (maybe a symlink)
243 // `name` was actually the same as root (maybe a symlink)
244 return Ok("".into());
244 return Ok("".into());
245 }
245 }
246 // `name` is a symlink to root, so `original_name` is under
246 // `name` is a symlink to root, so `original_name` is under
247 // root
247 // root
248 let rel_path = original_name.strip_prefix(&name).unwrap();
248 let rel_path = original_name.strip_prefix(&name).unwrap();
249 auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?;
249 auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?;
250 return Ok(rel_path.to_owned());
250 return Ok(rel_path.to_owned());
251 }
251 }
252 name = match name.parent() {
252 name = match name.parent() {
253 None => break,
253 None => break,
254 Some(p) => p,
254 Some(p) => p,
255 };
255 };
256 }
256 }
257 // TODO hint to the user about using --cwd
257 // TODO hint to the user about using --cwd
258 // Bubble up the responsibility to Python for now
258 // Bubble up the responsibility to Python for now
259 Err(HgPathError::NotUnderRoot {
259 Err(HgPathError::NotUnderRoot {
260 path: original_name.to_owned(),
260 path: original_name.to_owned(),
261 root: root.to_owned(),
261 root: root.to_owned(),
262 })
262 })
263 }
263 }
264 }
264 }
265
265
266 /// Returns the representation of the path relative to the current working
266 /// Returns the representation of the path relative to the current working
267 /// directory for display purposes.
267 /// directory for display purposes.
268 ///
268 ///
269 /// `cwd` is a `HgPath`, so it is considered relative to the root directory
269 /// `cwd` is a `HgPath`, so it is considered relative to the root directory
270 /// of the repository.
270 /// of the repository.
271 ///
271 ///
272 /// # Examples
272 /// # Examples
273 ///
273 ///
274 /// ```
274 /// ```
275 /// use hg::utils::hg_path::HgPath;
275 /// use hg::utils::hg_path::HgPath;
276 /// use hg::utils::files::relativize_path;
276 /// use hg::utils::files::relativize_path;
277 /// use std::borrow::Cow;
277 /// use std::borrow::Cow;
278 ///
278 ///
279 /// let file = HgPath::new(b"nested/file");
279 /// let file = HgPath::new(b"nested/file");
280 /// let cwd = HgPath::new(b"");
280 /// let cwd = HgPath::new(b"");
281 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
281 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
282 ///
282 ///
283 /// let cwd = HgPath::new(b"nested");
283 /// let cwd = HgPath::new(b"nested");
284 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
284 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
285 ///
285 ///
286 /// let cwd = HgPath::new(b"other");
286 /// let cwd = HgPath::new(b"other");
287 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
287 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
288 /// ```
288 /// ```
289 pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
289 pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
290 if cwd.as_ref().is_empty() {
290 if cwd.as_ref().is_empty() {
291 Cow::Borrowed(path.as_bytes())
291 Cow::Borrowed(path.as_bytes())
292 } else {
292 } else {
293 let mut res: Vec<u8> = Vec::new();
293 // This is not all accurate as to how large `res` will actually be, but
294 // profiling `rhg files` on a large-ish repo shows it’s better than
295 // starting from a zero-capacity `Vec` and letting `extend` reallocate
296 // repeatedly.
297 let guesstimate = path.as_bytes().len();
298
299 let mut res: Vec<u8> = Vec::with_capacity(guesstimate);
294 let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
300 let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
295 let mut cwd_iter =
301 let mut cwd_iter =
296 cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
302 cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
297 loop {
303 loop {
298 match (path_iter.peek(), cwd_iter.peek()) {
304 match (path_iter.peek(), cwd_iter.peek()) {
299 (Some(a), Some(b)) if a == b => (),
305 (Some(a), Some(b)) if a == b => (),
300 _ => break,
306 _ => break,
301 }
307 }
302 path_iter.next();
308 path_iter.next();
303 cwd_iter.next();
309 cwd_iter.next();
304 }
310 }
305 let mut need_sep = false;
311 let mut need_sep = false;
306 for _ in cwd_iter {
312 for _ in cwd_iter {
307 if need_sep {
313 if need_sep {
308 res.extend(b"/")
314 res.extend(b"/")
309 } else {
315 } else {
310 need_sep = true
316 need_sep = true
311 };
317 };
312 res.extend(b"..");
318 res.extend(b"..");
313 }
319 }
314 for c in path_iter {
320 for c in path_iter {
315 if need_sep {
321 if need_sep {
316 res.extend(b"/")
322 res.extend(b"/")
317 } else {
323 } else {
318 need_sep = true
324 need_sep = true
319 };
325 };
320 res.extend(c);
326 res.extend(c);
321 }
327 }
322 Cow::Owned(res)
328 Cow::Owned(res)
323 }
329 }
324 }
330 }
325
331
326 #[cfg(test)]
332 #[cfg(test)]
327 mod tests {
333 mod tests {
328 use super::*;
334 use super::*;
329 use pretty_assertions::assert_eq;
335 use pretty_assertions::assert_eq;
330
336
331 #[test]
337 #[test]
332 fn find_dirs_some() {
338 fn find_dirs_some() {
333 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
339 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
334 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
340 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
335 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
341 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
336 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
342 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
337 assert_eq!(dirs.next(), None);
343 assert_eq!(dirs.next(), None);
338 assert_eq!(dirs.next(), None);
344 assert_eq!(dirs.next(), None);
339 }
345 }
340
346
341 #[test]
347 #[test]
342 fn find_dirs_empty() {
348 fn find_dirs_empty() {
343 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
349 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
344 let mut dirs = super::find_dirs(HgPath::new(b""));
350 let mut dirs = super::find_dirs(HgPath::new(b""));
345 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
351 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
346 assert_eq!(dirs.next(), None);
352 assert_eq!(dirs.next(), None);
347 assert_eq!(dirs.next(), None);
353 assert_eq!(dirs.next(), None);
348 }
354 }
349
355
350 #[test]
356 #[test]
351 fn test_find_dirs_with_base_some() {
357 fn test_find_dirs_with_base_some() {
352 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
358 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
353 assert_eq!(
359 assert_eq!(
354 dirs.next(),
360 dirs.next(),
355 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
361 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
356 );
362 );
357 assert_eq!(
363 assert_eq!(
358 dirs.next(),
364 dirs.next(),
359 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
365 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
360 );
366 );
361 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
367 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
362 assert_eq!(dirs.next(), None);
368 assert_eq!(dirs.next(), None);
363 assert_eq!(dirs.next(), None);
369 assert_eq!(dirs.next(), None);
364 }
370 }
365
371
366 #[test]
372 #[test]
367 fn test_find_dirs_with_base_empty() {
373 fn test_find_dirs_with_base_empty() {
368 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
374 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
369 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
375 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
370 assert_eq!(dirs.next(), None);
376 assert_eq!(dirs.next(), None);
371 assert_eq!(dirs.next(), None);
377 assert_eq!(dirs.next(), None);
372 }
378 }
373
379
374 #[test]
380 #[test]
375 fn test_canonical_path() {
381 fn test_canonical_path() {
376 let root = Path::new("/repo");
382 let root = Path::new("/repo");
377 let cwd = Path::new("/dir");
383 let cwd = Path::new("/dir");
378 let name = Path::new("filename");
384 let name = Path::new("filename");
379 assert_eq!(
385 assert_eq!(
380 canonical_path(root, cwd, name),
386 canonical_path(root, cwd, name),
381 Err(HgPathError::NotUnderRoot {
387 Err(HgPathError::NotUnderRoot {
382 path: PathBuf::from("/dir/filename"),
388 path: PathBuf::from("/dir/filename"),
383 root: root.to_path_buf()
389 root: root.to_path_buf()
384 })
390 })
385 );
391 );
386
392
387 let root = Path::new("/repo");
393 let root = Path::new("/repo");
388 let cwd = Path::new("/");
394 let cwd = Path::new("/");
389 let name = Path::new("filename");
395 let name = Path::new("filename");
390 assert_eq!(
396 assert_eq!(
391 canonical_path(root, cwd, name),
397 canonical_path(root, cwd, name),
392 Err(HgPathError::NotUnderRoot {
398 Err(HgPathError::NotUnderRoot {
393 path: PathBuf::from("/filename"),
399 path: PathBuf::from("/filename"),
394 root: root.to_path_buf()
400 root: root.to_path_buf()
395 })
401 })
396 );
402 );
397
403
398 let root = Path::new("/repo");
404 let root = Path::new("/repo");
399 let cwd = Path::new("/");
405 let cwd = Path::new("/");
400 let name = Path::new("repo/filename");
406 let name = Path::new("repo/filename");
401 assert_eq!(
407 assert_eq!(
402 canonical_path(root, cwd, name),
408 canonical_path(root, cwd, name),
403 Ok(PathBuf::from("filename"))
409 Ok(PathBuf::from("filename"))
404 );
410 );
405
411
406 let root = Path::new("/repo");
412 let root = Path::new("/repo");
407 let cwd = Path::new("/repo");
413 let cwd = Path::new("/repo");
408 let name = Path::new("filename");
414 let name = Path::new("filename");
409 assert_eq!(
415 assert_eq!(
410 canonical_path(root, cwd, name),
416 canonical_path(root, cwd, name),
411 Ok(PathBuf::from("filename"))
417 Ok(PathBuf::from("filename"))
412 );
418 );
413
419
414 let root = Path::new("/repo");
420 let root = Path::new("/repo");
415 let cwd = Path::new("/repo/subdir");
421 let cwd = Path::new("/repo/subdir");
416 let name = Path::new("filename");
422 let name = Path::new("filename");
417 assert_eq!(
423 assert_eq!(
418 canonical_path(root, cwd, name),
424 canonical_path(root, cwd, name),
419 Ok(PathBuf::from("subdir/filename"))
425 Ok(PathBuf::from("subdir/filename"))
420 );
426 );
421 }
427 }
422
428
423 #[test]
429 #[test]
424 fn test_canonical_path_not_rooted() {
430 fn test_canonical_path_not_rooted() {
425 use std::fs::create_dir;
431 use std::fs::create_dir;
426 use tempfile::tempdir;
432 use tempfile::tempdir;
427
433
428 let base_dir = tempdir().unwrap();
434 let base_dir = tempdir().unwrap();
429 let base_dir_path = base_dir.path();
435 let base_dir_path = base_dir.path();
430 let beneath_repo = base_dir_path.join("a");
436 let beneath_repo = base_dir_path.join("a");
431 let root = base_dir_path.join("a/b");
437 let root = base_dir_path.join("a/b");
432 let out_of_repo = base_dir_path.join("c");
438 let out_of_repo = base_dir_path.join("c");
433 let under_repo_symlink = out_of_repo.join("d");
439 let under_repo_symlink = out_of_repo.join("d");
434
440
435 create_dir(&beneath_repo).unwrap();
441 create_dir(&beneath_repo).unwrap();
436 create_dir(&root).unwrap();
442 create_dir(&root).unwrap();
437
443
438 // TODO make portable
444 // TODO make portable
439 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap();
445 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap();
440
446
441 assert_eq!(
447 assert_eq!(
442 canonical_path(&root, Path::new(""), out_of_repo),
448 canonical_path(&root, Path::new(""), out_of_repo),
443 Ok(PathBuf::from(""))
449 Ok(PathBuf::from(""))
444 );
450 );
445 assert_eq!(
451 assert_eq!(
446 canonical_path(&root, Path::new(""), &beneath_repo),
452 canonical_path(&root, Path::new(""), &beneath_repo),
447 Err(HgPathError::NotUnderRoot {
453 Err(HgPathError::NotUnderRoot {
448 path: beneath_repo.to_owned(),
454 path: beneath_repo.to_owned(),
449 root: root.to_owned()
455 root: root.to_owned()
450 })
456 })
451 );
457 );
452 assert_eq!(
458 assert_eq!(
453 canonical_path(&root, Path::new(""), &under_repo_symlink),
459 canonical_path(&root, Path::new(""), &under_repo_symlink),
454 Ok(PathBuf::from("d"))
460 Ok(PathBuf::from("d"))
455 );
461 );
456 }
462 }
457 }
463 }
General Comments 0
You need to be logged in to leave comments. Login now