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