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