dirs_multiset.rs
346 lines
| 10.8 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
|
r43227 | use crate::utils::hg_path::{HgPath, HgPathBuf}; | ||
Raphaël Gomès
|
r42994 | use crate::{ | ||
Yuya Nishihara
|
r43070 | dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError, | ||
Raphaël Gomès
|
r44278 | FastHashMap, | ||
Raphaël Gomès
|
r42994 | }; | ||
Yuya Nishihara
|
r43155 | use std::collections::hash_map::{self, Entry}; | ||
Raphaël Gomès
|
r42736 | |||
Yuya Nishihara
|
r43155 | // could be encapsulated if we care API stability more seriously | ||
Raphaël Gomès
|
r43227 | pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>; | ||
Yuya Nishihara
|
r43155 | |||
Raphaël Gomès
|
r42736 | #[derive(PartialEq, Debug)] | ||
pub struct DirsMultiset { | ||||
Raphaël Gomès
|
r44278 | inner: FastHashMap<HgPathBuf, u32>, | ||
Raphaël Gomès
|
r42736 | } | ||
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( | ||
Raphaël Gomès
|
r44283 | dirstate: &FastHashMap<HgPathBuf, DirstateEntry>, | ||
Raphaël Gomès
|
r42994 | skip_state: Option<EntryState>, | ||
Raphaël Gomès
|
r44315 | ) -> Result<Self, DirstateMapError> { | ||
Raphaël Gomès
|
r42736 | let mut multiset = DirsMultiset { | ||
Raphaël Gomès
|
r44278 | inner: FastHashMap::default(), | ||
Raphaël Gomès
|
r42736 | }; | ||
Raphaël Gomès
|
r44283 | for (filename, DirstateEntry { state, .. }) in dirstate { | ||
Yuya Nishihara
|
r43070 | // This `if` is optimized out of the loop | ||
if let Some(skip) = skip_state { | ||||
if skip != *state { | ||||
Raphaël Gomès
|
r44315 | multiset.add_path(filename)?; | ||
Raphaël Gomès
|
r42736 | } | ||
Yuya Nishihara
|
r43070 | } else { | ||
Raphaël Gomès
|
r44315 | multiset.add_path(filename)?; | ||
Raphaël Gomès
|
r42736 | } | ||
} | ||||
Raphaël Gomès
|
r44315 | Ok(multiset) | ||
Raphaël Gomès
|
r42736 | } | ||
Yuya Nishihara
|
r43070 | /// Initializes the multiset from a manifest. | ||
Raphaël Gomès
|
r44315 | pub fn from_manifest( | ||
manifest: &[impl AsRef<HgPath>], | ||||
) -> Result<Self, DirstateMapError> { | ||||
Yuya Nishihara
|
r43070 | let mut multiset = DirsMultiset { | ||
Raphaël Gomès
|
r44278 | inner: FastHashMap::default(), | ||
Yuya Nishihara
|
r43070 | }; | ||
Raphaël Gomès
|
r44283 | for filename in manifest { | ||
Raphaël Gomès
|
r44315 | multiset.add_path(filename.as_ref())?; | ||
Yuya Nishihara
|
r43070 | } | ||
Raphaël Gomès
|
r44315 | Ok(multiset) | ||
Yuya Nishihara
|
r43070 | } | ||
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. | ||||
Raphaël Gomès
|
r44283 | pub fn add_path( | ||
&mut self, | ||||
path: impl AsRef<HgPath>, | ||||
) -> Result<(), DirstateMapError> { | ||||
for subpath in files::find_dirs(path.as_ref()) { | ||||
Raphaël Gomès
|
r44227 | if subpath.as_bytes().last() == Some(&b'/') { | ||
// TODO Remove this once PathAuditor is certified | ||||
// as the only entrypoint for path data | ||||
return Err(DirstateMapError::ConsecutiveSlashes); | ||||
} | ||||
Raphaël Gomès
|
r42736 | if let Some(val) = self.inner.get_mut(subpath) { | ||
*val += 1; | ||||
break; | ||||
} | ||||
self.inner.insert(subpath.to_owned(), 1); | ||||
} | ||||
Raphaël Gomès
|
r44227 | Ok(()) | ||
Raphaël Gomès
|
r42736 | } | ||
/// 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, | ||||
Raphaël Gomès
|
r44283 | path: impl AsRef<HgPath>, | ||
Raphaël Gomès
|
r42736 | ) -> Result<(), DirstateMapError> { | ||
Raphaël Gomès
|
r44283 | for subpath in files::find_dirs(path.as_ref()) { | ||
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( | ||
Raphaël Gomès
|
r44283 | path.as_ref().to_owned(), | ||
Raphaël Gomès
|
r42760 | )) | ||
Raphaël Gomès
|
r42736 | } | ||
}; | ||||
} | ||||
Ok(()) | ||||
} | ||||
Raphaël Gomès
|
r42762 | |||
Raphaël Gomès
|
r44283 | pub fn contains(&self, key: impl AsRef<HgPath>) -> bool { | ||
self.inner.contains_key(key.as_ref()) | ||||
Raphaël Gomès
|
r42762 | } | ||
Yuya Nishihara
|
r43155 | pub fn iter(&self) -> DirsMultisetIter { | ||
Raphaël Gomès
|
r42995 | 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::*; | ||||
#[test] | ||||
fn test_delete_path_path_not_found() { | ||||
Raphaël Gomès
|
r44283 | let manifest: Vec<HgPathBuf> = vec![]; | ||
Raphaël Gomès
|
r44315 | let mut map = DirsMultiset::from_manifest(&manifest).unwrap(); | ||
Raphaël Gomès
|
r43227 | let path = HgPathBuf::from_bytes(b"doesnotexist/"); | ||
Raphaël Gomès
|
r42736 | assert_eq!( | ||
Raphaël Gomès
|
r43227 | Err(DirstateMapError::PathNotFound(path.to_owned())), | ||
map.delete_path(&path) | ||||
Raphaël Gomès
|
r42736 | ); | ||
} | ||||
#[test] | ||||
fn test_delete_path_empty_path() { | ||||
Raphaël Gomès
|
r44315 | let mut map = | ||
DirsMultiset::from_manifest(&vec![HgPathBuf::new()]).unwrap(); | ||||
Raphaël Gomès
|
r43227 | let path = HgPath::new(b""); | ||
Raphaël Gomès
|
r42736 | assert_eq!(Ok(()), map.delete_path(path)); | ||
assert_eq!( | ||||
Raphaël Gomès
|
r43227 | Err(DirstateMapError::PathNotFound(path.to_owned())), | ||
Raphaël Gomès
|
r42736 | 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() | ||||
Raphaël Gomès
|
r43227 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||
Raphaël Gomès
|
r42736 | .collect(), | ||
}; | ||||
Raphaël Gomès
|
r43227 | assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/"))); | ||
eprintln!("{:?}", map); | ||||
assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/"))); | ||||
eprintln!("{:?}", map); | ||||
Raphaël Gomès
|
r42736 | assert_eq!( | ||
Raphaël Gomès
|
r43227 | Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes( | ||
b"a/b/" | ||||
))), | ||||
map.delete_path(HgPath::new(b"a/b/")) | ||||
Raphaël Gomès
|
r42736 | ); | ||
Raphaël Gomès
|
r43227 | assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||
assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap()); | ||||
Raphaël Gomès
|
r42736 | eprintln!("{:?}", map); | ||
Raphaël Gomès
|
r43227 | assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/"))); | ||
Raphaël Gomès
|
r42736 | eprintln!("{:?}", map); | ||
Raphaël Gomès
|
r43227 | assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/c/"))); | ||
Raphaël Gomès
|
r42736 | assert_eq!( | ||
Raphaël Gomès
|
r43227 | Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes( | ||
b"a/c/" | ||||
))), | ||||
map.delete_path(HgPath::new(b"a/c/")) | ||||
Raphaël Gomès
|
r42736 | ); | ||
} | ||||
#[test] | ||||
fn test_add_path_empty_path() { | ||||
Raphaël Gomès
|
r44283 | let manifest: Vec<HgPathBuf> = vec![]; | ||
Raphaël Gomès
|
r44315 | let mut map = DirsMultiset::from_manifest(&manifest).unwrap(); | ||
Raphaël Gomès
|
r43227 | let path = HgPath::new(b""); | ||
Raphaël Gomès
|
r42736 | map.add_path(path); | ||
assert_eq!(1, map.len()); | ||||
} | ||||
#[test] | ||||
fn test_add_path_successful() { | ||||
Raphaël Gomès
|
r44283 | let manifest: Vec<HgPathBuf> = vec![]; | ||
Raphaël Gomès
|
r44315 | let mut map = DirsMultiset::from_manifest(&manifest).unwrap(); | ||
Raphaël Gomès
|
r42736 | |||
Raphaël Gomès
|
r43227 | map.add_path(HgPath::new(b"a/")); | ||
assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(1, *map.inner.get(HgPath::new(b"")).unwrap()); | ||||
Raphaël Gomès
|
r42736 | assert_eq!(2, map.len()); | ||
// Non directory should be ignored | ||||
Raphaël Gomès
|
r43227 | map.add_path(HgPath::new(b"a")); | ||
assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
Raphaël Gomès
|
r42736 | assert_eq!(2, map.len()); | ||
// Non directory will still add its base | ||||
Raphaël Gomès
|
r43227 | map.add_path(HgPath::new(b"a/b")); | ||
assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
Raphaël Gomès
|
r42736 | assert_eq!(2, map.len()); | ||
// Duplicate path works | ||||
Raphaël Gomès
|
r43227 | map.add_path(HgPath::new(b"a/")); | ||
assert_eq!(3, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
Raphaël Gomès
|
r42736 | |||
// Nested dir adds to its base | ||||
Raphaël Gomès
|
r43227 | map.add_path(HgPath::new(b"a/b/")); | ||
assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(1, *map.inner.get(HgPath::new(b"a/b")).unwrap()); | ||||
Raphaël Gomès
|
r42736 | |||
// but not its base's base, because it already existed | ||||
Raphaël Gomès
|
r43227 | map.add_path(HgPath::new(b"a/b/c/")); | ||
assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(2, *map.inner.get(HgPath::new(b"a/b")).unwrap()); | ||||
Raphaël Gomès
|
r42736 | |||
Raphaël Gomès
|
r43227 | map.add_path(HgPath::new(b"a/c/")); | ||
assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap()); | ||||
Raphaël Gomès
|
r42736 | |||
let expected = DirsMultiset { | ||||
inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)] | ||||
.iter() | ||||
Raphaël Gomès
|
r43227 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||
Raphaël Gomès
|
r42736 | .collect(), | ||
}; | ||||
assert_eq!(map, expected); | ||||
} | ||||
#[test] | ||||
fn test_dirsmultiset_new_empty() { | ||||
Raphaël Gomès
|
r44283 | let manifest: Vec<HgPathBuf> = vec![]; | ||
Raphaël Gomès
|
r44315 | let new = DirsMultiset::from_manifest(&manifest).unwrap(); | ||
Raphaël Gomès
|
r42736 | let expected = DirsMultiset { | ||
Raphaël Gomès
|
r44278 | inner: FastHashMap::default(), | ||
Raphaël Gomès
|
r42736 | }; | ||
assert_eq!(expected, new); | ||||
Raphaël Gomès
|
r44315 | let new = DirsMultiset::from_dirstate(&FastHashMap::default(), None) | ||
.unwrap(); | ||||
Raphaël Gomès
|
r42736 | let expected = DirsMultiset { | ||
Raphaël Gomès
|
r44278 | inner: FastHashMap::default(), | ||
Raphaël Gomès
|
r42736 | }; | ||
assert_eq!(expected, new); | ||||
} | ||||
#[test] | ||||
fn test_dirsmultiset_new_no_skip() { | ||||
Raphaël Gomès
|
r44283 | let input_vec: Vec<HgPathBuf> = ["a/", "b/", "a/c", "a/d/"] | ||
Raphaël Gomès
|
r42736 | .iter() | ||
Raphaël Gomès
|
r43227 | .map(|e| HgPathBuf::from_bytes(e.as_bytes())) | ||
Raphaël Gomès
|
r42736 | .collect(); | ||
let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)] | ||||
.iter() | ||||
Raphaël Gomès
|
r43227 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||
Raphaël Gomès
|
r42736 | .collect(); | ||
Raphaël Gomès
|
r44315 | let new = DirsMultiset::from_manifest(&input_vec).unwrap(); | ||
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| { | ||||
( | ||||
Raphaël Gomès
|
r43227 | HgPathBuf::from_bytes(f.as_bytes()), | ||
Raphaël Gomès
|
r42736 | 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() | ||||
Raphaël Gomès
|
r43227 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||
Raphaël Gomès
|
r42736 | .collect(); | ||
Raphaël Gomès
|
r44315 | let new = DirsMultiset::from_dirstate(&input_map, None).unwrap(); | ||
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)| { | ||||
( | ||||
Raphaël Gomès
|
r43227 | HgPathBuf::from_bytes(f.as_bytes()), | ||
Raphaël Gomès
|
r42994 | 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() | ||||
Raphaël Gomès
|
r43227 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||
Raphaël Gomès
|
r42736 | .collect(); | ||
Raphaël Gomès
|
r42994 | let new = | ||
Raphaël Gomès
|
r44315 | DirsMultiset::from_dirstate(&input_map, Some(EntryState::Normal)) | ||
.unwrap(); | ||||
Raphaël Gomès
|
r42736 | let expected = DirsMultiset { | ||
inner: expected_inner, | ||||
}; | ||||
assert_eq!(expected, new); | ||||
} | ||||
} | ||||