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