WIP: Associate entry names with directory children

Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
Antonio Scandurra 2021-04-19 20:16:54 +02:00
parent f8f6a85ab0
commit 122926dcde
2 changed files with 85 additions and 207 deletions

View file

@ -12,7 +12,7 @@ use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
use ignore::dir::{Ignore, IgnoreBuilder}; use ignore::dir::{Ignore, IgnoreBuilder};
use parking_lot::Mutex; use parking_lot::Mutex;
use smol::{channel::Sender, Timer}; use smol::{channel::Sender, Timer};
use std::{collections::HashSet, future::Future}; use std::future::Future;
use std::{ use std::{
ffi::OsStr, ffi::OsStr,
fmt, fs, fmt, fs,
@ -204,15 +204,18 @@ impl Snapshot {
self.root_inode.and_then(|inode| self.entries.get(&inode)) self.root_inode.and_then(|inode| self.entries.get(&inode))
} }
pub fn root_name(&self) -> Option<&OsStr> {
self.path.file_name()
}
fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> { fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
let path = path.as_ref(); let path = path.as_ref();
self.root_inode.and_then(|mut inode| { self.root_inode.and_then(|mut inode| {
'components: for path_component in path { 'components: for path_component in path {
if let Some(Entry::Dir { children, .. }) = &self.entries.get(&inode) { if let Some(Entry::Dir { children, .. }) = &self.entries.get(&inode) {
for child in children.as_ref() { for (child_inode, name) in children.as_ref() {
if self.entries.get(child).map(|entry| entry.name()) == Some(path_component) if name.as_ref() == path_component {
{ inode = *child_inode;
inode = *child;
continue 'components; continue 'components;
} }
} }
@ -228,183 +231,91 @@ impl Snapshot {
.and_then(|inode| self.entries.get(&inode)) .and_then(|inode| self.entries.get(&inode))
} }
pub fn path_for_inode(&self, ino: u64, include_root: bool) -> Result<PathBuf> { pub fn path_for_inode(&self, mut ino: u64, include_root: bool) -> Result<PathBuf> {
let mut components = Vec::new(); let mut components = Vec::new();
let mut entry = self let mut entry = self
.entries .entries
.get(&ino) .get(&ino)
.ok_or_else(|| anyhow!("entry does not exist in worktree"))?; .ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
components.push(entry.name());
while let Some(parent) = entry.parent() { while let Some(parent) = entry.parent() {
entry = self.entries.get(&parent).unwrap(); entry = self.entries.get(&parent).unwrap();
components.push(entry.name()); if let Entry::Dir { children, .. } = entry {
let (_, child_name) = children
.iter()
.find(|(child_inode, _)| *child_inode == ino)
.unwrap();
components.push(child_name.as_ref());
ino = parent;
} else {
unreachable!();
}
}
if include_root {
components.push(self.path.file_name().unwrap());
} }
let mut components = components.into_iter().rev(); Ok(components.into_iter().rev().collect())
if !include_root {
components.next();
}
let mut path = PathBuf::new();
for component in components {
path.push(component);
}
Ok(path)
} }
fn remove_entry(&mut self, inode: u64) { fn remove_path(&mut self, path: &Path) {
if let Some(entry) = self.entries.get(&inode).cloned() { if let Some(parent_path) = path.parent() {
let mut edits = Vec::new(); let mut edits = Vec::new();
if let Some(parent) = entry.parent() { let mut parent_entry = self.entry_for_path(parent_path).unwrap().clone();
let mut parent_entry = self.entries.get(&parent).unwrap().clone(); let parent_inode = parent_entry.inode();
if let Entry::Dir { children, .. } = &mut parent_entry { let mut entry_inode = None;
*children = children if let Entry::Dir { children, .. } = &mut parent_entry {
.into_iter() let mut new_children = Vec::new();
.copied() for (child_inode, child_name) in children.as_ref() {
.filter(|child| *child != entry.inode()) if Some(child_name.as_ref()) == path.file_name() {
.collect::<Vec<_>>() entry_inode = Some(*child_inode);
.into(); } else {
edits.push(Edit::Insert(parent_entry)); new_children.push((*child_inode, child_name.clone()));
} else { }
unreachable!();
} }
if new_children.iter().any(|c| Some(c.0) == entry_inode) {
entry_inode = None;
}
dbg!(&children, &new_children, entry_inode, parent_inode);
*children = new_children.into();
edits.push(Edit::Insert(parent_entry));
} else {
unreachable!();
} }
// Recursively remove the orphaned nodes' descendants. if let Some(entry_inode) = entry_inode {
let mut descendant_stack = vec![entry.inode()]; let entry = self.entries.get(&entry_inode).unwrap();
while let Some(inode) = descendant_stack.pop() {
if let Some(entry) = self.entries.get(&inode) { // Recursively remove the orphaned nodes' descendants.
edits.push(Edit::Remove(inode)); let mut descendant_stack = Vec::new();
if let Entry::Dir { children, .. } = entry { if entry.parent() == Some(parent_inode) {
descendant_stack.extend_from_slice(children.as_ref()); descendant_stack.push(entry_inode);
while let Some(inode) = descendant_stack.pop() {
if let Some(entry) = self.entries.get(&inode) {
edits.push(Edit::Remove(inode));
if let Entry::Dir { children, .. } = entry {
descendant_stack.extend(children.iter().map(|c| c.0));
}
}
} }
} }
} }
// dbg!(&edits);
self.entries.edit(edits); self.entries.edit(edits);
} }
} }
fn move_entry( fn fmt_entry(
&mut self, &self,
child_inode: u64, f: &mut fmt::Formatter<'_>,
new_path: Option<&Path>, ino: u64,
old_parent_inode: Option<u64>, name: &OsStr,
new_parent_inode: Option<u64>, indent: usize,
) { ) -> fmt::Result {
let mut edits_len = 1;
if old_parent_inode.is_some() {
edits_len += 1;
}
if new_parent_inode.is_some() {
edits_len += 1;
}
let mut deletions = Vec::with_capacity(edits_len);
let mut insertions = Vec::with_capacity(edits_len);
// Remove the entries from the sum tree.
deletions.push(Edit::Remove(child_inode));
if old_parent_inode != new_parent_inode {
if let Some(old_parent_inode) = old_parent_inode {
deletions.push(Edit::Remove(old_parent_inode));
}
if let Some(new_parent_inode) = new_parent_inode {
deletions.push(Edit::Remove(new_parent_inode));
}
}
let removed_entries = self.entries.edit(deletions);
let mut child_entry = None;
let mut old_parent_entry = None;
let mut new_parent_entry = None;
for removed_entry in removed_entries {
if removed_entry.inode() == child_inode {
child_entry = Some(removed_entry);
} else if Some(removed_entry.inode()) == old_parent_inode {
old_parent_entry = Some(removed_entry);
} else if Some(removed_entry.inode()) == new_parent_inode {
new_parent_entry = Some(removed_entry);
}
}
// Update the child entry's parent.
let mut child_entry = child_entry.expect("cannot move non-existent entry");
child_entry.set_parent(new_parent_inode);
if let Some(new_path) = new_path {
let new_path = new_path.strip_prefix(self.path.parent().unwrap()).unwrap();
child_entry.set_name(new_path.file_name().unwrap());
// Recompute the PathEntry for each file under this subtree.
let mut stack = Vec::new();
stack.push((child_entry, new_path.parent().unwrap().to_path_buf()));
while let Some((mut entry, mut new_path)) = stack.pop() {
new_path.push(entry.name());
match &mut entry {
Entry::Dir {
children, inode, ..
} => {
for child_inode in children.as_ref() {
let child_entry = self.entries.get(child_inode).unwrap();
stack.push((child_entry.clone(), new_path.clone()));
}
// Descendant directories don't need to be mutated because their properties
// haven't changed, so only re-insert this directory if it is the top entry
// we were moving.
if *inode == child_inode {
insertions.push(Edit::Insert(entry));
}
}
Entry::File {
inode,
is_ignored,
path,
..
} => {
*path = PathEntry::new(*inode, &new_path, *is_ignored);
insertions.push(Edit::Insert(entry));
}
}
}
} else {
insertions.push(Edit::Insert(child_entry));
}
// Remove the child entry from its old parent's children.
if let Some(mut old_parent_entry) = old_parent_entry {
if let Entry::Dir { children, .. } = &mut old_parent_entry {
*children = children
.into_iter()
.cloned()
.filter(|c| *c != child_inode)
.collect();
insertions.push(Edit::Insert(old_parent_entry));
} else {
panic!("snapshot entry's new parent was not a directory");
}
}
// Add the child entry to its new parent's children.
if let Some(mut new_parent_entry) = new_parent_entry {
if let Entry::Dir { children, .. } = &mut new_parent_entry {
*children = children
.into_iter()
.cloned()
.chain(Some(child_inode))
.collect();
insertions.push(Edit::Insert(new_parent_entry));
} else {
panic!("snapshot entry's new parent is not a directory");
}
}
self.entries.edit(insertions);
}
fn fmt_entry(&self, f: &mut fmt::Formatter<'_>, ino: u64, indent: usize) -> fmt::Result {
match self.entries.get(&ino).unwrap() { match self.entries.get(&ino).unwrap() {
Entry::Dir { name, children, .. } => { Entry::Dir { children, .. } => {
write!( write!(
f, f,
"{}{}/ ({})\n", "{}{}/ ({})\n",
@ -412,12 +323,12 @@ impl Snapshot {
name.to_string_lossy(), name.to_string_lossy(),
ino ino
)?; )?;
for child_id in children.iter() { for (child_inode, child_name) in children.iter() {
self.fmt_entry(f, *child_id, indent + 2)?; self.fmt_entry(f, *child_inode, child_name, indent + 2)?;
} }
Ok(()) Ok(())
} }
Entry::File { name, .. } => write!( Entry::File { .. } => write!(
f, f,
"{}{} ({})\n", "{}{} ({})\n",
" ".repeat(indent), " ".repeat(indent),
@ -431,7 +342,7 @@ impl Snapshot {
impl fmt::Debug for Snapshot { impl fmt::Debug for Snapshot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(root_ino) = self.root_inode { if let Some(root_ino) = self.root_inode {
self.fmt_entry(f, root_ino, 0) self.fmt_entry(f, root_ino, self.path.file_name().unwrap(), 0)
} else { } else {
write!(f, "Empty tree\n") write!(f, "Empty tree\n")
} }
@ -464,16 +375,14 @@ impl FileHandle {
pub enum Entry { pub enum Entry {
Dir { Dir {
parent: Option<u64>, parent: Option<u64>,
name: Arc<OsStr>,
inode: u64, inode: u64,
is_symlink: bool, is_symlink: bool,
is_ignored: bool, is_ignored: bool,
children: Arc<[u64]>, children: Arc<[(u64, Arc<OsStr>)]>,
pending: bool, pending: bool,
}, },
File { File {
parent: Option<u64>, parent: Option<u64>,
name: Arc<OsStr>,
path: PathEntry, path: PathEntry,
inode: u64, inode: u64,
is_symlink: bool, is_symlink: bool,
@ -499,27 +408,6 @@ impl Entry {
Entry::File { parent, .. } => *parent, Entry::File { parent, .. } => *parent,
} }
} }
fn set_parent(&mut self, new_parent: Option<u64>) {
match self {
Entry::Dir { parent, .. } => *parent = new_parent,
Entry::File { parent, .. } => *parent = new_parent,
}
}
fn name(&self) -> &OsStr {
match self {
Entry::Dir { name, .. } => name,
Entry::File { name, .. } => name,
}
}
fn set_name(&mut self, new_name: &OsStr) {
match self {
Entry::Dir { name, .. } => *name = new_name.into(),
Entry::File { name, .. } => *name = new_name.into(),
}
}
} }
impl sum_tree::Item for Entry { impl sum_tree::Item for Entry {
@ -660,7 +548,6 @@ impl BackgroundScanner {
let is_ignored = is_ignored || name.as_ref() == ".git"; let is_ignored = is_ignored || name.as_ref() == ".git";
let dir_entry = Entry::Dir { let dir_entry = Entry::Dir {
parent: None, parent: None,
name,
inode, inode,
is_symlink, is_symlink,
is_ignored, is_ignored,
@ -702,7 +589,6 @@ impl BackgroundScanner {
} else { } else {
self.insert_entries(Some(Entry::File { self.insert_entries(Some(Entry::File {
parent: None, parent: None,
name,
path: PathEntry::new(inode, &relative_path, is_ignored), path: PathEntry::new(inode, &relative_path, is_ignored),
inode, inode,
is_symlink, is_symlink,
@ -731,7 +617,7 @@ impl BackgroundScanner {
let is_symlink = metadata.file_type().is_symlink(); let is_symlink = metadata.file_type().is_symlink();
let path = job.path.join(name.as_ref()); let path = job.path.join(name.as_ref());
new_children.push(ino); new_children.push((ino, name.clone()));
if metadata.is_dir() { if metadata.is_dir() {
let mut is_ignored = true; let mut is_ignored = true;
let mut ignore = None; let mut ignore = None;
@ -747,7 +633,6 @@ impl BackgroundScanner {
let dir_entry = Entry::Dir { let dir_entry = Entry::Dir {
parent: Some(job.inode), parent: Some(job.inode),
name,
inode: ino, inode: ino,
is_symlink, is_symlink,
is_ignored, is_ignored,
@ -770,7 +655,6 @@ impl BackgroundScanner {
.map_or(true, |i| i.matched(&path, false).is_ignore()); .map_or(true, |i| i.matched(&path, false).is_ignore());
new_entries.push(Entry::File { new_entries.push(Entry::File {
parent: Some(job.inode), parent: Some(job.inode),
name,
path: PathEntry::new(ino, &relative_path, is_ignored), path: PathEntry::new(ino, &relative_path, is_ignored),
inode: ino, inode: ino,
is_symlink, is_symlink,
@ -818,24 +702,22 @@ impl BackgroundScanner {
paths.next(); paths.next();
} }
if let Some(snapshot_inode) = snapshot.inode_for_path(&relative_path) { snapshot.remove_path(&relative_path);
snapshot.remove_entry(snapshot_inode);
}
match self.fs_entry_for_path(&snapshot.path, &path) { match self.fs_entry_for_path(&snapshot.path, &path) {
Ok(Some((fs_entry, ignore))) => { Ok(Some((fs_entry, ignore))) => {
snapshot.remove_entry(fs_entry.inode()); // snapshot.remove_entry(fs_entry.inode());
let mut edits = Vec::new(); let mut edits = Vec::new();
edits.push(Edit::Insert(fs_entry.clone())); edits.push(Edit::Insert(fs_entry.clone()));
if let Some(parent) = fs_entry.parent() { if let Some(parent) = fs_entry.parent() {
let mut parent_entry = snapshot.entries.get(&parent).unwrap().clone(); let mut parent_entry = snapshot.entries.get(&parent).unwrap().clone();
if let Entry::Dir { children, .. } = &mut parent_entry { if let Entry::Dir { children, .. } = &mut parent_entry {
if !children.contains(&fs_entry.inode()) { if !children.iter().any(|c| c.0 == fs_entry.inode()) {
let name = Arc::from(path.file_name().unwrap());
*children = children *children = children
.into_iter() .into_iter()
.copied() .cloned()
.chain(Some(fs_entry.inode())) .chain(Some((fs_entry.inode(), name)))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into(); .into();
edits.push(Edit::Insert(parent_entry)); edits.push(Edit::Insert(parent_entry));
@ -899,7 +781,6 @@ impl BackgroundScanner {
let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore(); let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore();
let inode = metadata.ino(); let inode = metadata.ino();
let name: Arc<OsStr> = Arc::from(path.file_name().unwrap_or(OsStr::new("/")));
let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink(); let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink();
let parent = if path == root_path { let parent = if path == root_path {
None None
@ -910,7 +791,6 @@ impl BackgroundScanner {
Ok(Some(( Ok(Some((
Entry::Dir { Entry::Dir {
parent, parent,
name,
inode, inode,
is_symlink, is_symlink,
is_ignored, is_ignored,
@ -923,7 +803,6 @@ impl BackgroundScanner {
Ok(Some(( Ok(Some((
Entry::File { Entry::File {
parent, parent,
name,
path: PathEntry::new( path: PathEntry::new(
inode, inode,
root_path root_path
@ -1349,7 +1228,7 @@ mod tests {
let computed_path = self.path_for_inode(inode, true).unwrap(); let computed_path = self.path_for_inode(inode, true).unwrap();
match self.entries.get(&inode).unwrap() { match self.entries.get(&inode).unwrap() {
Entry::Dir { children, .. } => { Entry::Dir { children, .. } => {
stack.extend_from_slice(children); stack.extend(children.iter().map(|c| c.0));
} }
Entry::File { path, .. } => { Entry::File { path, .. } => {
assert_eq!( assert_eq!(

View file

@ -128,12 +128,11 @@ where
let skipped_prefix_len = if include_root_name { let skipped_prefix_len = if include_root_name {
0 0
} else if let Some(Entry::Dir { name, .. }) = snapshot.root_entry() { } else if let Some(Entry::Dir { .. }) = snapshot.root_entry() {
let name = name.to_string_lossy(); if let Some(name) = snapshot.root_name() {
if name == "/" { name.to_string_lossy().chars().count() + 1
1
} else { } else {
name.chars().count() + 1 1
} }
} else { } else {
0 0