##// END OF EJS Templates
hghave: disallow symlinks on Windows...
hghave: disallow symlinks on Windows Symlinks on Windows require either a special priviledge, or enabling Developer Mode. It's probably the latter that is enabled on the new CI machine. But since Mercurial itself is saying no to symlinks on Windows, the tests for symlinks shouldn't be attempted. This should fix a lot of the noise in the py3 tests. Differential Revision: https://phab.mercurial-scm.org/D7233

File last commit:

r43227:7a01778b default
r43760:6792da44 default
Show More
dirstate_map.rs
424 lines | 12.3 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.
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 use crate::utils::hg_path::{HgPath, HgPathBuf};
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 use crate::{
dirstate::{parsers::PARENT_SIZE, EntryState},
Raphaël Gomès
rust-utils: add normalize_case util to mirror Python one...
r43108 pack_dirstate, parse_dirstate,
utils::files::normalize_case,
CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError,
DirstateParents, DirstateParseError, StateMap,
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 };
use core::borrow::Borrow;
use std::collections::{HashMap, 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-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 pub type FileFoldMap = HashMap<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;
const SIZE_DIRTY: i32 = -2;
#[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,
) {
if old_state == EntryState::Unknown || old_state == EntryState::Removed
{
if let Some(ref mut dirs) = self.dirs {
dirs.add_path(filename)
}
}
if old_state == EntryState::Unknown {
if let Some(ref mut all_dirs) = self.all_dirs {
all_dirs.add_path(filename)
}
}
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());
}
if entry.size == SIZE_DIRTY {
self.other_parent_set.insert(filename.to_owned());
}
}
/// 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 {
all_dirs.add_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 }
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());
}
if *state == EntryState::Normal && *size == SIZE_DIRTY {
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.
pub fn set_all_dirs(&mut self) {
if self.all_dirs.is_none() {
Yuya Nishihara
rust-dirstate: split DirsMultiset constructor per input type...
r43070 self.all_dirs =
Some(DirsMultiset::from_dirstate(&self.state_map, None));
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
}
pub fn set_dirs(&mut self) {
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-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 pub fn has_tracked_dir(&mut self, directory: &HgPath) -> bool {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 self.set_dirs();
Yuya Nishihara
rust-dirstate: remove too abstracted way of getting &[u8]
r43063 self.dirs.as_ref().unwrap().contains(directory)
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 }
Raphaël Gomès
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 pub fn has_dir(&mut self, directory: &HgPath) -> bool {
Raphaël Gomès
rust-dirstate: rust implementation of dirstatemap...
r42998 self.set_all_dirs();
Yuya Nishihara
rust-dirstate: remove too abstracted way of getting &[u8]
r43063 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 }
let mut new_file_fold_map = FileFoldMap::new();
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-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 assert_eq!(false, map.has_dir(HgPath::new(b"nope")));
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-hgpath: replace all paths and filenames with HgPath/HgPathBuf...
r43227 assert_eq!(false, map.has_tracked_dir(HgPath::new(b"nope")));
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,
},
);
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()
);
}
}