##// END OF EJS Templates
rhg: make output of `files` relative to the current directory and the root...
Raphaël Gomès -
r46007:1b319704 default
parent child Browse files
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_bytes())?;
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