codex_file_system/
find_up.rs1use crate::ExecutorFileSystem;
2use crate::FileSystemResult;
3use crate::FileSystemSandboxContext;
4use codex_utils_absolute_path::AbsolutePathBuf;
5use codex_utils_path_uri::PathUri;
6use futures::StreamExt;
7use std::io;
8
9const MAX_CONCURRENT_PROBES: usize = 256;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum FindUpErrorPolicy {
16 Propagate,
18 Ignore,
20}
21
22pub async fn find_nearest_ancestor_with_markers(
28 file_system: &dyn ExecutorFileSystem,
29 start: &PathUri,
30 markers: Vec<String>,
31 error_policy: FindUpErrorPolicy,
32 sandbox: Option<&FileSystemSandboxContext>,
33) -> FileSystemResult<Option<PathUri>> {
34 find_nearest_ancestor(
35 file_system,
36 start.clone(),
37 markers,
38 PathUri::parent,
39 |ancestor, marker| {
40 ancestor
41 .join(marker)
42 .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))
43 },
44 error_policy,
45 sandbox,
46 )
47 .await
48}
49
50pub async fn find_nearest_native_ancestor_with_markers(
55 file_system: &dyn ExecutorFileSystem,
56 start: &AbsolutePathBuf,
57 markers: Vec<String>,
58 error_policy: FindUpErrorPolicy,
59 sandbox: Option<&FileSystemSandboxContext>,
60) -> FileSystemResult<Option<AbsolutePathBuf>> {
61 find_nearest_ancestor(
62 file_system,
63 start.clone(),
64 markers,
65 AbsolutePathBuf::parent,
66 |ancestor, marker| Ok(PathUri::from_abs_path(&ancestor.join(marker))),
67 error_policy,
68 sandbox,
69 )
70 .await
71}
72
73async fn find_nearest_ancestor<P, Parent, MarkerPath>(
74 file_system: &dyn ExecutorFileSystem,
75 start: P,
76 markers: Vec<String>,
77 parent: Parent,
78 mut marker_path: MarkerPath,
79 error_policy: FindUpErrorPolicy,
80 sandbox: Option<&FileSystemSandboxContext>,
81) -> FileSystemResult<Option<P>>
82where
83 P: Clone + Send,
84 Parent: FnMut(&P) -> Option<P> + Send,
85 MarkerPath: FnMut(&P, &str) -> FileSystemResult<PathUri> + Send,
86{
87 let mut ancestors = std::iter::successors(Some(start), parent);
88 let mut ancestor = ancestors.next();
89 let mut marker_index = 0;
90 let probes = std::iter::from_fn(move || {
91 let current_ancestor = ancestor.clone()?;
92 let marker = markers.get(marker_index)?;
93 let marker_path = marker_path(¤t_ancestor, marker);
94
95 marker_index += 1;
96 if marker_index == markers.len() {
97 marker_index = 0;
98 ancestor = ancestors.next();
99 }
100
101 Some((current_ancestor, marker_path))
102 });
103 let mut results = futures::stream::iter(probes)
104 .map(|(ancestor, marker_path)| async move {
105 let marker_path = marker_path?;
106 match file_system.get_metadata(&marker_path, sandbox).await {
107 Ok(_) => Ok(Some(ancestor)),
108 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
109 Err(err) => match error_policy {
110 FindUpErrorPolicy::Propagate => Err(err),
111 FindUpErrorPolicy::Ignore => Ok(None),
112 },
113 }
114 })
115 .buffered(MAX_CONCURRENT_PROBES);
116
117 while let Some(result) = results.next().await {
118 if let Some(ancestor) = result? {
119 return Ok(Some(ancestor));
120 }
121 }
122 Ok(None)
123}