I suppose I ran the formatter on the tip but not on every patch.
Details
Details
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Branch
- default
- Lint
No Linters Available - Unit
No Unit Test Coverage
pulkit |
hg-reviewers |
I suppose I ran the formatter on the tip but not on every patch.
No Linters Available |
No Unit Test Coverage |
Path | Packages | |||
---|---|---|---|---|
M | rust/hg-core/src/dirstate/dirstate_map.rs (78 lines) | |||
M | rust/hg-core/src/dirstate/dirstate_tree/iter.rs (58 lines) | |||
M | rust/hg-core/src/dirstate/dirstate_tree/node.rs (36 lines) | |||
M | rust/hg-core/src/dirstate/dirstate_tree/tree.rs (37 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
64a2f0d36e7c | f9a3edf2dee4 | Raphaël Gomès | Sep 28 2020, 5:16 AM |
// dirstate_map.rs | // dirstate_map.rs | ||||
// | // | ||||
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | // | ||||
// This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
use crate::revlog::node::NULL_NODE_ID; | use crate::revlog::node::NULL_NODE_ID; | ||||
use crate::{ | use crate::{ | ||||
dirstate::{parsers::PARENT_SIZE, EntryState, SIZE_FROM_OTHER_PARENT}, | dirstate::{parsers::PARENT_SIZE, EntryState, SIZE_FROM_OTHER_PARENT}, | ||||
pack_dirstate, parse_dirstate, | pack_dirstate, parse_dirstate, | ||||
utils::{ | utils::{ | ||||
files::normalize_case, | files::normalize_case, | ||||
hg_path::{HgPath, HgPathBuf}, | hg_path::{HgPath, HgPathBuf}, | ||||
}, | }, | ||||
CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError, DirstateParents, | CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError, | ||||
DirstateParseError, FastHashMap, StateMap, | DirstateParents, DirstateParseError, FastHashMap, StateMap, | ||||
}; | }; | ||||
use core::borrow::Borrow; | use core::borrow::Borrow; | ||||
use micro_timer::timed; | use micro_timer::timed; | ||||
use std::collections::HashSet; | use std::collections::HashSet; | ||||
use std::convert::TryInto; | use std::convert::TryInto; | ||||
use std::iter::FromIterator; | use std::iter::FromIterator; | ||||
use std::ops::Deref; | use std::ops::Deref; | ||||
use std::time::Duration; | use std::time::Duration; | ||||
type Target = StateMap; | type Target = StateMap; | ||||
fn deref(&self) -> &Self::Target { | fn deref(&self) -> &Self::Target { | ||||
&self.state_map | &self.state_map | ||||
} | } | ||||
} | } | ||||
impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap { | impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap { | ||||
fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(iter: I) -> Self { | fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>( | ||||
iter: I, | |||||
) -> Self { | |||||
Self { | Self { | ||||
state_map: iter.into_iter().collect(), | state_map: iter.into_iter().collect(), | ||||
..Self::default() | ..Self::default() | ||||
} | } | ||||
} | } | ||||
} | } | ||||
impl DirstateMap { | impl DirstateMap { | ||||
/// Add a tracked file to the dirstate | /// Add a tracked file to the dirstate | ||||
pub fn add_file( | pub fn add_file( | ||||
&mut self, | &mut self, | ||||
filename: &HgPath, | filename: &HgPath, | ||||
old_state: EntryState, | old_state: EntryState, | ||||
entry: DirstateEntry, | entry: DirstateEntry, | ||||
) -> Result<(), DirstateMapError> { | ) -> Result<(), DirstateMapError> { | ||||
if old_state == EntryState::Unknown || old_state == EntryState::Removed { | if old_state == EntryState::Unknown || old_state == EntryState::Removed | ||||
{ | |||||
if let Some(ref mut dirs) = self.dirs { | if let Some(ref mut dirs) = self.dirs { | ||||
dirs.add_path(filename)?; | dirs.add_path(filename)?; | ||||
} | } | ||||
} | } | ||||
if old_state == EntryState::Unknown { | if old_state == EntryState::Unknown { | ||||
if let Some(ref mut all_dirs) = self.all_dirs { | if let Some(ref mut all_dirs) = self.all_dirs { | ||||
all_dirs.add_path(filename)?; | all_dirs.add_path(filename)?; | ||||
} | } | ||||
/// the file's previous state. In the future, we should refactor this | /// the file's previous state. In the future, we should refactor this | ||||
/// to be more explicit about what that state is. | /// to be more explicit about what that state is. | ||||
pub fn remove_file( | pub fn remove_file( | ||||
&mut self, | &mut self, | ||||
filename: &HgPath, | filename: &HgPath, | ||||
old_state: EntryState, | old_state: EntryState, | ||||
size: i32, | size: i32, | ||||
) -> Result<(), DirstateMapError> { | ) -> Result<(), DirstateMapError> { | ||||
if old_state != EntryState::Unknown && old_state != EntryState::Removed { | if old_state != EntryState::Unknown && old_state != EntryState::Removed | ||||
{ | |||||
if let Some(ref mut dirs) = self.dirs { | if let Some(ref mut dirs) = self.dirs { | ||||
dirs.delete_path(filename)?; | dirs.delete_path(filename)?; | ||||
} | } | ||||
} | } | ||||
if old_state == EntryState::Unknown { | if old_state == EntryState::Unknown { | ||||
if let Some(ref mut all_dirs) = self.all_dirs { | if let Some(ref mut all_dirs) = self.all_dirs { | ||||
all_dirs.add_path(filename)?; | all_dirs.add_path(filename)?; | ||||
} | } | ||||
} | } | ||||
self.get_non_normal_other_parent_entries() | self.get_non_normal_other_parent_entries() | ||||
.0 | .0 | ||||
.remove(filename); | .remove(filename); | ||||
Ok(exists) | Ok(exists) | ||||
} | } | ||||
pub fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) { | pub fn clear_ambiguous_times( | ||||
&mut self, | |||||
filenames: Vec<HgPathBuf>, | |||||
now: i32, | |||||
) { | |||||
for filename in filenames { | for filename in filenames { | ||||
let mut changed = false; | let mut changed = false; | ||||
self.state_map | self.state_map | ||||
.entry(filename.to_owned()) | .entry(filename.to_owned()) | ||||
.and_modify(|entry| { | .and_modify(|entry| { | ||||
if entry.state == EntryState::Normal && entry.mtime == now { | if entry.state == EntryState::Normal && entry.mtime == now | ||||
{ | |||||
changed = true; | changed = true; | ||||
*entry = DirstateEntry { | *entry = DirstateEntry { | ||||
mtime: MTIME_UNSET, | mtime: MTIME_UNSET, | ||||
..*entry | ..*entry | ||||
}; | }; | ||||
} | } | ||||
}); | }); | ||||
if changed { | if changed { | ||||
self.get_non_normal_other_parent_entries() | self.get_non_normal_other_parent_entries() | ||||
.0 | .0 | ||||
.insert(filename.to_owned()); | .insert(filename.to_owned()); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
pub fn non_normal_entries_remove(&mut self, key: impl AsRef<HgPath>) -> bool { | pub fn non_normal_entries_remove( | ||||
&mut self, | |||||
key: impl AsRef<HgPath>, | |||||
) -> bool { | |||||
self.get_non_normal_other_parent_entries() | self.get_non_normal_other_parent_entries() | ||||
.0 | .0 | ||||
.remove(key.as_ref()) | .remove(key.as_ref()) | ||||
} | } | ||||
pub fn non_normal_entries_union(&mut self, other: HashSet<HgPathBuf>) -> Vec<HgPathBuf> { | pub fn non_normal_entries_union( | ||||
&mut self, | |||||
other: HashSet<HgPathBuf>, | |||||
) -> Vec<HgPathBuf> { | |||||
self.get_non_normal_other_parent_entries() | self.get_non_normal_other_parent_entries() | ||||
.0 | .0 | ||||
.union(&other) | .union(&other) | ||||
.map(ToOwned::to_owned) | .map(ToOwned::to_owned) | ||||
.collect() | .collect() | ||||
} | } | ||||
pub fn get_non_normal_other_parent_entries( | pub fn get_non_normal_other_parent_entries( | ||||
) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) { | ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) { | ||||
( | ( | ||||
self.non_normal_set.as_ref().unwrap(), | self.non_normal_set.as_ref().unwrap(), | ||||
self.other_parent_set.as_ref().unwrap(), | self.other_parent_set.as_ref().unwrap(), | ||||
) | ) | ||||
} | } | ||||
pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { | pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { | ||||
if !force && self.non_normal_set.is_some() && self.other_parent_set.is_some() { | if !force | ||||
&& self.non_normal_set.is_some() | |||||
&& self.other_parent_set.is_some() | |||||
{ | |||||
return; | return; | ||||
} | } | ||||
let mut non_normal = HashSet::new(); | let mut non_normal = HashSet::new(); | ||||
let mut other_parent = HashSet::new(); | let mut other_parent = HashSet::new(); | ||||
for ( | for ( | ||||
filename, | filename, | ||||
DirstateEntry { | DirstateEntry { | ||||
state, size, mtime, .. | state, size, mtime, .. | ||||
}, | }, | ||||
) in self.state_map.iter() | ) in self.state_map.iter() | ||||
{ | { | ||||
if *state != EntryState::Normal || *mtime == MTIME_UNSET { | if *state != EntryState::Normal || *mtime == MTIME_UNSET { | ||||
non_normal.insert(filename.to_owned()); | non_normal.insert(filename.to_owned()); | ||||
} | } | ||||
if *state == EntryState::Normal && *size == SIZE_FROM_OTHER_PARENT { | if *state == EntryState::Normal && *size == SIZE_FROM_OTHER_PARENT | ||||
{ | |||||
other_parent.insert(filename.to_owned()); | other_parent.insert(filename.to_owned()); | ||||
} | } | ||||
} | } | ||||
self.non_normal_set = Some(non_normal); | self.non_normal_set = Some(non_normal); | ||||
self.other_parent_set = Some(other_parent); | self.other_parent_set = Some(other_parent); | ||||
} | } | ||||
/// Both of these setters and their uses appear to be the simplest way to | /// 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. | /// emulate a Python lazy property, but it is ugly and unidiomatic. | ||||
/// TODO One day, rewriting this struct using the typestate might be a | /// TODO One day, rewriting this struct using the typestate might be a | ||||
/// good idea. | /// good idea. | ||||
pub fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { | pub fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { | ||||
if self.all_dirs.is_none() { | if self.all_dirs.is_none() { | ||||
self.all_dirs = Some(DirsMultiset::from_dirstate(&self.state_map, None)?); | self.all_dirs = | ||||
Some(DirsMultiset::from_dirstate(&self.state_map, None)?); | |||||
} | } | ||||
Ok(()) | Ok(()) | ||||
} | } | ||||
pub fn set_dirs(&mut self) -> Result<(), DirstateMapError> { | pub fn set_dirs(&mut self) -> Result<(), DirstateMapError> { | ||||
if self.dirs.is_none() { | if self.dirs.is_none() { | ||||
self.dirs = Some(DirsMultiset::from_dirstate( | self.dirs = Some(DirsMultiset::from_dirstate( | ||||
&self.state_map, | &self.state_map, | ||||
Some(EntryState::Removed), | Some(EntryState::Removed), | ||||
)?); | )?); | ||||
} | } | ||||
Ok(()) | Ok(()) | ||||
} | } | ||||
pub fn has_tracked_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateMapError> { | pub fn has_tracked_dir( | ||||
&mut self, | |||||
directory: &HgPath, | |||||
) -> Result<bool, DirstateMapError> { | |||||
self.set_dirs()?; | self.set_dirs()?; | ||||
Ok(self.dirs.as_ref().unwrap().contains(directory)) | Ok(self.dirs.as_ref().unwrap().contains(directory)) | ||||
} | } | ||||
pub fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateMapError> { | pub fn has_dir( | ||||
&mut self, | |||||
directory: &HgPath, | |||||
) -> Result<bool, DirstateMapError> { | |||||
self.set_all_dirs()?; | self.set_all_dirs()?; | ||||
Ok(self.all_dirs.as_ref().unwrap().contains(directory)) | Ok(self.all_dirs.as_ref().unwrap().contains(directory)) | ||||
} | } | ||||
pub fn parents(&mut self, file_contents: &[u8]) -> Result<&DirstateParents, DirstateError> { | pub fn parents( | ||||
&mut self, | |||||
file_contents: &[u8], | |||||
) -> Result<&DirstateParents, DirstateError> { | |||||
if let Some(ref parents) = self.parents { | if let Some(ref parents) = self.parents { | ||||
return Ok(parents); | return Ok(parents); | ||||
} | } | ||||
let parents; | let parents; | ||||
if file_contents.len() == PARENT_SIZE * 2 { | if file_contents.len() == PARENT_SIZE * 2 { | ||||
parents = DirstateParents { | parents = DirstateParents { | ||||
p1: file_contents[..PARENT_SIZE].try_into().unwrap(), | p1: file_contents[..PARENT_SIZE].try_into().unwrap(), | ||||
p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2] | p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2] | ||||
} | } | ||||
pub fn set_parents(&mut self, parents: &DirstateParents) { | pub fn set_parents(&mut self, parents: &DirstateParents) { | ||||
self.parents = Some(parents.clone()); | self.parents = Some(parents.clone()); | ||||
self.dirty_parents = true; | self.dirty_parents = true; | ||||
} | } | ||||
#[timed] | #[timed] | ||||
pub fn read(&mut self, file_contents: &[u8]) -> Result<Option<DirstateParents>, DirstateError> { | pub fn read( | ||||
&mut self, | |||||
file_contents: &[u8], | |||||
) -> Result<Option<DirstateParents>, DirstateError> { | |||||
if file_contents.is_empty() { | if file_contents.is_empty() { | ||||
return Ok(None); | return Ok(None); | ||||
} | } | ||||
let (parents, entries, copies) = parse_dirstate(file_contents)?; | let (parents, entries, copies) = parse_dirstate(file_contents)?; | ||||
self.state_map.extend( | self.state_map.extend( | ||||
entries | entries | ||||
.into_iter() | .into_iter() | ||||
Ok(Some(parents)) | Ok(Some(parents)) | ||||
} | } | ||||
pub fn pack( | pub fn pack( | ||||
&mut self, | &mut self, | ||||
parents: DirstateParents, | parents: DirstateParents, | ||||
now: Duration, | now: Duration, | ||||
) -> Result<Vec<u8>, DirstateError> { | ) -> Result<Vec<u8>, DirstateError> { | ||||
let packed = pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?; | let packed = | ||||
pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?; | |||||
self.dirty_parents = false; | self.dirty_parents = false; | ||||
self.set_non_normal_other_parent_entries(true); | self.set_non_normal_other_parent_entries(true); | ||||
Ok(packed) | Ok(packed) | ||||
} | } | ||||
pub fn build_file_fold_map(&mut self) -> &FileFoldMap { | pub fn build_file_fold_map(&mut self) -> &FileFoldMap { | ||||
if let Some(ref file_fold_map) = self.file_fold_map { | if let Some(ref file_fold_map) = self.file_fold_map { | ||||
return file_fold_map; | return file_fold_map; | ||||
} | } | ||||
let mut new_file_fold_map = FileFoldMap::default(); | let mut new_file_fold_map = FileFoldMap::default(); | ||||
for (filename, DirstateEntry { state, .. }) in self.state_map.borrow() { | for (filename, DirstateEntry { state, .. }) in self.state_map.borrow() | ||||
{ | |||||
if *state == EntryState::Removed { | if *state == EntryState::Removed { | ||||
new_file_fold_map.insert(normalize_case(filename), filename.to_owned()); | new_file_fold_map | ||||
.insert(normalize_case(filename), filename.to_owned()); | |||||
} | } | ||||
} | } | ||||
self.file_fold_map = Some(new_file_fold_map); | self.file_fold_map = Some(new_file_fold_map); | ||||
self.file_fold_map.as_ref().unwrap() | self.file_fold_map.as_ref().unwrap() | ||||
} | } | ||||
} | } | ||||
#[cfg(test)] | #[cfg(test)] | ||||
.iter() | .iter() | ||||
.map(|x| HgPathBuf::from_bytes(x.as_ref())) | .map(|x| HgPathBuf::from_bytes(x.as_ref())) | ||||
.collect(); | .collect(); | ||||
let mut other_parent = HashSet::new(); | let mut other_parent = HashSet::new(); | ||||
other_parent.insert(HgPathBuf::from_bytes(b"f4")); | other_parent.insert(HgPathBuf::from_bytes(b"f4")); | ||||
let entries = map.get_non_normal_other_parent_entries(); | let entries = map.get_non_normal_other_parent_entries(); | ||||
assert_eq!((&mut non_normal, &mut other_parent), (entries.0, entries.1)); | assert_eq!( | ||||
(&mut non_normal, &mut other_parent), | |||||
(entries.0, entries.1) | |||||
); | |||||
} | } | ||||
} | } |
use crate::utils::hg_path::{hg_path_to_path_buf, HgPath, HgPathBuf}; | use crate::utils::hg_path::{hg_path_to_path_buf, HgPath, HgPathBuf}; | ||||
use crate::DirstateEntry; | use crate::DirstateEntry; | ||||
use std::borrow::Cow; | use std::borrow::Cow; | ||||
use std::collections::VecDeque; | use std::collections::VecDeque; | ||||
use std::iter::{FromIterator, FusedIterator}; | use std::iter::{FromIterator, FusedIterator}; | ||||
use std::path::PathBuf; | use std::path::PathBuf; | ||||
impl FromIterator<(HgPathBuf, DirstateEntry)> for Tree { | impl FromIterator<(HgPathBuf, DirstateEntry)> for Tree { | ||||
fn from_iter<T: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(iter: T) -> Self { | fn from_iter<T: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>( | ||||
iter: T, | |||||
) -> Self { | |||||
let mut tree = Self::new(); | let mut tree = Self::new(); | ||||
for (path, entry) in iter { | for (path, entry) in iter { | ||||
tree.insert(path, entry); | tree.insert(path, entry); | ||||
} | } | ||||
tree | tree | ||||
} | } | ||||
} | } | ||||
impl<'a> Iterator for Iter<'a> { | impl<'a> Iterator for Iter<'a> { | ||||
type Item = (HgPathBuf, DirstateEntry); | type Item = (HgPathBuf, DirstateEntry); | ||||
fn next(&mut self) -> Option<Self::Item> { | fn next(&mut self) -> Option<Self::Item> { | ||||
while let Some((base_path, node)) = self.to_visit.pop_front() { | while let Some((base_path, node)) = self.to_visit.pop_front() { | ||||
match &node.kind { | match &node.kind { | ||||
NodeKind::Directory(dir) => { | NodeKind::Directory(dir) => { | ||||
add_children_to_visit(&mut self.to_visit, &base_path, &dir); | add_children_to_visit( | ||||
&mut self.to_visit, | |||||
&base_path, | |||||
&dir, | |||||
); | |||||
if let Some(file) = &dir.was_file { | if let Some(file) = &dir.was_file { | ||||
return Some((HgPathBuf::from_bytes(&base_path), file.entry)); | return Some(( | ||||
HgPathBuf::from_bytes(&base_path), | |||||
file.entry, | |||||
)); | |||||
} | } | ||||
} | } | ||||
NodeKind::File(file) => { | NodeKind::File(file) => { | ||||
if let Some(dir) = &file.was_directory { | if let Some(dir) = &file.was_directory { | ||||
add_children_to_visit(&mut self.to_visit, &base_path, &dir); | add_children_to_visit( | ||||
&mut self.to_visit, | |||||
&base_path, | |||||
&dir, | |||||
); | |||||
} | } | ||||
return Some((HgPathBuf::from_bytes(&base_path), file.entry)); | return Some(( | ||||
HgPathBuf::from_bytes(&base_path), | |||||
file.entry, | |||||
)); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
None | None | ||||
} | } | ||||
} | } | ||||
impl<'a> FusedIterator for Iter<'a> {} | impl<'a> FusedIterator for Iter<'a> {} | ||||
/// $ mkdir foo | /// $ mkdir foo | ||||
/// $ touch foo/a | /// $ touch foo/a | ||||
/// $ # commit... | /// $ # commit... | ||||
/// $ mv foo bar | /// $ mv foo bar | ||||
/// $ ln -s bar foo | /// $ ln -s bar foo | ||||
/// ``` | /// ``` | ||||
/// We need to dispatch the new symlink as `Unknown` and all the | /// We need to dispatch the new symlink as `Unknown` and all the | ||||
/// descendents of the directory it replace as `Deleted`. | /// descendents of the directory it replace as `Deleted`. | ||||
fn dispatch_symlinked_directory(&mut self, path: impl AsRef<HgPath>, node: &Node) { | fn dispatch_symlinked_directory( | ||||
&mut self, | |||||
path: impl AsRef<HgPath>, | |||||
node: &Node, | |||||
) { | |||||
let path = path.as_ref(); | let path = path.as_ref(); | ||||
self.shortcuts | self.shortcuts.push_back(( | ||||
.push_back((path.to_owned(), StatusShortcut::Dispatch(Dispatch::Unknown))); | path.to_owned(), | ||||
StatusShortcut::Dispatch(Dispatch::Unknown), | |||||
)); | |||||
for (file, _) in node.iter() { | for (file, _) in node.iter() { | ||||
self.shortcuts.push_back(( | self.shortcuts.push_back(( | ||||
path.join(&file), | path.join(&file), | ||||
StatusShortcut::Dispatch(Dispatch::Deleted), | StatusShortcut::Dispatch(Dispatch::Deleted), | ||||
)); | )); | ||||
} | } | ||||
} | } | ||||
while let Some((base_path, node)) = self.to_visit.pop_front() { | while let Some((base_path, node)) = self.to_visit.pop_front() { | ||||
match &node.kind { | match &node.kind { | ||||
NodeKind::Directory(dir) => { | NodeKind::Directory(dir) => { | ||||
let canonical_path = HgPath::new(&base_path); | let canonical_path = HgPath::new(&base_path); | ||||
if self.directory_became_symlink(canonical_path) { | if self.directory_became_symlink(canonical_path) { | ||||
// Potential security issue, don't do a normal | // Potential security issue, don't do a normal | ||||
// traversal, force the results. | // traversal, force the results. | ||||
self.dispatch_symlinked_directory(canonical_path, &node); | self.dispatch_symlinked_directory( | ||||
canonical_path, | |||||
&node, | |||||
); | |||||
continue; | continue; | ||||
} | } | ||||
add_children_to_visit(&mut self.to_visit, &base_path, &dir); | add_children_to_visit( | ||||
&mut self.to_visit, | |||||
&base_path, | |||||
&dir, | |||||
); | |||||
if let Some(file) = &dir.was_file { | if let Some(file) = &dir.was_file { | ||||
return Some(( | return Some(( | ||||
HgPathBuf::from_bytes(&base_path), | HgPathBuf::from_bytes(&base_path), | ||||
StatusShortcut::Entry(file.entry), | StatusShortcut::Entry(file.entry), | ||||
)); | )); | ||||
} | } | ||||
} | } | ||||
NodeKind::File(file) => { | NodeKind::File(file) => { | ||||
if let Some(dir) = &file.was_directory { | if let Some(dir) = &file.was_directory { | ||||
add_children_to_visit(&mut self.to_visit, &base_path, &dir); | add_children_to_visit( | ||||
&mut self.to_visit, | |||||
&base_path, | |||||
&dir, | |||||
); | |||||
} | } | ||||
return Some(( | return Some(( | ||||
HgPathBuf::from_bytes(&base_path), | HgPathBuf::from_bytes(&base_path), | ||||
StatusShortcut::Entry(file.entry), | StatusShortcut::Entry(file.entry), | ||||
)); | )); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
size: 0, | size: 0, | ||||
} | } | ||||
), | ), | ||||
None | None | ||||
); | ); | ||||
assert_eq!(tree.len(), 4); | assert_eq!(tree.len(), 4); | ||||
let results: HashSet<_> = tree.iter().map(|(c, _)| c.to_owned()).collect(); | let results: HashSet<_> = | ||||
tree.iter().map(|(c, _)| c.to_owned()).collect(); | |||||
dbg!(&results); | dbg!(&results); | ||||
assert!(results.contains(HgPath::new(b"foo2"))); | assert!(results.contains(HgPath::new(b"foo2"))); | ||||
assert!(results.contains(HgPath::new(b"foo/bar"))); | assert!(results.contains(HgPath::new(b"foo/bar"))); | ||||
assert!(results.contains(HgPath::new(b"foo/baz"))); | assert!(results.contains(HgPath::new(b"foo/baz"))); | ||||
assert!(results.contains(HgPath::new(b"foo/bap/nested"))); | assert!(results.contains(HgPath::new(b"foo/bap/nested"))); | ||||
let mut iter = tree.iter(); | let mut iter = tree.iter(); | ||||
assert!(iter.next().is_some()); | assert!(iter.next().is_some()); |
impl Default for NodeKind { | impl Default for NodeKind { | ||||
fn default() -> Self { | fn default() -> Self { | ||||
NodeKind::Directory(Default::default()) | NodeKind::Directory(Default::default()) | ||||
} | } | ||||
} | } | ||||
impl Node { | impl Node { | ||||
pub fn insert(&mut self, path: &[u8], new_entry: DirstateEntry) -> InsertResult { | pub fn insert( | ||||
&mut self, | |||||
path: &[u8], | |||||
new_entry: DirstateEntry, | |||||
) -> InsertResult { | |||||
let mut split = path.splitn(2, |&c| c == b'/'); | let mut split = path.splitn(2, |&c| c == b'/'); | ||||
let head = split.next().unwrap_or(b""); | let head = split.next().unwrap_or(b""); | ||||
let tail = split.next().unwrap_or(b""); | let tail = split.next().unwrap_or(b""); | ||||
if let NodeKind::File(file) = &mut self.kind { | if let NodeKind::File(file) = &mut self.kind { | ||||
if tail.is_empty() && head.is_empty() { | if tail.is_empty() && head.is_empty() { | ||||
// We're modifying the current file | // We're modifying the current file | ||||
let new = Self { | let new = Self { | ||||
// Only replace the current file with a directory if it's | // Only replace the current file with a directory if it's | ||||
// marked as `Removed` | // marked as `Removed` | ||||
EntryState::Removed => { | EntryState::Removed => { | ||||
self.kind = NodeKind::Directory(Directory { | self.kind = NodeKind::Directory(Directory { | ||||
was_file: Some(Box::from(file.clone())), | was_file: Some(Box::from(file.clone())), | ||||
children: Default::default(), | children: Default::default(), | ||||
}) | }) | ||||
} | } | ||||
_ => return Node::insert_in_file(file, new_entry, head, tail), | _ => { | ||||
return Node::insert_in_file( | |||||
file, new_entry, head, tail, | |||||
) | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
match &mut self.kind { | match &mut self.kind { | ||||
NodeKind::Directory(directory) => { | NodeKind::Directory(directory) => { | ||||
return Node::insert_in_directory(directory, new_entry, head, tail); | return Node::insert_in_directory( | ||||
directory, new_entry, head, tail, | |||||
); | |||||
} | |||||
NodeKind::File(_) => { | |||||
unreachable!("The file case has already been handled") | |||||
} | } | ||||
NodeKind::File(_) => unreachable!("The file case has already been handled"), | |||||
} | } | ||||
} | } | ||||
/// The current file still exists and is not marked as `Removed`. | /// The current file still exists and is not marked as `Removed`. | ||||
/// Insert the entry in its `was_directory`. | /// Insert the entry in its `was_directory`. | ||||
fn insert_in_file( | fn insert_in_file( | ||||
file: &mut File, | file: &mut File, | ||||
new_entry: DirstateEntry, | new_entry: DirstateEntry, | ||||
head: &[u8], | head: &[u8], | ||||
tail: &[u8], | tail: &[u8], | ||||
) -> InsertResult { | ) -> InsertResult { | ||||
if let Some(d) = &mut file.was_directory { | if let Some(d) = &mut file.was_directory { | ||||
Node::insert_in_directory(d, new_entry, head, tail) | Node::insert_in_directory(d, new_entry, head, tail) | ||||
} else { | } else { | ||||
let mut dir = Directory { | let mut dir = Directory { | ||||
was_file: None, | was_file: None, | ||||
children: FastHashMap::default(), | children: FastHashMap::default(), | ||||
}; | }; | ||||
let res = Node::insert_in_directory(&mut dir, new_entry, head, tail); | let res = | ||||
Node::insert_in_directory(&mut dir, new_entry, head, tail); | |||||
file.was_directory = Some(Box::new(dir)); | file.was_directory = Some(Box::new(dir)); | ||||
res | res | ||||
} | } | ||||
} | } | ||||
/// Insert an entry in the subtree of `directory` | /// Insert an entry in the subtree of `directory` | ||||
fn insert_in_directory( | fn insert_in_directory( | ||||
directory: &mut Directory, | directory: &mut Directory, | ||||
NodeKind::Directory(subdir) => { | NodeKind::Directory(subdir) => { | ||||
if tail.is_empty() { | if tail.is_empty() { | ||||
let becomes_file = Self { | let becomes_file = Self { | ||||
kind: NodeKind::File(File { | kind: NodeKind::File(File { | ||||
was_directory: Some(Box::from(subdir.clone())), | was_directory: Some(Box::from(subdir.clone())), | ||||
entry: new_entry, | entry: new_entry, | ||||
}), | }), | ||||
}; | }; | ||||
let old_entry = directory.children.insert(head.to_owned(), becomes_file); | let old_entry = directory | ||||
.children | |||||
.insert(head.to_owned(), becomes_file); | |||||
return InsertResult { | return InsertResult { | ||||
did_insert: true, | did_insert: true, | ||||
old_entry, | old_entry, | ||||
}; | }; | ||||
} else { | } else { | ||||
res = node.insert(tail, new_entry); | res = node.insert(tail, new_entry); | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
if head == path { | if head == path { | ||||
match &mut self.kind { | match &mut self.kind { | ||||
NodeKind::Directory(d) => { | NodeKind::Directory(d) => { | ||||
return Node::remove_from_directory(head, d); | return Node::remove_from_directory(head, d); | ||||
} | } | ||||
NodeKind::File(f) => { | NodeKind::File(f) => { | ||||
if let Some(d) = &mut f.was_directory { | if let Some(d) = &mut f.was_directory { | ||||
let RemoveResult { old_entry, .. } = Node::remove_from_directory(head, d); | let RemoveResult { old_entry, .. } = | ||||
Node::remove_from_directory(head, d); | |||||
return RemoveResult { | return RemoveResult { | ||||
cleanup: false, | cleanup: false, | ||||
old_entry, | old_entry, | ||||
}; | }; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
empty_result | empty_result | ||||
} else { | } else { | ||||
// Look into the dirs | // Look into the dirs | ||||
match &mut self.kind { | match &mut self.kind { | ||||
NodeKind::Directory(d) => { | NodeKind::Directory(d) => { | ||||
if let Some(child) = d.children.get_mut(head) { | if let Some(child) = d.children.get_mut(head) { | ||||
let mut res = child.remove(tail); | let mut res = child.remove(tail); | ||||
if res.cleanup { | if res.cleanup { | ||||
d.children.remove(head); | d.children.remove(head); | ||||
} | } | ||||
res.cleanup = d.children.len() == 0 && d.was_file.is_none(); | res.cleanup = | ||||
d.children.len() == 0 && d.was_file.is_none(); | |||||
res | res | ||||
} else { | } else { | ||||
empty_result | empty_result | ||||
} | } | ||||
} | } | ||||
NodeKind::File(f) => { | NodeKind::File(f) => { | ||||
if let Some(d) = &mut f.was_directory { | if let Some(d) = &mut f.was_directory { | ||||
if let Some(child) = d.children.get_mut(head) { | if let Some(child) = d.children.get_mut(head) { | ||||
let RemoveResult { cleanup, old_entry } = child.remove(tail); | let RemoveResult { cleanup, old_entry } = | ||||
child.remove(tail); | |||||
if cleanup { | if cleanup { | ||||
d.children.remove(head); | d.children.remove(head); | ||||
} | } | ||||
if d.children.len() == 0 && d.was_file.is_none() { | if d.children.len() == 0 && d.was_file.is_none() { | ||||
f.was_directory = None; | f.was_directory = None; | ||||
} | } | ||||
return RemoveResult { | return RemoveResult { |
match old?.kind { | match old?.kind { | ||||
NodeKind::Directory(_) => None, | NodeKind::Directory(_) => None, | ||||
NodeKind::File(f) => Some(f.entry), | NodeKind::File(f) => Some(f.entry), | ||||
} | } | ||||
} | } | ||||
/// Low-level insertion method that returns the previous node (directories | /// Low-level insertion method that returns the previous node (directories | ||||
/// included). | /// included). | ||||
fn insert_node(&mut self, path: impl AsRef<HgPath>, kind: DirstateEntry) -> Option<Node> { | fn insert_node( | ||||
&mut self, | |||||
path: impl AsRef<HgPath>, | |||||
kind: DirstateEntry, | |||||
) -> Option<Node> { | |||||
let InsertResult { | let InsertResult { | ||||
did_insert, | did_insert, | ||||
old_entry, | old_entry, | ||||
} = self.root.insert(path.as_ref().as_bytes(), kind); | } = self.root.insert(path.as_ref().as_bytes(), kind); | ||||
self.files_count += if did_insert { 1 } else { 0 }; | self.files_count += if did_insert { 1 } else { 0 }; | ||||
old_entry | old_entry | ||||
} | } | ||||
/// Returns a reference to a node if it exists. | /// Returns a reference to a node if it exists. | ||||
pub fn get_node(&self, path: impl AsRef<HgPath>) -> Option<&Node> { | pub fn get_node(&self, path: impl AsRef<HgPath>) -> Option<&Node> { | ||||
self.root.get(path.as_ref().as_bytes()) | self.root.get(path.as_ref().as_bytes()) | ||||
} | } | ||||
/// Returns a reference to the entry corresponding to `path` if it exists. | /// Returns a reference to the entry corresponding to `path` if it exists. | ||||
pub fn get(&self, path: impl AsRef<HgPath>) -> Option<&DirstateEntry> { | pub fn get(&self, path: impl AsRef<HgPath>) -> Option<&DirstateEntry> { | ||||
if let Some(node) = self.get_node(&path) { | if let Some(node) = self.get_node(&path) { | ||||
return match &node.kind { | return match &node.kind { | ||||
NodeKind::Directory(d) => d.was_file.as_ref().map(|f| &f.entry), | NodeKind::Directory(d) => { | ||||
d.was_file.as_ref().map(|f| &f.entry) | |||||
} | |||||
NodeKind::File(f) => Some(&f.entry), | NodeKind::File(f) => Some(&f.entry), | ||||
}; | }; | ||||
} | } | ||||
None | None | ||||
} | } | ||||
/// Returns `true` if an entry is found for the given `path`. | /// Returns `true` if an entry is found for the given `path`. | ||||
pub fn contains_key(&self, path: impl AsRef<HgPath>) -> bool { | pub fn contains_key(&self, path: impl AsRef<HgPath>) -> bool { | ||||
self.get(path).is_some() | self.get(path).is_some() | ||||
} | } | ||||
/// Returns a mutable reference to the entry corresponding to `path` if it | /// Returns a mutable reference to the entry corresponding to `path` if it | ||||
/// exists. | /// exists. | ||||
pub fn get_mut(&mut self, path: impl AsRef<HgPath>) -> Option<&mut DirstateEntry> { | pub fn get_mut( | ||||
&mut self, | |||||
path: impl AsRef<HgPath>, | |||||
) -> Option<&mut DirstateEntry> { | |||||
if let Some(kind) = self.root.get_mut(path.as_ref().as_bytes()) { | if let Some(kind) = self.root.get_mut(path.as_ref().as_bytes()) { | ||||
return match kind { | return match kind { | ||||
NodeKind::Directory(d) => d.was_file.as_mut().map(|f| &mut f.entry), | NodeKind::Directory(d) => { | ||||
d.was_file.as_mut().map(|f| &mut f.entry) | |||||
} | |||||
NodeKind::File(f) => Some(&mut f.entry), | NodeKind::File(f) => Some(&mut f.entry), | ||||
}; | }; | ||||
} | } | ||||
None | None | ||||
} | } | ||||
/// Returns an iterator over the paths and corresponding entries in the | /// Returns an iterator over the paths and corresponding entries in the | ||||
/// tree. | /// tree. | ||||
pub fn iter(&self) -> Iter { | pub fn iter(&self) -> Iter { | ||||
Iter::new(&self.root) | Iter::new(&self.root) | ||||
} | } | ||||
/// Returns an iterator of all entries in the tree, with a special | /// Returns an iterator of all entries in the tree, with a special | ||||
/// filesystem handling for the directories containing said entries. See | /// filesystem handling for the directories containing said entries. See | ||||
/// the documentation of `FsIter` for more. | /// the documentation of `FsIter` for more. | ||||
pub fn fs_iter(&self, root_dir: PathBuf) -> FsIter { | pub fn fs_iter(&self, root_dir: PathBuf) -> FsIter { | ||||
FsIter::new(&self.root, root_dir) | FsIter::new(&self.root, root_dir) | ||||
} | } | ||||
/// Remove the entry at `path` and returns it, if it exists. | /// Remove the entry at `path` and returns it, if it exists. | ||||
pub fn remove(&mut self, path: impl AsRef<HgPath>) -> Option<DirstateEntry> { | pub fn remove( | ||||
let RemoveResult { old_entry, .. } = self.root.remove(path.as_ref().as_bytes()); | &mut self, | ||||
path: impl AsRef<HgPath>, | |||||
) -> Option<DirstateEntry> { | |||||
let RemoveResult { old_entry, .. } = | |||||
self.root.remove(path.as_ref().as_bytes()); | |||||
self.files_count = self | self.files_count = self | ||||
.files_count | .files_count | ||||
.checked_sub(if old_entry.is_some() { 1 } else { 0 }) | .checked_sub(if old_entry.is_some() { 1 } else { 0 }) | ||||
.expect("removed too many files"); | .expect("removed too many files"); | ||||
old_entry | old_entry | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
let removed_entry = DirstateEntry { | let removed_entry = DirstateEntry { | ||||
state: EntryState::Removed, | state: EntryState::Removed, | ||||
mode: 10, | mode: 10, | ||||
mtime: 20, | mtime: 20, | ||||
size: 30, | size: 30, | ||||
}; | }; | ||||
assert_eq!(tree.insert_node(HgPath::new(b"foo"), entry), None); | assert_eq!(tree.insert_node(HgPath::new(b"foo"), entry), None); | ||||
assert_eq!(tree.insert_node(HgPath::new(b"foo/a"), removed_entry), None); | assert_eq!( | ||||
tree.insert_node(HgPath::new(b"foo/a"), removed_entry), | |||||
None | |||||
); | |||||
// The insert should not turn `foo` into a directory as `foo` is not | // The insert should not turn `foo` into a directory as `foo` is not | ||||
// `Removed`. | // `Removed`. | ||||
match tree.get_node(HgPath::new(b"foo")).unwrap().kind { | match tree.get_node(HgPath::new(b"foo")).unwrap().kind { | ||||
NodeKind::Directory(_) => panic!("should be a file"), | NodeKind::Directory(_) => panic!("should be a file"), | ||||
NodeKind::File(_) => {} | NodeKind::File(_) => {} | ||||
} | } | ||||
let mut tree = Tree::new(); | let mut tree = Tree::new(); | ||||
// in its descendents | // in its descendents | ||||
assert_eq!(tree.root.children().unwrap().len(), 0); | assert_eq!(tree.root.children().unwrap().len(), 0); | ||||
let removed_entry = DirstateEntry { | let removed_entry = DirstateEntry { | ||||
state: EntryState::Removed, | state: EntryState::Removed, | ||||
..entry | ..entry | ||||
}; | }; | ||||
assert_eq!(tree.insert(HgPath::new(b"a"), entry), None); | assert_eq!(tree.insert(HgPath::new(b"a"), entry), None); | ||||
assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), removed_entry), None); | assert_eq!( | ||||
tree.insert_node(HgPath::new(b"a/b/x"), removed_entry), | |||||
None | |||||
); | |||||
assert_eq!(tree.files_count, 2); | assert_eq!(tree.files_count, 2); | ||||
dbg!(&tree); | dbg!(&tree); | ||||
assert_eq!(tree.remove(HgPath::new(b"a")), Some(entry)); | assert_eq!(tree.remove(HgPath::new(b"a")), Some(entry)); | ||||
assert_eq!(tree.files_count, 1); | assert_eq!(tree.files_count, 1); | ||||
dbg!(&tree); | dbg!(&tree); | ||||
assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(removed_entry)); | assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(removed_entry)); | ||||
assert_eq!(tree.files_count, 0); | assert_eq!(tree.files_count, 0); | ||||