##// END OF EJS Templates
fix: pass line ranges as value instead of callback...
fix: pass line ranges as value instead of callback The callback no longer takes any arguments from the inner function, so we might as well call it sooner and pass the value instead. Note the value still needs to be recomputed every iteration to account for the previous iteration's changes to the file content. Differential Revision: https://phab.mercurial-scm.org/D6727

File last commit:

r42977:4f9dff6f default
r43003:e9f50307 default
Show More
dirs_multiset.rs
341 lines | 10.1 KiB | application/rls-services+xml | RustLexer
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
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
rust-dirstate: use EntryState enum instead of literals...
r42994 use crate::{
dirstate::EntryState, utils::files, DirsIterable, DirstateEntry,
DirstateMapError,
};
Raphaël Gomès
rust-dirstate: improve API of `DirsMultiset`...
r42995 use std::collections::hash_map::Entry;
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
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.
Raphaël Gomès
rust-dirstate: use EntryState enum instead of literals...
r42994 pub fn new(
iterable: DirsIterable,
skip_state: Option<EntryState>,
) -> Self {
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 let mut multiset = DirsMultiset {
inner: HashMap::new(),
};
match iterable {
DirsIterable::Dirstate(vec) => {
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 for (filename, DirstateEntry { state, .. }) in vec {
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 // This `if` is optimized out of the loop
if let Some(skip) = skip_state {
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 if skip != *state {
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 multiset.add_path(filename);
}
} else {
multiset.add_path(filename);
}
}
}
DirsIterable::Manifest(vec) => {
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 for filename in vec {
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 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
rust-utils: use new find_dirs iterator...
r42814 for subpath in files::find_dirs(path) {
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
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
rust-utils: use new find_dirs iterator...
r42814 for subpath in files::find_dirs(path) {
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
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
rust: run rfmt on all hg-core/hg-cpython code...
r42760 return Err(DirstateMapError::PathNotFound(
path.to_owned(),
))
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 }
};
}
Ok(())
}
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
r42762
Raphaël Gomès
rust-dirstate: improve API of `DirsMultiset`...
r42995 pub fn contains(&self, key: &[u8]) -> bool {
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
r42762 self.inner.contains_key(key)
}
Raphaël Gomès
rust-dirstate: improve API of `DirsMultiset`...
r42995 pub fn iter(&self) -> impl Iterator<Item = &Vec<u8>> {
self.inner.keys()
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
r42762 }
pub fn len(&self) -> usize {
self.inner.len()
}
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 }
#[cfg(test)]
mod tests {
use super::*;
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 use std::collections::HashMap;
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736
#[test]
fn test_delete_path_path_not_found() {
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 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 =
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 DirsMultiset::new(DirsIterable::Manifest(&vec![vec![]]), None);
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
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
rust: remove Deref in favor of explicit methods...
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
rust-dirstate: add "dirs" Rust implementation...
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() {
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 let path = b"";
map.add_path(path);
assert_eq!(1, map.len());
}
#[test]
fn test_add_path_successful() {
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736
map.add_path(b"a/");
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
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
rust-dirstate: add "dirs" Rust implementation...
r42736 assert_eq!(2, map.len());
// Non directory should be ignored
map.add_path(b"a");
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
r42762 assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 assert_eq!(2, map.len());
// Non directory will still add its base
map.add_path(b"a/b");
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
r42762 assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 assert_eq!(2, map.len());
// Duplicate path works
map.add_path(b"a/");
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
r42762 assert_eq!(3, *map.inner.get(&b"a".to_vec()).unwrap());
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736
// Nested dir adds to its base
map.add_path(b"a/b/");
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
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
rust-dirstate: add "dirs" Rust implementation...
r42736
// but not its base's base, because it already existed
map.add_path(b"a/b/c/");
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
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
rust-dirstate: add "dirs" Rust implementation...
r42736
map.add_path(b"a/c/");
Raphaël Gomès
rust: remove Deref in favor of explicit methods...
r42762 assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
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};
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 let new = DirsMultiset::new(Manifest(&vec![]), None);
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 let expected = DirsMultiset {
inner: HashMap::new(),
};
assert_eq!(expected, new);
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 let new = DirsMultiset::new(Dirstate(&HashMap::new()), None);
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 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();
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 let new = DirsMultiset::new(Manifest(&input_vec), None);
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
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
rust-dirstate: use EntryState enum instead of literals...
r42994 state: EntryState::Normal,
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
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();
Raphaël Gomès
rust-parsers: switch to parse/pack_dirstate to mutate-on-loop...
r42993 let new = DirsMultiset::new(Dirstate(&input_map), None);
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 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();
Raphaël Gomès
rust-dirstate: use EntryState enum instead of literals...
r42994 let new =
DirsMultiset::new(Manifest(&input_vec), Some(EntryState::Normal));
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 let expected = DirsMultiset {
inner: expected_inner,
};
// Skip does not affect a manifest
assert_eq!(expected, new);
Raphaël Gomès
rust-dirstate: use EntryState enum instead of literals...
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
rust-dirstate: add "dirs" Rust implementation...
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
rust-dirstate: use EntryState enum instead of literals...
r42994 let new =
DirsMultiset::new(Dirstate(&input_map), Some(EntryState::Normal));
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 let expected = DirsMultiset {
inner: expected_inner,
};
assert_eq!(expected, new);
}
Raphaël Gomès
rust-dirstate: use EntryState enum instead of literals...
r42994
Raphaël Gomès
rust-dirstate: add "dirs" Rust implementation...
r42736 }