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