dirs_multiset.rs
328 lines
| 9.9 KiB
| application/rls-services+xml
|
RustLexer
Raphaël Gomès
|
r42736 | // dirs_multiset.rs | ||
// | ||||
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | ||||
// This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | ||||
//! A multiset of directory names. | ||||
//! | ||||
//! Used to counts the references to directories in a manifest or dirstate. | ||||
Raphaël Gomès
|
r42828 | use crate::{utils::files, DirsIterable, DirstateEntry, DirstateMapError}; | ||
Raphaël Gomès
|
r42762 | use std::collections::hash_map::{Entry, Iter}; | ||
Raphaël Gomès
|
r42736 | use std::collections::HashMap; | ||
#[derive(PartialEq, Debug)] | ||||
pub struct DirsMultiset { | ||||
inner: HashMap<Vec<u8>, u32>, | ||||
} | ||||
impl DirsMultiset { | ||||
/// Initializes the multiset from a dirstate or a manifest. | ||||
/// | ||||
/// If `skip_state` is provided, skips dirstate entries with equal state. | ||||
pub fn new(iterable: DirsIterable, skip_state: Option<i8>) -> Self { | ||||
let mut multiset = DirsMultiset { | ||||
inner: HashMap::new(), | ||||
}; | ||||
match iterable { | ||||
DirsIterable::Dirstate(vec) => { | ||||
for (ref filename, DirstateEntry { state, .. }) in vec { | ||||
// This `if` is optimized out of the loop | ||||
if let Some(skip) = skip_state { | ||||
if skip != state { | ||||
multiset.add_path(filename); | ||||
} | ||||
} else { | ||||
multiset.add_path(filename); | ||||
} | ||||
} | ||||
} | ||||
DirsIterable::Manifest(vec) => { | ||||
for ref filename in vec { | ||||
multiset.add_path(filename); | ||||
} | ||||
} | ||||
} | ||||
multiset | ||||
} | ||||
/// Increases the count of deepest directory contained in the path. | ||||
/// | ||||
/// If the directory is not yet in the map, adds its parents. | ||||
pub fn add_path(&mut self, path: &[u8]) { | ||||
Raphaël Gomès
|
r42814 | for subpath in files::find_dirs(path) { | ||
Raphaël Gomès
|
r42736 | if let Some(val) = self.inner.get_mut(subpath) { | ||
*val += 1; | ||||
break; | ||||
} | ||||
self.inner.insert(subpath.to_owned(), 1); | ||||
} | ||||
} | ||||
/// Decreases the count of deepest directory contained in the path. | ||||
/// | ||||
/// If it is the only reference, decreases all parents until one is | ||||
/// removed. | ||||
/// If the directory is not in the map, something horrible has happened. | ||||
pub fn delete_path( | ||||
&mut self, | ||||
path: &[u8], | ||||
) -> Result<(), DirstateMapError> { | ||||
Raphaël Gomès
|
r42814 | for subpath in files::find_dirs(path) { | ||
Raphaël Gomès
|
r42736 | match self.inner.entry(subpath.to_owned()) { | ||
Entry::Occupied(mut entry) => { | ||||
let val = entry.get().clone(); | ||||
if val > 1 { | ||||
entry.insert(val - 1); | ||||
break; | ||||
} | ||||
entry.remove(); | ||||
} | ||||
Entry::Vacant(_) => { | ||||
Raphaël Gomès
|
r42760 | return Err(DirstateMapError::PathNotFound( | ||
path.to_owned(), | ||||
)) | ||||
Raphaël Gomès
|
r42736 | } | ||
}; | ||||
} | ||||
Ok(()) | ||||
} | ||||
Raphaël Gomès
|
r42762 | |||
pub fn contains_key(&self, key: &[u8]) -> bool { | ||||
self.inner.contains_key(key) | ||||
} | ||||
pub fn iter(&self) -> Iter<Vec<u8>, u32> { | ||||
self.inner.iter() | ||||
} | ||||
pub fn len(&self) -> usize { | ||||
self.inner.len() | ||||
} | ||||
Raphaël Gomès
|
r42736 | } | ||
#[cfg(test)] | ||||
mod tests { | ||||
use super::*; | ||||
#[test] | ||||
fn test_delete_path_path_not_found() { | ||||
let mut map = DirsMultiset::new(DirsIterable::Manifest(vec![]), None); | ||||
let path = b"doesnotexist/"; | ||||
assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(path.to_vec())), | ||||
map.delete_path(path) | ||||
); | ||||
} | ||||
#[test] | ||||
fn test_delete_path_empty_path() { | ||||
let mut map = | ||||
DirsMultiset::new(DirsIterable::Manifest(vec![vec![]]), None); | ||||
let path = b""; | ||||
assert_eq!(Ok(()), map.delete_path(path)); | ||||
assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(path.to_vec())), | ||||
map.delete_path(path) | ||||
); | ||||
} | ||||
#[test] | ||||
fn test_delete_path_successful() { | ||||
let mut map = DirsMultiset { | ||||
inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)] | ||||
.iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | ||||
.collect(), | ||||
}; | ||||
assert_eq!(Ok(()), map.delete_path(b"a/b/")); | ||||
assert_eq!(Ok(()), map.delete_path(b"a/b/")); | ||||
assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(b"a/b/".to_vec())), | ||||
map.delete_path(b"a/b/") | ||||
); | ||||
Raphaël Gomès
|
r42762 | assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap()); | ||
assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap()); | ||||
Raphaël Gomès
|
r42736 | eprintln!("{:?}", map); | ||
assert_eq!(Ok(()), map.delete_path(b"a/")); | ||||
eprintln!("{:?}", map); | ||||
assert_eq!(Ok(()), map.delete_path(b"a/c/")); | ||||
assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(b"a/c/".to_vec())), | ||||
map.delete_path(b"a/c/") | ||||
); | ||||
} | ||||
#[test] | ||||
fn test_add_path_empty_path() { | ||||
let mut map = DirsMultiset::new(DirsIterable::Manifest(vec![]), None); | ||||
let path = b""; | ||||
map.add_path(path); | ||||
assert_eq!(1, map.len()); | ||||
} | ||||
#[test] | ||||
fn test_add_path_successful() { | ||||
let mut map = DirsMultiset::new(DirsIterable::Manifest(vec![]), None); | ||||
map.add_path(b"a/"); | ||||
Raphaël Gomès
|
r42762 | assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap()); | ||
assert_eq!(1, *map.inner.get(&Vec::new()).unwrap()); | ||||
Raphaël Gomès
|
r42736 | assert_eq!(2, map.len()); | ||
// Non directory should be ignored | ||||
map.add_path(b"a"); | ||||
Raphaël Gomès
|
r42762 | assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap()); | ||
Raphaël Gomès
|
r42736 | assert_eq!(2, map.len()); | ||
// Non directory will still add its base | ||||
map.add_path(b"a/b"); | ||||
Raphaël Gomès
|
r42762 | assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap()); | ||
Raphaël Gomès
|
r42736 | assert_eq!(2, map.len()); | ||
// Duplicate path works | ||||
map.add_path(b"a/"); | ||||
Raphaël Gomès
|
r42762 | assert_eq!(3, *map.inner.get(&b"a".to_vec()).unwrap()); | ||
Raphaël Gomès
|
r42736 | |||
// Nested dir adds to its base | ||||
map.add_path(b"a/b/"); | ||||
Raphaël Gomès
|
r42762 | assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap()); | ||
assert_eq!(1, *map.inner.get(&b"a/b".to_vec()).unwrap()); | ||||
Raphaël Gomès
|
r42736 | |||
// but not its base's base, because it already existed | ||||
map.add_path(b"a/b/c/"); | ||||
Raphaël Gomès
|
r42762 | assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap()); | ||
assert_eq!(2, *map.inner.get(&b"a/b".to_vec()).unwrap()); | ||||
Raphaël Gomès
|
r42736 | |||
map.add_path(b"a/c/"); | ||||
Raphaël Gomès
|
r42762 | assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap()); | ||
Raphaël Gomès
|
r42736 | |||
let expected = DirsMultiset { | ||||
inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)] | ||||
.iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | ||||
.collect(), | ||||
}; | ||||
assert_eq!(map, expected); | ||||
} | ||||
#[test] | ||||
fn test_dirsmultiset_new_empty() { | ||||
use DirsIterable::{Dirstate, Manifest}; | ||||
let new = DirsMultiset::new(Manifest(vec![]), None); | ||||
let expected = DirsMultiset { | ||||
inner: HashMap::new(), | ||||
}; | ||||
assert_eq!(expected, new); | ||||
let new = DirsMultiset::new(Dirstate(vec![]), None); | ||||
let expected = DirsMultiset { | ||||
inner: HashMap::new(), | ||||
}; | ||||
assert_eq!(expected, new); | ||||
} | ||||
#[test] | ||||
fn test_dirsmultiset_new_no_skip() { | ||||
use DirsIterable::{Dirstate, Manifest}; | ||||
let input_vec = ["a/", "b/", "a/c", "a/d/"] | ||||
.iter() | ||||
.map(|e| e.as_bytes().to_vec()) | ||||
.collect(); | ||||
let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)] | ||||
.iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | ||||
.collect(); | ||||
let new = DirsMultiset::new(Manifest(input_vec), None); | ||||
let expected = DirsMultiset { | ||||
inner: expected_inner, | ||||
}; | ||||
assert_eq!(expected, new); | ||||
let input_map = ["a/", "b/", "a/c", "a/d/"] | ||||
.iter() | ||||
.map(|f| { | ||||
( | ||||
f.as_bytes().to_vec(), | ||||
DirstateEntry { | ||||
state: 0, | ||||
mode: 0, | ||||
mtime: 0, | ||||
size: 0, | ||||
}, | ||||
) | ||||
}) | ||||
.collect(); | ||||
let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)] | ||||
.iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | ||||
.collect(); | ||||
let new = DirsMultiset::new(Dirstate(input_map), None); | ||||
let expected = DirsMultiset { | ||||
inner: expected_inner, | ||||
}; | ||||
assert_eq!(expected, new); | ||||
} | ||||
#[test] | ||||
fn test_dirsmultiset_new_skip() { | ||||
use DirsIterable::{Dirstate, Manifest}; | ||||
let input_vec = ["a/", "b/", "a/c", "a/d/"] | ||||
.iter() | ||||
.map(|e| e.as_bytes().to_vec()) | ||||
.collect(); | ||||
let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)] | ||||
.iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | ||||
.collect(); | ||||
let new = DirsMultiset::new(Manifest(input_vec), Some('n' as i8)); | ||||
let expected = DirsMultiset { | ||||
inner: expected_inner, | ||||
}; | ||||
// Skip does not affect a manifest | ||||
assert_eq!(expected, new); | ||||
let input_map = | ||||
[("a/", 'n'), ("a/b/", 'n'), ("a/c", 'r'), ("a/d/", 'm')] | ||||
.iter() | ||||
.map(|(f, state)| { | ||||
( | ||||
f.as_bytes().to_vec(), | ||||
DirstateEntry { | ||||
state: *state as i8, | ||||
mode: 0, | ||||
mtime: 0, | ||||
size: 0, | ||||
}, | ||||
) | ||||
}) | ||||
.collect(); | ||||
// "a" incremented with "a/c" and "a/d/" | ||||
let expected_inner = [("", 1), ("a", 2), ("a/d", 1)] | ||||
.iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | ||||
.collect(); | ||||
let new = DirsMultiset::new(Dirstate(input_map), Some('n' as i8)); | ||||
let expected = DirsMultiset { | ||||
inner: expected_inner, | ||||
}; | ||||
assert_eq!(expected, new); | ||||
} | ||||
} | ||||