node.rs
398 lines
| 13.1 KiB
| application/rls-services+xml
|
RustLexer
Raphaël Gomès
|
r46136 | // node.rs | ||
// | ||||
// Copyright 2020, 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 super::iter::Iter; | ||||
use crate::utils::hg_path::HgPathBuf; | ||||
use crate::{DirstateEntry, EntryState, FastHashMap}; | ||||
/// Represents a filesystem directory in the dirstate tree | ||||
#[derive(Debug, Default, Clone, PartialEq)] | ||||
pub struct Directory { | ||||
/// Contains the old file information if it existed between changesets. | ||||
/// Happens if a file `foo` is marked as removed, removed from the | ||||
/// filesystem then a directory `foo` is created and at least one of its | ||||
/// descendents is added to Mercurial. | ||||
pub(super) was_file: Option<Box<File>>, | ||||
pub(super) children: FastHashMap<Vec<u8>, Node>, | ||||
} | ||||
/// Represents a filesystem file (or symlink) in the dirstate tree | ||||
#[derive(Debug, Clone, PartialEq)] | ||||
pub struct File { | ||||
/// Contains the old structure if it existed between changesets. | ||||
/// Happens all descendents of `foo` marked as removed and removed from | ||||
/// the filesystem, then a file `foo` is created and added to Mercurial. | ||||
pub(super) was_directory: Option<Box<Directory>>, | ||||
pub(super) entry: DirstateEntry, | ||||
} | ||||
#[derive(Debug, Clone, PartialEq)] | ||||
pub enum NodeKind { | ||||
Directory(Directory), | ||||
File(File), | ||||
} | ||||
#[derive(Debug, Default, Clone, PartialEq)] | ||||
pub struct Node { | ||||
pub kind: NodeKind, | ||||
} | ||||
impl Default for NodeKind { | ||||
fn default() -> Self { | ||||
NodeKind::Directory(Default::default()) | ||||
} | ||||
} | ||||
impl Node { | ||||
Raphaël Gomès
|
r46162 | pub fn insert( | ||
&mut self, | ||||
path: &[u8], | ||||
new_entry: DirstateEntry, | ||||
) -> InsertResult { | ||||
Raphaël Gomès
|
r46136 | let mut split = path.splitn(2, |&c| c == b'/'); | ||
let head = split.next().unwrap_or(b""); | ||||
let tail = split.next().unwrap_or(b""); | ||||
r46302 | // Are we're modifying the current file ? Is the the end of the path ? | |||
let is_current_file = tail.is_empty() && head.is_empty(); | ||||
r46365 | // Potentially Replace the current file with a directory if it's marked | |||
// as `Removed` | ||||
if !is_current_file { | ||||
if let NodeKind::File(file) = &mut self.kind { | ||||
if file.entry.state == EntryState::Removed { | ||||
self.kind = NodeKind::Directory(Directory { | ||||
was_file: Some(Box::from(file.clone())), | ||||
children: Default::default(), | ||||
}) | ||||
Raphaël Gomès
|
r46136 | } | ||
} | ||||
} | ||||
match &mut self.kind { | ||||
NodeKind::Directory(directory) => { | ||||
Raphaël Gomès
|
r46183 | Node::insert_in_directory(directory, new_entry, head, tail) | ||
Raphaël Gomès
|
r46136 | } | ||
r46365 | NodeKind::File(file) => { | |||
if is_current_file { | ||||
let new = Self { | ||||
kind: NodeKind::File(File { | ||||
entry: new_entry, | ||||
..file.clone() | ||||
}), | ||||
}; | ||||
InsertResult { | ||||
did_insert: false, | ||||
old_entry: Some(std::mem::replace(self, new)), | ||||
} | ||||
} else { | ||||
match file.entry.state { | ||||
EntryState::Removed => { | ||||
unreachable!("Removed file turning into a directory was dealt with earlier") | ||||
} | ||||
_ => { | ||||
Node::insert_in_file( | ||||
file, new_entry, head, tail, | ||||
) | ||||
} | ||||
} | ||||
} | ||||
Raphaël Gomès
|
r46162 | } | ||
Raphaël Gomès
|
r46136 | } | ||
} | ||||
/// The current file still exists and is not marked as `Removed`. | ||||
/// Insert the entry in its `was_directory`. | ||||
fn insert_in_file( | ||||
file: &mut File, | ||||
new_entry: DirstateEntry, | ||||
head: &[u8], | ||||
tail: &[u8], | ||||
) -> InsertResult { | ||||
if let Some(d) = &mut file.was_directory { | ||||
Node::insert_in_directory(d, new_entry, head, tail) | ||||
} else { | ||||
let mut dir = Directory { | ||||
was_file: None, | ||||
children: FastHashMap::default(), | ||||
}; | ||||
Raphaël Gomès
|
r46162 | let res = | ||
Node::insert_in_directory(&mut dir, new_entry, head, tail); | ||||
Raphaël Gomès
|
r46136 | file.was_directory = Some(Box::new(dir)); | ||
res | ||||
} | ||||
} | ||||
/// Insert an entry in the subtree of `directory` | ||||
fn insert_in_directory( | ||||
directory: &mut Directory, | ||||
new_entry: DirstateEntry, | ||||
head: &[u8], | ||||
tail: &[u8], | ||||
) -> InsertResult { | ||||
let mut res = InsertResult::default(); | ||||
if let Some(node) = directory.children.get_mut(head) { | ||||
// Node exists | ||||
match &mut node.kind { | ||||
NodeKind::Directory(subdir) => { | ||||
if tail.is_empty() { | ||||
let becomes_file = Self { | ||||
kind: NodeKind::File(File { | ||||
was_directory: Some(Box::from(subdir.clone())), | ||||
entry: new_entry, | ||||
}), | ||||
}; | ||||
Raphaël Gomès
|
r46162 | let old_entry = directory | ||
.children | ||||
.insert(head.to_owned(), becomes_file); | ||||
Raphaël Gomès
|
r46136 | return InsertResult { | ||
did_insert: true, | ||||
old_entry, | ||||
}; | ||||
} else { | ||||
res = node.insert(tail, new_entry); | ||||
} | ||||
} | ||||
NodeKind::File(_) => { | ||||
res = node.insert(tail, new_entry); | ||||
} | ||||
} | ||||
} else if tail.is_empty() { | ||||
// File does not already exist | ||||
directory.children.insert( | ||||
head.to_owned(), | ||||
Self { | ||||
kind: NodeKind::File(File { | ||||
was_directory: None, | ||||
entry: new_entry, | ||||
}), | ||||
}, | ||||
); | ||||
res.did_insert = true; | ||||
} else { | ||||
// Directory does not already exist | ||||
let mut nested = Self { | ||||
kind: NodeKind::Directory(Directory { | ||||
was_file: None, | ||||
children: Default::default(), | ||||
}), | ||||
}; | ||||
res = nested.insert(tail, new_entry); | ||||
directory.children.insert(head.to_owned(), nested); | ||||
} | ||||
res | ||||
} | ||||
/// Removes an entry from the tree, returns a `RemoveResult`. | ||||
pub fn remove(&mut self, path: &[u8]) -> RemoveResult { | ||||
let empty_result = RemoveResult::default(); | ||||
if path.is_empty() { | ||||
return empty_result; | ||||
} | ||||
let mut split = path.splitn(2, |&c| c == b'/'); | ||||
let head = split.next(); | ||||
let tail = split.next().unwrap_or(b""); | ||||
let head = match head { | ||||
None => { | ||||
return empty_result; | ||||
} | ||||
Some(h) => h, | ||||
}; | ||||
if head == path { | ||||
match &mut self.kind { | ||||
NodeKind::Directory(d) => { | ||||
return Node::remove_from_directory(head, d); | ||||
} | ||||
NodeKind::File(f) => { | ||||
if let Some(d) = &mut f.was_directory { | ||||
Raphaël Gomès
|
r46162 | let RemoveResult { old_entry, .. } = | ||
Node::remove_from_directory(head, d); | ||||
Raphaël Gomès
|
r46136 | return RemoveResult { | ||
cleanup: false, | ||||
old_entry, | ||||
}; | ||||
} | ||||
} | ||||
} | ||||
empty_result | ||||
} else { | ||||
// Look into the dirs | ||||
match &mut self.kind { | ||||
NodeKind::Directory(d) => { | ||||
if let Some(child) = d.children.get_mut(head) { | ||||
let mut res = child.remove(tail); | ||||
if res.cleanup { | ||||
d.children.remove(head); | ||||
} | ||||
Raphaël Gomès
|
r46162 | res.cleanup = | ||
Raphaël Gomès
|
r46181 | d.children.is_empty() && d.was_file.is_none(); | ||
Raphaël Gomès
|
r46136 | res | ||
} else { | ||||
empty_result | ||||
} | ||||
} | ||||
NodeKind::File(f) => { | ||||
if let Some(d) = &mut f.was_directory { | ||||
if let Some(child) = d.children.get_mut(head) { | ||||
Raphaël Gomès
|
r46162 | let RemoveResult { cleanup, old_entry } = | ||
child.remove(tail); | ||||
Raphaël Gomès
|
r46136 | if cleanup { | ||
d.children.remove(head); | ||||
} | ||||
Raphaël Gomès
|
r46181 | if d.children.is_empty() && d.was_file.is_none() { | ||
Raphaël Gomès
|
r46136 | f.was_directory = None; | ||
} | ||||
return RemoveResult { | ||||
cleanup: false, | ||||
old_entry, | ||||
}; | ||||
} | ||||
} | ||||
empty_result | ||||
} | ||||
} | ||||
} | ||||
} | ||||
fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult { | ||||
if let Some(node) = d.children.get_mut(head) { | ||||
return match &mut node.kind { | ||||
NodeKind::Directory(d) => { | ||||
if let Some(f) = &mut d.was_file { | ||||
let entry = f.entry; | ||||
d.was_file = None; | ||||
RemoveResult { | ||||
cleanup: false, | ||||
old_entry: Some(entry), | ||||
} | ||||
} else { | ||||
RemoveResult::default() | ||||
} | ||||
} | ||||
NodeKind::File(f) => { | ||||
let entry = f.entry; | ||||
let mut cleanup = false; | ||||
match &f.was_directory { | ||||
None => { | ||||
if d.children.len() == 1 { | ||||
cleanup = true; | ||||
} | ||||
d.children.remove(head); | ||||
} | ||||
Some(dir) => { | ||||
node.kind = NodeKind::Directory(*dir.clone()); | ||||
} | ||||
} | ||||
RemoveResult { | ||||
Raphaël Gomès
|
r46181 | cleanup, | ||
Raphaël Gomès
|
r46136 | old_entry: Some(entry), | ||
} | ||||
} | ||||
}; | ||||
} | ||||
RemoveResult::default() | ||||
} | ||||
pub fn get(&self, path: &[u8]) -> Option<&Node> { | ||||
if path.is_empty() { | ||||
return Some(&self); | ||||
} | ||||
let mut split = path.splitn(2, |&c| c == b'/'); | ||||
let head = split.next(); | ||||
let tail = split.next().unwrap_or(b""); | ||||
let head = match head { | ||||
None => { | ||||
return Some(&self); | ||||
} | ||||
Some(h) => h, | ||||
}; | ||||
match &self.kind { | ||||
NodeKind::Directory(d) => { | ||||
if let Some(child) = d.children.get(head) { | ||||
return child.get(tail); | ||||
} | ||||
} | ||||
NodeKind::File(f) => { | ||||
if let Some(d) = &f.was_directory { | ||||
if let Some(child) = d.children.get(head) { | ||||
return child.get(tail); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
None | ||||
} | ||||
pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> { | ||||
if path.is_empty() { | ||||
return Some(&mut self.kind); | ||||
} | ||||
let mut split = path.splitn(2, |&c| c == b'/'); | ||||
let head = split.next(); | ||||
let tail = split.next().unwrap_or(b""); | ||||
let head = match head { | ||||
None => { | ||||
return Some(&mut self.kind); | ||||
} | ||||
Some(h) => h, | ||||
}; | ||||
match &mut self.kind { | ||||
NodeKind::Directory(d) => { | ||||
if let Some(child) = d.children.get_mut(head) { | ||||
return child.get_mut(tail); | ||||
} | ||||
} | ||||
NodeKind::File(f) => { | ||||
if let Some(d) = &mut f.was_directory { | ||||
if let Some(child) = d.children.get_mut(head) { | ||||
return child.get_mut(tail); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
None | ||||
} | ||||
pub fn iter(&self) -> Iter { | ||||
Iter::new(self) | ||||
} | ||||
} | ||||
/// Information returned to the caller of an `insert` operation for integrity. | ||||
#[derive(Debug, Default)] | ||||
pub struct InsertResult { | ||||
/// Whether the insertion resulted in an actual insertion and not an | ||||
/// update | ||||
pub(super) did_insert: bool, | ||||
/// The entry that was replaced, if it exists | ||||
pub(super) old_entry: Option<Node>, | ||||
} | ||||
/// Information returned to the caller of a `remove` operation integrity. | ||||
#[derive(Debug, Default)] | ||||
pub struct RemoveResult { | ||||
/// If the caller needs to remove the current node | ||||
pub(super) cleanup: bool, | ||||
/// The entry that was replaced, if it exists | ||||
pub(super) old_entry: Option<DirstateEntry>, | ||||
} | ||||
impl<'a> IntoIterator for &'a Node { | ||||
type Item = (HgPathBuf, DirstateEntry); | ||||
type IntoIter = Iter<'a>; | ||||
fn into_iter(self) -> Self::IntoIter { | ||||
self.iter() | ||||
} | ||||
} | ||||