From 0c8b211916ae219cacb455b95956f662fc273685 Mon Sep 17 00:00:00 2001 From: lawrence3699 Date: Sun, 19 Apr 2026 18:30:04 +1000 Subject: [PATCH] fix: ignore collapsed empty directory trees in status (#2490) Propagate the empty-directory property when a collapsed directory only contains empty subdirectories so status keeps treating the whole tree as empty. Add a regression fixture and status test for nested empty directory trees, and update the dirwalk expectation for collapsed empty-only directories. Co-authored-by: OpenAI Codex --- gix-dir/src/walk/readdir.rs | 6 ++++++ gix-dir/tests/dir/walk.rs | 2 +- gix/tests/fixtures/make_status_repos.sh | 9 +++++++++ gix/tests/gix/status.rs | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/gix-dir/src/walk/readdir.rs b/gix-dir/src/walk/readdir.rs index 178a032951..c520f39d7e 100644 --- a/gix-dir/src/walk/readdir.rs +++ b/gix-dir/src/walk/readdir.rs @@ -359,6 +359,11 @@ impl Mark { return None; } + let collapsed_property = state.on_hold[self.start_index..] + .iter() + .all(|entry| entry.property == Some(entry::Property::EmptyDirectory)) + .then_some(entry::Property::EmptyDirectory); + // Pathspecs affect the collapse of the next level, hence find the highest-value one. let dir_pathspec_match = state.on_hold[self.start_index..] .iter() @@ -396,6 +401,7 @@ impl Mark { Cow::Borrowed(dir_rela_path), classify::Outcome { status: dir_status, + property: dir_info.property.or(collapsed_property), pathspec_match: dir_pathspec_match, ..dir_info }, diff --git a/gix-dir/tests/dir/walk.rs b/gix-dir/tests/dir/walk.rs index 09ff80b170..4cfa6a3275 100644 --- a/gix-dir/tests/dir/walk.rs +++ b/gix-dir/tests/dir/walk.rs @@ -443,7 +443,7 @@ fn complex_empty() -> crate::Result { &[ entry("dirs-and-files", Untracked, Directory), entry("empty-toplevel", Untracked, Directory).with_property(EmptyDirectory), - entry("only-dirs", Untracked, Directory), + entry("only-dirs", Untracked, Directory).with_property(EmptyDirectory), ], "empty directories collapse just fine" ); diff --git a/gix/tests/fixtures/make_status_repos.sh b/gix/tests/fixtures/make_status_repos.sh index a4de9fbe00..418f6a3ca7 100755 --- a/gix/tests/fixtures/make_status_repos.sh +++ b/gix/tests/fixtures/make_status_repos.sh @@ -69,3 +69,12 @@ git init -q submodule-assume-unchanged-symlink rm -Rf sub ln -s ../module sub ) + +git init -q nested-empty-tree +(cd nested-empty-tree + touch tracked + git add tracked + git commit -q -m init + + mkdir -p empty/nested +) diff --git a/gix/tests/gix/status.rs b/gix/tests/gix/status.rs index 4fd061dc35..1cf960c738 100644 --- a/gix/tests/gix/status.rs +++ b/gix/tests/gix/status.rs @@ -342,6 +342,25 @@ mod index_worktree { Ok(()) } + #[test] + fn nested_empty_directories_are_ignored() -> crate::Result { + let repo = repo("nested-empty-tree")?; + let mut status = repo + .status(gix::progress::Discard)? + .index_worktree_options_mut(|opts| { + opts.sorting = + Some(gix::status::plumbing::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive); + }) + .into_index_worktree_iter(None)?; + let items: Vec<_> = status.by_ref().filter_map(Result::ok).collect(); + assert_eq!( + items, + [], + "directories that only contain empty subdirectories should not be reported as untracked" + ); + Ok(()) + } + #[test] fn untracked_files_collapse_by_default() -> crate::Result { let repo = repo("untracked-only")?;