Gracefully handle when linked worktree .git path is outside worktree root (#53443)
In `update_git_repositories`, a `.git` path outside the worktree root can occur legitimately when `.git` is a gitfile (as in linked worktrees and submodules) pointing to a directory in the parent repo. Previously this triggered a `debug_panic!`, crashing debug builds. Now we skip the path with a `debug_assert!` that it is indeed a file (not a directory), so a genuine `.git` directory outside the worktree root would still be caught in debug builds. (No release notes because this is extremely hard to encounter until https://github.com/zed-industries/zed/pull/53215 lands) Release Notes: - N/A
This commit is contained in:
1 parent
525f10a133
commit
fb949ae831
2 files changed
+87
-4
No files matched your search
@@ -68,7 +68,7 @@ use std::{
|
||||
use sum_tree::{Bias, Dimensions, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet};
|
||||
use text::{LineEnding, Rope};
|
||||
use util::{
|
||||
ResultExt, debug_panic, maybe,
|
||||
ResultExt, maybe,
|
||||
paths::{PathMatcher, PathStyle, SanitizedPath, home_dir},
|
||||
rel_path::RelPath,
|
||||
};
|
||||
@@ -5245,10 +5245,17 @@ impl BackgroundScanner {
|
||||
match existing_repository_entry {
|
||||
None => {
|
||||
let Ok(relative) = dot_git_dir.strip_prefix(state.snapshot.abs_path()) else {
|
||||
debug_panic!(
|
||||
"update_git_repositories called with .git directory outside the worktree root"
|
||||
// This can happen legitimately when `.git` is a
|
||||
// gitfile (e.g. in a linked worktree or submodule)
|
||||
// pointing to a directory outside the worktree root.
|
||||
// Skip it — the repository was already registered
|
||||
// during the initial scan via `discover_git_paths`.
|
||||
debug_assert!(
|
||||
self.fs.is_file(&dot_git_dir).await,
|
||||
"update_git_repositories: .git path outside worktree root \
|
||||
is not a gitfile: {dot_git_dir:?}",
|
||||
);
|
||||
return Vec::new();
|
||||
continue;
|
||||
};
|
||||
affected_repo_roots.push(dot_git_dir.parent().unwrap().into());
|
||||
state
|
||||
|
||||
@@ -2827,6 +2827,82 @@ async fn test_root_repo_common_dir(executor: BackgroundExecutor, cx: &mut TestAp
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_linked_worktree_git_file_event_does_not_panic(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
// Regression test: in a linked worktree, `.git` is a file (containing
|
||||
// "gitdir: ..."), not a directory. When the background scanner receives
|
||||
// a filesystem event for a path inside the main repo's `.git` directory
|
||||
// (which it watches via the commondir), the ancestor-walking code in
|
||||
// `process_events` calls `is_git_dir` on each ancestor. If `is_git_dir`
|
||||
// treats `.git` files the same as `.git` directories, it incorrectly
|
||||
// identifies the gitfile as a git dir, adds it to `dot_git_abs_paths`,
|
||||
// and `update_git_repositories` panics because the path is outside the
|
||||
// worktree root.
|
||||
init_test(cx);
|
||||
|
||||
use git::repository::Worktree as GitWorktree;
|
||||
|
||||
let fs = FakeFs::new(executor);
|
||||
|
||||
fs.insert_tree(
|
||||
path!("/main_repo"),
|
||||
json!({
|
||||
".git": {},
|
||||
"file.txt": "content",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
fs.add_linked_worktree_for_repo(
|
||||
Path::new(path!("/main_repo/.git")),
|
||||
false,
|
||||
GitWorktree {
|
||||
path: PathBuf::from(path!("/linked_worktree")),
|
||||
ref_name: Some("refs/heads/feature".into()),
|
||||
sha: "abc123".into(),
|
||||
is_main: false,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
fs.write(
|
||||
path!("/linked_worktree/file.txt").as_ref(),
|
||||
"content".as_bytes(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let tree = Worktree::local(
|
||||
path!("/linked_worktree").as_ref(),
|
||||
true,
|
||||
fs.clone(),
|
||||
Arc::default(),
|
||||
true,
|
||||
WorktreeId::from_proto(0),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
cx.run_until_parked();
|
||||
|
||||
// Trigger a filesystem event inside the main repo's .git directory
|
||||
// (which the linked worktree scanner watches via the commondir). This
|
||||
// uses the sentinel-file helper to ensure the event goes through the
|
||||
// real watcher path, exactly as it would in production.
|
||||
tree.flush_fs_events_in_root_git_repository(cx).await;
|
||||
|
||||
// The worktree should still be intact.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.snapshot().root_repo_common_dir().map(|p| p.as_ref()),
|
||||
Some(Path::new(path!("/main_repo/.git"))),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
zlog::init_test();
|
||||
|
||||
|
||||
Reference in new issue
Block a user