##// END OF EJS Templates
rust-dirstate: improve API of `DirsMultiset`...
Raphaël Gomès -
r42995:849e744b default
parent child Browse files
Show More
@@ -1,341 +1,341 b''
1 1 // dirs_multiset.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! A multiset of directory names.
9 9 //!
10 10 //! Used to counts the references to directories in a manifest or dirstate.
11 11 use crate::{
12 12 dirstate::EntryState, utils::files, DirsIterable, DirstateEntry,
13 13 DirstateMapError,
14 14 };
15 use std::collections::hash_map::{Entry, Iter};
15 use std::collections::hash_map::Entry;
16 16 use std::collections::HashMap;
17 17
18 18 #[derive(PartialEq, Debug)]
19 19 pub struct DirsMultiset {
20 20 inner: HashMap<Vec<u8>, u32>,
21 21 }
22 22
23 23 impl DirsMultiset {
24 24 /// Initializes the multiset from a dirstate or a manifest.
25 25 ///
26 26 /// If `skip_state` is provided, skips dirstate entries with equal state.
27 27 pub fn new(
28 28 iterable: DirsIterable,
29 29 skip_state: Option<EntryState>,
30 30 ) -> Self {
31 31 let mut multiset = DirsMultiset {
32 32 inner: HashMap::new(),
33 33 };
34 34
35 35 match iterable {
36 36 DirsIterable::Dirstate(vec) => {
37 37 for (filename, DirstateEntry { state, .. }) in vec {
38 38 // This `if` is optimized out of the loop
39 39 if let Some(skip) = skip_state {
40 40 if skip != *state {
41 41 multiset.add_path(filename);
42 42 }
43 43 } else {
44 44 multiset.add_path(filename);
45 45 }
46 46 }
47 47 }
48 48 DirsIterable::Manifest(vec) => {
49 49 for filename in vec {
50 50 multiset.add_path(filename);
51 51 }
52 52 }
53 53 }
54 54
55 55 multiset
56 56 }
57 57
58 58 /// Increases the count of deepest directory contained in the path.
59 59 ///
60 60 /// If the directory is not yet in the map, adds its parents.
61 61 pub fn add_path(&mut self, path: &[u8]) {
62 62 for subpath in files::find_dirs(path) {
63 63 if let Some(val) = self.inner.get_mut(subpath) {
64 64 *val += 1;
65 65 break;
66 66 }
67 67 self.inner.insert(subpath.to_owned(), 1);
68 68 }
69 69 }
70 70
71 71 /// Decreases the count of deepest directory contained in the path.
72 72 ///
73 73 /// If it is the only reference, decreases all parents until one is
74 74 /// removed.
75 75 /// If the directory is not in the map, something horrible has happened.
76 76 pub fn delete_path(
77 77 &mut self,
78 78 path: &[u8],
79 79 ) -> Result<(), DirstateMapError> {
80 80 for subpath in files::find_dirs(path) {
81 81 match self.inner.entry(subpath.to_owned()) {
82 82 Entry::Occupied(mut entry) => {
83 83 let val = entry.get().clone();
84 84 if val > 1 {
85 85 entry.insert(val - 1);
86 86 break;
87 87 }
88 88 entry.remove();
89 89 }
90 90 Entry::Vacant(_) => {
91 91 return Err(DirstateMapError::PathNotFound(
92 92 path.to_owned(),
93 93 ))
94 94 }
95 95 };
96 96 }
97 97
98 98 Ok(())
99 99 }
100 100
101 pub fn contains_key(&self, key: &[u8]) -> bool {
101 pub fn contains(&self, key: &[u8]) -> bool {
102 102 self.inner.contains_key(key)
103 103 }
104 104
105 pub fn iter(&self) -> Iter<Vec<u8>, u32> {
106 self.inner.iter()
105 pub fn iter(&self) -> impl Iterator<Item = &Vec<u8>> {
106 self.inner.keys()
107 107 }
108 108
109 109 pub fn len(&self) -> usize {
110 110 self.inner.len()
111 111 }
112 112 }
113 113
114 114 #[cfg(test)]
115 115 mod tests {
116 116 use super::*;
117 117 use std::collections::HashMap;
118 118
119 119 #[test]
120 120 fn test_delete_path_path_not_found() {
121 121 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
122 122 let path = b"doesnotexist/";
123 123 assert_eq!(
124 124 Err(DirstateMapError::PathNotFound(path.to_vec())),
125 125 map.delete_path(path)
126 126 );
127 127 }
128 128
129 129 #[test]
130 130 fn test_delete_path_empty_path() {
131 131 let mut map =
132 132 DirsMultiset::new(DirsIterable::Manifest(&vec![vec![]]), None);
133 133 let path = b"";
134 134 assert_eq!(Ok(()), map.delete_path(path));
135 135 assert_eq!(
136 136 Err(DirstateMapError::PathNotFound(path.to_vec())),
137 137 map.delete_path(path)
138 138 );
139 139 }
140 140
141 141 #[test]
142 142 fn test_delete_path_successful() {
143 143 let mut map = DirsMultiset {
144 144 inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)]
145 145 .iter()
146 146 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
147 147 .collect(),
148 148 };
149 149
150 150 assert_eq!(Ok(()), map.delete_path(b"a/b/"));
151 151 assert_eq!(Ok(()), map.delete_path(b"a/b/"));
152 152 assert_eq!(
153 153 Err(DirstateMapError::PathNotFound(b"a/b/".to_vec())),
154 154 map.delete_path(b"a/b/")
155 155 );
156 156
157 157 assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
158 158 assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
159 159 eprintln!("{:?}", map);
160 160 assert_eq!(Ok(()), map.delete_path(b"a/"));
161 161 eprintln!("{:?}", map);
162 162
163 163 assert_eq!(Ok(()), map.delete_path(b"a/c/"));
164 164 assert_eq!(
165 165 Err(DirstateMapError::PathNotFound(b"a/c/".to_vec())),
166 166 map.delete_path(b"a/c/")
167 167 );
168 168 }
169 169
170 170 #[test]
171 171 fn test_add_path_empty_path() {
172 172 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
173 173 let path = b"";
174 174 map.add_path(path);
175 175
176 176 assert_eq!(1, map.len());
177 177 }
178 178
179 179 #[test]
180 180 fn test_add_path_successful() {
181 181 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
182 182
183 183 map.add_path(b"a/");
184 184 assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
185 185 assert_eq!(1, *map.inner.get(&Vec::new()).unwrap());
186 186 assert_eq!(2, map.len());
187 187
188 188 // Non directory should be ignored
189 189 map.add_path(b"a");
190 190 assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
191 191 assert_eq!(2, map.len());
192 192
193 193 // Non directory will still add its base
194 194 map.add_path(b"a/b");
195 195 assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
196 196 assert_eq!(2, map.len());
197 197
198 198 // Duplicate path works
199 199 map.add_path(b"a/");
200 200 assert_eq!(3, *map.inner.get(&b"a".to_vec()).unwrap());
201 201
202 202 // Nested dir adds to its base
203 203 map.add_path(b"a/b/");
204 204 assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap());
205 205 assert_eq!(1, *map.inner.get(&b"a/b".to_vec()).unwrap());
206 206
207 207 // but not its base's base, because it already existed
208 208 map.add_path(b"a/b/c/");
209 209 assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap());
210 210 assert_eq!(2, *map.inner.get(&b"a/b".to_vec()).unwrap());
211 211
212 212 map.add_path(b"a/c/");
213 213 assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
214 214
215 215 let expected = DirsMultiset {
216 216 inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)]
217 217 .iter()
218 218 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
219 219 .collect(),
220 220 };
221 221 assert_eq!(map, expected);
222 222 }
223 223
224 224 #[test]
225 225 fn test_dirsmultiset_new_empty() {
226 226 use DirsIterable::{Dirstate, Manifest};
227 227
228 228 let new = DirsMultiset::new(Manifest(&vec![]), None);
229 229 let expected = DirsMultiset {
230 230 inner: HashMap::new(),
231 231 };
232 232 assert_eq!(expected, new);
233 233
234 234 let new = DirsMultiset::new(Dirstate(&HashMap::new()), None);
235 235 let expected = DirsMultiset {
236 236 inner: HashMap::new(),
237 237 };
238 238 assert_eq!(expected, new);
239 239 }
240 240
241 241 #[test]
242 242 fn test_dirsmultiset_new_no_skip() {
243 243 use DirsIterable::{Dirstate, Manifest};
244 244
245 245 let input_vec = ["a/", "b/", "a/c", "a/d/"]
246 246 .iter()
247 247 .map(|e| e.as_bytes().to_vec())
248 248 .collect();
249 249 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
250 250 .iter()
251 251 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
252 252 .collect();
253 253
254 254 let new = DirsMultiset::new(Manifest(&input_vec), None);
255 255 let expected = DirsMultiset {
256 256 inner: expected_inner,
257 257 };
258 258 assert_eq!(expected, new);
259 259
260 260 let input_map = ["a/", "b/", "a/c", "a/d/"]
261 261 .iter()
262 262 .map(|f| {
263 263 (
264 264 f.as_bytes().to_vec(),
265 265 DirstateEntry {
266 266 state: EntryState::Normal,
267 267 mode: 0,
268 268 mtime: 0,
269 269 size: 0,
270 270 },
271 271 )
272 272 })
273 273 .collect();
274 274 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
275 275 .iter()
276 276 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
277 277 .collect();
278 278
279 279 let new = DirsMultiset::new(Dirstate(&input_map), None);
280 280 let expected = DirsMultiset {
281 281 inner: expected_inner,
282 282 };
283 283 assert_eq!(expected, new);
284 284 }
285 285
286 286 #[test]
287 287 fn test_dirsmultiset_new_skip() {
288 288 use DirsIterable::{Dirstate, Manifest};
289 289
290 290 let input_vec = ["a/", "b/", "a/c", "a/d/"]
291 291 .iter()
292 292 .map(|e| e.as_bytes().to_vec())
293 293 .collect();
294 294 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
295 295 .iter()
296 296 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
297 297 .collect();
298 298
299 299 let new =
300 300 DirsMultiset::new(Manifest(&input_vec), Some(EntryState::Normal));
301 301 let expected = DirsMultiset {
302 302 inner: expected_inner,
303 303 };
304 304 // Skip does not affect a manifest
305 305 assert_eq!(expected, new);
306 306
307 307 let input_map = [
308 308 ("a/", EntryState::Normal),
309 309 ("a/b/", EntryState::Normal),
310 310 ("a/c", EntryState::Removed),
311 311 ("a/d/", EntryState::Merged),
312 312 ]
313 313 .iter()
314 314 .map(|(f, state)| {
315 315 (
316 316 f.as_bytes().to_vec(),
317 317 DirstateEntry {
318 318 state: *state,
319 319 mode: 0,
320 320 mtime: 0,
321 321 size: 0,
322 322 },
323 323 )
324 324 })
325 325 .collect();
326 326
327 327 // "a" incremented with "a/c" and "a/d/"
328 328 let expected_inner = [("", 1), ("a", 2), ("a/d", 1)]
329 329 .iter()
330 330 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
331 331 .collect();
332 332
333 333 let new =
334 334 DirsMultiset::new(Dirstate(&input_map), Some(EntryState::Normal));
335 335 let expected = DirsMultiset {
336 336 inner: expected_inner,
337 337 };
338 338 assert_eq!(expected, new);
339 339 }
340 340
341 341 }
@@ -1,118 +1,113 b''
1 1 // dirs_multiset.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Bindings for the `hg::dirstate::dirs_multiset` file provided by the
9 9 //! `hg-core` package.
10 10
11 11 use std::cell::RefCell;
12 12
13 13 use cpython::{
14 14 exc, ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult,
15 ToPyObject,
15 PythonObject, ToPyObject,
16 16 };
17 17
18 18 use crate::dirstate::extract_dirstate;
19 19 use hg::{
20 20 DirsIterable, DirsMultiset, DirstateMapError, DirstateParseError,
21 21 EntryState,
22 22 };
23 23 use std::convert::TryInto;
24 24
25 25 py_class!(pub class Dirs |py| {
26 26 data dirs_map: RefCell<DirsMultiset>;
27 27
28 28 // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
29 29 // a `list`)
30 30 def __new__(
31 31 _cls,
32 32 map: PyObject,
33 33 skip: Option<PyObject> = None
34 34 ) -> PyResult<Self> {
35 35 let mut skip_state: Option<EntryState> = None;
36 36 if let Some(skip) = skip {
37 37 skip_state = Some(
38 38 skip.extract::<PyBytes>(py)?.data(py)[0]
39 39 .try_into()
40 40 .map_err(|e: DirstateParseError| {
41 41 PyErr::new::<exc::ValueError, _>(py, e.to_string())
42 42 })?,
43 43 );
44 44 }
45 45 let inner = if let Ok(map) = map.cast_as::<PyDict>(py) {
46 46 let dirstate = extract_dirstate(py, &map)?;
47 47 DirsMultiset::new(
48 48 DirsIterable::Dirstate(&dirstate),
49 49 skip_state,
50 50 )
51 51 } else {
52 52 let map: Result<Vec<Vec<u8>>, PyErr> = map
53 53 .iter(py)?
54 54 .map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned()))
55 55 .collect();
56 56 DirsMultiset::new(
57 57 DirsIterable::Manifest(&map?),
58 58 skip_state,
59 59 )
60 60 };
61 61
62 62 Self::create_instance(py, RefCell::new(inner))
63 63 }
64 64
65 65 def addpath(&self, path: PyObject) -> PyResult<PyObject> {
66 66 self.dirs_map(py).borrow_mut().add_path(
67 67 path.extract::<PyBytes>(py)?.data(py),
68 68 );
69 69 Ok(py.None())
70 70 }
71 71
72 72 def delpath(&self, path: PyObject) -> PyResult<PyObject> {
73 73 self.dirs_map(py).borrow_mut().delete_path(
74 74 path.extract::<PyBytes>(py)?.data(py),
75 75 )
76 76 .and(Ok(py.None()))
77 77 .or_else(|e| {
78 78 match e {
79 79 DirstateMapError::PathNotFound(_p) => {
80 80 Err(PyErr::new::<exc::ValueError, _>(
81 81 py,
82 82 "expected a value, found none".to_string(),
83 83 ))
84 84 }
85 85 DirstateMapError::EmptyPath => {
86 86 Ok(py.None())
87 87 }
88 88 }
89 89 })
90 90 }
91 91
92 92 // This is really inefficient on top of being ugly, but it's an easy way
93 93 // of having it work to continue working on the rest of the module
94 94 // hopefully bypassing Python entirely pretty soon.
95 95 def __iter__(&self) -> PyResult<PyObject> {
96 let dict = PyDict::new(py);
97
98 for (key, value) in self.dirs_map(py).borrow().iter() {
99 dict.set_item(
100 py,
101 PyBytes::new(py, &key[..]),
102 value.to_py_object(py),
103 )?;
104 }
105
106 let locals = PyDict::new(py);
107 locals.set_item(py, "obj", dict)?;
108
109 py.eval("iter(obj)", None, Some(&locals))
96 let dirs = self.dirs_map(py).borrow();
97 let dirs: Vec<_> = dirs
98 .iter()
99 .map(|d| PyBytes::new(py, d))
100 .collect();
101 dirs.to_py_object(py)
102 .into_object()
103 .iter(py)
104 .map(|o| o.into_object())
110 105 }
111 106
112 107 def __contains__(&self, item: PyObject) -> PyResult<bool> {
113 108 Ok(self
114 109 .dirs_map(py)
115 110 .borrow()
116 .contains_key(item.extract::<PyBytes>(py)?.data(py).as_ref()))
111 .contains(item.extract::<PyBytes>(py)?.data(py).as_ref()))
117 112 }
118 113 });
General Comments 0
You need to be logged in to leave comments. Login now