Update filehandle paths when renames occur

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld
2021-04-26 12:56:13 -07:00
co-authored by Nathan Sobo
parent 6535304da8
commit 9753e67410
+87 -14
View File
@@ -59,6 +59,7 @@ pub struct FileHandle {
state: Arc<Mutex<FileHandleState>>,
}
#[derive(Debug)]
struct FileHandleState {
path: Arc<Path>,
is_deleted: bool,
@@ -386,6 +387,10 @@ impl FileHandle {
self.state.lock().path.clone()
}
pub fn is_deleted(&self) -> bool {
self.state.lock().is_deleted
}
pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
self.worktree.read(ctx).load_history(&self.path(), ctx)
}
@@ -799,6 +804,38 @@ impl BackgroundScanner {
return false;
};
let mut renamed_paths: HashMap<u64, PathBuf> = HashMap::new();
let mut updated_handles = HashMap::new();
for event in &events {
if event.flags.contains(fsevent::StreamFlags::ITEM_RENAMED) {
if let Ok(path) = event.path.strip_prefix(&root_abs_path) {
if let Some(inode) = snapshot.inode_for_path(path) {
renamed_paths.insert(inode, path.to_path_buf());
} else if let Ok(metadata) = fs::metadata(&event.path) {
let new_path = path;
let mut handles = self.handles.lock();
if let Some(old_path) = renamed_paths.get(&metadata.ino()) {
handles.retain(|handle_path, handle_state| {
if let Ok(path_suffix) = handle_path.strip_prefix(&old_path) {
let new_handle_path: Arc<Path> =
new_path.join(path_suffix).into();
if let Some(handle_state) = Weak::upgrade(&handle_state) {
handle_state.lock().path = new_handle_path.clone();
updated_handles
.insert(new_handle_path, Arc::downgrade(&handle_state));
}
false
} else {
true
}
});
handles.extend(updated_handles.drain());
}
}
}
}
}
events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
let mut abs_paths = events.into_iter().map(|e| e.path).peekable();
let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
@@ -865,6 +902,19 @@ impl BackgroundScanner {
self.update_ignore_statuses();
let mut handles = self.handles.lock();
let snapshot = self.snapshot.lock();
handles.retain(|path, handle_state| {
if let Some(handle_state) = Weak::upgrade(&handle_state) {
if snapshot.entry_for_path(&path).is_none() {
handle_state.lock().is_deleted = true;
}
true
} else {
false
}
});
true
}
@@ -1239,26 +1289,34 @@ mod tests {
let dir = temp_tree(json!({
"a": {
"file1": "",
"file2": "",
"file3": "",
},
"b": {
"c": {
"file2": "",
"file4": "",
"file5": "",
}
}
}));
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
app.read(|ctx| tree.read(ctx).scan_complete()).await;
app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 2));
app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 5));
let file2 = app.read(|ctx| {
let file2 = tree.file("b/c/file2", ctx).unwrap();
assert_eq!(file2.path().as_ref(), Path::new("b/c/file2"));
file2
let (file2, file3, file4, file5) = app.read(|ctx| {
(
tree.file("a/file2", ctx).unwrap(),
tree.file("a/file3", ctx).unwrap(),
tree.file("b/c/file4", ctx).unwrap(),
tree.file("b/c/file5", ctx).unwrap(),
)
});
std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
app.read(|ctx| tree.read(ctx).next_scan_complete()).await;
app.read(|ctx| {
@@ -1267,14 +1325,29 @@ mod tests {
.paths()
.map(|p| p.to_str().unwrap())
.collect::<Vec<_>>(),
vec!["a", "a/file1", "b", "d", "d/file2"]
)
});
vec![
"a",
"a/file1",
"a/file2.new",
"b",
"d",
"d/file3",
"d/file4"
]
);
assert_eq!(file2.path().as_ref(), Path::new("a/file2.new"));
assert_eq!(file4.path().as_ref(), Path::new("d/file4"));
assert_eq!(file5.path().as_ref(), Path::new("d/file5"));
assert!(!file2.is_deleted());
assert!(!file4.is_deleted());
assert!(file5.is_deleted());
// tree.condition(&app, move |_, _| {
// file2.path().as_ref() == Path::new("d/file2")
// })
// .await;
// Right now, this rename isn't detected because the target path
// no longer exists on the file system by the time we process the
// rename event.
assert_eq!(file3.path().as_ref(), Path::new("a/file3"));
assert!(file3.is_deleted());
});
});
}