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