##// END OF EJS Templates
dirstate-tree: Make Rust DirstateMap bindings go through a trait object...
Simon Sapin -
r47863:787ff5d2 default
parent child Browse files
Show More
@@ -0,0 +1,1 b''
1 pub mod dispatch;
@@ -0,0 +1,310 b''
1 use std::collections::HashSet;
2 use std::path::PathBuf;
3 use std::time::Duration;
4
5 use crate::matchers::Matcher;
6 use crate::utils::hg_path::{HgPath, HgPathBuf};
7 use crate::CopyMapIter;
8 use crate::DirstateEntry;
9 use crate::DirstateError;
10 use crate::DirstateMap;
11 use crate::DirstateMapError;
12 use crate::DirstateParents;
13 use crate::DirstateStatus;
14 use crate::EntryState;
15 use crate::FastHashMap;
16 use crate::HgPathCow;
17 use crate::PatternFileWarning;
18 use crate::StateMapIter;
19 use crate::StatusError;
20 use crate::StatusOptions;
21
22 pub trait DirstateMapMethods {
23 fn clear(&mut self);
24
25 fn add_file(
26 &mut self,
27 filename: &HgPath,
28 old_state: EntryState,
29 entry: DirstateEntry,
30 ) -> Result<(), DirstateMapError>;
31
32 fn remove_file(
33 &mut self,
34 filename: &HgPath,
35 old_state: EntryState,
36 size: i32,
37 ) -> Result<(), DirstateMapError>;
38
39 fn drop_file(
40 &mut self,
41 filename: &HgPath,
42 old_state: EntryState,
43 ) -> Result<bool, DirstateMapError>;
44
45 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32);
46
47 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool;
48
49 fn non_normal_entries_union(
50 &mut self,
51 other: HashSet<HgPathBuf>,
52 ) -> Vec<HgPathBuf>;
53
54 fn set_non_normal_other_parent_entries(&mut self, force: bool);
55
56 fn get_non_normal_other_parent_entries_panic(
57 &self,
58 ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>);
59
60 fn get_non_normal_other_parent_entries(
61 &mut self,
62 ) -> (&mut HashSet<HgPathBuf>, &mut HashSet<HgPathBuf>);
63
64 fn has_tracked_dir(
65 &mut self,
66 directory: &HgPath,
67 ) -> Result<bool, DirstateMapError>;
68
69 fn has_dir(
70 &mut self,
71 directory: &HgPath,
72 ) -> Result<bool, DirstateMapError>;
73
74 fn parents(
75 &mut self,
76 file_contents: &[u8],
77 ) -> Result<&DirstateParents, DirstateError>;
78
79 fn set_parents(&mut self, parents: &DirstateParents);
80
81 fn read<'a>(
82 &mut self,
83 file_contents: &'a [u8],
84 ) -> Result<Option<&'a DirstateParents>, DirstateError>;
85
86 fn pack(
87 &mut self,
88 parents: DirstateParents,
89 now: Duration,
90 ) -> Result<Vec<u8>, DirstateError>;
91
92 fn build_file_fold_map(&mut self) -> &FastHashMap<HgPathBuf, HgPathBuf>;
93
94 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError>;
95
96 fn set_dirs(&mut self) -> Result<(), DirstateMapError>;
97
98 fn status<'a>(
99 &'a self,
100 matcher: &'a (dyn Matcher + Sync),
101 root_dir: PathBuf,
102 ignore_files: Vec<PathBuf>,
103 options: StatusOptions,
104 ) -> Result<
105 (
106 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
107 Vec<PatternFileWarning>,
108 ),
109 StatusError,
110 >;
111
112 fn copy_map_len(&self) -> usize;
113
114 fn copy_map_iter(&self) -> CopyMapIter<'_>;
115
116 fn copy_map_contains_key(&self, key: &HgPath) -> bool;
117
118 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf>;
119
120 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf>;
121
122 fn copy_map_insert(
123 &mut self,
124 key: HgPathBuf,
125 value: HgPathBuf,
126 ) -> Option<HgPathBuf>;
127
128 fn len(&self) -> usize;
129
130 fn contains_key(&self, key: &HgPath) -> bool;
131
132 fn get(&self, key: &HgPath) -> Option<&DirstateEntry>;
133
134 fn iter(&self) -> StateMapIter<'_>;
135 }
136
137 impl DirstateMapMethods for DirstateMap {
138 fn clear(&mut self) {
139 self.clear()
140 }
141
142 fn add_file(
143 &mut self,
144 filename: &HgPath,
145 old_state: EntryState,
146 entry: DirstateEntry,
147 ) -> Result<(), DirstateMapError> {
148 self.add_file(filename, old_state, entry)
149 }
150
151 fn remove_file(
152 &mut self,
153 filename: &HgPath,
154 old_state: EntryState,
155 size: i32,
156 ) -> Result<(), DirstateMapError> {
157 self.remove_file(filename, old_state, size)
158 }
159
160 fn drop_file(
161 &mut self,
162 filename: &HgPath,
163 old_state: EntryState,
164 ) -> Result<bool, DirstateMapError> {
165 self.drop_file(filename, old_state)
166 }
167
168 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
169 self.clear_ambiguous_times(filenames, now)
170 }
171
172 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
173 self.non_normal_entries_remove(key)
174 }
175
176 fn non_normal_entries_union(
177 &mut self,
178 other: HashSet<HgPathBuf>,
179 ) -> Vec<HgPathBuf> {
180 self.non_normal_entries_union(other)
181 }
182
183 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
184 self.set_non_normal_other_parent_entries(force)
185 }
186
187 fn get_non_normal_other_parent_entries_panic(
188 &self,
189 ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) {
190 self.get_non_normal_other_parent_entries_panic()
191 }
192
193 fn get_non_normal_other_parent_entries(
194 &mut self,
195 ) -> (&mut HashSet<HgPathBuf>, &mut HashSet<HgPathBuf>) {
196 self.get_non_normal_other_parent_entries()
197 }
198
199 fn has_tracked_dir(
200 &mut self,
201 directory: &HgPath,
202 ) -> Result<bool, DirstateMapError> {
203 self.has_tracked_dir(directory)
204 }
205
206 fn has_dir(
207 &mut self,
208 directory: &HgPath,
209 ) -> Result<bool, DirstateMapError> {
210 self.has_dir(directory)
211 }
212
213 fn parents(
214 &mut self,
215 file_contents: &[u8],
216 ) -> Result<&DirstateParents, DirstateError> {
217 self.parents(file_contents)
218 }
219
220 fn set_parents(&mut self, parents: &DirstateParents) {
221 self.set_parents(parents)
222 }
223
224 fn read<'a>(
225 &mut self,
226 file_contents: &'a [u8],
227 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
228 self.read(file_contents)
229 }
230
231 fn pack(
232 &mut self,
233 parents: DirstateParents,
234 now: Duration,
235 ) -> Result<Vec<u8>, DirstateError> {
236 self.pack(parents, now)
237 }
238
239 fn build_file_fold_map(&mut self) -> &FastHashMap<HgPathBuf, HgPathBuf> {
240 self.build_file_fold_map()
241 }
242
243 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
244 self.set_all_dirs()
245 }
246
247 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
248 self.set_dirs()
249 }
250
251 fn status<'a>(
252 &'a self,
253 matcher: &'a (dyn Matcher + Sync),
254 root_dir: PathBuf,
255 ignore_files: Vec<PathBuf>,
256 options: StatusOptions,
257 ) -> Result<
258 (
259 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
260 Vec<PatternFileWarning>,
261 ),
262 StatusError,
263 > {
264 crate::status(self, matcher, root_dir, ignore_files, options)
265 }
266
267 fn copy_map_len(&self) -> usize {
268 self.copy_map.len()
269 }
270
271 fn copy_map_iter(&self) -> CopyMapIter<'_> {
272 Box::new(self.copy_map.iter())
273 }
274
275 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
276 self.copy_map.contains_key(key)
277 }
278
279 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
280 self.copy_map.get(key)
281 }
282
283 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
284 self.copy_map.remove(key)
285 }
286
287 fn copy_map_insert(
288 &mut self,
289 key: HgPathBuf,
290 value: HgPathBuf,
291 ) -> Option<HgPathBuf> {
292 self.copy_map.insert(key, value)
293 }
294
295 fn len(&self) -> usize {
296 (&**self).len()
297 }
298
299 fn contains_key(&self, key: &HgPath) -> bool {
300 (&**self).contains_key(key)
301 }
302
303 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
304 (&**self).get(key)
305 }
306
307 fn iter(&self) -> StateMapIter<'_> {
308 Box::new((&**self).iter())
309 }
310 }
@@ -1,96 +1,97 b''
1 // dirstate module
1 // dirstate module
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@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 crate::errors::HgError;
8 use crate::errors::HgError;
9 use crate::revlog::Node;
9 use crate::revlog::Node;
10 use crate::{utils::hg_path::HgPathBuf, FastHashMap};
10 use crate::{utils::hg_path::HgPathBuf, FastHashMap};
11 use bytes_cast::{unaligned, BytesCast};
11 use bytes_cast::{unaligned, BytesCast};
12 use std::collections::hash_map;
13 use std::convert::TryFrom;
12 use std::convert::TryFrom;
14
13
15 pub mod dirs_multiset;
14 pub mod dirs_multiset;
16 pub mod dirstate_map;
15 pub mod dirstate_map;
17 pub mod parsers;
16 pub mod parsers;
18 pub mod status;
17 pub mod status;
19
18
20 #[derive(Debug, PartialEq, Clone, BytesCast)]
19 #[derive(Debug, PartialEq, Clone, BytesCast)]
21 #[repr(C)]
20 #[repr(C)]
22 pub struct DirstateParents {
21 pub struct DirstateParents {
23 pub p1: Node,
22 pub p1: Node,
24 pub p2: Node,
23 pub p2: Node,
25 }
24 }
26
25
27 /// The C implementation uses all signed types. This will be an issue
26 /// The C implementation uses all signed types. This will be an issue
28 /// either when 4GB+ source files are commonplace or in 2038, whichever
27 /// either when 4GB+ source files are commonplace or in 2038, whichever
29 /// comes first.
28 /// comes first.
30 #[derive(Debug, PartialEq, Copy, Clone)]
29 #[derive(Debug, PartialEq, Copy, Clone)]
31 pub struct DirstateEntry {
30 pub struct DirstateEntry {
32 pub state: EntryState,
31 pub state: EntryState,
33 pub mode: i32,
32 pub mode: i32,
34 pub mtime: i32,
33 pub mtime: i32,
35 pub size: i32,
34 pub size: i32,
36 }
35 }
37
36
38 #[derive(BytesCast)]
37 #[derive(BytesCast)]
39 #[repr(C)]
38 #[repr(C)]
40 struct RawEntry {
39 struct RawEntry {
41 state: u8,
40 state: u8,
42 mode: unaligned::I32Be,
41 mode: unaligned::I32Be,
43 size: unaligned::I32Be,
42 size: unaligned::I32Be,
44 mtime: unaligned::I32Be,
43 mtime: unaligned::I32Be,
45 length: unaligned::I32Be,
44 length: unaligned::I32Be,
46 }
45 }
47
46
48 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
47 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
49 /// other parent. This allows revert to pick the right status back during a
48 /// other parent. This allows revert to pick the right status back during a
50 /// merge.
49 /// merge.
51 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
50 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
52
51
53 pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>;
52 pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>;
54 pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>;
53 pub type StateMapIter<'a> =
54 Box<dyn Iterator<Item = (&'a HgPathBuf, &'a DirstateEntry)> + Send + 'a>;
55
55
56 pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>;
56 pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>;
57 pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>;
57 pub type CopyMapIter<'a> =
58 Box<dyn Iterator<Item = (&'a HgPathBuf, &'a HgPathBuf)> + Send + 'a>;
58
59
59 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
60 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
60 pub enum EntryState {
61 pub enum EntryState {
61 Normal,
62 Normal,
62 Added,
63 Added,
63 Removed,
64 Removed,
64 Merged,
65 Merged,
65 Unknown,
66 Unknown,
66 }
67 }
67
68
68 impl TryFrom<u8> for EntryState {
69 impl TryFrom<u8> for EntryState {
69 type Error = HgError;
70 type Error = HgError;
70
71
71 fn try_from(value: u8) -> Result<Self, Self::Error> {
72 fn try_from(value: u8) -> Result<Self, Self::Error> {
72 match value {
73 match value {
73 b'n' => Ok(EntryState::Normal),
74 b'n' => Ok(EntryState::Normal),
74 b'a' => Ok(EntryState::Added),
75 b'a' => Ok(EntryState::Added),
75 b'r' => Ok(EntryState::Removed),
76 b'r' => Ok(EntryState::Removed),
76 b'm' => Ok(EntryState::Merged),
77 b'm' => Ok(EntryState::Merged),
77 b'?' => Ok(EntryState::Unknown),
78 b'?' => Ok(EntryState::Unknown),
78 _ => Err(HgError::CorruptedRepository(format!(
79 _ => Err(HgError::CorruptedRepository(format!(
79 "Incorrect dirstate entry state {}",
80 "Incorrect dirstate entry state {}",
80 value
81 value
81 ))),
82 ))),
82 }
83 }
83 }
84 }
84 }
85 }
85
86
86 impl Into<u8> for EntryState {
87 impl Into<u8> for EntryState {
87 fn into(self) -> u8 {
88 fn into(self) -> u8 {
88 match self {
89 match self {
89 EntryState::Normal => b'n',
90 EntryState::Normal => b'n',
90 EntryState::Added => b'a',
91 EntryState::Added => b'a',
91 EntryState::Removed => b'r',
92 EntryState::Removed => b'r',
92 EntryState::Merged => b'm',
93 EntryState::Merged => b'm',
93 EntryState::Unknown => b'?',
94 EntryState::Unknown => b'?',
94 }
95 }
95 }
96 }
96 }
97 }
@@ -1,421 +1,422 b''
1 // dirs_multiset.rs
1 // dirs_multiset.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@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 //! A multiset of directory names.
8 //! A multiset of directory names.
9 //!
9 //!
10 //! Used to counts the references to directories in a manifest or dirstate.
10 //! Used to counts the references to directories in a manifest or dirstate.
11 use crate::{
11 use crate::{
12 dirstate::EntryState,
12 dirstate::EntryState,
13 utils::{
13 utils::{
14 files,
14 files,
15 hg_path::{HgPath, HgPathBuf, HgPathError},
15 hg_path::{HgPath, HgPathBuf, HgPathError},
16 },
16 },
17 DirstateEntry, DirstateMapError, FastHashMap, StateMap,
17 DirstateEntry, DirstateMapError, FastHashMap,
18 };
18 };
19 use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet};
19 use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet};
20
20
21 // could be encapsulated if we care API stability more seriously
21 // could be encapsulated if we care API stability more seriously
22 pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>;
22 pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>;
23
23
24 #[derive(PartialEq, Debug)]
24 #[derive(PartialEq, Debug)]
25 pub struct DirsMultiset {
25 pub struct DirsMultiset {
26 inner: FastHashMap<HgPathBuf, u32>,
26 inner: FastHashMap<HgPathBuf, u32>,
27 }
27 }
28
28
29 impl DirsMultiset {
29 impl DirsMultiset {
30 /// Initializes the multiset from a dirstate.
30 /// Initializes the multiset from a dirstate.
31 ///
31 ///
32 /// If `skip_state` is provided, skips dirstate entries with equal state.
32 /// If `skip_state` is provided, skips dirstate entries with equal state.
33 pub fn from_dirstate(
33 pub fn from_dirstate<'a>(
34 dirstate: &StateMap,
34 dirstate: impl IntoIterator<Item = (&'a HgPathBuf, &'a DirstateEntry)>,
35 skip_state: Option<EntryState>,
35 skip_state: Option<EntryState>,
36 ) -> Result<Self, DirstateMapError> {
36 ) -> Result<Self, DirstateMapError> {
37 let mut multiset = DirsMultiset {
37 let mut multiset = DirsMultiset {
38 inner: FastHashMap::default(),
38 inner: FastHashMap::default(),
39 };
39 };
40 for (filename, DirstateEntry { state, .. }) in dirstate.iter() {
40 for (filename, DirstateEntry { state, .. }) in dirstate {
41 // This `if` is optimized out of the loop
41 // This `if` is optimized out of the loop
42 if let Some(skip) = skip_state {
42 if let Some(skip) = skip_state {
43 if skip != *state {
43 if skip != *state {
44 multiset.add_path(filename)?;
44 multiset.add_path(filename)?;
45 }
45 }
46 } else {
46 } else {
47 multiset.add_path(filename)?;
47 multiset.add_path(filename)?;
48 }
48 }
49 }
49 }
50
50
51 Ok(multiset)
51 Ok(multiset)
52 }
52 }
53
53
54 /// Initializes the multiset from a manifest.
54 /// Initializes the multiset from a manifest.
55 pub fn from_manifest(
55 pub fn from_manifest(
56 manifest: &[impl AsRef<HgPath>],
56 manifest: &[impl AsRef<HgPath>],
57 ) -> Result<Self, DirstateMapError> {
57 ) -> Result<Self, DirstateMapError> {
58 let mut multiset = DirsMultiset {
58 let mut multiset = DirsMultiset {
59 inner: FastHashMap::default(),
59 inner: FastHashMap::default(),
60 };
60 };
61
61
62 for filename in manifest {
62 for filename in manifest {
63 multiset.add_path(filename.as_ref())?;
63 multiset.add_path(filename.as_ref())?;
64 }
64 }
65
65
66 Ok(multiset)
66 Ok(multiset)
67 }
67 }
68
68
69 /// Increases the count of deepest directory contained in the path.
69 /// Increases the count of deepest directory contained in the path.
70 ///
70 ///
71 /// If the directory is not yet in the map, adds its parents.
71 /// If the directory is not yet in the map, adds its parents.
72 pub fn add_path(
72 pub fn add_path(
73 &mut self,
73 &mut self,
74 path: impl AsRef<HgPath>,
74 path: impl AsRef<HgPath>,
75 ) -> Result<(), DirstateMapError> {
75 ) -> Result<(), DirstateMapError> {
76 for subpath in files::find_dirs(path.as_ref()) {
76 for subpath in files::find_dirs(path.as_ref()) {
77 if subpath.as_bytes().last() == Some(&b'/') {
77 if subpath.as_bytes().last() == Some(&b'/') {
78 // TODO Remove this once PathAuditor is certified
78 // TODO Remove this once PathAuditor is certified
79 // as the only entrypoint for path data
79 // as the only entrypoint for path data
80 let second_slash_index = subpath.len() - 1;
80 let second_slash_index = subpath.len() - 1;
81
81
82 return Err(DirstateMapError::InvalidPath(
82 return Err(DirstateMapError::InvalidPath(
83 HgPathError::ConsecutiveSlashes {
83 HgPathError::ConsecutiveSlashes {
84 bytes: path.as_ref().as_bytes().to_owned(),
84 bytes: path.as_ref().as_bytes().to_owned(),
85 second_slash_index,
85 second_slash_index,
86 },
86 },
87 ));
87 ));
88 }
88 }
89 if let Some(val) = self.inner.get_mut(subpath) {
89 if let Some(val) = self.inner.get_mut(subpath) {
90 *val += 1;
90 *val += 1;
91 break;
91 break;
92 }
92 }
93 self.inner.insert(subpath.to_owned(), 1);
93 self.inner.insert(subpath.to_owned(), 1);
94 }
94 }
95 Ok(())
95 Ok(())
96 }
96 }
97
97
98 /// Decreases the count of deepest directory contained in the path.
98 /// Decreases the count of deepest directory contained in the path.
99 ///
99 ///
100 /// If it is the only reference, decreases all parents until one is
100 /// If it is the only reference, decreases all parents until one is
101 /// removed.
101 /// removed.
102 /// If the directory is not in the map, something horrible has happened.
102 /// If the directory is not in the map, something horrible has happened.
103 pub fn delete_path(
103 pub fn delete_path(
104 &mut self,
104 &mut self,
105 path: impl AsRef<HgPath>,
105 path: impl AsRef<HgPath>,
106 ) -> Result<(), DirstateMapError> {
106 ) -> Result<(), DirstateMapError> {
107 for subpath in files::find_dirs(path.as_ref()) {
107 for subpath in files::find_dirs(path.as_ref()) {
108 match self.inner.entry(subpath.to_owned()) {
108 match self.inner.entry(subpath.to_owned()) {
109 Entry::Occupied(mut entry) => {
109 Entry::Occupied(mut entry) => {
110 let val = *entry.get();
110 let val = *entry.get();
111 if val > 1 {
111 if val > 1 {
112 entry.insert(val - 1);
112 entry.insert(val - 1);
113 break;
113 break;
114 }
114 }
115 entry.remove();
115 entry.remove();
116 }
116 }
117 Entry::Vacant(_) => {
117 Entry::Vacant(_) => {
118 return Err(DirstateMapError::PathNotFound(
118 return Err(DirstateMapError::PathNotFound(
119 path.as_ref().to_owned(),
119 path.as_ref().to_owned(),
120 ))
120 ))
121 }
121 }
122 };
122 };
123 }
123 }
124
124
125 Ok(())
125 Ok(())
126 }
126 }
127
127
128 pub fn contains(&self, key: impl AsRef<HgPath>) -> bool {
128 pub fn contains(&self, key: impl AsRef<HgPath>) -> bool {
129 self.inner.contains_key(key.as_ref())
129 self.inner.contains_key(key.as_ref())
130 }
130 }
131
131
132 pub fn iter(&self) -> DirsMultisetIter {
132 pub fn iter(&self) -> DirsMultisetIter {
133 self.inner.keys()
133 self.inner.keys()
134 }
134 }
135
135
136 pub fn len(&self) -> usize {
136 pub fn len(&self) -> usize {
137 self.inner.len()
137 self.inner.len()
138 }
138 }
139
139
140 pub fn is_empty(&self) -> bool {
140 pub fn is_empty(&self) -> bool {
141 self.len() == 0
141 self.len() == 0
142 }
142 }
143 }
143 }
144
144
145 /// This is basically a reimplementation of `DirsMultiset` that stores the
145 /// This is basically a reimplementation of `DirsMultiset` that stores the
146 /// children instead of just a count of them, plus a small optional
146 /// children instead of just a count of them, plus a small optional
147 /// optimization to avoid some directories we don't need.
147 /// optimization to avoid some directories we don't need.
148 #[derive(PartialEq, Debug)]
148 #[derive(PartialEq, Debug)]
149 pub struct DirsChildrenMultiset<'a> {
149 pub struct DirsChildrenMultiset<'a> {
150 inner: FastHashMap<&'a HgPath, HashSet<&'a HgPath>>,
150 inner: FastHashMap<&'a HgPath, HashSet<&'a HgPath>>,
151 only_include: Option<HashSet<&'a HgPath>>,
151 only_include: Option<HashSet<&'a HgPath>>,
152 }
152 }
153
153
154 impl<'a> DirsChildrenMultiset<'a> {
154 impl<'a> DirsChildrenMultiset<'a> {
155 pub fn new(
155 pub fn new(
156 paths: impl Iterator<Item = &'a HgPathBuf>,
156 paths: impl Iterator<Item = &'a HgPathBuf>,
157 only_include: Option<&'a HashSet<impl AsRef<HgPath> + 'a>>,
157 only_include: Option<&'a HashSet<impl AsRef<HgPath> + 'a>>,
158 ) -> Self {
158 ) -> Self {
159 let mut new = Self {
159 let mut new = Self {
160 inner: HashMap::default(),
160 inner: HashMap::default(),
161 only_include: only_include
161 only_include: only_include
162 .map(|s| s.iter().map(AsRef::as_ref).collect()),
162 .map(|s| s.iter().map(AsRef::as_ref).collect()),
163 };
163 };
164
164
165 for path in paths {
165 for path in paths {
166 new.add_path(path)
166 new.add_path(path)
167 }
167 }
168
168
169 new
169 new
170 }
170 }
171 fn add_path(&mut self, path: &'a (impl AsRef<HgPath> + 'a)) {
171 fn add_path(&mut self, path: &'a (impl AsRef<HgPath> + 'a)) {
172 if path.as_ref().is_empty() {
172 if path.as_ref().is_empty() {
173 return;
173 return;
174 }
174 }
175 for (directory, basename) in files::find_dirs_with_base(path.as_ref())
175 for (directory, basename) in files::find_dirs_with_base(path.as_ref())
176 {
176 {
177 if !self.is_dir_included(directory) {
177 if !self.is_dir_included(directory) {
178 continue;
178 continue;
179 }
179 }
180 self.inner
180 self.inner
181 .entry(directory)
181 .entry(directory)
182 .and_modify(|e| {
182 .and_modify(|e| {
183 e.insert(basename);
183 e.insert(basename);
184 })
184 })
185 .or_insert_with(|| {
185 .or_insert_with(|| {
186 let mut set = HashSet::new();
186 let mut set = HashSet::new();
187 set.insert(basename);
187 set.insert(basename);
188 set
188 set
189 });
189 });
190 }
190 }
191 }
191 }
192 fn is_dir_included(&self, dir: impl AsRef<HgPath>) -> bool {
192 fn is_dir_included(&self, dir: impl AsRef<HgPath>) -> bool {
193 match &self.only_include {
193 match &self.only_include {
194 None => false,
194 None => false,
195 Some(i) => i.contains(dir.as_ref()),
195 Some(i) => i.contains(dir.as_ref()),
196 }
196 }
197 }
197 }
198
198
199 pub fn get(
199 pub fn get(
200 &self,
200 &self,
201 path: impl AsRef<HgPath>,
201 path: impl AsRef<HgPath>,
202 ) -> Option<&HashSet<&'a HgPath>> {
202 ) -> Option<&HashSet<&'a HgPath>> {
203 self.inner.get(path.as_ref())
203 self.inner.get(path.as_ref())
204 }
204 }
205 }
205 }
206
206
207 #[cfg(test)]
207 #[cfg(test)]
208 mod tests {
208 mod tests {
209 use super::*;
209 use super::*;
210 use crate::StateMap;
210
211
211 #[test]
212 #[test]
212 fn test_delete_path_path_not_found() {
213 fn test_delete_path_path_not_found() {
213 let manifest: Vec<HgPathBuf> = vec![];
214 let manifest: Vec<HgPathBuf> = vec![];
214 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
215 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
215 let path = HgPathBuf::from_bytes(b"doesnotexist/");
216 let path = HgPathBuf::from_bytes(b"doesnotexist/");
216 assert_eq!(
217 assert_eq!(
217 Err(DirstateMapError::PathNotFound(path.to_owned())),
218 Err(DirstateMapError::PathNotFound(path.to_owned())),
218 map.delete_path(&path)
219 map.delete_path(&path)
219 );
220 );
220 }
221 }
221
222
222 #[test]
223 #[test]
223 fn test_delete_path_empty_path() {
224 fn test_delete_path_empty_path() {
224 let mut map =
225 let mut map =
225 DirsMultiset::from_manifest(&vec![HgPathBuf::new()]).unwrap();
226 DirsMultiset::from_manifest(&vec![HgPathBuf::new()]).unwrap();
226 let path = HgPath::new(b"");
227 let path = HgPath::new(b"");
227 assert_eq!(Ok(()), map.delete_path(path));
228 assert_eq!(Ok(()), map.delete_path(path));
228 assert_eq!(
229 assert_eq!(
229 Err(DirstateMapError::PathNotFound(path.to_owned())),
230 Err(DirstateMapError::PathNotFound(path.to_owned())),
230 map.delete_path(path)
231 map.delete_path(path)
231 );
232 );
232 }
233 }
233
234
234 #[test]
235 #[test]
235 fn test_delete_path_successful() {
236 fn test_delete_path_successful() {
236 let mut map = DirsMultiset {
237 let mut map = DirsMultiset {
237 inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)]
238 inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)]
238 .iter()
239 .iter()
239 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
240 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
240 .collect(),
241 .collect(),
241 };
242 };
242
243
243 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
244 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
244 eprintln!("{:?}", map);
245 eprintln!("{:?}", map);
245 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
246 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
246 eprintln!("{:?}", map);
247 eprintln!("{:?}", map);
247 assert_eq!(
248 assert_eq!(
248 Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
249 Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
249 b"a/b/"
250 b"a/b/"
250 ))),
251 ))),
251 map.delete_path(HgPath::new(b"a/b/"))
252 map.delete_path(HgPath::new(b"a/b/"))
252 );
253 );
253
254
254 assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
255 assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
255 assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
256 assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
256 eprintln!("{:?}", map);
257 eprintln!("{:?}", map);
257 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/")));
258 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/")));
258 eprintln!("{:?}", map);
259 eprintln!("{:?}", map);
259
260
260 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/c/")));
261 assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/c/")));
261 assert_eq!(
262 assert_eq!(
262 Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
263 Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
263 b"a/c/"
264 b"a/c/"
264 ))),
265 ))),
265 map.delete_path(HgPath::new(b"a/c/"))
266 map.delete_path(HgPath::new(b"a/c/"))
266 );
267 );
267 }
268 }
268
269
269 #[test]
270 #[test]
270 fn test_add_path_empty_path() {
271 fn test_add_path_empty_path() {
271 let manifest: Vec<HgPathBuf> = vec![];
272 let manifest: Vec<HgPathBuf> = vec![];
272 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
273 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
273 let path = HgPath::new(b"");
274 let path = HgPath::new(b"");
274 map.add_path(path).unwrap();
275 map.add_path(path).unwrap();
275
276
276 assert_eq!(1, map.len());
277 assert_eq!(1, map.len());
277 }
278 }
278
279
279 #[test]
280 #[test]
280 fn test_add_path_successful() {
281 fn test_add_path_successful() {
281 let manifest: Vec<HgPathBuf> = vec![];
282 let manifest: Vec<HgPathBuf> = vec![];
282 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
283 let mut map = DirsMultiset::from_manifest(&manifest).unwrap();
283
284
284 map.add_path(HgPath::new(b"a/")).unwrap();
285 map.add_path(HgPath::new(b"a/")).unwrap();
285 assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
286 assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
286 assert_eq!(1, *map.inner.get(HgPath::new(b"")).unwrap());
287 assert_eq!(1, *map.inner.get(HgPath::new(b"")).unwrap());
287 assert_eq!(2, map.len());
288 assert_eq!(2, map.len());
288
289
289 // Non directory should be ignored
290 // Non directory should be ignored
290 map.add_path(HgPath::new(b"a")).unwrap();
291 map.add_path(HgPath::new(b"a")).unwrap();
291 assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
292 assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
292 assert_eq!(2, map.len());
293 assert_eq!(2, map.len());
293
294
294 // Non directory will still add its base
295 // Non directory will still add its base
295 map.add_path(HgPath::new(b"a/b")).unwrap();
296 map.add_path(HgPath::new(b"a/b")).unwrap();
296 assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
297 assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
297 assert_eq!(2, map.len());
298 assert_eq!(2, map.len());
298
299
299 // Duplicate path works
300 // Duplicate path works
300 map.add_path(HgPath::new(b"a/")).unwrap();
301 map.add_path(HgPath::new(b"a/")).unwrap();
301 assert_eq!(3, *map.inner.get(HgPath::new(b"a")).unwrap());
302 assert_eq!(3, *map.inner.get(HgPath::new(b"a")).unwrap());
302
303
303 // Nested dir adds to its base
304 // Nested dir adds to its base
304 map.add_path(HgPath::new(b"a/b/")).unwrap();
305 map.add_path(HgPath::new(b"a/b/")).unwrap();
305 assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
306 assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
306 assert_eq!(1, *map.inner.get(HgPath::new(b"a/b")).unwrap());
307 assert_eq!(1, *map.inner.get(HgPath::new(b"a/b")).unwrap());
307
308
308 // but not its base's base, because it already existed
309 // but not its base's base, because it already existed
309 map.add_path(HgPath::new(b"a/b/c/")).unwrap();
310 map.add_path(HgPath::new(b"a/b/c/")).unwrap();
310 assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
311 assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
311 assert_eq!(2, *map.inner.get(HgPath::new(b"a/b")).unwrap());
312 assert_eq!(2, *map.inner.get(HgPath::new(b"a/b")).unwrap());
312
313
313 map.add_path(HgPath::new(b"a/c/")).unwrap();
314 map.add_path(HgPath::new(b"a/c/")).unwrap();
314 assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
315 assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
315
316
316 let expected = DirsMultiset {
317 let expected = DirsMultiset {
317 inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)]
318 inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)]
318 .iter()
319 .iter()
319 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
320 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
320 .collect(),
321 .collect(),
321 };
322 };
322 assert_eq!(map, expected);
323 assert_eq!(map, expected);
323 }
324 }
324
325
325 #[test]
326 #[test]
326 fn test_dirsmultiset_new_empty() {
327 fn test_dirsmultiset_new_empty() {
327 let manifest: Vec<HgPathBuf> = vec![];
328 let manifest: Vec<HgPathBuf> = vec![];
328 let new = DirsMultiset::from_manifest(&manifest).unwrap();
329 let new = DirsMultiset::from_manifest(&manifest).unwrap();
329 let expected = DirsMultiset {
330 let expected = DirsMultiset {
330 inner: FastHashMap::default(),
331 inner: FastHashMap::default(),
331 };
332 };
332 assert_eq!(expected, new);
333 assert_eq!(expected, new);
333
334
334 let new =
335 let new =
335 DirsMultiset::from_dirstate(&StateMap::default(), None).unwrap();
336 DirsMultiset::from_dirstate(&StateMap::default(), None).unwrap();
336 let expected = DirsMultiset {
337 let expected = DirsMultiset {
337 inner: FastHashMap::default(),
338 inner: FastHashMap::default(),
338 };
339 };
339 assert_eq!(expected, new);
340 assert_eq!(expected, new);
340 }
341 }
341
342
342 #[test]
343 #[test]
343 fn test_dirsmultiset_new_no_skip() {
344 fn test_dirsmultiset_new_no_skip() {
344 let input_vec: Vec<HgPathBuf> = ["a/", "b/", "a/c", "a/d/"]
345 let input_vec: Vec<HgPathBuf> = ["a/", "b/", "a/c", "a/d/"]
345 .iter()
346 .iter()
346 .map(|e| HgPathBuf::from_bytes(e.as_bytes()))
347 .map(|e| HgPathBuf::from_bytes(e.as_bytes()))
347 .collect();
348 .collect();
348 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
349 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
349 .iter()
350 .iter()
350 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
351 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
351 .collect();
352 .collect();
352
353
353 let new = DirsMultiset::from_manifest(&input_vec).unwrap();
354 let new = DirsMultiset::from_manifest(&input_vec).unwrap();
354 let expected = DirsMultiset {
355 let expected = DirsMultiset {
355 inner: expected_inner,
356 inner: expected_inner,
356 };
357 };
357 assert_eq!(expected, new);
358 assert_eq!(expected, new);
358
359
359 let input_map = ["b/x", "a/c", "a/d/x"]
360 let input_map: HashMap<_, _> = ["b/x", "a/c", "a/d/x"]
360 .iter()
361 .iter()
361 .map(|f| {
362 .map(|f| {
362 (
363 (
363 HgPathBuf::from_bytes(f.as_bytes()),
364 HgPathBuf::from_bytes(f.as_bytes()),
364 DirstateEntry {
365 DirstateEntry {
365 state: EntryState::Normal,
366 state: EntryState::Normal,
366 mode: 0,
367 mode: 0,
367 mtime: 0,
368 mtime: 0,
368 size: 0,
369 size: 0,
369 },
370 },
370 )
371 )
371 })
372 })
372 .collect();
373 .collect();
373 let expected_inner = [("", 2), ("a", 2), ("b", 1), ("a/d", 1)]
374 let expected_inner = [("", 2), ("a", 2), ("b", 1), ("a/d", 1)]
374 .iter()
375 .iter()
375 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
376 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
376 .collect();
377 .collect();
377
378
378 let new = DirsMultiset::from_dirstate(&input_map, None).unwrap();
379 let new = DirsMultiset::from_dirstate(&input_map, None).unwrap();
379 let expected = DirsMultiset {
380 let expected = DirsMultiset {
380 inner: expected_inner,
381 inner: expected_inner,
381 };
382 };
382 assert_eq!(expected, new);
383 assert_eq!(expected, new);
383 }
384 }
384
385
385 #[test]
386 #[test]
386 fn test_dirsmultiset_new_skip() {
387 fn test_dirsmultiset_new_skip() {
387 let input_map = [
388 let input_map: HashMap<_, _> = [
388 ("a/", EntryState::Normal),
389 ("a/", EntryState::Normal),
389 ("a/b", EntryState::Normal),
390 ("a/b", EntryState::Normal),
390 ("a/c", EntryState::Removed),
391 ("a/c", EntryState::Removed),
391 ("a/d", EntryState::Merged),
392 ("a/d", EntryState::Merged),
392 ]
393 ]
393 .iter()
394 .iter()
394 .map(|(f, state)| {
395 .map(|(f, state)| {
395 (
396 (
396 HgPathBuf::from_bytes(f.as_bytes()),
397 HgPathBuf::from_bytes(f.as_bytes()),
397 DirstateEntry {
398 DirstateEntry {
398 state: *state,
399 state: *state,
399 mode: 0,
400 mode: 0,
400 mtime: 0,
401 mtime: 0,
401 size: 0,
402 size: 0,
402 },
403 },
403 )
404 )
404 })
405 })
405 .collect();
406 .collect();
406
407
407 // "a" incremented with "a/c" and "a/d/"
408 // "a" incremented with "a/c" and "a/d/"
408 let expected_inner = [("", 1), ("a", 2)]
409 let expected_inner = [("", 1), ("a", 2)]
409 .iter()
410 .iter()
410 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
411 .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
411 .collect();
412 .collect();
412
413
413 let new =
414 let new =
414 DirsMultiset::from_dirstate(&input_map, Some(EntryState::Normal))
415 DirsMultiset::from_dirstate(&input_map, Some(EntryState::Normal))
415 .unwrap();
416 .unwrap();
416 let expected = DirsMultiset {
417 let expected = DirsMultiset {
417 inner: expected_inner,
418 inner: expected_inner,
418 };
419 };
419 assert_eq!(expected, new);
420 assert_eq!(expected, new);
420 }
421 }
421 }
422 }
@@ -1,503 +1,505 b''
1 // dirstate_map.rs
1 // dirstate_map.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@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 crate::errors::HgError;
8 use crate::errors::HgError;
9 use crate::revlog::node::NULL_NODE;
9 use crate::revlog::node::NULL_NODE;
10 use crate::{
10 use crate::{
11 dirstate::{parsers::PARENT_SIZE, EntryState, SIZE_FROM_OTHER_PARENT},
11 dirstate::{parsers::PARENT_SIZE, EntryState, SIZE_FROM_OTHER_PARENT},
12 pack_dirstate, parse_dirstate,
12 pack_dirstate, parse_dirstate,
13 utils::{
13 utils::{
14 files::normalize_case,
14 files::normalize_case,
15 hg_path::{HgPath, HgPathBuf},
15 hg_path::{HgPath, HgPathBuf},
16 },
16 },
17 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError,
17 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError,
18 DirstateParents, FastHashMap, StateMap,
18 DirstateParents, FastHashMap, StateMap,
19 };
19 };
20 use micro_timer::timed;
20 use micro_timer::timed;
21 use std::collections::HashSet;
21 use std::collections::HashSet;
22 use std::convert::TryInto;
22 use std::convert::TryInto;
23 use std::iter::FromIterator;
23 use std::iter::FromIterator;
24 use std::ops::Deref;
24 use std::ops::Deref;
25 use std::time::Duration;
25 use std::time::Duration;
26
26
27 pub type FileFoldMap = FastHashMap<HgPathBuf, HgPathBuf>;
27 pub type FileFoldMap = FastHashMap<HgPathBuf, HgPathBuf>;
28
28
29 const MTIME_UNSET: i32 = -1;
29 const MTIME_UNSET: i32 = -1;
30
30
31 #[derive(Default)]
31 #[derive(Default)]
32 pub struct DirstateMap {
32 pub struct DirstateMap {
33 state_map: StateMap,
33 state_map: StateMap,
34 pub copy_map: CopyMap,
34 pub copy_map: CopyMap,
35 file_fold_map: Option<FileFoldMap>,
35 file_fold_map: Option<FileFoldMap>,
36 pub dirs: Option<DirsMultiset>,
36 pub dirs: Option<DirsMultiset>,
37 pub all_dirs: Option<DirsMultiset>,
37 pub all_dirs: Option<DirsMultiset>,
38 non_normal_set: Option<HashSet<HgPathBuf>>,
38 non_normal_set: Option<HashSet<HgPathBuf>>,
39 other_parent_set: Option<HashSet<HgPathBuf>>,
39 other_parent_set: Option<HashSet<HgPathBuf>>,
40 parents: Option<DirstateParents>,
40 parents: Option<DirstateParents>,
41 dirty_parents: bool,
41 dirty_parents: bool,
42 }
42 }
43
43
44 /// Should only really be used in python interface code, for clarity
44 /// Should only really be used in python interface code, for clarity
45 impl Deref for DirstateMap {
45 impl Deref for DirstateMap {
46 type Target = StateMap;
46 type Target = StateMap;
47
47
48 fn deref(&self) -> &Self::Target {
48 fn deref(&self) -> &Self::Target {
49 &self.state_map
49 &self.state_map
50 }
50 }
51 }
51 }
52
52
53 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
53 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
54 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
54 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
55 iter: I,
55 iter: I,
56 ) -> Self {
56 ) -> Self {
57 Self {
57 Self {
58 state_map: iter.into_iter().collect(),
58 state_map: iter.into_iter().collect(),
59 ..Self::default()
59 ..Self::default()
60 }
60 }
61 }
61 }
62 }
62 }
63
63
64 impl DirstateMap {
64 impl DirstateMap {
65 pub fn new() -> Self {
65 pub fn new() -> Self {
66 Self::default()
66 Self::default()
67 }
67 }
68
68
69 pub fn clear(&mut self) {
69 pub fn clear(&mut self) {
70 self.state_map = StateMap::default();
70 self.state_map = StateMap::default();
71 self.copy_map.clear();
71 self.copy_map.clear();
72 self.file_fold_map = None;
72 self.file_fold_map = None;
73 self.non_normal_set = None;
73 self.non_normal_set = None;
74 self.other_parent_set = None;
74 self.other_parent_set = None;
75 self.set_parents(&DirstateParents {
75 self.set_parents(&DirstateParents {
76 p1: NULL_NODE,
76 p1: NULL_NODE,
77 p2: NULL_NODE,
77 p2: NULL_NODE,
78 })
78 })
79 }
79 }
80
80
81 /// Add a tracked file to the dirstate
81 /// Add a tracked file to the dirstate
82 pub fn add_file(
82 pub fn add_file(
83 &mut self,
83 &mut self,
84 filename: &HgPath,
84 filename: &HgPath,
85 old_state: EntryState,
85 old_state: EntryState,
86 entry: DirstateEntry,
86 entry: DirstateEntry,
87 ) -> Result<(), DirstateMapError> {
87 ) -> Result<(), DirstateMapError> {
88 if old_state == EntryState::Unknown || old_state == EntryState::Removed
88 if old_state == EntryState::Unknown || old_state == EntryState::Removed
89 {
89 {
90 if let Some(ref mut dirs) = self.dirs {
90 if let Some(ref mut dirs) = self.dirs {
91 dirs.add_path(filename)?;
91 dirs.add_path(filename)?;
92 }
92 }
93 }
93 }
94 if old_state == EntryState::Unknown {
94 if old_state == EntryState::Unknown {
95 if let Some(ref mut all_dirs) = self.all_dirs {
95 if let Some(ref mut all_dirs) = self.all_dirs {
96 all_dirs.add_path(filename)?;
96 all_dirs.add_path(filename)?;
97 }
97 }
98 }
98 }
99 self.state_map.insert(filename.to_owned(), entry.to_owned());
99 self.state_map.insert(filename.to_owned(), entry.to_owned());
100
100
101 if entry.state != EntryState::Normal || entry.mtime == MTIME_UNSET {
101 if entry.state != EntryState::Normal || entry.mtime == MTIME_UNSET {
102 self.get_non_normal_other_parent_entries()
102 self.get_non_normal_other_parent_entries()
103 .0
103 .0
104 .insert(filename.to_owned());
104 .insert(filename.to_owned());
105 }
105 }
106
106
107 if entry.size == SIZE_FROM_OTHER_PARENT {
107 if entry.size == SIZE_FROM_OTHER_PARENT {
108 self.get_non_normal_other_parent_entries()
108 self.get_non_normal_other_parent_entries()
109 .1
109 .1
110 .insert(filename.to_owned());
110 .insert(filename.to_owned());
111 }
111 }
112 Ok(())
112 Ok(())
113 }
113 }
114
114
115 /// Mark a file as removed in the dirstate.
115 /// Mark a file as removed in the dirstate.
116 ///
116 ///
117 /// The `size` parameter is used to store sentinel values that indicate
117 /// The `size` parameter is used to store sentinel values that indicate
118 /// the file's previous state. In the future, we should refactor this
118 /// the file's previous state. In the future, we should refactor this
119 /// to be more explicit about what that state is.
119 /// to be more explicit about what that state is.
120 pub fn remove_file(
120 pub fn remove_file(
121 &mut self,
121 &mut self,
122 filename: &HgPath,
122 filename: &HgPath,
123 old_state: EntryState,
123 old_state: EntryState,
124 size: i32,
124 size: i32,
125 ) -> Result<(), DirstateMapError> {
125 ) -> Result<(), DirstateMapError> {
126 if old_state != EntryState::Unknown && old_state != EntryState::Removed
126 if old_state != EntryState::Unknown && old_state != EntryState::Removed
127 {
127 {
128 if let Some(ref mut dirs) = self.dirs {
128 if let Some(ref mut dirs) = self.dirs {
129 dirs.delete_path(filename)?;
129 dirs.delete_path(filename)?;
130 }
130 }
131 }
131 }
132 if old_state == EntryState::Unknown {
132 if old_state == EntryState::Unknown {
133 if let Some(ref mut all_dirs) = self.all_dirs {
133 if let Some(ref mut all_dirs) = self.all_dirs {
134 all_dirs.add_path(filename)?;
134 all_dirs.add_path(filename)?;
135 }
135 }
136 }
136 }
137
137
138 if let Some(ref mut file_fold_map) = self.file_fold_map {
138 if let Some(ref mut file_fold_map) = self.file_fold_map {
139 file_fold_map.remove(&normalize_case(filename));
139 file_fold_map.remove(&normalize_case(filename));
140 }
140 }
141 self.state_map.insert(
141 self.state_map.insert(
142 filename.to_owned(),
142 filename.to_owned(),
143 DirstateEntry {
143 DirstateEntry {
144 state: EntryState::Removed,
144 state: EntryState::Removed,
145 mode: 0,
145 mode: 0,
146 size,
146 size,
147 mtime: 0,
147 mtime: 0,
148 },
148 },
149 );
149 );
150 self.get_non_normal_other_parent_entries()
150 self.get_non_normal_other_parent_entries()
151 .0
151 .0
152 .insert(filename.to_owned());
152 .insert(filename.to_owned());
153 Ok(())
153 Ok(())
154 }
154 }
155
155
156 /// Remove a file from the dirstate.
156 /// Remove a file from the dirstate.
157 /// Returns `true` if the file was previously recorded.
157 /// Returns `true` if the file was previously recorded.
158 pub fn drop_file(
158 pub fn drop_file(
159 &mut self,
159 &mut self,
160 filename: &HgPath,
160 filename: &HgPath,
161 old_state: EntryState,
161 old_state: EntryState,
162 ) -> Result<bool, DirstateMapError> {
162 ) -> Result<bool, DirstateMapError> {
163 let exists = self.state_map.remove(filename).is_some();
163 let exists = self.state_map.remove(filename).is_some();
164
164
165 if exists {
165 if exists {
166 if old_state != EntryState::Removed {
166 if old_state != EntryState::Removed {
167 if let Some(ref mut dirs) = self.dirs {
167 if let Some(ref mut dirs) = self.dirs {
168 dirs.delete_path(filename)?;
168 dirs.delete_path(filename)?;
169 }
169 }
170 }
170 }
171 if let Some(ref mut all_dirs) = self.all_dirs {
171 if let Some(ref mut all_dirs) = self.all_dirs {
172 all_dirs.delete_path(filename)?;
172 all_dirs.delete_path(filename)?;
173 }
173 }
174 }
174 }
175 if let Some(ref mut file_fold_map) = self.file_fold_map {
175 if let Some(ref mut file_fold_map) = self.file_fold_map {
176 file_fold_map.remove(&normalize_case(filename));
176 file_fold_map.remove(&normalize_case(filename));
177 }
177 }
178 self.get_non_normal_other_parent_entries()
178 self.get_non_normal_other_parent_entries()
179 .0
179 .0
180 .remove(filename);
180 .remove(filename);
181
181
182 Ok(exists)
182 Ok(exists)
183 }
183 }
184
184
185 pub fn clear_ambiguous_times(
185 pub fn clear_ambiguous_times(
186 &mut self,
186 &mut self,
187 filenames: Vec<HgPathBuf>,
187 filenames: Vec<HgPathBuf>,
188 now: i32,
188 now: i32,
189 ) {
189 ) {
190 for filename in filenames {
190 for filename in filenames {
191 let mut changed = false;
191 let mut changed = false;
192 if let Some(entry) = self.state_map.get_mut(&filename) {
192 if let Some(entry) = self.state_map.get_mut(&filename) {
193 if entry.state == EntryState::Normal && entry.mtime == now {
193 if entry.state == EntryState::Normal && entry.mtime == now {
194 changed = true;
194 changed = true;
195 *entry = DirstateEntry {
195 *entry = DirstateEntry {
196 mtime: MTIME_UNSET,
196 mtime: MTIME_UNSET,
197 ..*entry
197 ..*entry
198 };
198 };
199 }
199 }
200 }
200 }
201 if changed {
201 if changed {
202 self.get_non_normal_other_parent_entries()
202 self.get_non_normal_other_parent_entries()
203 .0
203 .0
204 .insert(filename.to_owned());
204 .insert(filename.to_owned());
205 }
205 }
206 }
206 }
207 }
207 }
208
208
209 pub fn non_normal_entries_remove(
209 pub fn non_normal_entries_remove(
210 &mut self,
210 &mut self,
211 key: impl AsRef<HgPath>,
211 key: impl AsRef<HgPath>,
212 ) -> bool {
212 ) -> bool {
213 self.get_non_normal_other_parent_entries()
213 self.get_non_normal_other_parent_entries()
214 .0
214 .0
215 .remove(key.as_ref())
215 .remove(key.as_ref())
216 }
216 }
217 pub fn non_normal_entries_union(
217 pub fn non_normal_entries_union(
218 &mut self,
218 &mut self,
219 other: HashSet<HgPathBuf>,
219 other: HashSet<HgPathBuf>,
220 ) -> Vec<HgPathBuf> {
220 ) -> Vec<HgPathBuf> {
221 self.get_non_normal_other_parent_entries()
221 self.get_non_normal_other_parent_entries()
222 .0
222 .0
223 .union(&other)
223 .union(&other)
224 .map(ToOwned::to_owned)
224 .map(ToOwned::to_owned)
225 .collect()
225 .collect()
226 }
226 }
227
227
228 pub fn get_non_normal_other_parent_entries(
228 pub fn get_non_normal_other_parent_entries(
229 &mut self,
229 &mut self,
230 ) -> (&mut HashSet<HgPathBuf>, &mut HashSet<HgPathBuf>) {
230 ) -> (&mut HashSet<HgPathBuf>, &mut HashSet<HgPathBuf>) {
231 self.set_non_normal_other_parent_entries(false);
231 self.set_non_normal_other_parent_entries(false);
232 (
232 (
233 self.non_normal_set.as_mut().unwrap(),
233 self.non_normal_set.as_mut().unwrap(),
234 self.other_parent_set.as_mut().unwrap(),
234 self.other_parent_set.as_mut().unwrap(),
235 )
235 )
236 }
236 }
237
237
238 /// Useful to get immutable references to those sets in contexts where
238 /// Useful to get immutable references to those sets in contexts where
239 /// you only have an immutable reference to the `DirstateMap`, like when
239 /// you only have an immutable reference to the `DirstateMap`, like when
240 /// sharing references with Python.
240 /// sharing references with Python.
241 ///
241 ///
242 /// TODO, get rid of this along with the other "setter/getter" stuff when
242 /// TODO, get rid of this along with the other "setter/getter" stuff when
243 /// a nice typestate plan is defined.
243 /// a nice typestate plan is defined.
244 ///
244 ///
245 /// # Panics
245 /// # Panics
246 ///
246 ///
247 /// Will panic if either set is `None`.
247 /// Will panic if either set is `None`.
248 pub fn get_non_normal_other_parent_entries_panic(
248 pub fn get_non_normal_other_parent_entries_panic(
249 &self,
249 &self,
250 ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) {
250 ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) {
251 (
251 (
252 self.non_normal_set.as_ref().unwrap(),
252 self.non_normal_set.as_ref().unwrap(),
253 self.other_parent_set.as_ref().unwrap(),
253 self.other_parent_set.as_ref().unwrap(),
254 )
254 )
255 }
255 }
256
256
257 pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
257 pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
258 if !force
258 if !force
259 && self.non_normal_set.is_some()
259 && self.non_normal_set.is_some()
260 && self.other_parent_set.is_some()
260 && self.other_parent_set.is_some()
261 {
261 {
262 return;
262 return;
263 }
263 }
264 let mut non_normal = HashSet::new();
264 let mut non_normal = HashSet::new();
265 let mut other_parent = HashSet::new();
265 let mut other_parent = HashSet::new();
266
266
267 for (
267 for (
268 filename,
268 filename,
269 DirstateEntry {
269 DirstateEntry {
270 state, size, mtime, ..
270 state, size, mtime, ..
271 },
271 },
272 ) in self.state_map.iter()
272 ) in self.state_map.iter()
273 {
273 {
274 if *state != EntryState::Normal || *mtime == MTIME_UNSET {
274 if *state != EntryState::Normal || *mtime == MTIME_UNSET {
275 non_normal.insert(filename.to_owned());
275 non_normal.insert(filename.to_owned());
276 }
276 }
277 if *state == EntryState::Normal && *size == SIZE_FROM_OTHER_PARENT
277 if *state == EntryState::Normal && *size == SIZE_FROM_OTHER_PARENT
278 {
278 {
279 other_parent.insert(filename.to_owned());
279 other_parent.insert(filename.to_owned());
280 }
280 }
281 }
281 }
282 self.non_normal_set = Some(non_normal);
282 self.non_normal_set = Some(non_normal);
283 self.other_parent_set = Some(other_parent);
283 self.other_parent_set = Some(other_parent);
284 }
284 }
285
285
286 /// Both of these setters and their uses appear to be the simplest way to
286 /// Both of these setters and their uses appear to be the simplest way to
287 /// emulate a Python lazy property, but it is ugly and unidiomatic.
287 /// emulate a Python lazy property, but it is ugly and unidiomatic.
288 /// TODO One day, rewriting this struct using the typestate might be a
288 /// TODO One day, rewriting this struct using the typestate might be a
289 /// good idea.
289 /// good idea.
290 pub fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
290 pub fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
291 if self.all_dirs.is_none() {
291 if self.all_dirs.is_none() {
292 self.all_dirs =
292 self.all_dirs = Some(DirsMultiset::from_dirstate(
293 Some(DirsMultiset::from_dirstate(&self.state_map, None)?);
293 self.state_map.iter(),
294 None,
295 )?);
294 }
296 }
295 Ok(())
297 Ok(())
296 }
298 }
297
299
298 pub fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
300 pub fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
299 if self.dirs.is_none() {
301 if self.dirs.is_none() {
300 self.dirs = Some(DirsMultiset::from_dirstate(
302 self.dirs = Some(DirsMultiset::from_dirstate(
301 &self.state_map,
303 &self.state_map,
302 Some(EntryState::Removed),
304 Some(EntryState::Removed),
303 )?);
305 )?);
304 }
306 }
305 Ok(())
307 Ok(())
306 }
308 }
307
309
308 pub fn has_tracked_dir(
310 pub fn has_tracked_dir(
309 &mut self,
311 &mut self,
310 directory: &HgPath,
312 directory: &HgPath,
311 ) -> Result<bool, DirstateMapError> {
313 ) -> Result<bool, DirstateMapError> {
312 self.set_dirs()?;
314 self.set_dirs()?;
313 Ok(self.dirs.as_ref().unwrap().contains(directory))
315 Ok(self.dirs.as_ref().unwrap().contains(directory))
314 }
316 }
315
317
316 pub fn has_dir(
318 pub fn has_dir(
317 &mut self,
319 &mut self,
318 directory: &HgPath,
320 directory: &HgPath,
319 ) -> Result<bool, DirstateMapError> {
321 ) -> Result<bool, DirstateMapError> {
320 self.set_all_dirs()?;
322 self.set_all_dirs()?;
321 Ok(self.all_dirs.as_ref().unwrap().contains(directory))
323 Ok(self.all_dirs.as_ref().unwrap().contains(directory))
322 }
324 }
323
325
324 pub fn parents(
326 pub fn parents(
325 &mut self,
327 &mut self,
326 file_contents: &[u8],
328 file_contents: &[u8],
327 ) -> Result<&DirstateParents, DirstateError> {
329 ) -> Result<&DirstateParents, DirstateError> {
328 if let Some(ref parents) = self.parents {
330 if let Some(ref parents) = self.parents {
329 return Ok(parents);
331 return Ok(parents);
330 }
332 }
331 let parents;
333 let parents;
332 if file_contents.len() == PARENT_SIZE * 2 {
334 if file_contents.len() == PARENT_SIZE * 2 {
333 parents = DirstateParents {
335 parents = DirstateParents {
334 p1: file_contents[..PARENT_SIZE].try_into().unwrap(),
336 p1: file_contents[..PARENT_SIZE].try_into().unwrap(),
335 p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2]
337 p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2]
336 .try_into()
338 .try_into()
337 .unwrap(),
339 .unwrap(),
338 };
340 };
339 } else if file_contents.is_empty() {
341 } else if file_contents.is_empty() {
340 parents = DirstateParents {
342 parents = DirstateParents {
341 p1: NULL_NODE,
343 p1: NULL_NODE,
342 p2: NULL_NODE,
344 p2: NULL_NODE,
343 };
345 };
344 } else {
346 } else {
345 return Err(
347 return Err(
346 HgError::corrupted("Dirstate appears to be damaged").into()
348 HgError::corrupted("Dirstate appears to be damaged").into()
347 );
349 );
348 }
350 }
349
351
350 self.parents = Some(parents);
352 self.parents = Some(parents);
351 Ok(self.parents.as_ref().unwrap())
353 Ok(self.parents.as_ref().unwrap())
352 }
354 }
353
355
354 pub fn set_parents(&mut self, parents: &DirstateParents) {
356 pub fn set_parents(&mut self, parents: &DirstateParents) {
355 self.parents = Some(parents.clone());
357 self.parents = Some(parents.clone());
356 self.dirty_parents = true;
358 self.dirty_parents = true;
357 }
359 }
358
360
359 #[timed]
361 #[timed]
360 pub fn read<'a>(
362 pub fn read<'a>(
361 &mut self,
363 &mut self,
362 file_contents: &'a [u8],
364 file_contents: &'a [u8],
363 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
365 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
364 if file_contents.is_empty() {
366 if file_contents.is_empty() {
365 return Ok(None);
367 return Ok(None);
366 }
368 }
367
369
368 let (parents, entries, copies) = parse_dirstate(file_contents)?;
370 let (parents, entries, copies) = parse_dirstate(file_contents)?;
369 self.state_map.extend(
371 self.state_map.extend(
370 entries
372 entries
371 .into_iter()
373 .into_iter()
372 .map(|(path, entry)| (path.to_owned(), entry)),
374 .map(|(path, entry)| (path.to_owned(), entry)),
373 );
375 );
374 self.copy_map.extend(
376 self.copy_map.extend(
375 copies
377 copies
376 .into_iter()
378 .into_iter()
377 .map(|(path, copy)| (path.to_owned(), copy.to_owned())),
379 .map(|(path, copy)| (path.to_owned(), copy.to_owned())),
378 );
380 );
379
381
380 if !self.dirty_parents {
382 if !self.dirty_parents {
381 self.set_parents(&parents);
383 self.set_parents(&parents);
382 }
384 }
383
385
384 Ok(Some(parents))
386 Ok(Some(parents))
385 }
387 }
386
388
387 pub fn pack(
389 pub fn pack(
388 &mut self,
390 &mut self,
389 parents: DirstateParents,
391 parents: DirstateParents,
390 now: Duration,
392 now: Duration,
391 ) -> Result<Vec<u8>, DirstateError> {
393 ) -> Result<Vec<u8>, DirstateError> {
392 let packed =
394 let packed =
393 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
395 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
394
396
395 self.dirty_parents = false;
397 self.dirty_parents = false;
396
398
397 self.set_non_normal_other_parent_entries(true);
399 self.set_non_normal_other_parent_entries(true);
398 Ok(packed)
400 Ok(packed)
399 }
401 }
400 pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
402 pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
401 if let Some(ref file_fold_map) = self.file_fold_map {
403 if let Some(ref file_fold_map) = self.file_fold_map {
402 return file_fold_map;
404 return file_fold_map;
403 }
405 }
404 let mut new_file_fold_map = FileFoldMap::default();
406 let mut new_file_fold_map = FileFoldMap::default();
405
407
406 for (filename, DirstateEntry { state, .. }) in self.state_map.iter() {
408 for (filename, DirstateEntry { state, .. }) in self.state_map.iter() {
407 if *state != EntryState::Removed {
409 if *state != EntryState::Removed {
408 new_file_fold_map
410 new_file_fold_map
409 .insert(normalize_case(&filename), filename.to_owned());
411 .insert(normalize_case(&filename), filename.to_owned());
410 }
412 }
411 }
413 }
412 self.file_fold_map = Some(new_file_fold_map);
414 self.file_fold_map = Some(new_file_fold_map);
413 self.file_fold_map.as_ref().unwrap()
415 self.file_fold_map.as_ref().unwrap()
414 }
416 }
415 }
417 }
416
418
417 #[cfg(test)]
419 #[cfg(test)]
418 mod tests {
420 mod tests {
419 use super::*;
421 use super::*;
420
422
421 #[test]
423 #[test]
422 fn test_dirs_multiset() {
424 fn test_dirs_multiset() {
423 let mut map = DirstateMap::new();
425 let mut map = DirstateMap::new();
424 assert!(map.dirs.is_none());
426 assert!(map.dirs.is_none());
425 assert!(map.all_dirs.is_none());
427 assert!(map.all_dirs.is_none());
426
428
427 assert_eq!(map.has_dir(HgPath::new(b"nope")).unwrap(), false);
429 assert_eq!(map.has_dir(HgPath::new(b"nope")).unwrap(), false);
428 assert!(map.all_dirs.is_some());
430 assert!(map.all_dirs.is_some());
429 assert!(map.dirs.is_none());
431 assert!(map.dirs.is_none());
430
432
431 assert_eq!(map.has_tracked_dir(HgPath::new(b"nope")).unwrap(), false);
433 assert_eq!(map.has_tracked_dir(HgPath::new(b"nope")).unwrap(), false);
432 assert!(map.dirs.is_some());
434 assert!(map.dirs.is_some());
433 }
435 }
434
436
435 #[test]
437 #[test]
436 fn test_add_file() {
438 fn test_add_file() {
437 let mut map = DirstateMap::new();
439 let mut map = DirstateMap::new();
438
440
439 assert_eq!(0, map.len());
441 assert_eq!(0, map.len());
440
442
441 map.add_file(
443 map.add_file(
442 HgPath::new(b"meh"),
444 HgPath::new(b"meh"),
443 EntryState::Normal,
445 EntryState::Normal,
444 DirstateEntry {
446 DirstateEntry {
445 state: EntryState::Normal,
447 state: EntryState::Normal,
446 mode: 1337,
448 mode: 1337,
447 mtime: 1337,
449 mtime: 1337,
448 size: 1337,
450 size: 1337,
449 },
451 },
450 )
452 )
451 .unwrap();
453 .unwrap();
452
454
453 assert_eq!(1, map.len());
455 assert_eq!(1, map.len());
454 assert_eq!(0, map.get_non_normal_other_parent_entries().0.len());
456 assert_eq!(0, map.get_non_normal_other_parent_entries().0.len());
455 assert_eq!(0, map.get_non_normal_other_parent_entries().1.len());
457 assert_eq!(0, map.get_non_normal_other_parent_entries().1.len());
456 }
458 }
457
459
458 #[test]
460 #[test]
459 fn test_non_normal_other_parent_entries() {
461 fn test_non_normal_other_parent_entries() {
460 let mut map: DirstateMap = [
462 let mut map: DirstateMap = [
461 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
463 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
462 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
464 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
463 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
465 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
464 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
466 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
465 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
467 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
466 (b"f6", (EntryState::Added, 1337, 1337, -1)),
468 (b"f6", (EntryState::Added, 1337, 1337, -1)),
467 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
469 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
468 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
470 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
469 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
471 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
470 (b"fa", (EntryState::Added, 1337, -2, 1337)),
472 (b"fa", (EntryState::Added, 1337, -2, 1337)),
471 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
473 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
472 ]
474 ]
473 .iter()
475 .iter()
474 .map(|(fname, (state, mode, size, mtime))| {
476 .map(|(fname, (state, mode, size, mtime))| {
475 (
477 (
476 HgPathBuf::from_bytes(fname.as_ref()),
478 HgPathBuf::from_bytes(fname.as_ref()),
477 DirstateEntry {
479 DirstateEntry {
478 state: *state,
480 state: *state,
479 mode: *mode,
481 mode: *mode,
480 size: *size,
482 size: *size,
481 mtime: *mtime,
483 mtime: *mtime,
482 },
484 },
483 )
485 )
484 })
486 })
485 .collect();
487 .collect();
486
488
487 let mut non_normal = [
489 let mut non_normal = [
488 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
490 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
489 ]
491 ]
490 .iter()
492 .iter()
491 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
493 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
492 .collect();
494 .collect();
493
495
494 let mut other_parent = HashSet::new();
496 let mut other_parent = HashSet::new();
495 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
497 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
496 let entries = map.get_non_normal_other_parent_entries();
498 let entries = map.get_non_normal_other_parent_entries();
497
499
498 assert_eq!(
500 assert_eq!(
499 (&mut non_normal, &mut other_parent),
501 (&mut non_normal, &mut other_parent),
500 (entries.0, entries.1)
502 (entries.0, entries.1)
501 );
503 );
502 }
504 }
503 }
505 }
@@ -1,913 +1,913 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@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 //! Rust implementation of dirstate.status (dirstate.py).
8 //! Rust implementation of dirstate.status (dirstate.py).
9 //! It is currently missing a lot of functionality compared to the Python one
9 //! It is currently missing a lot of functionality compared to the Python one
10 //! and will only be triggered in narrow cases.
10 //! and will only be triggered in narrow cases.
11
11
12 use crate::utils::path_auditor::PathAuditor;
12 use crate::utils::path_auditor::PathAuditor;
13 use crate::{
13 use crate::{
14 dirstate::SIZE_FROM_OTHER_PARENT,
14 dirstate::SIZE_FROM_OTHER_PARENT,
15 filepatterns::PatternFileWarning,
15 filepatterns::PatternFileWarning,
16 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
16 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
17 utils::{
17 utils::{
18 files::{find_dirs, HgMetadata},
18 files::{find_dirs, HgMetadata},
19 hg_path::{
19 hg_path::{
20 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
20 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
21 HgPathError,
21 HgPathError,
22 },
22 },
23 },
23 },
24 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
24 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
25 PatternError,
25 PatternError,
26 };
26 };
27 use lazy_static::lazy_static;
27 use lazy_static::lazy_static;
28 use micro_timer::timed;
28 use micro_timer::timed;
29 use rayon::prelude::*;
29 use rayon::prelude::*;
30 use std::{
30 use std::{
31 borrow::Cow,
31 borrow::Cow,
32 collections::HashSet,
32 collections::HashSet,
33 fmt,
33 fmt,
34 fs::{read_dir, DirEntry},
34 fs::{read_dir, DirEntry},
35 io::ErrorKind,
35 io::ErrorKind,
36 ops::Deref,
36 ops::Deref,
37 path::{Path, PathBuf},
37 path::{Path, PathBuf},
38 };
38 };
39
39
40 /// Wrong type of file from a `BadMatch`
40 /// Wrong type of file from a `BadMatch`
41 /// Note: a lot of those don't exist on all platforms.
41 /// Note: a lot of those don't exist on all platforms.
42 #[derive(Debug, Copy, Clone)]
42 #[derive(Debug, Copy, Clone)]
43 pub enum BadType {
43 pub enum BadType {
44 CharacterDevice,
44 CharacterDevice,
45 BlockDevice,
45 BlockDevice,
46 FIFO,
46 FIFO,
47 Socket,
47 Socket,
48 Directory,
48 Directory,
49 Unknown,
49 Unknown,
50 }
50 }
51
51
52 impl fmt::Display for BadType {
52 impl fmt::Display for BadType {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 f.write_str(match self {
54 f.write_str(match self {
55 BadType::CharacterDevice => "character device",
55 BadType::CharacterDevice => "character device",
56 BadType::BlockDevice => "block device",
56 BadType::BlockDevice => "block device",
57 BadType::FIFO => "fifo",
57 BadType::FIFO => "fifo",
58 BadType::Socket => "socket",
58 BadType::Socket => "socket",
59 BadType::Directory => "directory",
59 BadType::Directory => "directory",
60 BadType::Unknown => "unknown",
60 BadType::Unknown => "unknown",
61 })
61 })
62 }
62 }
63 }
63 }
64
64
65 /// Was explicitly matched but cannot be found/accessed
65 /// Was explicitly matched but cannot be found/accessed
66 #[derive(Debug, Copy, Clone)]
66 #[derive(Debug, Copy, Clone)]
67 pub enum BadMatch {
67 pub enum BadMatch {
68 OsError(i32),
68 OsError(i32),
69 BadType(BadType),
69 BadType(BadType),
70 }
70 }
71
71
72 /// Enum used to dispatch new status entries into the right collections.
72 /// Enum used to dispatch new status entries into the right collections.
73 /// Is similar to `crate::EntryState`, but represents the transient state of
73 /// Is similar to `crate::EntryState`, but represents the transient state of
74 /// entries during the lifetime of a command.
74 /// entries during the lifetime of a command.
75 #[derive(Debug, Copy, Clone)]
75 #[derive(Debug, Copy, Clone)]
76 pub enum Dispatch {
76 pub enum Dispatch {
77 Unsure,
77 Unsure,
78 Modified,
78 Modified,
79 Added,
79 Added,
80 Removed,
80 Removed,
81 Deleted,
81 Deleted,
82 Clean,
82 Clean,
83 Unknown,
83 Unknown,
84 Ignored,
84 Ignored,
85 /// Empty dispatch, the file is not worth listing
85 /// Empty dispatch, the file is not worth listing
86 None,
86 None,
87 /// Was explicitly matched but cannot be found/accessed
87 /// Was explicitly matched but cannot be found/accessed
88 Bad(BadMatch),
88 Bad(BadMatch),
89 Directory {
89 Directory {
90 /// True if the directory used to be a file in the dmap so we can say
90 /// True if the directory used to be a file in the dmap so we can say
91 /// that it's been removed.
91 /// that it's been removed.
92 was_file: bool,
92 was_file: bool,
93 },
93 },
94 }
94 }
95
95
96 type IoResult<T> = std::io::Result<T>;
96 type IoResult<T> = std::io::Result<T>;
97
97
98 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
98 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
99 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
99 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
100 type IgnoreFnType<'a> = Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
100 type IgnoreFnType<'a> = Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
101
101
102 /// We have a good mix of owned (from directory traversal) and borrowed (from
102 /// We have a good mix of owned (from directory traversal) and borrowed (from
103 /// the dirstate/explicit) paths, this comes up a lot.
103 /// the dirstate/explicit) paths, this comes up a lot.
104 pub type HgPathCow<'a> = Cow<'a, HgPath>;
104 pub type HgPathCow<'a> = Cow<'a, HgPath>;
105
105
106 /// A path with its computed ``Dispatch`` information
106 /// A path with its computed ``Dispatch`` information
107 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
107 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
108
108
109 /// The conversion from `HgPath` to a real fs path failed.
109 /// The conversion from `HgPath` to a real fs path failed.
110 /// `22` is the error code for "Invalid argument"
110 /// `22` is the error code for "Invalid argument"
111 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
111 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
112
112
113 /// Dates and times that are outside the 31-bit signed range are compared
113 /// Dates and times that are outside the 31-bit signed range are compared
114 /// modulo 2^31. This should prevent hg from behaving badly with very large
114 /// modulo 2^31. This should prevent hg from behaving badly with very large
115 /// files or corrupt dates while still having a high probability of detecting
115 /// files or corrupt dates while still having a high probability of detecting
116 /// changes. (issue2608)
116 /// changes. (issue2608)
117 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
117 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
118 /// is not defined for `i32`, and there is no `As` trait. This forces the
118 /// is not defined for `i32`, and there is no `As` trait. This forces the
119 /// caller to cast `b` as `i32`.
119 /// caller to cast `b` as `i32`.
120 fn mod_compare(a: i32, b: i32) -> bool {
120 fn mod_compare(a: i32, b: i32) -> bool {
121 a & i32::max_value() != b & i32::max_value()
121 a & i32::max_value() != b & i32::max_value()
122 }
122 }
123
123
124 /// Return a sorted list containing information about the entries
124 /// Return a sorted list containing information about the entries
125 /// in the directory.
125 /// in the directory.
126 ///
126 ///
127 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
127 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
128 fn list_directory(
128 fn list_directory(
129 path: impl AsRef<Path>,
129 path: impl AsRef<Path>,
130 skip_dot_hg: bool,
130 skip_dot_hg: bool,
131 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
131 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
132 let mut results = vec![];
132 let mut results = vec![];
133 let entries = read_dir(path.as_ref())?;
133 let entries = read_dir(path.as_ref())?;
134
134
135 for entry in entries {
135 for entry in entries {
136 let entry = entry?;
136 let entry = entry?;
137 let filename = os_string_to_hg_path_buf(entry.file_name())?;
137 let filename = os_string_to_hg_path_buf(entry.file_name())?;
138 let file_type = entry.file_type()?;
138 let file_type = entry.file_type()?;
139 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
139 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
140 return Ok(vec![]);
140 return Ok(vec![]);
141 } else {
141 } else {
142 results.push((filename, entry))
142 results.push((filename, entry))
143 }
143 }
144 }
144 }
145
145
146 results.sort_unstable_by_key(|e| e.0.clone());
146 results.sort_unstable_by_key(|e| e.0.clone());
147 Ok(results)
147 Ok(results)
148 }
148 }
149
149
150 /// The file corresponding to the dirstate entry was found on the filesystem.
150 /// The file corresponding to the dirstate entry was found on the filesystem.
151 fn dispatch_found(
151 fn dispatch_found(
152 filename: impl AsRef<HgPath>,
152 filename: impl AsRef<HgPath>,
153 entry: DirstateEntry,
153 entry: DirstateEntry,
154 metadata: HgMetadata,
154 metadata: HgMetadata,
155 copy_map: &CopyMap,
155 copy_map: &CopyMap,
156 options: StatusOptions,
156 options: StatusOptions,
157 ) -> Dispatch {
157 ) -> Dispatch {
158 let DirstateEntry {
158 let DirstateEntry {
159 state,
159 state,
160 mode,
160 mode,
161 mtime,
161 mtime,
162 size,
162 size,
163 } = entry;
163 } = entry;
164
164
165 let HgMetadata {
165 let HgMetadata {
166 st_mode,
166 st_mode,
167 st_size,
167 st_size,
168 st_mtime,
168 st_mtime,
169 ..
169 ..
170 } = metadata;
170 } = metadata;
171
171
172 match state {
172 match state {
173 EntryState::Normal => {
173 EntryState::Normal => {
174 let size_changed = mod_compare(size, st_size as i32);
174 let size_changed = mod_compare(size, st_size as i32);
175 let mode_changed =
175 let mode_changed =
176 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
176 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
177 let metadata_changed = size >= 0 && (size_changed || mode_changed);
177 let metadata_changed = size >= 0 && (size_changed || mode_changed);
178 let other_parent = size == SIZE_FROM_OTHER_PARENT;
178 let other_parent = size == SIZE_FROM_OTHER_PARENT;
179
179
180 if metadata_changed
180 if metadata_changed
181 || other_parent
181 || other_parent
182 || copy_map.contains_key(filename.as_ref())
182 || copy_map.contains_key(filename.as_ref())
183 {
183 {
184 if metadata.is_symlink() && size_changed {
184 if metadata.is_symlink() && size_changed {
185 // issue6456: Size returned may be longer due to encryption
185 // issue6456: Size returned may be longer due to encryption
186 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
186 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
187 Dispatch::Unsure
187 Dispatch::Unsure
188 } else {
188 } else {
189 Dispatch::Modified
189 Dispatch::Modified
190 }
190 }
191 } else if mod_compare(mtime, st_mtime as i32)
191 } else if mod_compare(mtime, st_mtime as i32)
192 || st_mtime == options.last_normal_time
192 || st_mtime == options.last_normal_time
193 {
193 {
194 // the file may have just been marked as normal and
194 // the file may have just been marked as normal and
195 // it may have changed in the same second without
195 // it may have changed in the same second without
196 // changing its size. This can happen if we quickly
196 // changing its size. This can happen if we quickly
197 // do multiple commits. Force lookup, so we don't
197 // do multiple commits. Force lookup, so we don't
198 // miss such a racy file change.
198 // miss such a racy file change.
199 Dispatch::Unsure
199 Dispatch::Unsure
200 } else if options.list_clean {
200 } else if options.list_clean {
201 Dispatch::Clean
201 Dispatch::Clean
202 } else {
202 } else {
203 Dispatch::None
203 Dispatch::None
204 }
204 }
205 }
205 }
206 EntryState::Merged => Dispatch::Modified,
206 EntryState::Merged => Dispatch::Modified,
207 EntryState::Added => Dispatch::Added,
207 EntryState::Added => Dispatch::Added,
208 EntryState::Removed => Dispatch::Removed,
208 EntryState::Removed => Dispatch::Removed,
209 EntryState::Unknown => Dispatch::Unknown,
209 EntryState::Unknown => Dispatch::Unknown,
210 }
210 }
211 }
211 }
212
212
213 /// The file corresponding to this Dirstate entry is missing.
213 /// The file corresponding to this Dirstate entry is missing.
214 fn dispatch_missing(state: EntryState) -> Dispatch {
214 fn dispatch_missing(state: EntryState) -> Dispatch {
215 match state {
215 match state {
216 // File was removed from the filesystem during commands
216 // File was removed from the filesystem during commands
217 EntryState::Normal | EntryState::Merged | EntryState::Added => {
217 EntryState::Normal | EntryState::Merged | EntryState::Added => {
218 Dispatch::Deleted
218 Dispatch::Deleted
219 }
219 }
220 // File was removed, everything is normal
220 // File was removed, everything is normal
221 EntryState::Removed => Dispatch::Removed,
221 EntryState::Removed => Dispatch::Removed,
222 // File is unknown to Mercurial, everything is normal
222 // File is unknown to Mercurial, everything is normal
223 EntryState::Unknown => Dispatch::Unknown,
223 EntryState::Unknown => Dispatch::Unknown,
224 }
224 }
225 }
225 }
226
226
227 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
227 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
228 Dispatch::Bad(BadMatch::OsError(
228 Dispatch::Bad(BadMatch::OsError(
229 e.raw_os_error().expect("expected real OS error"),
229 e.raw_os_error().expect("expected real OS error"),
230 ))
230 ))
231 }
231 }
232
232
233 lazy_static! {
233 lazy_static! {
234 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
234 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
235 let mut h = HashSet::new();
235 let mut h = HashSet::new();
236 h.insert(HgPath::new(b""));
236 h.insert(HgPath::new(b""));
237 h
237 h
238 };
238 };
239 }
239 }
240
240
241 #[derive(Debug, Copy, Clone)]
241 #[derive(Debug, Copy, Clone)]
242 pub struct StatusOptions {
242 pub struct StatusOptions {
243 /// Remember the most recent modification timeslot for status, to make
243 /// Remember the most recent modification timeslot for status, to make
244 /// sure we won't miss future size-preserving file content modifications
244 /// sure we won't miss future size-preserving file content modifications
245 /// that happen within the same timeslot.
245 /// that happen within the same timeslot.
246 pub last_normal_time: i64,
246 pub last_normal_time: i64,
247 /// Whether we are on a filesystem with UNIX-like exec flags
247 /// Whether we are on a filesystem with UNIX-like exec flags
248 pub check_exec: bool,
248 pub check_exec: bool,
249 pub list_clean: bool,
249 pub list_clean: bool,
250 pub list_unknown: bool,
250 pub list_unknown: bool,
251 pub list_ignored: bool,
251 pub list_ignored: bool,
252 /// Whether to collect traversed dirs for applying a callback later.
252 /// Whether to collect traversed dirs for applying a callback later.
253 /// Used by `hg purge` for example.
253 /// Used by `hg purge` for example.
254 pub collect_traversed_dirs: bool,
254 pub collect_traversed_dirs: bool,
255 }
255 }
256
256
257 #[derive(Debug)]
257 #[derive(Debug)]
258 pub struct DirstateStatus<'a> {
258 pub struct DirstateStatus<'a> {
259 pub modified: Vec<HgPathCow<'a>>,
259 pub modified: Vec<HgPathCow<'a>>,
260 pub added: Vec<HgPathCow<'a>>,
260 pub added: Vec<HgPathCow<'a>>,
261 pub removed: Vec<HgPathCow<'a>>,
261 pub removed: Vec<HgPathCow<'a>>,
262 pub deleted: Vec<HgPathCow<'a>>,
262 pub deleted: Vec<HgPathCow<'a>>,
263 pub clean: Vec<HgPathCow<'a>>,
263 pub clean: Vec<HgPathCow<'a>>,
264 pub ignored: Vec<HgPathCow<'a>>,
264 pub ignored: Vec<HgPathCow<'a>>,
265 pub unknown: Vec<HgPathCow<'a>>,
265 pub unknown: Vec<HgPathCow<'a>>,
266 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
266 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
267 /// Only filled if `collect_traversed_dirs` is `true`
267 /// Only filled if `collect_traversed_dirs` is `true`
268 pub traversed: Vec<HgPathBuf>,
268 pub traversed: Vec<HgPathBuf>,
269 }
269 }
270
270
271 #[derive(Debug, derive_more::From)]
271 #[derive(Debug, derive_more::From)]
272 pub enum StatusError {
272 pub enum StatusError {
273 /// Generic IO error
273 /// Generic IO error
274 IO(std::io::Error),
274 IO(std::io::Error),
275 /// An invalid path that cannot be represented in Mercurial was found
275 /// An invalid path that cannot be represented in Mercurial was found
276 Path(HgPathError),
276 Path(HgPathError),
277 /// An invalid "ignore" pattern was found
277 /// An invalid "ignore" pattern was found
278 Pattern(PatternError),
278 Pattern(PatternError),
279 }
279 }
280
280
281 pub type StatusResult<T> = Result<T, StatusError>;
281 pub type StatusResult<T> = Result<T, StatusError>;
282
282
283 impl fmt::Display for StatusError {
283 impl fmt::Display for StatusError {
284 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
284 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285 match self {
285 match self {
286 StatusError::IO(error) => error.fmt(f),
286 StatusError::IO(error) => error.fmt(f),
287 StatusError::Path(error) => error.fmt(f),
287 StatusError::Path(error) => error.fmt(f),
288 StatusError::Pattern(error) => error.fmt(f),
288 StatusError::Pattern(error) => error.fmt(f),
289 }
289 }
290 }
290 }
291 }
291 }
292
292
293 /// Gives information about which files are changed in the working directory
293 /// Gives information about which files are changed in the working directory
294 /// and how, compared to the revision we're based on
294 /// and how, compared to the revision we're based on
295 pub struct Status<'a, M: Matcher + Sync> {
295 pub struct Status<'a, M: ?Sized + Matcher + Sync> {
296 dmap: &'a DirstateMap,
296 dmap: &'a DirstateMap,
297 pub(crate) matcher: &'a M,
297 pub(crate) matcher: &'a M,
298 root_dir: PathBuf,
298 root_dir: PathBuf,
299 pub(crate) options: StatusOptions,
299 pub(crate) options: StatusOptions,
300 ignore_fn: IgnoreFnType<'a>,
300 ignore_fn: IgnoreFnType<'a>,
301 }
301 }
302
302
303 impl<'a, M> Status<'a, M>
303 impl<'a, M> Status<'a, M>
304 where
304 where
305 M: Matcher + Sync,
305 M: ?Sized + Matcher + Sync,
306 {
306 {
307 pub fn new(
307 pub fn new(
308 dmap: &'a DirstateMap,
308 dmap: &'a DirstateMap,
309 matcher: &'a M,
309 matcher: &'a M,
310 root_dir: PathBuf,
310 root_dir: PathBuf,
311 ignore_files: Vec<PathBuf>,
311 ignore_files: Vec<PathBuf>,
312 options: StatusOptions,
312 options: StatusOptions,
313 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
313 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
314 // Needs to outlive `dir_ignore_fn` since it's captured.
314 // Needs to outlive `dir_ignore_fn` since it's captured.
315
315
316 let (ignore_fn, warnings): (IgnoreFnType, _) =
316 let (ignore_fn, warnings): (IgnoreFnType, _) =
317 if options.list_ignored || options.list_unknown {
317 if options.list_ignored || options.list_unknown {
318 get_ignore_function(ignore_files, &root_dir)?
318 get_ignore_function(ignore_files, &root_dir)?
319 } else {
319 } else {
320 (Box::new(|&_| true), vec![])
320 (Box::new(|&_| true), vec![])
321 };
321 };
322
322
323 Ok((
323 Ok((
324 Self {
324 Self {
325 dmap,
325 dmap,
326 matcher,
326 matcher,
327 root_dir,
327 root_dir,
328 options,
328 options,
329 ignore_fn,
329 ignore_fn,
330 },
330 },
331 warnings,
331 warnings,
332 ))
332 ))
333 }
333 }
334
334
335 /// Is the path ignored?
335 /// Is the path ignored?
336 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
336 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
337 (self.ignore_fn)(path.as_ref())
337 (self.ignore_fn)(path.as_ref())
338 }
338 }
339
339
340 /// Is the path or one of its ancestors ignored?
340 /// Is the path or one of its ancestors ignored?
341 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
341 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
342 // Only involve ignore mechanism if we're listing unknowns or ignored.
342 // Only involve ignore mechanism if we're listing unknowns or ignored.
343 if self.options.list_ignored || self.options.list_unknown {
343 if self.options.list_ignored || self.options.list_unknown {
344 if self.is_ignored(&dir) {
344 if self.is_ignored(&dir) {
345 true
345 true
346 } else {
346 } else {
347 for p in find_dirs(dir.as_ref()) {
347 for p in find_dirs(dir.as_ref()) {
348 if self.is_ignored(p) {
348 if self.is_ignored(p) {
349 return true;
349 return true;
350 }
350 }
351 }
351 }
352 false
352 false
353 }
353 }
354 } else {
354 } else {
355 true
355 true
356 }
356 }
357 }
357 }
358
358
359 /// Get stat data about the files explicitly specified by the matcher.
359 /// Get stat data about the files explicitly specified by the matcher.
360 /// Returns a tuple of the directories that need to be traversed and the
360 /// Returns a tuple of the directories that need to be traversed and the
361 /// files with their corresponding `Dispatch`.
361 /// files with their corresponding `Dispatch`.
362 /// TODO subrepos
362 /// TODO subrepos
363 #[timed]
363 #[timed]
364 pub fn walk_explicit(
364 pub fn walk_explicit(
365 &self,
365 &self,
366 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
366 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
367 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
367 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
368 self.matcher
368 self.matcher
369 .file_set()
369 .file_set()
370 .unwrap_or(&DEFAULT_WORK)
370 .unwrap_or(&DEFAULT_WORK)
371 .par_iter()
371 .par_iter()
372 .flat_map(|&filename| -> Option<_> {
372 .flat_map(|&filename| -> Option<_> {
373 // TODO normalization
373 // TODO normalization
374 let normalized = filename;
374 let normalized = filename;
375
375
376 let buf = match hg_path_to_path_buf(normalized) {
376 let buf = match hg_path_to_path_buf(normalized) {
377 Ok(x) => x,
377 Ok(x) => x,
378 Err(_) => {
378 Err(_) => {
379 return Some((
379 return Some((
380 Cow::Borrowed(normalized),
380 Cow::Borrowed(normalized),
381 INVALID_PATH_DISPATCH,
381 INVALID_PATH_DISPATCH,
382 ))
382 ))
383 }
383 }
384 };
384 };
385 let target = self.root_dir.join(buf);
385 let target = self.root_dir.join(buf);
386 let st = target.symlink_metadata();
386 let st = target.symlink_metadata();
387 let in_dmap = self.dmap.get(normalized);
387 let in_dmap = self.dmap.get(normalized);
388 match st {
388 match st {
389 Ok(meta) => {
389 Ok(meta) => {
390 let file_type = meta.file_type();
390 let file_type = meta.file_type();
391 return if file_type.is_file() || file_type.is_symlink()
391 return if file_type.is_file() || file_type.is_symlink()
392 {
392 {
393 if let Some(entry) = in_dmap {
393 if let Some(entry) = in_dmap {
394 return Some((
394 return Some((
395 Cow::Borrowed(normalized),
395 Cow::Borrowed(normalized),
396 dispatch_found(
396 dispatch_found(
397 &normalized,
397 &normalized,
398 *entry,
398 *entry,
399 HgMetadata::from_metadata(meta),
399 HgMetadata::from_metadata(meta),
400 &self.dmap.copy_map,
400 &self.dmap.copy_map,
401 self.options,
401 self.options,
402 ),
402 ),
403 ));
403 ));
404 }
404 }
405 Some((
405 Some((
406 Cow::Borrowed(normalized),
406 Cow::Borrowed(normalized),
407 Dispatch::Unknown,
407 Dispatch::Unknown,
408 ))
408 ))
409 } else if file_type.is_dir() {
409 } else if file_type.is_dir() {
410 if self.options.collect_traversed_dirs {
410 if self.options.collect_traversed_dirs {
411 traversed_sender
411 traversed_sender
412 .send(normalized.to_owned())
412 .send(normalized.to_owned())
413 .expect("receiver should outlive sender");
413 .expect("receiver should outlive sender");
414 }
414 }
415 Some((
415 Some((
416 Cow::Borrowed(normalized),
416 Cow::Borrowed(normalized),
417 Dispatch::Directory {
417 Dispatch::Directory {
418 was_file: in_dmap.is_some(),
418 was_file: in_dmap.is_some(),
419 },
419 },
420 ))
420 ))
421 } else {
421 } else {
422 Some((
422 Some((
423 Cow::Borrowed(normalized),
423 Cow::Borrowed(normalized),
424 Dispatch::Bad(BadMatch::BadType(
424 Dispatch::Bad(BadMatch::BadType(
425 // TODO do more than unknown
425 // TODO do more than unknown
426 // Support for all `BadType` variant
426 // Support for all `BadType` variant
427 // varies greatly between platforms.
427 // varies greatly between platforms.
428 // So far, no tests check the type and
428 // So far, no tests check the type and
429 // this should be good enough for most
429 // this should be good enough for most
430 // users.
430 // users.
431 BadType::Unknown,
431 BadType::Unknown,
432 )),
432 )),
433 ))
433 ))
434 };
434 };
435 }
435 }
436 Err(_) => {
436 Err(_) => {
437 if let Some(entry) = in_dmap {
437 if let Some(entry) = in_dmap {
438 return Some((
438 return Some((
439 Cow::Borrowed(normalized),
439 Cow::Borrowed(normalized),
440 dispatch_missing(entry.state),
440 dispatch_missing(entry.state),
441 ));
441 ));
442 }
442 }
443 }
443 }
444 };
444 };
445 None
445 None
446 })
446 })
447 .partition(|(_, dispatch)| match dispatch {
447 .partition(|(_, dispatch)| match dispatch {
448 Dispatch::Directory { .. } => true,
448 Dispatch::Directory { .. } => true,
449 _ => false,
449 _ => false,
450 })
450 })
451 }
451 }
452
452
453 /// Walk the working directory recursively to look for changes compared to
453 /// Walk the working directory recursively to look for changes compared to
454 /// the current `DirstateMap`.
454 /// the current `DirstateMap`.
455 ///
455 ///
456 /// This takes a mutable reference to the results to account for the
456 /// This takes a mutable reference to the results to account for the
457 /// `extend` in timings
457 /// `extend` in timings
458 #[timed]
458 #[timed]
459 pub fn traverse(
459 pub fn traverse(
460 &self,
460 &self,
461 path: impl AsRef<HgPath>,
461 path: impl AsRef<HgPath>,
462 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
462 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
463 results: &mut Vec<DispatchedPath<'a>>,
463 results: &mut Vec<DispatchedPath<'a>>,
464 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
464 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
465 ) {
465 ) {
466 // The traversal is done in parallel, so use a channel to gather
466 // The traversal is done in parallel, so use a channel to gather
467 // entries. `crossbeam_channel::Sender` is `Sync`, while `mpsc::Sender`
467 // entries. `crossbeam_channel::Sender` is `Sync`, while `mpsc::Sender`
468 // is not.
468 // is not.
469 let (files_transmitter, files_receiver) =
469 let (files_transmitter, files_receiver) =
470 crossbeam_channel::unbounded();
470 crossbeam_channel::unbounded();
471
471
472 self.traverse_dir(
472 self.traverse_dir(
473 &files_transmitter,
473 &files_transmitter,
474 path,
474 path,
475 &old_results,
475 &old_results,
476 traversed_sender,
476 traversed_sender,
477 );
477 );
478
478
479 // Disconnect the channel so the receiver stops waiting
479 // Disconnect the channel so the receiver stops waiting
480 drop(files_transmitter);
480 drop(files_transmitter);
481
481
482 let new_results = files_receiver
482 let new_results = files_receiver
483 .into_iter()
483 .into_iter()
484 .par_bridge()
484 .par_bridge()
485 .map(|(f, d)| (Cow::Owned(f), d));
485 .map(|(f, d)| (Cow::Owned(f), d));
486
486
487 results.par_extend(new_results);
487 results.par_extend(new_results);
488 }
488 }
489
489
490 /// Dispatch a single entry (file, folder, symlink...) found during
490 /// Dispatch a single entry (file, folder, symlink...) found during
491 /// `traverse`. If the entry is a folder that needs to be traversed, it
491 /// `traverse`. If the entry is a folder that needs to be traversed, it
492 /// will be handled in a separate thread.
492 /// will be handled in a separate thread.
493 fn handle_traversed_entry<'b>(
493 fn handle_traversed_entry<'b>(
494 &'a self,
494 &'a self,
495 scope: &rayon::Scope<'b>,
495 scope: &rayon::Scope<'b>,
496 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
496 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
497 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
497 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
498 filename: HgPathBuf,
498 filename: HgPathBuf,
499 dir_entry: DirEntry,
499 dir_entry: DirEntry,
500 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
500 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
501 ) -> IoResult<()>
501 ) -> IoResult<()>
502 where
502 where
503 'a: 'b,
503 'a: 'b,
504 {
504 {
505 let file_type = dir_entry.file_type()?;
505 let file_type = dir_entry.file_type()?;
506 let entry_option = self.dmap.get(&filename);
506 let entry_option = self.dmap.get(&filename);
507
507
508 if filename.as_bytes() == b".hg" {
508 if filename.as_bytes() == b".hg" {
509 // Could be a directory or a symlink
509 // Could be a directory or a symlink
510 return Ok(());
510 return Ok(());
511 }
511 }
512
512
513 if file_type.is_dir() {
513 if file_type.is_dir() {
514 self.handle_traversed_dir(
514 self.handle_traversed_dir(
515 scope,
515 scope,
516 files_sender,
516 files_sender,
517 old_results,
517 old_results,
518 entry_option,
518 entry_option,
519 filename,
519 filename,
520 traversed_sender,
520 traversed_sender,
521 );
521 );
522 } else if file_type.is_file() || file_type.is_symlink() {
522 } else if file_type.is_file() || file_type.is_symlink() {
523 if let Some(entry) = entry_option {
523 if let Some(entry) = entry_option {
524 if self.matcher.matches_everything()
524 if self.matcher.matches_everything()
525 || self.matcher.matches(&filename)
525 || self.matcher.matches(&filename)
526 {
526 {
527 let metadata = dir_entry.metadata()?;
527 let metadata = dir_entry.metadata()?;
528 files_sender
528 files_sender
529 .send((
529 .send((
530 filename.to_owned(),
530 filename.to_owned(),
531 dispatch_found(
531 dispatch_found(
532 &filename,
532 &filename,
533 *entry,
533 *entry,
534 HgMetadata::from_metadata(metadata),
534 HgMetadata::from_metadata(metadata),
535 &self.dmap.copy_map,
535 &self.dmap.copy_map,
536 self.options,
536 self.options,
537 ),
537 ),
538 ))
538 ))
539 .unwrap();
539 .unwrap();
540 }
540 }
541 } else if (self.matcher.matches_everything()
541 } else if (self.matcher.matches_everything()
542 || self.matcher.matches(&filename))
542 || self.matcher.matches(&filename))
543 && !self.is_ignored(&filename)
543 && !self.is_ignored(&filename)
544 {
544 {
545 if (self.options.list_ignored
545 if (self.options.list_ignored
546 || self.matcher.exact_match(&filename))
546 || self.matcher.exact_match(&filename))
547 && self.dir_ignore(&filename)
547 && self.dir_ignore(&filename)
548 {
548 {
549 if self.options.list_ignored {
549 if self.options.list_ignored {
550 files_sender
550 files_sender
551 .send((filename.to_owned(), Dispatch::Ignored))
551 .send((filename.to_owned(), Dispatch::Ignored))
552 .unwrap();
552 .unwrap();
553 }
553 }
554 } else if self.options.list_unknown {
554 } else if self.options.list_unknown {
555 files_sender
555 files_sender
556 .send((filename.to_owned(), Dispatch::Unknown))
556 .send((filename.to_owned(), Dispatch::Unknown))
557 .unwrap();
557 .unwrap();
558 }
558 }
559 } else if self.is_ignored(&filename) && self.options.list_ignored {
559 } else if self.is_ignored(&filename) && self.options.list_ignored {
560 files_sender
560 files_sender
561 .send((filename.to_owned(), Dispatch::Ignored))
561 .send((filename.to_owned(), Dispatch::Ignored))
562 .unwrap();
562 .unwrap();
563 }
563 }
564 } else if let Some(entry) = entry_option {
564 } else if let Some(entry) = entry_option {
565 // Used to be a file or a folder, now something else.
565 // Used to be a file or a folder, now something else.
566 if self.matcher.matches_everything()
566 if self.matcher.matches_everything()
567 || self.matcher.matches(&filename)
567 || self.matcher.matches(&filename)
568 {
568 {
569 files_sender
569 files_sender
570 .send((filename.to_owned(), dispatch_missing(entry.state)))
570 .send((filename.to_owned(), dispatch_missing(entry.state)))
571 .unwrap();
571 .unwrap();
572 }
572 }
573 }
573 }
574
574
575 Ok(())
575 Ok(())
576 }
576 }
577
577
578 /// A directory was found in the filesystem and needs to be traversed
578 /// A directory was found in the filesystem and needs to be traversed
579 fn handle_traversed_dir<'b>(
579 fn handle_traversed_dir<'b>(
580 &'a self,
580 &'a self,
581 scope: &rayon::Scope<'b>,
581 scope: &rayon::Scope<'b>,
582 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
582 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
583 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
583 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
584 entry_option: Option<&'a DirstateEntry>,
584 entry_option: Option<&'a DirstateEntry>,
585 directory: HgPathBuf,
585 directory: HgPathBuf,
586 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
586 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
587 ) where
587 ) where
588 'a: 'b,
588 'a: 'b,
589 {
589 {
590 scope.spawn(move |_| {
590 scope.spawn(move |_| {
591 // Nested `if` until `rust-lang/rust#53668` is stable
591 // Nested `if` until `rust-lang/rust#53668` is stable
592 if let Some(entry) = entry_option {
592 if let Some(entry) = entry_option {
593 // Used to be a file, is now a folder
593 // Used to be a file, is now a folder
594 if self.matcher.matches_everything()
594 if self.matcher.matches_everything()
595 || self.matcher.matches(&directory)
595 || self.matcher.matches(&directory)
596 {
596 {
597 files_sender
597 files_sender
598 .send((
598 .send((
599 directory.to_owned(),
599 directory.to_owned(),
600 dispatch_missing(entry.state),
600 dispatch_missing(entry.state),
601 ))
601 ))
602 .unwrap();
602 .unwrap();
603 }
603 }
604 }
604 }
605 // Do we need to traverse it?
605 // Do we need to traverse it?
606 if !self.is_ignored(&directory) || self.options.list_ignored {
606 if !self.is_ignored(&directory) || self.options.list_ignored {
607 self.traverse_dir(
607 self.traverse_dir(
608 files_sender,
608 files_sender,
609 directory,
609 directory,
610 &old_results,
610 &old_results,
611 traversed_sender,
611 traversed_sender,
612 )
612 )
613 }
613 }
614 });
614 });
615 }
615 }
616
616
617 /// Decides whether the directory needs to be listed, and if so handles the
617 /// Decides whether the directory needs to be listed, and if so handles the
618 /// entries in a separate thread.
618 /// entries in a separate thread.
619 fn traverse_dir(
619 fn traverse_dir(
620 &self,
620 &self,
621 files_sender: &crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
621 files_sender: &crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
622 directory: impl AsRef<HgPath>,
622 directory: impl AsRef<HgPath>,
623 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
623 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
624 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
624 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
625 ) {
625 ) {
626 let directory = directory.as_ref();
626 let directory = directory.as_ref();
627
627
628 if self.options.collect_traversed_dirs {
628 if self.options.collect_traversed_dirs {
629 traversed_sender
629 traversed_sender
630 .send(directory.to_owned())
630 .send(directory.to_owned())
631 .expect("receiver should outlive sender");
631 .expect("receiver should outlive sender");
632 }
632 }
633
633
634 let visit_entries = match self.matcher.visit_children_set(directory) {
634 let visit_entries = match self.matcher.visit_children_set(directory) {
635 VisitChildrenSet::Empty => return,
635 VisitChildrenSet::Empty => return,
636 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
636 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
637 VisitChildrenSet::Set(set) => Some(set),
637 VisitChildrenSet::Set(set) => Some(set),
638 };
638 };
639 let buf = match hg_path_to_path_buf(directory) {
639 let buf = match hg_path_to_path_buf(directory) {
640 Ok(b) => b,
640 Ok(b) => b,
641 Err(_) => {
641 Err(_) => {
642 files_sender
642 files_sender
643 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
643 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
644 .expect("receiver should outlive sender");
644 .expect("receiver should outlive sender");
645 return;
645 return;
646 }
646 }
647 };
647 };
648 let dir_path = self.root_dir.join(buf);
648 let dir_path = self.root_dir.join(buf);
649
649
650 let skip_dot_hg = !directory.as_bytes().is_empty();
650 let skip_dot_hg = !directory.as_bytes().is_empty();
651 let entries = match list_directory(dir_path, skip_dot_hg) {
651 let entries = match list_directory(dir_path, skip_dot_hg) {
652 Err(e) => {
652 Err(e) => {
653 files_sender
653 files_sender
654 .send((directory.to_owned(), dispatch_os_error(&e)))
654 .send((directory.to_owned(), dispatch_os_error(&e)))
655 .expect("receiver should outlive sender");
655 .expect("receiver should outlive sender");
656 return;
656 return;
657 }
657 }
658 Ok(entries) => entries,
658 Ok(entries) => entries,
659 };
659 };
660
660
661 rayon::scope(|scope| {
661 rayon::scope(|scope| {
662 for (filename, dir_entry) in entries {
662 for (filename, dir_entry) in entries {
663 if let Some(ref set) = visit_entries {
663 if let Some(ref set) = visit_entries {
664 if !set.contains(filename.deref()) {
664 if !set.contains(filename.deref()) {
665 continue;
665 continue;
666 }
666 }
667 }
667 }
668 // TODO normalize
668 // TODO normalize
669 let filename = if directory.is_empty() {
669 let filename = if directory.is_empty() {
670 filename.to_owned()
670 filename.to_owned()
671 } else {
671 } else {
672 directory.join(&filename)
672 directory.join(&filename)
673 };
673 };
674
674
675 if !old_results.contains_key(filename.deref()) {
675 if !old_results.contains_key(filename.deref()) {
676 match self.handle_traversed_entry(
676 match self.handle_traversed_entry(
677 scope,
677 scope,
678 files_sender,
678 files_sender,
679 old_results,
679 old_results,
680 filename,
680 filename,
681 dir_entry,
681 dir_entry,
682 traversed_sender.clone(),
682 traversed_sender.clone(),
683 ) {
683 ) {
684 Err(e) => {
684 Err(e) => {
685 files_sender
685 files_sender
686 .send((
686 .send((
687 directory.to_owned(),
687 directory.to_owned(),
688 dispatch_os_error(&e),
688 dispatch_os_error(&e),
689 ))
689 ))
690 .expect("receiver should outlive sender");
690 .expect("receiver should outlive sender");
691 }
691 }
692 Ok(_) => {}
692 Ok(_) => {}
693 }
693 }
694 }
694 }
695 }
695 }
696 })
696 })
697 }
697 }
698
698
699 /// Add the files in the dirstate to the results.
699 /// Add the files in the dirstate to the results.
700 ///
700 ///
701 /// This takes a mutable reference to the results to account for the
701 /// This takes a mutable reference to the results to account for the
702 /// `extend` in timings
702 /// `extend` in timings
703 #[timed]
703 #[timed]
704 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
704 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
705 results.par_extend(
705 results.par_extend(
706 self.dmap
706 self.dmap
707 .par_iter()
707 .par_iter()
708 .filter(|(path, _)| self.matcher.matches(path))
708 .filter(|(path, _)| self.matcher.matches(path))
709 .map(move |(filename, entry)| {
709 .map(move |(filename, entry)| {
710 let filename: &HgPath = filename;
710 let filename: &HgPath = filename;
711 let filename_as_path = match hg_path_to_path_buf(filename)
711 let filename_as_path = match hg_path_to_path_buf(filename)
712 {
712 {
713 Ok(f) => f,
713 Ok(f) => f,
714 Err(_) => {
714 Err(_) => {
715 return (
715 return (
716 Cow::Borrowed(filename),
716 Cow::Borrowed(filename),
717 INVALID_PATH_DISPATCH,
717 INVALID_PATH_DISPATCH,
718 )
718 )
719 }
719 }
720 };
720 };
721 let meta = self
721 let meta = self
722 .root_dir
722 .root_dir
723 .join(filename_as_path)
723 .join(filename_as_path)
724 .symlink_metadata();
724 .symlink_metadata();
725 match meta {
725 match meta {
726 Ok(m)
726 Ok(m)
727 if !(m.file_type().is_file()
727 if !(m.file_type().is_file()
728 || m.file_type().is_symlink()) =>
728 || m.file_type().is_symlink()) =>
729 {
729 {
730 (
730 (
731 Cow::Borrowed(filename),
731 Cow::Borrowed(filename),
732 dispatch_missing(entry.state),
732 dispatch_missing(entry.state),
733 )
733 )
734 }
734 }
735 Ok(m) => (
735 Ok(m) => (
736 Cow::Borrowed(filename),
736 Cow::Borrowed(filename),
737 dispatch_found(
737 dispatch_found(
738 filename,
738 filename,
739 *entry,
739 *entry,
740 HgMetadata::from_metadata(m),
740 HgMetadata::from_metadata(m),
741 &self.dmap.copy_map,
741 &self.dmap.copy_map,
742 self.options,
742 self.options,
743 ),
743 ),
744 ),
744 ),
745 Err(e)
745 Err(e)
746 if e.kind() == ErrorKind::NotFound
746 if e.kind() == ErrorKind::NotFound
747 || e.raw_os_error() == Some(20) =>
747 || e.raw_os_error() == Some(20) =>
748 {
748 {
749 // Rust does not yet have an `ErrorKind` for
749 // Rust does not yet have an `ErrorKind` for
750 // `NotADirectory` (errno 20)
750 // `NotADirectory` (errno 20)
751 // It happens if the dirstate contains `foo/bar`
751 // It happens if the dirstate contains `foo/bar`
752 // and foo is not a
752 // and foo is not a
753 // directory
753 // directory
754 (
754 (
755 Cow::Borrowed(filename),
755 Cow::Borrowed(filename),
756 dispatch_missing(entry.state),
756 dispatch_missing(entry.state),
757 )
757 )
758 }
758 }
759 Err(e) => {
759 Err(e) => {
760 (Cow::Borrowed(filename), dispatch_os_error(&e))
760 (Cow::Borrowed(filename), dispatch_os_error(&e))
761 }
761 }
762 }
762 }
763 }),
763 }),
764 );
764 );
765 }
765 }
766
766
767 /// Checks all files that are in the dirstate but were not found during the
767 /// Checks all files that are in the dirstate but were not found during the
768 /// working directory traversal. This means that the rest must
768 /// working directory traversal. This means that the rest must
769 /// be either ignored, under a symlink or under a new nested repo.
769 /// be either ignored, under a symlink or under a new nested repo.
770 ///
770 ///
771 /// This takes a mutable reference to the results to account for the
771 /// This takes a mutable reference to the results to account for the
772 /// `extend` in timings
772 /// `extend` in timings
773 #[timed]
773 #[timed]
774 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
774 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
775 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
775 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
776 if results.is_empty() && self.matcher.matches_everything() {
776 if results.is_empty() && self.matcher.matches_everything() {
777 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
777 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
778 } else {
778 } else {
779 // Only convert to a hashmap if needed.
779 // Only convert to a hashmap if needed.
780 let old_results: FastHashMap<_, _> =
780 let old_results: FastHashMap<_, _> =
781 results.iter().cloned().collect();
781 results.iter().cloned().collect();
782 self.dmap
782 self.dmap
783 .iter()
783 .iter()
784 .filter_map(move |(f, e)| {
784 .filter_map(move |(f, e)| {
785 if !old_results.contains_key(f.deref())
785 if !old_results.contains_key(f.deref())
786 && self.matcher.matches(f)
786 && self.matcher.matches(f)
787 {
787 {
788 Some((f.deref(), e))
788 Some((f.deref(), e))
789 } else {
789 } else {
790 None
790 None
791 }
791 }
792 })
792 })
793 .collect()
793 .collect()
794 };
794 };
795
795
796 let path_auditor = PathAuditor::new(&self.root_dir);
796 let path_auditor = PathAuditor::new(&self.root_dir);
797
797
798 let new_results = to_visit.into_par_iter().filter_map(
798 let new_results = to_visit.into_par_iter().filter_map(
799 |(filename, entry)| -> Option<_> {
799 |(filename, entry)| -> Option<_> {
800 // Report ignored items in the dmap as long as they are not
800 // Report ignored items in the dmap as long as they are not
801 // under a symlink directory.
801 // under a symlink directory.
802 if path_auditor.check(filename) {
802 if path_auditor.check(filename) {
803 // TODO normalize for case-insensitive filesystems
803 // TODO normalize for case-insensitive filesystems
804 let buf = match hg_path_to_path_buf(filename) {
804 let buf = match hg_path_to_path_buf(filename) {
805 Ok(x) => x,
805 Ok(x) => x,
806 Err(_) => {
806 Err(_) => {
807 return Some((
807 return Some((
808 Cow::Owned(filename.to_owned()),
808 Cow::Owned(filename.to_owned()),
809 INVALID_PATH_DISPATCH,
809 INVALID_PATH_DISPATCH,
810 ));
810 ));
811 }
811 }
812 };
812 };
813 Some((
813 Some((
814 Cow::Owned(filename.to_owned()),
814 Cow::Owned(filename.to_owned()),
815 match self.root_dir.join(&buf).symlink_metadata() {
815 match self.root_dir.join(&buf).symlink_metadata() {
816 // File was just ignored, no links, and exists
816 // File was just ignored, no links, and exists
817 Ok(meta) => {
817 Ok(meta) => {
818 let metadata = HgMetadata::from_metadata(meta);
818 let metadata = HgMetadata::from_metadata(meta);
819 dispatch_found(
819 dispatch_found(
820 filename,
820 filename,
821 *entry,
821 *entry,
822 metadata,
822 metadata,
823 &self.dmap.copy_map,
823 &self.dmap.copy_map,
824 self.options,
824 self.options,
825 )
825 )
826 }
826 }
827 // File doesn't exist
827 // File doesn't exist
828 Err(_) => dispatch_missing(entry.state),
828 Err(_) => dispatch_missing(entry.state),
829 },
829 },
830 ))
830 ))
831 } else {
831 } else {
832 // It's either missing or under a symlink directory which
832 // It's either missing or under a symlink directory which
833 // we, in this case, report as missing.
833 // we, in this case, report as missing.
834 Some((
834 Some((
835 Cow::Owned(filename.to_owned()),
835 Cow::Owned(filename.to_owned()),
836 dispatch_missing(entry.state),
836 dispatch_missing(entry.state),
837 ))
837 ))
838 }
838 }
839 },
839 },
840 );
840 );
841
841
842 results.par_extend(new_results);
842 results.par_extend(new_results);
843 }
843 }
844 }
844 }
845
845
846 #[timed]
846 #[timed]
847 pub fn build_response<'a>(
847 pub fn build_response<'a>(
848 results: impl IntoIterator<Item = DispatchedPath<'a>>,
848 results: impl IntoIterator<Item = DispatchedPath<'a>>,
849 traversed: Vec<HgPathBuf>,
849 traversed: Vec<HgPathBuf>,
850 ) -> (Vec<HgPathCow<'a>>, DirstateStatus<'a>) {
850 ) -> (Vec<HgPathCow<'a>>, DirstateStatus<'a>) {
851 let mut lookup = vec![];
851 let mut lookup = vec![];
852 let mut modified = vec![];
852 let mut modified = vec![];
853 let mut added = vec![];
853 let mut added = vec![];
854 let mut removed = vec![];
854 let mut removed = vec![];
855 let mut deleted = vec![];
855 let mut deleted = vec![];
856 let mut clean = vec![];
856 let mut clean = vec![];
857 let mut ignored = vec![];
857 let mut ignored = vec![];
858 let mut unknown = vec![];
858 let mut unknown = vec![];
859 let mut bad = vec![];
859 let mut bad = vec![];
860
860
861 for (filename, dispatch) in results.into_iter() {
861 for (filename, dispatch) in results.into_iter() {
862 match dispatch {
862 match dispatch {
863 Dispatch::Unknown => unknown.push(filename),
863 Dispatch::Unknown => unknown.push(filename),
864 Dispatch::Unsure => lookup.push(filename),
864 Dispatch::Unsure => lookup.push(filename),
865 Dispatch::Modified => modified.push(filename),
865 Dispatch::Modified => modified.push(filename),
866 Dispatch::Added => added.push(filename),
866 Dispatch::Added => added.push(filename),
867 Dispatch::Removed => removed.push(filename),
867 Dispatch::Removed => removed.push(filename),
868 Dispatch::Deleted => deleted.push(filename),
868 Dispatch::Deleted => deleted.push(filename),
869 Dispatch::Clean => clean.push(filename),
869 Dispatch::Clean => clean.push(filename),
870 Dispatch::Ignored => ignored.push(filename),
870 Dispatch::Ignored => ignored.push(filename),
871 Dispatch::None => {}
871 Dispatch::None => {}
872 Dispatch::Bad(reason) => bad.push((filename, reason)),
872 Dispatch::Bad(reason) => bad.push((filename, reason)),
873 Dispatch::Directory { .. } => {}
873 Dispatch::Directory { .. } => {}
874 }
874 }
875 }
875 }
876
876
877 (
877 (
878 lookup,
878 lookup,
879 DirstateStatus {
879 DirstateStatus {
880 modified,
880 modified,
881 added,
881 added,
882 removed,
882 removed,
883 deleted,
883 deleted,
884 clean,
884 clean,
885 ignored,
885 ignored,
886 unknown,
886 unknown,
887 bad,
887 bad,
888 traversed,
888 traversed,
889 },
889 },
890 )
890 )
891 }
891 }
892
892
893 /// Get the status of files in the working directory.
893 /// Get the status of files in the working directory.
894 ///
894 ///
895 /// This is the current entry-point for `hg-core` and is realistically unusable
895 /// This is the current entry-point for `hg-core` and is realistically unusable
896 /// outside of a Python context because its arguments need to provide a lot of
896 /// outside of a Python context because its arguments need to provide a lot of
897 /// information that will not be necessary in the future.
897 /// information that will not be necessary in the future.
898 #[timed]
898 #[timed]
899 pub fn status<'a>(
899 pub fn status<'a>(
900 dmap: &'a DirstateMap,
900 dmap: &'a DirstateMap,
901 matcher: &'a (impl Matcher + Sync),
901 matcher: &'a (dyn Matcher + Sync),
902 root_dir: PathBuf,
902 root_dir: PathBuf,
903 ignore_files: Vec<PathBuf>,
903 ignore_files: Vec<PathBuf>,
904 options: StatusOptions,
904 options: StatusOptions,
905 ) -> StatusResult<(
905 ) -> StatusResult<(
906 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
906 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
907 Vec<PatternFileWarning>,
907 Vec<PatternFileWarning>,
908 )> {
908 )> {
909 let (status, warnings) =
909 let (status, warnings) =
910 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
910 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
911
911
912 Ok((status.run()?, warnings))
912 Ok((status.run()?, warnings))
913 }
913 }
@@ -1,122 +1,123 b''
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 // and Mercurial contributors
2 // and Mercurial contributors
3 //
3 //
4 // This software may be used and distributed according to the terms of the
4 // This software may be used and distributed according to the terms of the
5 // GNU General Public License version 2 or any later version.
5 // GNU General Public License version 2 or any later version.
6
6
7 mod ancestors;
7 mod ancestors;
8 pub mod dagops;
8 pub mod dagops;
9 pub mod errors;
9 pub mod errors;
10 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
10 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
11 mod dirstate;
11 mod dirstate;
12 pub mod dirstate_tree;
12 pub mod discovery;
13 pub mod discovery;
13 pub mod requirements;
14 pub mod requirements;
14 pub mod testing; // unconditionally built, for use from integration tests
15 pub mod testing; // unconditionally built, for use from integration tests
15 pub use dirstate::{
16 pub use dirstate::{
16 dirs_multiset::{DirsMultiset, DirsMultisetIter},
17 dirs_multiset::{DirsMultiset, DirsMultisetIter},
17 dirstate_map::DirstateMap,
18 dirstate_map::DirstateMap,
18 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
19 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
19 status::{
20 status::{
20 status, BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
21 status, BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
21 StatusOptions,
22 StatusOptions,
22 },
23 },
23 CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState,
24 CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState,
24 StateMap, StateMapIter,
25 StateMap, StateMapIter,
25 };
26 };
26 pub mod copy_tracing;
27 pub mod copy_tracing;
27 mod filepatterns;
28 mod filepatterns;
28 pub mod matchers;
29 pub mod matchers;
29 pub mod repo;
30 pub mod repo;
30 pub mod revlog;
31 pub mod revlog;
31 pub use revlog::*;
32 pub use revlog::*;
32 pub mod config;
33 pub mod config;
33 pub mod logging;
34 pub mod logging;
34 pub mod operations;
35 pub mod operations;
35 pub mod revset;
36 pub mod revset;
36 pub mod utils;
37 pub mod utils;
37
38
38 use crate::utils::hg_path::{HgPathBuf, HgPathError};
39 use crate::utils::hg_path::{HgPathBuf, HgPathError};
39 pub use filepatterns::{
40 pub use filepatterns::{
40 parse_pattern_syntax, read_pattern_file, IgnorePattern,
41 parse_pattern_syntax, read_pattern_file, IgnorePattern,
41 PatternFileWarning, PatternSyntax,
42 PatternFileWarning, PatternSyntax,
42 };
43 };
43 use std::collections::HashMap;
44 use std::collections::HashMap;
44 use std::fmt;
45 use std::fmt;
45 use twox_hash::RandomXxHashBuilder64;
46 use twox_hash::RandomXxHashBuilder64;
46
47
47 /// This is a contract between the `micro-timer` crate and us, to expose
48 /// This is a contract between the `micro-timer` crate and us, to expose
48 /// the `log` crate as `crate::log`.
49 /// the `log` crate as `crate::log`.
49 use log;
50 use log;
50
51
51 pub type LineNumber = usize;
52 pub type LineNumber = usize;
52
53
53 /// Rust's default hasher is too slow because it tries to prevent collision
54 /// Rust's default hasher is too slow because it tries to prevent collision
54 /// attacks. We are not concerned about those: if an ill-minded person has
55 /// attacks. We are not concerned about those: if an ill-minded person has
55 /// write access to your repository, you have other issues.
56 /// write access to your repository, you have other issues.
56 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
57 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
57
58
58 #[derive(Debug, PartialEq)]
59 #[derive(Debug, PartialEq)]
59 pub enum DirstateMapError {
60 pub enum DirstateMapError {
60 PathNotFound(HgPathBuf),
61 PathNotFound(HgPathBuf),
61 EmptyPath,
62 EmptyPath,
62 InvalidPath(HgPathError),
63 InvalidPath(HgPathError),
63 }
64 }
64
65
65 impl fmt::Display for DirstateMapError {
66 impl fmt::Display for DirstateMapError {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 match self {
68 match self {
68 DirstateMapError::PathNotFound(_) => {
69 DirstateMapError::PathNotFound(_) => {
69 f.write_str("expected a value, found none")
70 f.write_str("expected a value, found none")
70 }
71 }
71 DirstateMapError::EmptyPath => {
72 DirstateMapError::EmptyPath => {
72 f.write_str("Overflow in dirstate.")
73 f.write_str("Overflow in dirstate.")
73 }
74 }
74 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
75 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
75 }
76 }
76 }
77 }
77 }
78 }
78
79
79 #[derive(Debug, derive_more::From)]
80 #[derive(Debug, derive_more::From)]
80 pub enum DirstateError {
81 pub enum DirstateError {
81 Map(DirstateMapError),
82 Map(DirstateMapError),
82 Common(errors::HgError),
83 Common(errors::HgError),
83 }
84 }
84
85
85 #[derive(Debug, derive_more::From)]
86 #[derive(Debug, derive_more::From)]
86 pub enum PatternError {
87 pub enum PatternError {
87 #[from]
88 #[from]
88 Path(HgPathError),
89 Path(HgPathError),
89 UnsupportedSyntax(String),
90 UnsupportedSyntax(String),
90 UnsupportedSyntaxInFile(String, String, usize),
91 UnsupportedSyntaxInFile(String, String, usize),
91 TooLong(usize),
92 TooLong(usize),
92 #[from]
93 #[from]
93 IO(std::io::Error),
94 IO(std::io::Error),
94 /// Needed a pattern that can be turned into a regex but got one that
95 /// Needed a pattern that can be turned into a regex but got one that
95 /// can't. This should only happen through programmer error.
96 /// can't. This should only happen through programmer error.
96 NonRegexPattern(IgnorePattern),
97 NonRegexPattern(IgnorePattern),
97 }
98 }
98
99
99 impl fmt::Display for PatternError {
100 impl fmt::Display for PatternError {
100 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101 match self {
102 match self {
102 PatternError::UnsupportedSyntax(syntax) => {
103 PatternError::UnsupportedSyntax(syntax) => {
103 write!(f, "Unsupported syntax {}", syntax)
104 write!(f, "Unsupported syntax {}", syntax)
104 }
105 }
105 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
106 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
106 write!(
107 write!(
107 f,
108 f,
108 "{}:{}: unsupported syntax {}",
109 "{}:{}: unsupported syntax {}",
109 file_path, line, syntax
110 file_path, line, syntax
110 )
111 )
111 }
112 }
112 PatternError::TooLong(size) => {
113 PatternError::TooLong(size) => {
113 write!(f, "matcher pattern is too long ({} bytes)", size)
114 write!(f, "matcher pattern is too long ({} bytes)", size)
114 }
115 }
115 PatternError::IO(error) => error.fmt(f),
116 PatternError::IO(error) => error.fmt(f),
116 PatternError::Path(error) => error.fmt(f),
117 PatternError::Path(error) => error.fmt(f),
117 PatternError::NonRegexPattern(pattern) => {
118 PatternError::NonRegexPattern(pattern) => {
118 write!(f, "'{:?}' cannot be turned into a regex", pattern)
119 write!(f, "'{:?}' cannot be turned into a regex", pattern)
119 }
120 }
120 }
121 }
121 }
122 }
122 }
123 }
@@ -1,73 +1,73 b''
1 // dirstate_status.rs
1 // dirstate_status.rs
2 //
2 //
3 // Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019, Raphaël Gomès <rgomes@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 crate::dirstate::status::{build_response, Dispatch, HgPathCow, Status};
8 use crate::dirstate::status::{build_response, Dispatch, HgPathCow, Status};
9 use crate::matchers::Matcher;
9 use crate::matchers::Matcher;
10 use crate::{DirstateStatus, StatusError};
10 use crate::{DirstateStatus, StatusError};
11
11
12 /// A tuple of the paths that need to be checked in the filelog because it's
12 /// A tuple of the paths that need to be checked in the filelog because it's
13 /// ambiguous whether they've changed, and the rest of the already dispatched
13 /// ambiguous whether they've changed, and the rest of the already dispatched
14 /// files.
14 /// files.
15 pub type LookupAndStatus<'a> = (Vec<HgPathCow<'a>>, DirstateStatus<'a>);
15 pub type LookupAndStatus<'a> = (Vec<HgPathCow<'a>>, DirstateStatus<'a>);
16
16
17 impl<'a, M: Matcher + Sync> Status<'a, M> {
17 impl<'a, M: ?Sized + Matcher + Sync> Status<'a, M> {
18 pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> {
18 pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> {
19 let (traversed_sender, traversed_receiver) =
19 let (traversed_sender, traversed_receiver) =
20 crossbeam_channel::unbounded();
20 crossbeam_channel::unbounded();
21
21
22 // Step 1: check the files explicitly mentioned by the user
22 // Step 1: check the files explicitly mentioned by the user
23 let (work, mut results) = self.walk_explicit(traversed_sender.clone());
23 let (work, mut results) = self.walk_explicit(traversed_sender.clone());
24
24
25 if !work.is_empty() {
25 if !work.is_empty() {
26 // Hashmaps are quite a bit slower to build than vecs, so only
26 // Hashmaps are quite a bit slower to build than vecs, so only
27 // build it if needed.
27 // build it if needed.
28 let old_results = results.iter().cloned().collect();
28 let old_results = results.iter().cloned().collect();
29
29
30 // Step 2: recursively check the working directory for changes if
30 // Step 2: recursively check the working directory for changes if
31 // needed
31 // needed
32 for (dir, dispatch) in work {
32 for (dir, dispatch) in work {
33 match dispatch {
33 match dispatch {
34 Dispatch::Directory { was_file } => {
34 Dispatch::Directory { was_file } => {
35 if was_file {
35 if was_file {
36 results.push((dir.to_owned(), Dispatch::Removed));
36 results.push((dir.to_owned(), Dispatch::Removed));
37 }
37 }
38 if self.options.list_ignored
38 if self.options.list_ignored
39 || self.options.list_unknown
39 || self.options.list_unknown
40 && !self.dir_ignore(&dir)
40 && !self.dir_ignore(&dir)
41 {
41 {
42 self.traverse(
42 self.traverse(
43 &dir,
43 &dir,
44 &old_results,
44 &old_results,
45 &mut results,
45 &mut results,
46 traversed_sender.clone(),
46 traversed_sender.clone(),
47 );
47 );
48 }
48 }
49 }
49 }
50 _ => {
50 _ => {
51 unreachable!("There can only be directories in `work`")
51 unreachable!("There can only be directories in `work`")
52 }
52 }
53 }
53 }
54 }
54 }
55 }
55 }
56
56
57 if !self.matcher.is_exact() {
57 if !self.matcher.is_exact() {
58 if self.options.list_unknown {
58 if self.options.list_unknown {
59 self.handle_unknowns(&mut results);
59 self.handle_unknowns(&mut results);
60 } else {
60 } else {
61 // TODO this is incorrect, see issue6335
61 // TODO this is incorrect, see issue6335
62 // This requires a fix in both Python and Rust that can happen
62 // This requires a fix in both Python and Rust that can happen
63 // with other pending changes to `status`.
63 // with other pending changes to `status`.
64 self.extend_from_dmap(&mut results);
64 self.extend_from_dmap(&mut results);
65 }
65 }
66 }
66 }
67
67
68 drop(traversed_sender);
68 drop(traversed_sender);
69 let traversed = traversed_receiver.into_iter().collect();
69 let traversed = traversed_receiver.into_iter().collect();
70
70
71 Ok(build_response(results, traversed))
71 Ok(build_response(results, traversed))
72 }
72 }
73 }
73 }
@@ -1,581 +1,579 b''
1 // dirstate_map.rs
1 // dirstate_map.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@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 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::{Ref, RefCell};
11 use std::cell::{Ref, RefCell};
12 use std::convert::TryInto;
12 use std::convert::TryInto;
13 use std::time::Duration;
13 use std::time::Duration;
14
14
15 use cpython::{
15 use cpython::{
16 exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList,
16 exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList,
17 PyObject, PyResult, PySet, PyString, PyTuple, Python, PythonObject,
17 PyObject, PyResult, PySet, PyString, PyTuple, Python, PythonObject,
18 ToPyObject, UnsafePyLeaked,
18 ToPyObject, UnsafePyLeaked,
19 };
19 };
20
20
21 use crate::{
21 use crate::{
22 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
22 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
23 dirstate::non_normal_entries::{
23 dirstate::non_normal_entries::{
24 NonNormalEntries, NonNormalEntriesIterator,
24 NonNormalEntries, NonNormalEntriesIterator,
25 },
25 },
26 dirstate::{dirs_multiset::Dirs, make_dirstate_tuple},
26 dirstate::{dirs_multiset::Dirs, make_dirstate_tuple},
27 parsers::dirstate_parents_to_pytuple,
27 parsers::dirstate_parents_to_pytuple,
28 };
28 };
29 use hg::{
29 use hg::{
30 dirstate_tree::dispatch::DirstateMapMethods,
30 errors::HgError,
31 errors::HgError,
31 revlog::Node,
32 revlog::Node,
32 utils::hg_path::{HgPath, HgPathBuf},
33 utils::hg_path::{HgPath, HgPathBuf},
33 DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap,
34 DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap,
34 DirstateMapError, DirstateParents, EntryState, StateMapIter,
35 DirstateMapError, DirstateParents, EntryState, StateMapIter,
35 };
36 };
36
37
37 // TODO
38 // TODO
38 // This object needs to share references to multiple members of its Rust
39 // This object needs to share references to multiple members of its Rust
39 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
40 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
40 // Right now `CopyMap` is done, but it needs to have an explicit reference
41 // Right now `CopyMap` is done, but it needs to have an explicit reference
41 // to `RustDirstateMap` which itself needs to have an encapsulation for
42 // to `RustDirstateMap` which itself needs to have an encapsulation for
42 // every method in `CopyMap` (copymapcopy, etc.).
43 // every method in `CopyMap` (copymapcopy, etc.).
43 // This is ugly and hard to maintain.
44 // This is ugly and hard to maintain.
44 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
45 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
45 // `py_class!` is already implemented and does not mention
46 // `py_class!` is already implemented and does not mention
46 // `RustDirstateMap`, rightfully so.
47 // `RustDirstateMap`, rightfully so.
47 // All attributes also have to have a separate refcount data attribute for
48 // All attributes also have to have a separate refcount data attribute for
48 // leaks, with all methods that go along for reference sharing.
49 // leaks, with all methods that go along for reference sharing.
49 py_class!(pub class DirstateMap |py| {
50 py_class!(pub class DirstateMap |py| {
50 @shared data inner: RustDirstateMap;
51 @shared data inner: Box<dyn DirstateMapMethods + Send>;
51
52
52 def __new__(_cls, _root: PyObject) -> PyResult<Self> {
53 def __new__(_cls, _root: PyObject) -> PyResult<Self> {
53 let inner = RustDirstateMap::default();
54 let inner = Box::new(RustDirstateMap::default());
54 Self::create_instance(py, inner)
55 Self::create_instance(py, inner)
55 }
56 }
56
57
57 def clear(&self) -> PyResult<PyObject> {
58 def clear(&self) -> PyResult<PyObject> {
58 self.inner(py).borrow_mut().clear();
59 self.inner(py).borrow_mut().clear();
59 Ok(py.None())
60 Ok(py.None())
60 }
61 }
61
62
62 def get(
63 def get(
63 &self,
64 &self,
64 key: PyObject,
65 key: PyObject,
65 default: Option<PyObject> = None
66 default: Option<PyObject> = None
66 ) -> PyResult<Option<PyObject>> {
67 ) -> PyResult<Option<PyObject>> {
67 let key = key.extract::<PyBytes>(py)?;
68 let key = key.extract::<PyBytes>(py)?;
68 match self.inner(py).borrow().get(HgPath::new(key.data(py))) {
69 match self.inner(py).borrow().get(HgPath::new(key.data(py))) {
69 Some(entry) => {
70 Some(entry) => {
70 Ok(Some(make_dirstate_tuple(py, entry)?))
71 Ok(Some(make_dirstate_tuple(py, entry)?))
71 },
72 },
72 None => Ok(default)
73 None => Ok(default)
73 }
74 }
74 }
75 }
75
76
76 def addfile(
77 def addfile(
77 &self,
78 &self,
78 f: PyObject,
79 f: PyObject,
79 oldstate: PyObject,
80 oldstate: PyObject,
80 state: PyObject,
81 state: PyObject,
81 mode: PyObject,
82 mode: PyObject,
82 size: PyObject,
83 size: PyObject,
83 mtime: PyObject
84 mtime: PyObject
84 ) -> PyResult<PyObject> {
85 ) -> PyResult<PyObject> {
85 self.inner(py).borrow_mut().add_file(
86 self.inner(py).borrow_mut().add_file(
86 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
87 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
87 oldstate.extract::<PyBytes>(py)?.data(py)[0]
88 oldstate.extract::<PyBytes>(py)?.data(py)[0]
88 .try_into()
89 .try_into()
89 .map_err(|e: HgError| {
90 .map_err(|e: HgError| {
90 PyErr::new::<exc::ValueError, _>(py, e.to_string())
91 PyErr::new::<exc::ValueError, _>(py, e.to_string())
91 })?,
92 })?,
92 DirstateEntry {
93 DirstateEntry {
93 state: state.extract::<PyBytes>(py)?.data(py)[0]
94 state: state.extract::<PyBytes>(py)?.data(py)[0]
94 .try_into()
95 .try_into()
95 .map_err(|e: HgError| {
96 .map_err(|e: HgError| {
96 PyErr::new::<exc::ValueError, _>(py, e.to_string())
97 PyErr::new::<exc::ValueError, _>(py, e.to_string())
97 })?,
98 })?,
98 mode: mode.extract(py)?,
99 mode: mode.extract(py)?,
99 size: size.extract(py)?,
100 size: size.extract(py)?,
100 mtime: mtime.extract(py)?,
101 mtime: mtime.extract(py)?,
101 },
102 },
102 ).and(Ok(py.None())).or_else(|e: DirstateMapError| {
103 ).and(Ok(py.None())).or_else(|e: DirstateMapError| {
103 Err(PyErr::new::<exc::ValueError, _>(py, e.to_string()))
104 Err(PyErr::new::<exc::ValueError, _>(py, e.to_string()))
104 })
105 })
105 }
106 }
106
107
107 def removefile(
108 def removefile(
108 &self,
109 &self,
109 f: PyObject,
110 f: PyObject,
110 oldstate: PyObject,
111 oldstate: PyObject,
111 size: PyObject
112 size: PyObject
112 ) -> PyResult<PyObject> {
113 ) -> PyResult<PyObject> {
113 self.inner(py).borrow_mut()
114 self.inner(py).borrow_mut()
114 .remove_file(
115 .remove_file(
115 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
116 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
116 oldstate.extract::<PyBytes>(py)?.data(py)[0]
117 oldstate.extract::<PyBytes>(py)?.data(py)[0]
117 .try_into()
118 .try_into()
118 .map_err(|e: HgError| {
119 .map_err(|e: HgError| {
119 PyErr::new::<exc::ValueError, _>(py, e.to_string())
120 PyErr::new::<exc::ValueError, _>(py, e.to_string())
120 })?,
121 })?,
121 size.extract(py)?,
122 size.extract(py)?,
122 )
123 )
123 .or_else(|_| {
124 .or_else(|_| {
124 Err(PyErr::new::<exc::OSError, _>(
125 Err(PyErr::new::<exc::OSError, _>(
125 py,
126 py,
126 "Dirstate error".to_string(),
127 "Dirstate error".to_string(),
127 ))
128 ))
128 })?;
129 })?;
129 Ok(py.None())
130 Ok(py.None())
130 }
131 }
131
132
132 def dropfile(
133 def dropfile(
133 &self,
134 &self,
134 f: PyObject,
135 f: PyObject,
135 oldstate: PyObject
136 oldstate: PyObject
136 ) -> PyResult<PyBool> {
137 ) -> PyResult<PyBool> {
137 self.inner(py).borrow_mut()
138 self.inner(py).borrow_mut()
138 .drop_file(
139 .drop_file(
139 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
140 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
140 oldstate.extract::<PyBytes>(py)?.data(py)[0]
141 oldstate.extract::<PyBytes>(py)?.data(py)[0]
141 .try_into()
142 .try_into()
142 .map_err(|e: HgError| {
143 .map_err(|e: HgError| {
143 PyErr::new::<exc::ValueError, _>(py, e.to_string())
144 PyErr::new::<exc::ValueError, _>(py, e.to_string())
144 })?,
145 })?,
145 )
146 )
146 .and_then(|b| Ok(b.to_py_object(py)))
147 .and_then(|b| Ok(b.to_py_object(py)))
147 .or_else(|e| {
148 .or_else(|e| {
148 Err(PyErr::new::<exc::OSError, _>(
149 Err(PyErr::new::<exc::OSError, _>(
149 py,
150 py,
150 format!("Dirstate error: {}", e.to_string()),
151 format!("Dirstate error: {}", e.to_string()),
151 ))
152 ))
152 })
153 })
153 }
154 }
154
155
155 def clearambiguoustimes(
156 def clearambiguoustimes(
156 &self,
157 &self,
157 files: PyObject,
158 files: PyObject,
158 now: PyObject
159 now: PyObject
159 ) -> PyResult<PyObject> {
160 ) -> PyResult<PyObject> {
160 let files: PyResult<Vec<HgPathBuf>> = files
161 let files: PyResult<Vec<HgPathBuf>> = files
161 .iter(py)?
162 .iter(py)?
162 .map(|filename| {
163 .map(|filename| {
163 Ok(HgPathBuf::from_bytes(
164 Ok(HgPathBuf::from_bytes(
164 filename?.extract::<PyBytes>(py)?.data(py),
165 filename?.extract::<PyBytes>(py)?.data(py),
165 ))
166 ))
166 })
167 })
167 .collect();
168 .collect();
168 self.inner(py).borrow_mut()
169 self.inner(py).borrow_mut()
169 .clear_ambiguous_times(files?, now.extract(py)?);
170 .clear_ambiguous_times(files?, now.extract(py)?);
170 Ok(py.None())
171 Ok(py.None())
171 }
172 }
172
173
173 def other_parent_entries(&self) -> PyResult<PyObject> {
174 def other_parent_entries(&self) -> PyResult<PyObject> {
174 let mut inner_shared = self.inner(py).borrow_mut();
175 let mut inner_shared = self.inner(py).borrow_mut();
175 let (_, other_parent) =
176 let (_, other_parent) =
176 inner_shared.get_non_normal_other_parent_entries();
177 inner_shared.get_non_normal_other_parent_entries();
177
178
178 let set = PySet::empty(py)?;
179 let set = PySet::empty(py)?;
179 for path in other_parent.iter() {
180 for path in other_parent.iter() {
180 set.add(py, PyBytes::new(py, path.as_bytes()))?;
181 set.add(py, PyBytes::new(py, path.as_bytes()))?;
181 }
182 }
182 Ok(set.into_object())
183 Ok(set.into_object())
183 }
184 }
184
185
185 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
186 def non_normal_entries(&self) -> PyResult<NonNormalEntries> {
186 NonNormalEntries::from_inner(py, self.clone_ref(py))
187 NonNormalEntries::from_inner(py, self.clone_ref(py))
187 }
188 }
188
189
189 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
190 def non_normal_entries_contains(&self, key: PyObject) -> PyResult<bool> {
190 let key = key.extract::<PyBytes>(py)?;
191 let key = key.extract::<PyBytes>(py)?;
191 Ok(self
192 Ok(self
192 .inner(py)
193 .inner(py)
193 .borrow_mut()
194 .borrow_mut()
194 .get_non_normal_other_parent_entries().0
195 .get_non_normal_other_parent_entries().0
195 .contains(HgPath::new(key.data(py))))
196 .contains(HgPath::new(key.data(py))))
196 }
197 }
197
198
198 def non_normal_entries_display(&self) -> PyResult<PyString> {
199 def non_normal_entries_display(&self) -> PyResult<PyString> {
199 Ok(
200 Ok(
200 PyString::new(
201 PyString::new(
201 py,
202 py,
202 &format!(
203 &format!(
203 "NonNormalEntries: {:?}",
204 "NonNormalEntries: {:?}",
204 self
205 self
205 .inner(py)
206 .inner(py)
206 .borrow_mut()
207 .borrow_mut()
207 .get_non_normal_other_parent_entries().0
208 .get_non_normal_other_parent_entries().0
208 .iter().map(|o| o))
209 .iter().map(|o| o))
209 )
210 )
210 )
211 )
211 }
212 }
212
213
213 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
214 def non_normal_entries_remove(&self, key: PyObject) -> PyResult<PyObject> {
214 let key = key.extract::<PyBytes>(py)?;
215 let key = key.extract::<PyBytes>(py)?;
215 self
216 self
216 .inner(py)
217 .inner(py)
217 .borrow_mut()
218 .borrow_mut()
218 .non_normal_entries_remove(HgPath::new(key.data(py)));
219 .non_normal_entries_remove(HgPath::new(key.data(py)));
219 Ok(py.None())
220 Ok(py.None())
220 }
221 }
221
222
222 def non_normal_entries_union(&self, other: PyObject) -> PyResult<PyList> {
223 def non_normal_entries_union(&self, other: PyObject) -> PyResult<PyList> {
223 let other: PyResult<_> = other.iter(py)?
224 let other: PyResult<_> = other.iter(py)?
224 .map(|f| {
225 .map(|f| {
225 Ok(HgPathBuf::from_bytes(
226 Ok(HgPathBuf::from_bytes(
226 f?.extract::<PyBytes>(py)?.data(py),
227 f?.extract::<PyBytes>(py)?.data(py),
227 ))
228 ))
228 })
229 })
229 .collect();
230 .collect();
230
231
231 let res = self
232 let res = self
232 .inner(py)
233 .inner(py)
233 .borrow_mut()
234 .borrow_mut()
234 .non_normal_entries_union(other?);
235 .non_normal_entries_union(other?);
235
236
236 let ret = PyList::new(py, &[]);
237 let ret = PyList::new(py, &[]);
237 for filename in res.iter() {
238 for filename in res.iter() {
238 let as_pystring = PyBytes::new(py, filename.as_bytes());
239 let as_pystring = PyBytes::new(py, filename.as_bytes());
239 ret.append(py, as_pystring.into_object());
240 ret.append(py, as_pystring.into_object());
240 }
241 }
241 Ok(ret)
242 Ok(ret)
242 }
243 }
243
244
244 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
245 def non_normal_entries_iter(&self) -> PyResult<NonNormalEntriesIterator> {
245 // Make sure the sets are defined before we no longer have a mutable
246 // Make sure the sets are defined before we no longer have a mutable
246 // reference to the dmap.
247 // reference to the dmap.
247 self.inner(py)
248 self.inner(py)
248 .borrow_mut()
249 .borrow_mut()
249 .set_non_normal_other_parent_entries(false);
250 .set_non_normal_other_parent_entries(false);
250
251
251 let leaked_ref = self.inner(py).leak_immutable();
252 let leaked_ref = self.inner(py).leak_immutable();
252
253
253 NonNormalEntriesIterator::from_inner(py, unsafe {
254 NonNormalEntriesIterator::from_inner(py, unsafe {
254 leaked_ref.map(py, |o| {
255 leaked_ref.map(py, |o| {
255 o.get_non_normal_other_parent_entries_panic().0.iter()
256 o.get_non_normal_other_parent_entries_panic().0.iter()
256 })
257 })
257 })
258 })
258 }
259 }
259
260
260 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
261 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
261 let d = d.extract::<PyBytes>(py)?;
262 let d = d.extract::<PyBytes>(py)?;
262 Ok(self.inner(py).borrow_mut()
263 Ok(self.inner(py).borrow_mut()
263 .has_tracked_dir(HgPath::new(d.data(py)))
264 .has_tracked_dir(HgPath::new(d.data(py)))
264 .map_err(|e| {
265 .map_err(|e| {
265 PyErr::new::<exc::ValueError, _>(py, e.to_string())
266 PyErr::new::<exc::ValueError, _>(py, e.to_string())
266 })?
267 })?
267 .to_py_object(py))
268 .to_py_object(py))
268 }
269 }
269
270
270 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
271 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
271 let d = d.extract::<PyBytes>(py)?;
272 let d = d.extract::<PyBytes>(py)?;
272 Ok(self.inner(py).borrow_mut()
273 Ok(self.inner(py).borrow_mut()
273 .has_dir(HgPath::new(d.data(py)))
274 .has_dir(HgPath::new(d.data(py)))
274 .map_err(|e| {
275 .map_err(|e| {
275 PyErr::new::<exc::ValueError, _>(py, e.to_string())
276 PyErr::new::<exc::ValueError, _>(py, e.to_string())
276 })?
277 })?
277 .to_py_object(py))
278 .to_py_object(py))
278 }
279 }
279
280
280 def parents(&self, st: PyObject) -> PyResult<PyTuple> {
281 def parents(&self, st: PyObject) -> PyResult<PyTuple> {
281 self.inner(py).borrow_mut()
282 self.inner(py).borrow_mut()
282 .parents(st.extract::<PyBytes>(py)?.data(py))
283 .parents(st.extract::<PyBytes>(py)?.data(py))
283 .map(|parents| dirstate_parents_to_pytuple(py, parents))
284 .map(|parents| dirstate_parents_to_pytuple(py, parents))
284 .or_else(|_| {
285 .or_else(|_| {
285 Err(PyErr::new::<exc::OSError, _>(
286 Err(PyErr::new::<exc::OSError, _>(
286 py,
287 py,
287 "Dirstate error".to_string(),
288 "Dirstate error".to_string(),
288 ))
289 ))
289 })
290 })
290 }
291 }
291
292
292 def setparents(&self, p1: PyObject, p2: PyObject) -> PyResult<PyObject> {
293 def setparents(&self, p1: PyObject, p2: PyObject) -> PyResult<PyObject> {
293 let p1 = extract_node_id(py, &p1)?;
294 let p1 = extract_node_id(py, &p1)?;
294 let p2 = extract_node_id(py, &p2)?;
295 let p2 = extract_node_id(py, &p2)?;
295
296
296 self.inner(py).borrow_mut()
297 self.inner(py).borrow_mut()
297 .set_parents(&DirstateParents { p1, p2 });
298 .set_parents(&DirstateParents { p1, p2 });
298 Ok(py.None())
299 Ok(py.None())
299 }
300 }
300
301
301 def read(&self, st: PyObject) -> PyResult<Option<PyObject>> {
302 def read(&self, st: PyObject) -> PyResult<Option<PyObject>> {
302 match self.inner(py).borrow_mut()
303 match self.inner(py).borrow_mut()
303 .read(st.extract::<PyBytes>(py)?.data(py))
304 .read(st.extract::<PyBytes>(py)?.data(py))
304 {
305 {
305 Ok(Some(parents)) => Ok(Some(
306 Ok(Some(parents)) => Ok(Some(
306 dirstate_parents_to_pytuple(py, parents)
307 dirstate_parents_to_pytuple(py, parents)
307 .into_object()
308 .into_object()
308 )),
309 )),
309 Ok(None) => Ok(Some(py.None())),
310 Ok(None) => Ok(Some(py.None())),
310 Err(_) => Err(PyErr::new::<exc::OSError, _>(
311 Err(_) => Err(PyErr::new::<exc::OSError, _>(
311 py,
312 py,
312 "Dirstate error".to_string(),
313 "Dirstate error".to_string(),
313 )),
314 )),
314 }
315 }
315 }
316 }
316 def write(
317 def write(
317 &self,
318 &self,
318 p1: PyObject,
319 p1: PyObject,
319 p2: PyObject,
320 p2: PyObject,
320 now: PyObject
321 now: PyObject
321 ) -> PyResult<PyBytes> {
322 ) -> PyResult<PyBytes> {
322 let now = Duration::new(now.extract(py)?, 0);
323 let now = Duration::new(now.extract(py)?, 0);
323 let parents = DirstateParents {
324 let parents = DirstateParents {
324 p1: extract_node_id(py, &p1)?,
325 p1: extract_node_id(py, &p1)?,
325 p2: extract_node_id(py, &p2)?,
326 p2: extract_node_id(py, &p2)?,
326 };
327 };
327
328
328 match self.inner(py).borrow_mut().pack(parents, now) {
329 match self.inner(py).borrow_mut().pack(parents, now) {
329 Ok(packed) => Ok(PyBytes::new(py, &packed)),
330 Ok(packed) => Ok(PyBytes::new(py, &packed)),
330 Err(_) => Err(PyErr::new::<exc::OSError, _>(
331 Err(_) => Err(PyErr::new::<exc::OSError, _>(
331 py,
332 py,
332 "Dirstate error".to_string(),
333 "Dirstate error".to_string(),
333 )),
334 )),
334 }
335 }
335 }
336 }
336
337
337 def filefoldmapasdict(&self) -> PyResult<PyDict> {
338 def filefoldmapasdict(&self) -> PyResult<PyDict> {
338 let dict = PyDict::new(py);
339 let dict = PyDict::new(py);
339 for (key, value) in
340 for (key, value) in
340 self.inner(py).borrow_mut().build_file_fold_map().iter()
341 self.inner(py).borrow_mut().build_file_fold_map().iter()
341 {
342 {
342 dict.set_item(
343 dict.set_item(
343 py,
344 py,
344 PyBytes::new(py, key.as_bytes()).into_object(),
345 PyBytes::new(py, key.as_bytes()).into_object(),
345 PyBytes::new(py, value.as_bytes()).into_object(),
346 PyBytes::new(py, value.as_bytes()).into_object(),
346 )?;
347 )?;
347 }
348 }
348 Ok(dict)
349 Ok(dict)
349 }
350 }
350
351
351 def __len__(&self) -> PyResult<usize> {
352 def __len__(&self) -> PyResult<usize> {
352 Ok(self.inner(py).borrow().len())
353 Ok(self.inner(py).borrow().len())
353 }
354 }
354
355
355 def __contains__(&self, key: PyObject) -> PyResult<bool> {
356 def __contains__(&self, key: PyObject) -> PyResult<bool> {
356 let key = key.extract::<PyBytes>(py)?;
357 let key = key.extract::<PyBytes>(py)?;
357 Ok(self.inner(py).borrow().contains_key(HgPath::new(key.data(py))))
358 Ok(self.inner(py).borrow().contains_key(HgPath::new(key.data(py))))
358 }
359 }
359
360
360 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
361 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
361 let key = key.extract::<PyBytes>(py)?;
362 let key = key.extract::<PyBytes>(py)?;
362 let key = HgPath::new(key.data(py));
363 let key = HgPath::new(key.data(py));
363 match self.inner(py).borrow().get(key) {
364 match self.inner(py).borrow().get(key) {
364 Some(entry) => {
365 Some(entry) => {
365 Ok(make_dirstate_tuple(py, entry)?)
366 Ok(make_dirstate_tuple(py, entry)?)
366 },
367 },
367 None => Err(PyErr::new::<exc::KeyError, _>(
368 None => Err(PyErr::new::<exc::KeyError, _>(
368 py,
369 py,
369 String::from_utf8_lossy(key.as_bytes()),
370 String::from_utf8_lossy(key.as_bytes()),
370 )),
371 )),
371 }
372 }
372 }
373 }
373
374
374 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
375 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
375 let leaked_ref = self.inner(py).leak_immutable();
376 let leaked_ref = self.inner(py).leak_immutable();
376 DirstateMapKeysIterator::from_inner(
377 DirstateMapKeysIterator::from_inner(
377 py,
378 py,
378 unsafe { leaked_ref.map(py, |o| o.iter()) },
379 unsafe { leaked_ref.map(py, |o| o.iter()) },
379 )
380 )
380 }
381 }
381
382
382 def items(&self) -> PyResult<DirstateMapItemsIterator> {
383 def items(&self) -> PyResult<DirstateMapItemsIterator> {
383 let leaked_ref = self.inner(py).leak_immutable();
384 let leaked_ref = self.inner(py).leak_immutable();
384 DirstateMapItemsIterator::from_inner(
385 DirstateMapItemsIterator::from_inner(
385 py,
386 py,
386 unsafe { leaked_ref.map(py, |o| o.iter()) },
387 unsafe { leaked_ref.map(py, |o| o.iter()) },
387 )
388 )
388 }
389 }
389
390
390 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
391 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
391 let leaked_ref = self.inner(py).leak_immutable();
392 let leaked_ref = self.inner(py).leak_immutable();
392 DirstateMapKeysIterator::from_inner(
393 DirstateMapKeysIterator::from_inner(
393 py,
394 py,
394 unsafe { leaked_ref.map(py, |o| o.iter()) },
395 unsafe { leaked_ref.map(py, |o| o.iter()) },
395 )
396 )
396 }
397 }
397
398
398 def getdirs(&self) -> PyResult<Dirs> {
399 def getdirs(&self) -> PyResult<Dirs> {
399 // TODO don't copy, share the reference
400 // TODO don't copy, share the reference
400 self.inner(py).borrow_mut().set_dirs()
401 self.inner(py).borrow_mut().set_dirs()
401 .map_err(|e| {
402 .map_err(|e| {
402 PyErr::new::<exc::ValueError, _>(py, e.to_string())
403 PyErr::new::<exc::ValueError, _>(py, e.to_string())
403 })?;
404 })?;
404 Dirs::from_inner(
405 Dirs::from_inner(
405 py,
406 py,
406 DirsMultiset::from_dirstate(
407 DirsMultiset::from_dirstate(
407 &self.inner(py).borrow(),
408 self.inner(py).borrow().iter(),
408 Some(EntryState::Removed),
409 Some(EntryState::Removed),
409 )
410 )
410 .map_err(|e| {
411 .map_err(|e| {
411 PyErr::new::<exc::ValueError, _>(py, e.to_string())
412 PyErr::new::<exc::ValueError, _>(py, e.to_string())
412 })?,
413 })?,
413 )
414 )
414 }
415 }
415 def getalldirs(&self) -> PyResult<Dirs> {
416 def getalldirs(&self) -> PyResult<Dirs> {
416 // TODO don't copy, share the reference
417 // TODO don't copy, share the reference
417 self.inner(py).borrow_mut().set_all_dirs()
418 self.inner(py).borrow_mut().set_all_dirs()
418 .map_err(|e| {
419 .map_err(|e| {
419 PyErr::new::<exc::ValueError, _>(py, e.to_string())
420 PyErr::new::<exc::ValueError, _>(py, e.to_string())
420 })?;
421 })?;
421 Dirs::from_inner(
422 Dirs::from_inner(
422 py,
423 py,
423 DirsMultiset::from_dirstate(
424 DirsMultiset::from_dirstate(
424 &self.inner(py).borrow(),
425 self.inner(py).borrow().iter(),
425 None,
426 None,
426 ).map_err(|e| {
427 ).map_err(|e| {
427 PyErr::new::<exc::ValueError, _>(py, e.to_string())
428 PyErr::new::<exc::ValueError, _>(py, e.to_string())
428 })?,
429 })?,
429 )
430 )
430 }
431 }
431
432
432 // TODO all copymap* methods, see docstring above
433 // TODO all copymap* methods, see docstring above
433 def copymapcopy(&self) -> PyResult<PyDict> {
434 def copymapcopy(&self) -> PyResult<PyDict> {
434 let dict = PyDict::new(py);
435 let dict = PyDict::new(py);
435 for (key, value) in self.inner(py).borrow().copy_map.iter() {
436 for (key, value) in self.inner(py).borrow().copy_map_iter() {
436 dict.set_item(
437 dict.set_item(
437 py,
438 py,
438 PyBytes::new(py, key.as_bytes()),
439 PyBytes::new(py, key.as_bytes()),
439 PyBytes::new(py, value.as_bytes()),
440 PyBytes::new(py, value.as_bytes()),
440 )?;
441 )?;
441 }
442 }
442 Ok(dict)
443 Ok(dict)
443 }
444 }
444
445
445 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
446 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
446 let key = key.extract::<PyBytes>(py)?;
447 let key = key.extract::<PyBytes>(py)?;
447 match self.inner(py).borrow().copy_map.get(HgPath::new(key.data(py))) {
448 match self.inner(py).borrow().copy_map_get(HgPath::new(key.data(py))) {
448 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
449 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
449 None => Err(PyErr::new::<exc::KeyError, _>(
450 None => Err(PyErr::new::<exc::KeyError, _>(
450 py,
451 py,
451 String::from_utf8_lossy(key.data(py)),
452 String::from_utf8_lossy(key.data(py)),
452 )),
453 )),
453 }
454 }
454 }
455 }
455 def copymap(&self) -> PyResult<CopyMap> {
456 def copymap(&self) -> PyResult<CopyMap> {
456 CopyMap::from_inner(py, self.clone_ref(py))
457 CopyMap::from_inner(py, self.clone_ref(py))
457 }
458 }
458
459
459 def copymaplen(&self) -> PyResult<usize> {
460 def copymaplen(&self) -> PyResult<usize> {
460 Ok(self.inner(py).borrow().copy_map.len())
461 Ok(self.inner(py).borrow().copy_map_len())
461 }
462 }
462 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
463 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
463 let key = key.extract::<PyBytes>(py)?;
464 let key = key.extract::<PyBytes>(py)?;
464 Ok(self
465 Ok(self
465 .inner(py)
466 .inner(py)
466 .borrow()
467 .borrow()
467 .copy_map
468 .copy_map_contains_key(HgPath::new(key.data(py))))
468 .contains_key(HgPath::new(key.data(py))))
469 }
469 }
470 def copymapget(
470 def copymapget(
471 &self,
471 &self,
472 key: PyObject,
472 key: PyObject,
473 default: Option<PyObject>
473 default: Option<PyObject>
474 ) -> PyResult<Option<PyObject>> {
474 ) -> PyResult<Option<PyObject>> {
475 let key = key.extract::<PyBytes>(py)?;
475 let key = key.extract::<PyBytes>(py)?;
476 match self
476 match self
477 .inner(py)
477 .inner(py)
478 .borrow()
478 .borrow()
479 .copy_map
479 .copy_map_get(HgPath::new(key.data(py)))
480 .get(HgPath::new(key.data(py)))
481 {
480 {
482 Some(copy) => Ok(Some(
481 Some(copy) => Ok(Some(
483 PyBytes::new(py, copy.as_bytes()).into_object(),
482 PyBytes::new(py, copy.as_bytes()).into_object(),
484 )),
483 )),
485 None => Ok(default),
484 None => Ok(default),
486 }
485 }
487 }
486 }
488 def copymapsetitem(
487 def copymapsetitem(
489 &self,
488 &self,
490 key: PyObject,
489 key: PyObject,
491 value: PyObject
490 value: PyObject
492 ) -> PyResult<PyObject> {
491 ) -> PyResult<PyObject> {
493 let key = key.extract::<PyBytes>(py)?;
492 let key = key.extract::<PyBytes>(py)?;
494 let value = value.extract::<PyBytes>(py)?;
493 let value = value.extract::<PyBytes>(py)?;
495 self.inner(py).borrow_mut().copy_map.insert(
494 self.inner(py).borrow_mut().copy_map_insert(
496 HgPathBuf::from_bytes(key.data(py)),
495 HgPathBuf::from_bytes(key.data(py)),
497 HgPathBuf::from_bytes(value.data(py)),
496 HgPathBuf::from_bytes(value.data(py)),
498 );
497 );
499 Ok(py.None())
498 Ok(py.None())
500 }
499 }
501 def copymappop(
500 def copymappop(
502 &self,
501 &self,
503 key: PyObject,
502 key: PyObject,
504 default: Option<PyObject>
503 default: Option<PyObject>
505 ) -> PyResult<Option<PyObject>> {
504 ) -> PyResult<Option<PyObject>> {
506 let key = key.extract::<PyBytes>(py)?;
505 let key = key.extract::<PyBytes>(py)?;
507 match self
506 match self
508 .inner(py)
507 .inner(py)
509 .borrow_mut()
508 .borrow_mut()
510 .copy_map
509 .copy_map_remove(HgPath::new(key.data(py)))
511 .remove(HgPath::new(key.data(py)))
512 {
510 {
513 Some(_) => Ok(None),
511 Some(_) => Ok(None),
514 None => Ok(default),
512 None => Ok(default),
515 }
513 }
516 }
514 }
517
515
518 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
516 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
519 let leaked_ref = self.inner(py).leak_immutable();
517 let leaked_ref = self.inner(py).leak_immutable();
520 CopyMapKeysIterator::from_inner(
518 CopyMapKeysIterator::from_inner(
521 py,
519 py,
522 unsafe { leaked_ref.map(py, |o| o.copy_map.iter()) },
520 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
523 )
521 )
524 }
522 }
525
523
526 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
524 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
527 let leaked_ref = self.inner(py).leak_immutable();
525 let leaked_ref = self.inner(py).leak_immutable();
528 CopyMapItemsIterator::from_inner(
526 CopyMapItemsIterator::from_inner(
529 py,
527 py,
530 unsafe { leaked_ref.map(py, |o| o.copy_map.iter()) },
528 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
531 )
529 )
532 }
530 }
533
531
534 });
532 });
535
533
536 impl DirstateMap {
534 impl DirstateMap {
537 pub fn get_inner<'a>(
535 pub fn get_inner<'a>(
538 &'a self,
536 &'a self,
539 py: Python<'a>,
537 py: Python<'a>,
540 ) -> Ref<'a, RustDirstateMap> {
538 ) -> Ref<'a, Box<dyn DirstateMapMethods + Send>> {
541 self.inner(py).borrow()
539 self.inner(py).borrow()
542 }
540 }
543 fn translate_key(
541 fn translate_key(
544 py: Python,
542 py: Python,
545 res: (&HgPathBuf, &DirstateEntry),
543 res: (&HgPathBuf, &DirstateEntry),
546 ) -> PyResult<Option<PyBytes>> {
544 ) -> PyResult<Option<PyBytes>> {
547 Ok(Some(PyBytes::new(py, res.0.as_bytes())))
545 Ok(Some(PyBytes::new(py, res.0.as_bytes())))
548 }
546 }
549 fn translate_key_value(
547 fn translate_key_value(
550 py: Python,
548 py: Python,
551 res: (&HgPathBuf, &DirstateEntry),
549 res: (&HgPathBuf, &DirstateEntry),
552 ) -> PyResult<Option<(PyBytes, PyObject)>> {
550 ) -> PyResult<Option<(PyBytes, PyObject)>> {
553 let (f, entry) = res;
551 let (f, entry) = res;
554 Ok(Some((
552 Ok(Some((
555 PyBytes::new(py, f.as_bytes()),
553 PyBytes::new(py, f.as_bytes()),
556 make_dirstate_tuple(py, &entry)?,
554 make_dirstate_tuple(py, &entry)?,
557 )))
555 )))
558 }
556 }
559 }
557 }
560
558
561 py_shared_iterator!(
559 py_shared_iterator!(
562 DirstateMapKeysIterator,
560 DirstateMapKeysIterator,
563 UnsafePyLeaked<StateMapIter<'static>>,
561 UnsafePyLeaked<StateMapIter<'static>>,
564 DirstateMap::translate_key,
562 DirstateMap::translate_key,
565 Option<PyBytes>
563 Option<PyBytes>
566 );
564 );
567
565
568 py_shared_iterator!(
566 py_shared_iterator!(
569 DirstateMapItemsIterator,
567 DirstateMapItemsIterator,
570 UnsafePyLeaked<StateMapIter<'static>>,
568 UnsafePyLeaked<StateMapIter<'static>>,
571 DirstateMap::translate_key_value,
569 DirstateMap::translate_key_value,
572 Option<(PyBytes, PyObject)>
570 Option<(PyBytes, PyObject)>
573 );
571 );
574
572
575 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
573 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
576 let bytes = obj.extract::<PyBytes>(py)?;
574 let bytes = obj.extract::<PyBytes>(py)?;
577 match bytes.data(py).try_into() {
575 match bytes.data(py).try_into() {
578 Ok(s) => Ok(s),
576 Ok(s) => Ok(s),
579 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
577 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
580 }
578 }
581 }
579 }
@@ -1,303 +1,303 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019, Raphaël Gomès <rgomes@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 //! Bindings for the `hg::status` module provided by the
8 //! Bindings for the `hg::status` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as
9 //! `hg-core` crate. From Python, this will be seen as
10 //! `rustext.dirstate.status`.
10 //! `rustext.dirstate.status`.
11
11
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
13 use cpython::exc::OSError;
13 use cpython::exc::OSError;
14 use cpython::{
14 use cpython::{
15 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
15 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
16 PyResult, PyTuple, Python, PythonObject, ToPyObject,
16 PyResult, PyTuple, Python, PythonObject, ToPyObject,
17 };
17 };
18 use hg::{
18 use hg::{
19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
20 parse_pattern_syntax, status,
20 parse_pattern_syntax,
21 utils::{
21 utils::{
22 files::{get_bytes_from_path, get_path_from_bytes},
22 files::{get_bytes_from_path, get_path_from_bytes},
23 hg_path::{HgPath, HgPathBuf},
23 hg_path::{HgPath, HgPathBuf},
24 },
24 },
25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
26 StatusOptions,
26 StatusOptions,
27 };
27 };
28 use std::borrow::{Borrow, Cow};
28 use std::borrow::{Borrow, Cow};
29
29
30 /// This will be useless once trait impls for collection are added to `PyBytes`
30 /// This will be useless once trait impls for collection are added to `PyBytes`
31 /// upstream.
31 /// upstream.
32 fn collect_pybytes_list(
32 fn collect_pybytes_list(
33 py: Python,
33 py: Python,
34 collection: &[impl AsRef<HgPath>],
34 collection: &[impl AsRef<HgPath>],
35 ) -> PyList {
35 ) -> PyList {
36 let list = PyList::new(py, &[]);
36 let list = PyList::new(py, &[]);
37
37
38 for path in collection.iter() {
38 for path in collection.iter() {
39 list.append(
39 list.append(
40 py,
40 py,
41 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
41 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
42 )
42 )
43 }
43 }
44
44
45 list
45 list
46 }
46 }
47
47
48 fn collect_bad_matches(
48 fn collect_bad_matches(
49 py: Python,
49 py: Python,
50 collection: &[(impl AsRef<HgPath>, BadMatch)],
50 collection: &[(impl AsRef<HgPath>, BadMatch)],
51 ) -> PyResult<PyList> {
51 ) -> PyResult<PyList> {
52 let list = PyList::new(py, &[]);
52 let list = PyList::new(py, &[]);
53
53
54 let os = py.import("os")?;
54 let os = py.import("os")?;
55 let get_error_message = |code: i32| -> PyResult<_> {
55 let get_error_message = |code: i32| -> PyResult<_> {
56 os.call(
56 os.call(
57 py,
57 py,
58 "strerror",
58 "strerror",
59 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
59 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
60 None,
60 None,
61 )
61 )
62 };
62 };
63
63
64 for (path, bad_match) in collection.iter() {
64 for (path, bad_match) in collection.iter() {
65 let message = match bad_match {
65 let message = match bad_match {
66 BadMatch::OsError(code) => get_error_message(*code)?,
66 BadMatch::OsError(code) => get_error_message(*code)?,
67 BadMatch::BadType(bad_type) => format!(
67 BadMatch::BadType(bad_type) => format!(
68 "unsupported file type (type is {})",
68 "unsupported file type (type is {})",
69 bad_type.to_string()
69 bad_type.to_string()
70 )
70 )
71 .to_py_object(py)
71 .to_py_object(py)
72 .into_object(),
72 .into_object(),
73 };
73 };
74 list.append(
74 list.append(
75 py,
75 py,
76 (PyBytes::new(py, path.as_ref().as_bytes()), message)
76 (PyBytes::new(py, path.as_ref().as_bytes()), message)
77 .to_py_object(py)
77 .to_py_object(py)
78 .into_object(),
78 .into_object(),
79 )
79 )
80 }
80 }
81
81
82 Ok(list)
82 Ok(list)
83 }
83 }
84
84
85 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
85 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
86 match err {
86 match err {
87 StatusError::Pattern(e) => {
87 StatusError::Pattern(e) => {
88 let as_string = e.to_string();
88 let as_string = e.to_string();
89 log::trace!("Rust status fallback: `{}`", &as_string);
89 log::trace!("Rust status fallback: `{}`", &as_string);
90
90
91 PyErr::new::<FallbackError, _>(py, &as_string)
91 PyErr::new::<FallbackError, _>(py, &as_string)
92 }
92 }
93 StatusError::IO(e) => PyErr::new::<OSError, _>(py, e.to_string()),
93 StatusError::IO(e) => PyErr::new::<OSError, _>(py, e.to_string()),
94 e => PyErr::new::<ValueError, _>(py, e.to_string()),
94 e => PyErr::new::<ValueError, _>(py, e.to_string()),
95 }
95 }
96 }
96 }
97
97
98 pub fn status_wrapper(
98 pub fn status_wrapper(
99 py: Python,
99 py: Python,
100 dmap: DirstateMap,
100 dmap: DirstateMap,
101 matcher: PyObject,
101 matcher: PyObject,
102 root_dir: PyObject,
102 root_dir: PyObject,
103 ignore_files: PyList,
103 ignore_files: PyList,
104 check_exec: bool,
104 check_exec: bool,
105 last_normal_time: i64,
105 last_normal_time: i64,
106 list_clean: bool,
106 list_clean: bool,
107 list_ignored: bool,
107 list_ignored: bool,
108 list_unknown: bool,
108 list_unknown: bool,
109 collect_traversed_dirs: bool,
109 collect_traversed_dirs: bool,
110 ) -> PyResult<PyTuple> {
110 ) -> PyResult<PyTuple> {
111 let bytes = root_dir.extract::<PyBytes>(py)?;
111 let bytes = root_dir.extract::<PyBytes>(py)?;
112 let root_dir = get_path_from_bytes(bytes.data(py));
112 let root_dir = get_path_from_bytes(bytes.data(py));
113
113
114 let dmap: DirstateMap = dmap.to_py_object(py);
114 let dmap: DirstateMap = dmap.to_py_object(py);
115 let dmap = dmap.get_inner(py);
115 let dmap = dmap.get_inner(py);
116
116
117 let ignore_files: PyResult<Vec<_>> = ignore_files
117 let ignore_files: PyResult<Vec<_>> = ignore_files
118 .iter(py)
118 .iter(py)
119 .map(|b| {
119 .map(|b| {
120 let file = b.extract::<PyBytes>(py)?;
120 let file = b.extract::<PyBytes>(py)?;
121 Ok(get_path_from_bytes(file.data(py)).to_owned())
121 Ok(get_path_from_bytes(file.data(py)).to_owned())
122 })
122 })
123 .collect();
123 .collect();
124 let ignore_files = ignore_files?;
124 let ignore_files = ignore_files?;
125
125
126 match matcher.get_type(py).name(py).borrow() {
126 match matcher.get_type(py).name(py).borrow() {
127 "alwaysmatcher" => {
127 "alwaysmatcher" => {
128 let matcher = AlwaysMatcher;
128 let matcher = AlwaysMatcher;
129 let ((lookup, status_res), warnings) = status(
129 let ((lookup, status_res), warnings) = dmap
130 &dmap,
130 .status(
131 &matcher,
131 &matcher,
132 root_dir.to_path_buf(),
132 root_dir.to_path_buf(),
133 ignore_files,
133 ignore_files,
134 StatusOptions {
134 StatusOptions {
135 check_exec,
135 check_exec,
136 last_normal_time,
136 last_normal_time,
137 list_clean,
137 list_clean,
138 list_ignored,
138 list_ignored,
139 list_unknown,
139 list_unknown,
140 collect_traversed_dirs,
140 collect_traversed_dirs,
141 },
141 },
142 )
142 )
143 .map_err(|e| handle_fallback(py, e))?;
143 .map_err(|e| handle_fallback(py, e))?;
144 build_response(py, lookup, status_res, warnings)
144 build_response(py, lookup, status_res, warnings)
145 }
145 }
146 "exactmatcher" => {
146 "exactmatcher" => {
147 let files = matcher.call_method(
147 let files = matcher.call_method(
148 py,
148 py,
149 "files",
149 "files",
150 PyTuple::new(py, &[]),
150 PyTuple::new(py, &[]),
151 None,
151 None,
152 )?;
152 )?;
153 let files: PyList = files.cast_into(py)?;
153 let files: PyList = files.cast_into(py)?;
154 let files: PyResult<Vec<HgPathBuf>> = files
154 let files: PyResult<Vec<HgPathBuf>> = files
155 .iter(py)
155 .iter(py)
156 .map(|f| {
156 .map(|f| {
157 Ok(HgPathBuf::from_bytes(
157 Ok(HgPathBuf::from_bytes(
158 f.extract::<PyBytes>(py)?.data(py),
158 f.extract::<PyBytes>(py)?.data(py),
159 ))
159 ))
160 })
160 })
161 .collect();
161 .collect();
162
162
163 let files = files?;
163 let files = files?;
164 let matcher = FileMatcher::new(files.as_ref())
164 let matcher = FileMatcher::new(files.as_ref())
165 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
165 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
166 let ((lookup, status_res), warnings) = status(
166 let ((lookup, status_res), warnings) = dmap
167 &dmap,
167 .status(
168 &matcher,
168 &matcher,
169 root_dir.to_path_buf(),
169 root_dir.to_path_buf(),
170 ignore_files,
170 ignore_files,
171 StatusOptions {
171 StatusOptions {
172 check_exec,
172 check_exec,
173 last_normal_time,
173 last_normal_time,
174 list_clean,
174 list_clean,
175 list_ignored,
175 list_ignored,
176 list_unknown,
176 list_unknown,
177 collect_traversed_dirs,
177 collect_traversed_dirs,
178 },
178 },
179 )
179 )
180 .map_err(|e| handle_fallback(py, e))?;
180 .map_err(|e| handle_fallback(py, e))?;
181 build_response(py, lookup, status_res, warnings)
181 build_response(py, lookup, status_res, warnings)
182 }
182 }
183 "includematcher" => {
183 "includematcher" => {
184 // Get the patterns from Python even though most of them are
184 // Get the patterns from Python even though most of them are
185 // redundant with those we will parse later on, as they include
185 // redundant with those we will parse later on, as they include
186 // those passed from the command line.
186 // those passed from the command line.
187 let ignore_patterns: PyResult<Vec<_>> = matcher
187 let ignore_patterns: PyResult<Vec<_>> = matcher
188 .getattr(py, "_kindpats")?
188 .getattr(py, "_kindpats")?
189 .iter(py)?
189 .iter(py)?
190 .map(|k| {
190 .map(|k| {
191 let k = k?;
191 let k = k?;
192 let syntax = parse_pattern_syntax(
192 let syntax = parse_pattern_syntax(
193 &[
193 &[
194 k.get_item(py, 0)?
194 k.get_item(py, 0)?
195 .extract::<PyBytes>(py)?
195 .extract::<PyBytes>(py)?
196 .data(py),
196 .data(py),
197 &b":"[..],
197 &b":"[..],
198 ]
198 ]
199 .concat(),
199 .concat(),
200 )
200 )
201 .map_err(|e| {
201 .map_err(|e| {
202 handle_fallback(py, StatusError::Pattern(e))
202 handle_fallback(py, StatusError::Pattern(e))
203 })?;
203 })?;
204 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
204 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
205 let pattern = pattern.data(py);
205 let pattern = pattern.data(py);
206 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
206 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
207 let source = get_path_from_bytes(source.data(py));
207 let source = get_path_from_bytes(source.data(py));
208 let new = IgnorePattern::new(syntax, pattern, source);
208 let new = IgnorePattern::new(syntax, pattern, source);
209 Ok(new)
209 Ok(new)
210 })
210 })
211 .collect();
211 .collect();
212
212
213 let ignore_patterns = ignore_patterns?;
213 let ignore_patterns = ignore_patterns?;
214 let mut all_warnings = vec![];
214 let mut all_warnings = vec![];
215
215
216 let (matcher, warnings) =
216 let (matcher, warnings) =
217 IncludeMatcher::new(ignore_patterns, &root_dir)
217 IncludeMatcher::new(ignore_patterns, &root_dir)
218 .map_err(|e| handle_fallback(py, e.into()))?;
218 .map_err(|e| handle_fallback(py, e.into()))?;
219 all_warnings.extend(warnings);
219 all_warnings.extend(warnings);
220
220
221 let ((lookup, status_res), warnings) = status(
221 let ((lookup, status_res), warnings) = dmap
222 &dmap,
222 .status(
223 &matcher,
223 &matcher,
224 root_dir.to_path_buf(),
224 root_dir.to_path_buf(),
225 ignore_files,
225 ignore_files,
226 StatusOptions {
226 StatusOptions {
227 check_exec,
227 check_exec,
228 last_normal_time,
228 last_normal_time,
229 list_clean,
229 list_clean,
230 list_ignored,
230 list_ignored,
231 list_unknown,
231 list_unknown,
232 collect_traversed_dirs,
232 collect_traversed_dirs,
233 },
233 },
234 )
234 )
235 .map_err(|e| handle_fallback(py, e))?;
235 .map_err(|e| handle_fallback(py, e))?;
236
236
237 all_warnings.extend(warnings);
237 all_warnings.extend(warnings);
238
238
239 build_response(py, lookup, status_res, all_warnings)
239 build_response(py, lookup, status_res, all_warnings)
240 }
240 }
241 e => Err(PyErr::new::<ValueError, _>(
241 e => Err(PyErr::new::<ValueError, _>(
242 py,
242 py,
243 format!("Unsupported matcher {}", e),
243 format!("Unsupported matcher {}", e),
244 )),
244 )),
245 }
245 }
246 }
246 }
247
247
248 fn build_response(
248 fn build_response(
249 py: Python,
249 py: Python,
250 lookup: Vec<Cow<HgPath>>,
250 lookup: Vec<Cow<HgPath>>,
251 status_res: DirstateStatus,
251 status_res: DirstateStatus,
252 warnings: Vec<PatternFileWarning>,
252 warnings: Vec<PatternFileWarning>,
253 ) -> PyResult<PyTuple> {
253 ) -> PyResult<PyTuple> {
254 let modified = collect_pybytes_list(py, status_res.modified.as_ref());
254 let modified = collect_pybytes_list(py, status_res.modified.as_ref());
255 let added = collect_pybytes_list(py, status_res.added.as_ref());
255 let added = collect_pybytes_list(py, status_res.added.as_ref());
256 let removed = collect_pybytes_list(py, status_res.removed.as_ref());
256 let removed = collect_pybytes_list(py, status_res.removed.as_ref());
257 let deleted = collect_pybytes_list(py, status_res.deleted.as_ref());
257 let deleted = collect_pybytes_list(py, status_res.deleted.as_ref());
258 let clean = collect_pybytes_list(py, status_res.clean.as_ref());
258 let clean = collect_pybytes_list(py, status_res.clean.as_ref());
259 let ignored = collect_pybytes_list(py, status_res.ignored.as_ref());
259 let ignored = collect_pybytes_list(py, status_res.ignored.as_ref());
260 let unknown = collect_pybytes_list(py, status_res.unknown.as_ref());
260 let unknown = collect_pybytes_list(py, status_res.unknown.as_ref());
261 let lookup = collect_pybytes_list(py, lookup.as_ref());
261 let lookup = collect_pybytes_list(py, lookup.as_ref());
262 let bad = collect_bad_matches(py, status_res.bad.as_ref())?;
262 let bad = collect_bad_matches(py, status_res.bad.as_ref())?;
263 let traversed = collect_pybytes_list(py, status_res.traversed.as_ref());
263 let traversed = collect_pybytes_list(py, status_res.traversed.as_ref());
264 let py_warnings = PyList::new(py, &[]);
264 let py_warnings = PyList::new(py, &[]);
265 for warning in warnings.iter() {
265 for warning in warnings.iter() {
266 // We use duck-typing on the Python side for dispatch, good enough for
266 // We use duck-typing on the Python side for dispatch, good enough for
267 // now.
267 // now.
268 match warning {
268 match warning {
269 PatternFileWarning::InvalidSyntax(file, syn) => {
269 PatternFileWarning::InvalidSyntax(file, syn) => {
270 py_warnings.append(
270 py_warnings.append(
271 py,
271 py,
272 (
272 (
273 PyBytes::new(py, &get_bytes_from_path(&file)),
273 PyBytes::new(py, &get_bytes_from_path(&file)),
274 PyBytes::new(py, syn),
274 PyBytes::new(py, syn),
275 )
275 )
276 .to_py_object(py)
276 .to_py_object(py)
277 .into_object(),
277 .into_object(),
278 );
278 );
279 }
279 }
280 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
280 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
281 py,
281 py,
282 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
282 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
283 ),
283 ),
284 }
284 }
285 }
285 }
286
286
287 Ok(PyTuple::new(
287 Ok(PyTuple::new(
288 py,
288 py,
289 &[
289 &[
290 lookup.into_object(),
290 lookup.into_object(),
291 modified.into_object(),
291 modified.into_object(),
292 added.into_object(),
292 added.into_object(),
293 removed.into_object(),
293 removed.into_object(),
294 deleted.into_object(),
294 deleted.into_object(),
295 clean.into_object(),
295 clean.into_object(),
296 ignored.into_object(),
296 ignored.into_object(),
297 unknown.into_object(),
297 unknown.into_object(),
298 py_warnings.into_object(),
298 py_warnings.into_object(),
299 bad.into_object(),
299 bad.into_object(),
300 traversed.into_object(),
300 traversed.into_object(),
301 ][..],
301 ][..],
302 ))
302 ))
303 }
303 }
General Comments 0
You need to be logged in to leave comments. Login now