From 0ef4dd61ebd1674a178a8fce38446bc81973a7f6 Mon Sep 17 00:00:00 2001 From: Dylan Arbour Date: Wed, 29 Jul 2026 06:22:08 -0400 Subject: [PATCH] worktree: Fix global gitignore on ancestors (#61689) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Fixes #61687 Prior to #60772, an `abs_path` outside the `repo_root` would cause a panic. That PR changed it to return the `abs_path` itself, preventing the panic, but allowing all parents of `repo_root` to be subject to the global ignore. This is not what git itself does. ## Solution - This change returns `false` when the `abs_path` is outside the `root_repo`, preventing the panic but also not returning the `abs_path` ## Testing - One in-memory test - Manual test by building Zed and confirming the project pane changes (pictured below) ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase ### Before, with the bug Screenshot 2026-07-26 at 11 44
11 AM ### After the fix Screenshot 2026-07-26 at 12 34
58 PM Release Notes: - Fixed the project panel's global gitignore incorrectly matching parent directories of a repository --------- Co-authored-by: dino --- crates/worktree/src/ignore.rs | 14 ++++---- .../tests/integration/worktree_tests.rs | 35 ++++++++++--------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/crates/worktree/src/ignore.rs b/crates/worktree/src/ignore.rs index 5b0b3da52f..b1f801afd4 100644 --- a/crates/worktree/src/ignore.rs +++ b/crates/worktree/src/ignore.rs @@ -85,12 +85,14 @@ impl IgnoreStack { IgnoreStackEntry::Global { ignore } => { let combined_path; let abs_path = if let Some(repo_root) = self.repo_root.as_ref() { - if let Ok(relative_path) = abs_path.strip_prefix(repo_root) { - combined_path = ignore.path().join(relative_path); - &combined_path - } else { - abs_path - } + let Ok(relative_path) = abs_path.strip_prefix(repo_root) else { + // The provided absolute path is outside of the repository's folderabs_path is outside repo_root and cannot be ignored + // by this global ignore + return false; + }; + + combined_path = ignore.path().join(relative_path); + &combined_path } else { abs_path }; diff --git a/crates/worktree/tests/integration/worktree_tests.rs b/crates/worktree/tests/integration/worktree_tests.rs index 2ef74669b2..dcd303e5fd 100644 --- a/crates/worktree/tests/integration/worktree_tests.rs +++ b/crates/worktree/tests/integration/worktree_tests.rs @@ -3621,34 +3621,37 @@ async fn test_global_gitignore(executor: BackgroundExecutor, cx: &mut TestAppCon init_test(cx); let home = paths::home_dir(); + let project_path = home.join("example.com").join("project"); let fs = FakeFs::new(executor); fs.insert_tree( home, json!({ ".config": { "git": { - "ignore": "foo\n/bar\nbaz\n" + "ignore": "foo\n/bar\nbaz\n*.com\n" } }, - "project": { - ".git": {}, - ".gitignore": "!baz", - "foo": "", - "bar": "", - "sub": { - "bar": "", - }, - "subrepo": { + "example.com": { + "project": { ".git": {}, - "bar": "" - }, - "baz": "" + ".gitignore": "!baz", + "foo": "", + "bar": "", + "sub": { + "bar": "", + }, + "subrepo": { + ".git": {}, + "bar": "" + }, + "baz": "" + } } }), ) .await; let worktree = Worktree::local( - home.join("project"), + project_path.clone(), true, fs.clone(), Arc::default(), @@ -3681,7 +3684,7 @@ async fn test_global_gitignore(executor: BackgroundExecutor, cx: &mut TestAppCon // Ignore statuses are updated when excludesFile changes fs.write( &home.join(".config").join("git").join("ignore"), - "/bar\nbaz\n".as_bytes(), + "/bar\nbaz\n*.com\n".as_bytes(), ) .await .unwrap(); @@ -3705,7 +3708,7 @@ async fn test_global_gitignore(executor: BackgroundExecutor, cx: &mut TestAppCon // Statuses are updated when .git added/removed fs.remove_dir( - &home.join("project").join("subrepo").join(".git"), + &project_path.join("subrepo").join(".git"), RemoveOptions { recursive: true, ..Default::default()