Fix data loss when project settings opened with ".zed" in file_scan_exclusions
(#29578)
Closes #28640 Before creating an entry for a file opened with `open_local_file`, make sure it doesn't exist, in addition to checking that it isn't already tracked in the workspace Release Notes: - Fixed an issue where the project settings file would be truncated when opened with `zed: open project settings` if the ".zed" directory was excluded from the files scanned in a workspace (in "file_scan_exclusions")
This commit is contained in:
parent
4dc8ce8cf7
commit
3a212e72a4
1 changed files with 139 additions and 14 deletions
|
@ -1502,28 +1502,45 @@ fn open_local_file(
|
||||||
if let Some(worktree) = worktree {
|
if let Some(worktree) = worktree {
|
||||||
let tree_id = worktree.read(cx).id();
|
let tree_id = worktree.read(cx).id();
|
||||||
cx.spawn_in(window, async move |workspace, cx| {
|
cx.spawn_in(window, async move |workspace, cx| {
|
||||||
if let Some(dir_path) = settings_relative_path.parent() {
|
// Check if the file actually exists on disk (even if it's excluded from worktree)
|
||||||
if worktree.update(cx, |tree, _| tree.entry_for_path(dir_path).is_none())? {
|
let file_exists = {
|
||||||
|
let full_path =
|
||||||
|
worktree.update(cx, |tree, _| tree.abs_path().join(settings_relative_path))?;
|
||||||
|
|
||||||
|
let fs = project.update(cx, |project, _| project.fs().clone())?;
|
||||||
|
let file_exists = fs
|
||||||
|
.metadata(&full_path)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map_or(false, |metadata| !metadata.is_dir && !metadata.is_fifo);
|
||||||
|
file_exists
|
||||||
|
};
|
||||||
|
|
||||||
|
if !file_exists {
|
||||||
|
if let Some(dir_path) = settings_relative_path.parent() {
|
||||||
|
if worktree.update(cx, |tree, _| tree.entry_for_path(dir_path).is_none())? {
|
||||||
|
project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.create_entry((tree_id, dir_path), true, cx)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
.context("worktree was removed")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if worktree.update(cx, |tree, _| {
|
||||||
|
tree.entry_for_path(settings_relative_path).is_none()
|
||||||
|
})? {
|
||||||
project
|
project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.create_entry((tree_id, dir_path), true, cx)
|
project.create_entry((tree_id, settings_relative_path), false, cx)
|
||||||
})?
|
})?
|
||||||
.await
|
.await
|
||||||
.context("worktree was removed")?;
|
.context("worktree was removed")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if worktree.update(cx, |tree, _| {
|
|
||||||
tree.entry_for_path(settings_relative_path).is_none()
|
|
||||||
})? {
|
|
||||||
project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.create_entry((tree_id, settings_relative_path), false, cx)
|
|
||||||
})?
|
|
||||||
.await
|
|
||||||
.context("worktree was removed")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let editor = workspace
|
let editor = workspace
|
||||||
.update_in(cx, |workspace, window, cx| {
|
.update_in(cx, |workspace, window, cx| {
|
||||||
workspace.open_path((tree_id, settings_relative_path), None, true, window, cx)
|
workspace.open_path((tree_id, settings_relative_path), None, true, window, cx)
|
||||||
|
@ -4316,4 +4333,112 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_opening_project_settings_when_excluded(cx: &mut gpui::TestAppContext) {
|
||||||
|
// Use the proper initialization for runtime state
|
||||||
|
let app_state = init_keymap_test(cx);
|
||||||
|
|
||||||
|
eprintln!("Running test_opening_project_settings_when_excluded");
|
||||||
|
|
||||||
|
// 1. Set up a project with some project settings
|
||||||
|
let settings_init =
|
||||||
|
r#"{ "UNIQUEVALUE": true, "git": { "inline_blame": { "enabled": false } } }"#;
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree(
|
||||||
|
Path::new("/root"),
|
||||||
|
json!({
|
||||||
|
".zed": {
|
||||||
|
"settings.json": settings_init
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
eprintln!("Created project with .zed/settings.json containing UNIQUEVALUE");
|
||||||
|
|
||||||
|
// 2. Create a project with the file system and load it
|
||||||
|
let project = Project::test(app_state.fs.clone(), [Path::new("/root")], cx).await;
|
||||||
|
|
||||||
|
// Save original settings content for comparison
|
||||||
|
let original_settings = app_state
|
||||||
|
.fs
|
||||||
|
.load(Path::new("/root/.zed/settings.json"))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let original_settings_str = original_settings.clone();
|
||||||
|
|
||||||
|
// Verify settings exist on disk and have expected content
|
||||||
|
eprintln!("Original settings content: {}", original_settings_str);
|
||||||
|
assert!(
|
||||||
|
original_settings_str.contains("UNIQUEVALUE"),
|
||||||
|
"Test setup failed - settings file doesn't contain our marker"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Add .zed to file scan exclusions in user settings
|
||||||
|
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||||
|
store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
|
||||||
|
worktree_settings.file_scan_exclusions = Some(vec![".zed".to_string()]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
eprintln!("Added .zed to file_scan_exclusions in settings");
|
||||||
|
|
||||||
|
// 4. Run tasks to apply settings
|
||||||
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
|
// 5. Critical: Verify .zed is actually excluded from worktree
|
||||||
|
let worktree = cx.update(|cx| project.read(cx).worktrees(cx).next().unwrap().clone());
|
||||||
|
|
||||||
|
let has_zed_entry = cx.update(|cx| worktree.read(cx).entry_for_path(".zed").is_some());
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"Is .zed directory visible in worktree after exclusion: {}",
|
||||||
|
has_zed_entry
|
||||||
|
);
|
||||||
|
|
||||||
|
// This assertion verifies the test is set up correctly to show the bug
|
||||||
|
// If .zed is not excluded, the test will fail here
|
||||||
|
assert!(
|
||||||
|
!has_zed_entry,
|
||||||
|
"Test precondition failed: .zed directory should be excluded but was found in worktree"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Create workspace and trigger the actual function that causes the bug
|
||||||
|
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
window
|
||||||
|
.update(cx, |workspace, window, cx| {
|
||||||
|
// Call the exact function that contains the bug
|
||||||
|
eprintln!("About to call open_project_settings_file");
|
||||||
|
open_project_settings_file(workspace, &OpenProjectSettings, window, cx);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// 7. Run background tasks until completion
|
||||||
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
|
// 8. Verify file contents after calling function
|
||||||
|
let new_content = app_state
|
||||||
|
.fs
|
||||||
|
.load(Path::new("/root/.zed/settings.json"))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let new_content_str = new_content.clone();
|
||||||
|
eprintln!("New settings content: {}", new_content_str);
|
||||||
|
|
||||||
|
// The bug causes the settings to be overwritten with empty settings
|
||||||
|
// So if the unique value is no longer present, the bug has been reproduced
|
||||||
|
let bug_exists = !new_content_str.contains("UNIQUEVALUE");
|
||||||
|
eprintln!("Bug reproduced: {}", bug_exists);
|
||||||
|
|
||||||
|
// This assertion should fail if the bug exists - showing the bug is real
|
||||||
|
assert!(
|
||||||
|
new_content_str.contains("UNIQUEVALUE"),
|
||||||
|
"BUG FOUND: Project settings were overwritten when opening via command - original custom content was lost"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue