From 3ae16904b403dfc6e276491c585d9eb49bb2067e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 30 Aug 2022 16:13:07 -0700 Subject: [PATCH] Avoid changing entry's kind from Dir to PendingDir in refresh_entry When lots of filesystem changes are occurring, the filesystem event for the directory creation may be delivered before the call to fs::metadata resolves. --- crates/project/src/worktree.rs | 66 +++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 3888e1ea36..96ebb59de0 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -807,15 +807,10 @@ impl LocalWorktree { next_entry_id = snapshot.next_entry_id.clone(); } cx.spawn_weak(|this, mut cx| async move { - let mut entry = Entry::new( - path.clone(), - &fs.metadata(&abs_path) - .await? - .ok_or_else(|| anyhow!("could not read saved file metadata"))?, - &next_entry_id, - root_char_bag, - ); - + let metadata = fs + .metadata(&abs_path) + .await? + .ok_or_else(|| anyhow!("could not read saved file metadata"))?; let this = this .upgrade(&cx) .ok_or_else(|| anyhow!("worktree was dropped"))?; @@ -824,6 +819,7 @@ impl LocalWorktree { let inserted_entry; { let mut snapshot = this.background_snapshot.lock(); + let mut entry = Entry::new(path, &metadata, &next_entry_id, root_char_bag); entry.is_ignored = snapshot .ignore_stack_for_abs_path(&abs_path, entry.is_dir()) .is_abs_path_ignored(&abs_path, entry.is_dir()); @@ -1354,6 +1350,15 @@ impl LocalSnapshot { } self.reuse_entry_id(&mut entry); + + if entry.kind == EntryKind::PendingDir { + if let Some(existing_entry) = + self.entries_by_path.get(&PathKey(entry.path.clone()), &()) + { + entry.kind = existing_entry.kind; + } + } + self.entries_by_path.insert_or_replace(entry.clone(), &()); let scan_id = self.scan_id; let removed_entry = self.entries_by_id.insert_or_replace( @@ -3057,6 +3062,49 @@ mod tests { }); } + #[gpui::test(iterations = 30)] + async fn test_create_directory(cx: &mut TestAppContext) { + let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "b": {}, + "c": {}, + "d": {}, + }), + ) + .await; + + let tree = Worktree::local( + client, + "/a".as_ref(), + true, + fs, + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + let entry = tree + .update(cx, |tree, cx| { + tree.as_local_mut() + .unwrap() + .create_entry("a/e".as_ref(), true, cx) + }) + .await + .unwrap(); + assert!(entry.is_dir()); + + cx.foreground().run_until_parked(); + tree.read_with(cx, |tree, _| { + assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir); + }); + } + #[gpui::test(iterations = 100)] fn test_random(mut rng: StdRng) { let operations = env::var("OPERATIONS")