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