dirs_multiset.rs
319 lines
| 9.3 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
|
r42994 | use crate::{ | ||
Yuya Nishihara
|
r43070 | dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError, | ||
Raphaël Gomès
|
r42994 | }; | ||
Raphaël Gomès
|
r42995 | use std::collections::hash_map::Entry; | ||
Raphaël Gomès
|
r42736 | use std::collections::HashMap; | ||
#[derive(PartialEq, Debug)] | ||||
pub struct DirsMultiset { | ||||
inner: HashMap<Vec<u8>, u32>, | ||||
} | ||||
impl DirsMultiset { | ||||
Yuya Nishihara
|
r43070 | /// Initializes the multiset from a dirstate. | ||
Raphaël Gomès
|
r42736 | /// | ||
/// If `skip_state` is provided, skips dirstate entries with equal state. | ||||
Yuya Nishihara
|
r43070 | pub fn from_dirstate( | ||
vec: &HashMap<Vec<u8>, DirstateEntry>, | ||||
Raphaël Gomès
|
r42994 | skip_state: Option<EntryState>, | ||
) -> Self { | ||||
Raphaël Gomès
|
r42736 | let mut multiset = DirsMultiset { | ||
inner: HashMap::new(), | ||||
}; | ||||
Yuya Nishihara
|
r43070 | for (filename, DirstateEntry { state, .. }) in vec { | ||
// This `if` is optimized out of the loop | ||||
if let Some(skip) = skip_state { | ||||
if skip != *state { | ||||
Raphaël Gomès
|
r42736 | multiset.add_path(filename); | ||
} | ||||
Yuya Nishihara
|
r43070 | } else { | ||
multiset.add_path(filename); | ||||
Raphaël Gomès
|
r42736 | } | ||
} | ||||
multiset | ||||
} | ||||
Yuya Nishihara
|
r43070 | /// Initializes the multiset from a manifest. | ||
pub fn from_manifest(vec: &Vec<Vec<u8>>) -> Self { | ||||
let mut multiset = DirsMultiset { | ||||
inner: HashMap::new(), | ||||
}; | ||||
for filename in vec { | ||||
multiset.add_path(filename); | ||||
} | ||||
multiset | ||||
} | ||||
Raphaël Gomès
|
r42736 | /// 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 | |||
Raphaël Gomès
|
r42995 | pub fn contains(&self, key: &[u8]) -> bool { | ||
Raphaël Gomès
|
r42762 | self.inner.contains_key(key) | ||
} | ||||
Raphaël Gomès
|
r42995 | pub fn iter(&self) -> impl Iterator<Item = &Vec<u8>> { | ||
self.inner.keys() | ||||
Raphaël Gomès
|
r42762 | } | ||
pub fn len(&self) -> usize { | ||||
self.inner.len() | ||||
} | ||||
Raphaël Gomès
|
r42736 | } | ||
#[cfg(test)] | ||||
mod tests { | ||||
use super::*; | ||||
Raphaël Gomès
|
r42993 | use std::collections::HashMap; | ||
Raphaël Gomès
|
r42736 | |||
#[test] | ||||
fn test_delete_path_path_not_found() { | ||||
Yuya Nishihara
|
r43070 | let mut map = DirsMultiset::from_manifest(&vec![]); | ||
Raphaël Gomès
|
r42736 | let path = b"doesnotexist/"; | ||
assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(path.to_vec())), | ||||
map.delete_path(path) | ||||
); | ||||
} | ||||
#[test] | ||||
fn test_delete_path_empty_path() { | ||||
Yuya Nishihara
|
r43070 | let mut map = DirsMultiset::from_manifest(&vec![vec![]]); | ||
Raphaël Gomès
|
r42736 | 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() { | ||||
Yuya Nishihara
|
r43070 | let mut map = DirsMultiset::from_manifest(&vec![]); | ||
Raphaël Gomès
|
r42736 | let path = b""; | ||
map.add_path(path); | ||||
assert_eq!(1, map.len()); | ||||
} | ||||
#[test] | ||||
fn test_add_path_successful() { | ||||
Yuya Nishihara
|
r43070 | let mut map = DirsMultiset::from_manifest(&vec![]); | ||
Raphaël Gomès
|
r42736 | |||
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() { | ||||
Yuya Nishihara
|
r43070 | let new = DirsMultiset::from_manifest(&vec![]); | ||
Raphaël Gomès
|
r42736 | let expected = DirsMultiset { | ||
inner: HashMap::new(), | ||||
}; | ||||
assert_eq!(expected, new); | ||||
Yuya Nishihara
|
r43070 | let new = DirsMultiset::from_dirstate(&HashMap::new(), None); | ||
Raphaël Gomès
|
r42736 | let expected = DirsMultiset { | ||
inner: HashMap::new(), | ||||
}; | ||||
assert_eq!(expected, new); | ||||
} | ||||
#[test] | ||||
fn test_dirsmultiset_new_no_skip() { | ||||
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(); | ||||
Yuya Nishihara
|
r43070 | let new = DirsMultiset::from_manifest(&input_vec); | ||
Raphaël Gomès
|
r42736 | 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 { | ||||
Raphaël Gomès
|
r42994 | state: EntryState::Normal, | ||
Raphaël Gomès
|
r42736 | 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(); | ||||
Yuya Nishihara
|
r43070 | let new = DirsMultiset::from_dirstate(&input_map, None); | ||
Raphaël Gomès
|
r42736 | let expected = DirsMultiset { | ||
inner: expected_inner, | ||||
}; | ||||
assert_eq!(expected, new); | ||||
} | ||||
#[test] | ||||
fn test_dirsmultiset_new_skip() { | ||||
Raphaël Gomès
|
r42994 | let input_map = [ | ||
("a/", EntryState::Normal), | ||||
("a/b/", EntryState::Normal), | ||||
("a/c", EntryState::Removed), | ||||
("a/d/", EntryState::Merged), | ||||
] | ||||
.iter() | ||||
.map(|(f, state)| { | ||||
( | ||||
f.as_bytes().to_vec(), | ||||
DirstateEntry { | ||||
state: *state, | ||||
mode: 0, | ||||
mtime: 0, | ||||
size: 0, | ||||
}, | ||||
) | ||||
}) | ||||
.collect(); | ||||
Raphaël Gomès
|
r42736 | |||
// "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(); | ||||
Raphaël Gomès
|
r42994 | let new = | ||
Yuya Nishihara
|
r43070 | DirsMultiset::from_dirstate(&input_map, Some(EntryState::Normal)); | ||
Raphaël Gomès
|
r42736 | let expected = DirsMultiset { | ||
inner: expected_inner, | ||||
}; | ||||
assert_eq!(expected, new); | ||||
} | ||||
} | ||||