mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
fs: support pruning hidden directories during walks (#31570)
Why Filtering hidden directories after a walk is too late: their descendants consume traversal limits, and canonical directory deduplication can let a hidden path claim a target before a visible symlink reaches it. What this changes - Add an optional pruneHiddenDirectories walk option. It defaults to false and is omitted from the wire when disabled. - Return hidden directory entries, but do not traverse them or add their canonical identities to the visited set. - Cover the visible-symlink-to-hidden-directory case through both local and remote filesystem implementations. This is the small filesystem prerequisite for #31566. Skill-specific behavior remains in that PR.
This commit is contained in:
@@ -160,6 +160,7 @@ pub async fn load_environment_skills_from_root(
|
||||
max_directories: MAX_SKILLS_DIRS_PER_ROOT,
|
||||
max_entries: MAX_SKILLS_ENTRIES_PER_ROOT,
|
||||
follow_directory_symlinks: true,
|
||||
prune_hidden_directories: false,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
|
||||
@@ -402,6 +402,7 @@ async fn file_system_walk_returns_a_bounded_tree(
|
||||
max_directories: 10,
|
||||
max_entries: 10,
|
||||
follow_directory_symlinks: false,
|
||||
prune_hidden_directories: false,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
@@ -447,6 +448,7 @@ async fn file_system_walk_returns_a_bounded_tree(
|
||||
max_directories: 10,
|
||||
max_entries: 10,
|
||||
follow_directory_symlinks: false,
|
||||
prune_hidden_directories: false,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
@@ -469,6 +471,7 @@ async fn file_system_walk_returns_a_bounded_tree(
|
||||
max_directories: 1,
|
||||
max_entries: 10,
|
||||
follow_directory_symlinks: false,
|
||||
prune_hidden_directories: false,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
@@ -491,6 +494,7 @@ async fn file_system_walk_returns_a_bounded_tree(
|
||||
max_directories: 10,
|
||||
max_entries: 1,
|
||||
follow_directory_symlinks: false,
|
||||
prune_hidden_directories: false,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
@@ -535,6 +539,7 @@ async fn file_system_walk_honors_read_sandbox(
|
||||
max_directories: 2,
|
||||
max_entries: 2,
|
||||
follow_directory_symlinks: false,
|
||||
prune_hidden_directories: false,
|
||||
},
|
||||
Some(&sandbox),
|
||||
)
|
||||
|
||||
@@ -299,6 +299,7 @@ async fn file_system_walk_handles_directory_symlinks(
|
||||
max_directories: 4,
|
||||
max_entries: 8,
|
||||
follow_directory_symlinks: false,
|
||||
prune_hidden_directories: false,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
@@ -321,6 +322,7 @@ async fn file_system_walk_handles_directory_symlinks(
|
||||
max_directories: 4,
|
||||
max_entries: 8,
|
||||
follow_directory_symlinks: true,
|
||||
prune_hidden_directories: false,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
@@ -351,6 +353,68 @@ async fn file_system_walk_handles_directory_symlinks(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(FileSystemImplementation::Local ; "local")]
|
||||
#[test_case(FileSystemImplementation::Remote ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_walk_prunes_hidden_directories_without_claiming_visible_aliases(
|
||||
implementation: FileSystemImplementation,
|
||||
) -> Result<()> {
|
||||
let context = create_file_system_context(implementation).await?;
|
||||
let file_system = context.file_system;
|
||||
|
||||
let tmp = TempDir::new()?;
|
||||
let root = tmp.path().join("root");
|
||||
let hidden = root.join(".hidden");
|
||||
let hidden_nested = hidden.join("nested");
|
||||
let visible = root.join("visible");
|
||||
std::fs::create_dir_all(&hidden_nested)?;
|
||||
std::fs::write(hidden_nested.join("note.txt"), "visible through alias")?;
|
||||
symlink(&hidden, &visible)?;
|
||||
|
||||
let outcome = file_system
|
||||
.walk(
|
||||
&PathUri::from_host_native_path(&root)?,
|
||||
WalkOptions {
|
||||
max_depth: 3,
|
||||
max_directories: 3,
|
||||
max_entries: 6,
|
||||
follow_directory_symlinks: true,
|
||||
prune_hidden_directories: true,
|
||||
},
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("mode={implementation}"))?;
|
||||
|
||||
assert_eq!(
|
||||
outcome,
|
||||
WalkOutcome {
|
||||
entries: vec![
|
||||
WalkEntry {
|
||||
path: PathUri::from_host_native_path(hidden)?,
|
||||
kind: WalkEntryKind::Directory,
|
||||
},
|
||||
WalkEntry {
|
||||
path: PathUri::from_host_native_path(&visible)?,
|
||||
kind: WalkEntryKind::Directory,
|
||||
},
|
||||
WalkEntry {
|
||||
path: PathUri::from_host_native_path(visible.join("nested"))?,
|
||||
kind: WalkEntryKind::Directory,
|
||||
},
|
||||
WalkEntry {
|
||||
path: PathUri::from_host_native_path(visible.join("nested/note.txt"))?,
|
||||
kind: WalkEntryKind::File,
|
||||
},
|
||||
],
|
||||
errors: Vec::new(),
|
||||
truncated: false,
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(FileSystemImplementation::Local ; "local")]
|
||||
#[test_case(FileSystemImplementation::Remote ; "remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
@@ -81,6 +81,9 @@ pub struct WalkOptions {
|
||||
pub max_entries: usize,
|
||||
/// Whether directory symlinks should be followed.
|
||||
pub follow_directory_symlinks: bool,
|
||||
/// Whether directories whose names start with `.` should be returned but not traversed.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub prune_hidden_directories: bool,
|
||||
}
|
||||
|
||||
/// Type of a filesystem entry returned by a walk.
|
||||
@@ -463,6 +466,9 @@ async fn walk_via_directory_reads<F: ExecutorFileSystem + ?Sized>(
|
||||
});
|
||||
|
||||
if kind == WalkEntryKind::Directory && depth < options.max_depth {
|
||||
if options.prune_hidden_directories && entry.file_name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
let directory_identity = if options.follow_directory_symlinks {
|
||||
match file_system.canonicalize(&path, sandbox).await {
|
||||
Ok(path) => path,
|
||||
|
||||
Reference in New Issue
Block a user