##// END OF EJS Templates
merge: move initialization of copy dicts to one place...
merge: move initialization of copy dicts to one place Differential Revision: https://phab.mercurial-scm.org/D7988

File last commit:

r44342:d8a96ceb default
r44680:8ad263c3 default
Show More
dirstate_map.rs
436 lines | 12.7 KiB | application/rls-services+xml | RustLexer
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 // dirstate_map.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.
use crate::{
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 dirstate::{parsers::PARENT_SIZE, EntryState, SIZE_FROM_OTHER_PARENT},
Raphaël Gomès
rust-utils: add normalize_case util to mirror Python one...
r43108 pack_dirstate, parse_dirstate,
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 utils::{
files::normalize_case,
hg_path::{HgPath, HgPathBuf},
},
Raphaël Gomès
rust-utils: add normalize_case util to mirror Python one...
r43108 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError,
Raphaël Gomès
rust-performance: introduce FastHashMap type alias for HashMap...
r44278 DirstateParents, DirstateParseError, FastHashMap, StateMap,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 };
use core::borrow::Borrow;
Raphaël Gomès
rust-performance: introduce FastHashMap type alias for HashMap...
r44278 use std::collections::HashSet;
Yuya Nishihara
rust: simply use TryInto to convert slice to array...
r43067 use std::convert::TryInto;
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 use std::iter::FromIterator;
use std::ops::Deref;
use std::time::Duration;
Raphaël Gomès
rust-performance: introduce FastHashMap type alias for HashMap...
r44278 pub type FileFoldMap = FastHashMap<HgPathBuf, HgPathBuf>;
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998
Yuya Nishihara
rust-dirstate: rename NULL_REVISION to NULL_ID which isn't a revision number
r43065 const NULL_ID: [u8; 20] = [0; 20];
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 const MTIME_UNSET: i32 = -1;
#[derive(Default)]
pub struct DirstateMap {
state_map: StateMap,
pub copy_map: CopyMap,
file_fold_map: Option<FileFoldMap>,
pub dirs: Option<DirsMultiset>,
pub all_dirs: Option<DirsMultiset>,
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 non_normal_set: HashSet<HgPathBuf>,
other_parent_set: HashSet<HgPathBuf>,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 parents: Option<DirstateParents>,
dirty_parents: bool,
}
/// Should only really be used in python interface code, for clarity
impl Deref for DirstateMap {
type Target = StateMap;
fn deref(&self) -> &Self::Target {
&self.state_map
}
}
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 iter: I,
) -> Self {
Self {
state_map: iter.into_iter().collect(),
..Self::default()
}
}
}
impl DirstateMap {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.state_map.clear();
self.copy_map.clear();
self.file_fold_map = None;
self.non_normal_set.clear();
self.other_parent_set.clear();
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 self.set_parents(&DirstateParents {
Yuya Nishihara
rust-dirstate: rename NULL_REVISION to NULL_ID which isn't a revision number
r43065 p1: NULL_ID,
p2: NULL_ID,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 })
}
/// Add a tracked file to the dirstate
pub fn add_file(
&mut self,
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 filename: &HgPath,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 old_state: EntryState,
entry: DirstateEntry,
Raphaël Gomès
rust-dirs: address failing tests for `dirs` impl with a temporary fix...
r44227 ) -> Result<(), DirstateMapError> {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 if old_state == EntryState::Unknown || old_state == EntryState::Removed
{
if let Some(ref mut dirs) = self.dirs {
Raphaël Gomès
rust-dirs: address failing tests for `dirs` impl with a temporary fix...
r44227 dirs.add_path(filename)?;
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
}
if old_state == EntryState::Unknown {
if let Some(ref mut all_dirs) = self.all_dirs {
Raphaël Gomès
rust-dirs: address failing tests for `dirs` impl with a temporary fix...
r44227 all_dirs.add_path(filename)?;
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
}
self.state_map.insert(filename.to_owned(), entry.to_owned());
if entry.state != EntryState::Normal || entry.mtime == MTIME_UNSET {
self.non_normal_set.insert(filename.to_owned());
}
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 if entry.size == SIZE_FROM_OTHER_PARENT {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 self.other_parent_set.insert(filename.to_owned());
}
Raphaël Gomès
rust-dirs: address failing tests for `dirs` impl with a temporary fix...
r44227 Ok(())
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
/// Mark a file as removed in the dirstate.
///
/// The `size` parameter is used to store sentinel values that indicate
/// the file's previous state. In the future, we should refactor this
/// to be more explicit about what that state is.
pub fn remove_file(
&mut self,
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 filename: &HgPath,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 old_state: EntryState,
size: i32,
) -> Result<(), DirstateMapError> {
if old_state != EntryState::Unknown && old_state != EntryState::Removed
{
if let Some(ref mut dirs) = self.dirs {
dirs.delete_path(filename)?;
}
}
if old_state == EntryState::Unknown {
if let Some(ref mut all_dirs) = self.all_dirs {
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 all_dirs.add_path(filename)?;
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
}
if let Some(ref mut file_fold_map) = self.file_fold_map {
Raphaël Gomès
rust-utils: add normalize_case util to mirror Python one...
r43108 file_fold_map.remove(&normalize_case(filename));
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
self.state_map.insert(
filename.to_owned(),
DirstateEntry {
state: EntryState::Removed,
mode: 0,
size,
mtime: 0,
},
);
self.non_normal_set.insert(filename.to_owned());
Ok(())
}
/// Remove a file from the dirstate.
/// Returns `true` if the file was previously recorded.
pub fn drop_file(
&mut self,
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 filename: &HgPath,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 old_state: EntryState,
) -> Result<bool, DirstateMapError> {
Yuya Nishihara
rust-dirstate: remove too abstracted way of getting &[u8]
r43063 let exists = self.state_map.remove(filename).is_some();
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998
if exists {
if old_state != EntryState::Removed {
if let Some(ref mut dirs) = self.dirs {
dirs.delete_path(filename)?;
}
}
if let Some(ref mut all_dirs) = self.all_dirs {
all_dirs.delete_path(filename)?;
}
}
if let Some(ref mut file_fold_map) = self.file_fold_map {
Raphaël Gomès
rust-utils: add normalize_case util to mirror Python one...
r43108 file_fold_map.remove(&normalize_case(filename));
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Yuya Nishihara
rust-dirstate: remove too abstracted way of getting &[u8]
r43063 self.non_normal_set.remove(filename);
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998
Ok(exists)
}
pub fn clear_ambiguous_times(
&mut self,
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 filenames: Vec<HgPathBuf>,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 now: i32,
) {
for filename in filenames {
let mut changed = false;
self.state_map
.entry(filename.to_owned())
.and_modify(|entry| {
if entry.state == EntryState::Normal && entry.mtime == now
{
changed = true;
*entry = DirstateEntry {
mtime: MTIME_UNSET,
..*entry
};
}
});
if changed {
self.non_normal_set.insert(filename.to_owned());
}
}
}
pub fn non_normal_other_parent_entries(
&self,
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 ) -> (HashSet<HgPathBuf>, HashSet<HgPathBuf>) {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 let mut non_normal = HashSet::new();
let mut other_parent = HashSet::new();
for (
filename,
DirstateEntry {
state, size, mtime, ..
},
) in self.state_map.iter()
{
if *state != EntryState::Normal || *mtime == MTIME_UNSET {
non_normal.insert(filename.to_owned());
}
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 if *state == EntryState::Normal && *size == SIZE_FROM_OTHER_PARENT
{
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 other_parent.insert(filename.to_owned());
}
}
(non_normal, other_parent)
}
/// Both of these setters and their uses appear to be the simplest way to
/// emulate a Python lazy property, but it is ugly and unidiomatic.
/// TODO One day, rewriting this struct using the typestate might be a
/// good idea.
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 pub fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 if self.all_dirs.is_none() {
Yuya Nishihara
rust-dirstate: split DirsMultiset constructor per input type...
r43070 self.all_dirs =
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 Some(DirsMultiset::from_dirstate(&self.state_map, None)?);
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 Ok(())
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 pub fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 if self.dirs.is_none() {
Yuya Nishihara
rust-dirstate: split DirsMultiset constructor per input type...
r43070 self.dirs = Some(DirsMultiset::from_dirstate(
&self.state_map,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 Some(EntryState::Removed),
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 )?);
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 Ok(())
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 pub fn has_tracked_dir(
&mut self,
directory: &HgPath,
) -> Result<bool, DirstateMapError> {
self.set_dirs()?;
Ok(self.dirs.as_ref().unwrap().contains(directory))
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 pub fn has_dir(
&mut self,
directory: &HgPath,
) -> Result<bool, DirstateMapError> {
self.set_all_dirs()?;
Ok(self.all_dirs.as_ref().unwrap().contains(directory))
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
pub fn parents(
&mut self,
file_contents: &[u8],
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 ) -> Result<&DirstateParents, DirstateError> {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 if let Some(ref parents) = self.parents {
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 return Ok(parents);
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
let parents;
Yuya Nishihara
rust-dirstate: use PARENT_SIZE constant where appropriate
r43066 if file_contents.len() == PARENT_SIZE * 2 {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 parents = DirstateParents {
Yuya Nishihara
rust: simply use TryInto to convert slice to array...
r43067 p1: file_contents[..PARENT_SIZE].try_into().unwrap(),
p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2]
.try_into()
.unwrap(),
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 };
} else if file_contents.is_empty() {
parents = DirstateParents {
Yuya Nishihara
rust-dirstate: rename NULL_REVISION to NULL_ID which isn't a revision number
r43065 p1: NULL_ID,
p2: NULL_ID,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 };
} else {
return Err(DirstateError::Parse(DirstateParseError::Damaged));
}
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 self.parents = Some(parents);
Ok(self.parents.as_ref().unwrap())
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 pub fn set_parents(&mut self, parents: &DirstateParents) {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 self.parents = Some(parents.clone());
self.dirty_parents = true;
}
pub fn read(
&mut self,
file_contents: &[u8],
) -> Result<Option<DirstateParents>, DirstateError> {
if file_contents.is_empty() {
return Ok(None);
}
let parents = parse_dirstate(
&mut self.state_map,
&mut self.copy_map,
file_contents,
)?;
if !self.dirty_parents {
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 self.set_parents(&parents);
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Ok(Some(parents))
}
pub fn pack(
&mut self,
parents: DirstateParents,
now: Duration,
) -> Result<Vec<u8>, DirstateError> {
let packed =
pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
self.dirty_parents = false;
let result = self.non_normal_other_parent_entries();
self.non_normal_set = result.0;
self.other_parent_set = result.1;
Ok(packed)
}
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 if let Some(ref file_fold_map) = self.file_fold_map {
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 return file_fold_map;
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Raphaël Gomès
rust-performance: introduce FastHashMap type alias for HashMap...
r44278 let mut new_file_fold_map = FileFoldMap::default();
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 for (filename, DirstateEntry { state, .. }) in self.state_map.borrow()
{
if *state == EntryState::Removed {
Raphaël Gomès
rust-utils: add normalize_case util to mirror Python one...
r43108 new_file_fold_map
.insert(normalize_case(filename), filename.to_owned());
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
}
self.file_fold_map = Some(new_file_fold_map);
Yuya Nishihara
rust-dirstate: remove excessive clone() of parameter and return value...
r43069 self.file_fold_map.as_ref().unwrap()
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dirs_multiset() {
let mut map = DirstateMap::new();
assert!(map.dirs.is_none());
assert!(map.all_dirs.is_none());
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 assert_eq!(map.has_dir(HgPath::new(b"nope")).unwrap(), false);
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 assert!(map.all_dirs.is_some());
assert!(map.dirs.is_none());
Raphaël Gomès
rust-dirs: handle forgotten `Result`s...
r44315 assert_eq!(map.has_tracked_dir(HgPath::new(b"nope")).unwrap(), false);
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 assert!(map.dirs.is_some());
}
#[test]
fn test_add_file() {
let mut map = DirstateMap::new();
assert_eq!(0, map.len());
map.add_file(
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 HgPath::new(b"meh"),
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 EntryState::Normal,
DirstateEntry {
state: EntryState::Normal,
mode: 1337,
mtime: 1337,
size: 1337,
},
Raphaël Gomès
rust-warnings: fix warnings in tests...
r44342 )
.unwrap();
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998
assert_eq!(1, map.len());
assert_eq!(0, map.non_normal_set.len());
assert_eq!(0, map.other_parent_set.len());
}
#[test]
fn test_non_normal_other_parent_entries() {
let map: DirstateMap = [
(b"f1", (EntryState::Removed, 1337, 1337, 1337)),
(b"f2", (EntryState::Normal, 1337, 1337, -1)),
(b"f3", (EntryState::Normal, 1337, 1337, 1337)),
(b"f4", (EntryState::Normal, 1337, -2, 1337)),
(b"f5", (EntryState::Added, 1337, 1337, 1337)),
(b"f6", (EntryState::Added, 1337, 1337, -1)),
(b"f7", (EntryState::Merged, 1337, 1337, -1)),
(b"f8", (EntryState::Merged, 1337, 1337, 1337)),
(b"f9", (EntryState::Merged, 1337, -2, 1337)),
(b"fa", (EntryState::Added, 1337, -2, 1337)),
(b"fb", (EntryState::Removed, 1337, -2, 1337)),
]
.iter()
.map(|(fname, (state, mode, size, mtime))| {
(
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 HgPathBuf::from_bytes(fname.as_ref()),
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 DirstateEntry {
state: *state,
mode: *mode,
size: *size,
mtime: *mtime,
},
)
})
.collect();
let non_normal = [
b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
]
.iter()
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 .collect();
let mut other_parent = HashSet::new();
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998
assert_eq!(
(non_normal, other_parent),
map.non_normal_other_parent_entries()
);
}
}