worktree: Fix privacy check for singleton files (#21861)

Cole Miller created

Closes #20676

Release Notes:

- Fixed private files not being redacted when not part of a larger
worktree

Change summary

crates/language/src/buffer.rs         |  5 ----
crates/worktree/src/worktree.rs       | 33 +++++++++++++++-------------
crates/worktree/src/worktree_tests.rs | 23 ++++++++++++++++++++
3 files changed, 41 insertions(+), 20 deletions(-)

Detailed changes

crates/language/src/buffer.rs 🔗

@@ -427,11 +427,6 @@ pub trait LocalFile: File {
 
     /// Loads the file's contents from disk.
     fn load_bytes(&self, cx: &AppContext) -> Task<Result<Vec<u8>>>;
-
-    /// Returns true if the file should not be shared with collaborators.
-    fn is_private(&self, _: &AppContext) -> bool {
-        false
-    }
 }
 
 /// The auto-indent behavior associated with an editing operation.

crates/worktree/src/worktree.rs 🔗

@@ -409,24 +409,11 @@ impl Worktree {
                     abs_path
                         .file_name()
                         .map_or(String::new(), |f| f.to_string_lossy().to_string()),
-                    abs_path,
+                    abs_path.clone(),
                 ),
                 root_file_handle,
             };
 
-            if let Some(metadata) = metadata {
-                snapshot.insert_entry(
-                    Entry::new(
-                        Arc::from(Path::new("")),
-                        &metadata,
-                        &next_entry_id,
-                        snapshot.root_char_bag,
-                        None,
-                    ),
-                    fs.as_ref(),
-                );
-            }
-
             let worktree_id = snapshot.id();
             let settings_location = Some(SettingsLocation {
                 worktree_id,
@@ -445,10 +432,26 @@ impl Worktree {
             })
             .detach();
 
+            let share_private_files = false;
+            if let Some(metadata) = metadata {
+                let mut entry = Entry::new(
+                    Arc::from(Path::new("")),
+                    &metadata,
+                    &next_entry_id,
+                    snapshot.root_char_bag,
+                    None,
+                );
+                if !metadata.is_dir {
+                    entry.is_private = !share_private_files
+                        && settings.is_path_private(abs_path.file_name().unwrap().as_ref());
+                }
+                snapshot.insert_entry(entry, fs.as_ref());
+            }
+
             let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
             let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded();
             let mut worktree = LocalWorktree {
-                share_private_files: false,
+                share_private_files,
                 next_entry_id,
                 snapshot,
                 is_scanning: watch::channel_with(true),

crates/worktree/src/worktree_tests.rs 🔗

@@ -2712,6 +2712,29 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
     );
 }
 
+#[gpui::test]
+async fn test_private_single_file_worktree(cx: &mut TestAppContext) {
+    init_test(cx);
+    let fs = FakeFs::new(cx.background_executor.clone());
+    fs.insert_tree("/", json!({".env": "PRIVATE=secret\n"}))
+        .await;
+    let tree = Worktree::local(
+        Path::new("/.env"),
+        true,
+        fs.clone(),
+        Default::default(),
+        &mut cx.to_async(),
+    )
+    .await
+    .unwrap();
+    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+        .await;
+    tree.read_with(cx, |tree, _| {
+        let entry = tree.entry_for_path("").unwrap();
+        assert!(entry.is_private);
+    });
+}
+
 #[track_caller]
 fn check_propagated_statuses(
     snapshot: &Snapshot,