Mercurial > public > mercurial-scm > hg-stable
comparison rust/hg-core/src/utils/files.rs @ 44345:4caac36c66bc
rust-utils: add util for canonical path
Differential Revision: https://phab.mercurial-scm.org/D7871
author | Rapha?l Gom?s <rgomes@octobus.net> |
---|---|
date | Tue, 14 Jan 2020 17:10:20 +0100 |
parents | 0e9ac3968b56 |
children | 07d9fd6097e6 |
comparison
equal
deleted
inserted
replaced
44344:0e8b28fb751b | 44345:4caac36c66bc |
---|---|
7 // This software may be used and distributed according to the terms of the | 7 // This software may be used and distributed according to the terms of the |
8 // GNU General Public License version 2 or any later version. | 8 // GNU General Public License version 2 or any later version. |
9 | 9 |
10 //! Functions for fiddling with files. | 10 //! Functions for fiddling with files. |
11 | 11 |
12 use crate::utils::hg_path::{HgPath, HgPathBuf}; | 12 use crate::utils::{ |
13 | 13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError}, |
14 use crate::utils::replace_slice; | 14 path_auditor::PathAuditor, |
15 replace_slice, | |
16 }; | |
15 use lazy_static::lazy_static; | 17 use lazy_static::lazy_static; |
18 use same_file::is_same_file; | |
19 use std::borrow::ToOwned; | |
16 use std::fs::Metadata; | 20 use std::fs::Metadata; |
17 use std::iter::FusedIterator; | 21 use std::iter::FusedIterator; |
18 use std::path::Path; | 22 use std::ops::Deref; |
23 use std::path::{Path, PathBuf}; | |
19 | 24 |
20 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { | 25 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { |
21 let os_str; | 26 let os_str; |
22 #[cfg(unix)] | 27 #[cfg(unix)] |
23 { | 28 { |
187 st_ctime: metadata.ctime(), | 192 st_ctime: metadata.ctime(), |
188 } | 193 } |
189 } | 194 } |
190 } | 195 } |
191 | 196 |
197 /// Returns the canonical path of `name`, given `cwd` and `root` | |
198 pub fn canonical_path( | |
199 root: impl AsRef<Path>, | |
200 cwd: impl AsRef<Path>, | |
201 name: impl AsRef<Path>, | |
202 ) -> Result<PathBuf, HgPathError> { | |
203 // TODO add missing normalization for other platforms | |
204 let root = root.as_ref(); | |
205 let cwd = cwd.as_ref(); | |
206 let name = name.as_ref(); | |
207 | |
208 let name = if !name.is_absolute() { | |
209 root.join(&cwd).join(&name) | |
210 } else { | |
211 name.to_owned() | |
212 }; | |
213 let mut auditor = PathAuditor::new(&root); | |
214 if name != root && name.starts_with(&root) { | |
215 let name = name.strip_prefix(&root).unwrap(); | |
216 auditor.audit_path(path_to_hg_path_buf(name)?)?; | |
217 return Ok(name.to_owned()); | |
218 } else if name == root { | |
219 return Ok("".into()); | |
220 } else { | |
221 // Determine whether `name' is in the hierarchy at or beneath `root', | |
222 // by iterating name=name.parent() until it returns `None` (can't | |
223 // check name == '/', because that doesn't work on windows). | |
224 let mut name = name.deref(); | |
225 let original_name = name.to_owned(); | |
226 loop { | |
227 let same = is_same_file(&name, &root).unwrap_or(false); | |
228 if same { | |
229 if name == original_name { | |
230 // `name` was actually the same as root (maybe a symlink) | |
231 return Ok("".into()); | |
232 } | |
233 // `name` is a symlink to root, so `original_name` is under | |
234 // root | |
235 let rel_path = original_name.strip_prefix(&name).unwrap(); | |
236 auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?; | |
237 return Ok(rel_path.to_owned()); | |
238 } | |
239 name = match name.parent() { | |
240 None => break, | |
241 Some(p) => p, | |
242 }; | |
243 } | |
244 // TODO hint to the user about using --cwd | |
245 // Bubble up the responsibility to Python for now | |
246 Err(HgPathError::NotUnderRoot { | |
247 path: original_name.to_owned(), | |
248 root: root.to_owned(), | |
249 }) | |
250 } | |
251 } | |
252 | |
192 #[cfg(test)] | 253 #[cfg(test)] |
193 mod tests { | 254 mod tests { |
194 use super::*; | 255 use super::*; |
256 use pretty_assertions::assert_eq; | |
195 | 257 |
196 #[test] | 258 #[test] |
197 fn find_dirs_some() { | 259 fn find_dirs_some() { |
198 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz")); | 260 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz")); |
199 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar"))); | 261 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar"))); |
233 let mut dirs = super::find_dirs_with_base(HgPath::new(b"")); | 295 let mut dirs = super::find_dirs_with_base(HgPath::new(b"")); |
234 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"")))); | 296 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"")))); |
235 assert_eq!(dirs.next(), None); | 297 assert_eq!(dirs.next(), None); |
236 assert_eq!(dirs.next(), None); | 298 assert_eq!(dirs.next(), None); |
237 } | 299 } |
238 } | 300 |
301 #[test] | |
302 fn test_canonical_path() { | |
303 let root = Path::new("/repo"); | |
304 let cwd = Path::new("/dir"); | |
305 let name = Path::new("filename"); | |
306 assert_eq!( | |
307 canonical_path(root, cwd, name), | |
308 Err(HgPathError::NotUnderRoot { | |
309 path: PathBuf::from("/dir/filename"), | |
310 root: root.to_path_buf() | |
311 }) | |
312 ); | |
313 | |
314 let root = Path::new("/repo"); | |
315 let cwd = Path::new("/"); | |
316 let name = Path::new("filename"); | |
317 assert_eq!( | |
318 canonical_path(root, cwd, name), | |
319 Err(HgPathError::NotUnderRoot { | |
320 path: PathBuf::from("/filename"), | |
321 root: root.to_path_buf() | |
322 }) | |
323 ); | |
324 | |
325 let root = Path::new("/repo"); | |
326 let cwd = Path::new("/"); | |
327 let name = Path::new("repo/filename"); | |
328 assert_eq!( | |
329 canonical_path(root, cwd, name), | |
330 Ok(PathBuf::from("filename")) | |
331 ); | |
332 | |
333 let root = Path::new("/repo"); | |
334 let cwd = Path::new("/repo"); | |
335 let name = Path::new("filename"); | |
336 assert_eq!( | |
337 canonical_path(root, cwd, name), | |
338 Ok(PathBuf::from("filename")) | |
339 ); | |
340 | |
341 let root = Path::new("/repo"); | |
342 let cwd = Path::new("/repo/subdir"); | |
343 let name = Path::new("filename"); | |
344 assert_eq!( | |
345 canonical_path(root, cwd, name), | |
346 Ok(PathBuf::from("subdir/filename")) | |
347 ); | |
348 } | |
349 | |
350 #[test] | |
351 fn test_canonical_path_not_rooted() { | |
352 use std::fs::create_dir; | |
353 use tempfile::tempdir; | |
354 | |
355 let base_dir = tempdir().unwrap(); | |
356 let base_dir_path = base_dir.path(); | |
357 let beneath_repo = base_dir_path.join("a"); | |
358 let root = base_dir_path.join("a/b"); | |
359 let out_of_repo = base_dir_path.join("c"); | |
360 let under_repo_symlink = out_of_repo.join("d"); | |
361 | |
362 create_dir(&beneath_repo).unwrap(); | |
363 create_dir(&root).unwrap(); | |
364 | |
365 // TODO make portable | |
366 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap(); | |
367 | |
368 assert_eq!( | |
369 canonical_path(&root, Path::new(""), out_of_repo), | |
370 Ok(PathBuf::from("")) | |
371 ); | |
372 assert_eq!( | |
373 canonical_path(&root, Path::new(""), &beneath_repo), | |
374 Err(HgPathError::NotUnderRoot { | |
375 path: beneath_repo.to_owned(), | |
376 root: root.to_owned() | |
377 }) | |
378 ); | |
379 assert_eq!( | |
380 canonical_path(&root, Path::new(""), &under_repo_symlink), | |
381 Ok(PathBuf::from("d")) | |
382 ); | |
383 } | |
384 } |